mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
Enter after a /** that opens a new comment now closes it with the caret on the summary line, in every language with /* */ comments. Above a PHP function or method the stub lists its parameters (@param, with the declared type and variadic ...) and the declared return type (@return; none for void and constructors), as PhpStorm does. Settings › Editor › General › Smart Keys › Enter › Insert documentation comment stub turns it off.
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
/**
|
||
* Smart Keys › Enter › Insert documentation comment stub: Enter right after a `/**` that opens a
|
||
* new comment closes it, the caret on the summary line. In PHP the stub of a function lists its
|
||
* parameters (`@param`) and return type (`@return`), as PhpStorm does.
|
||
*/
|
||
import { EditorSelection } from "@codemirror/state";
|
||
import type { Command } from "@codemirror/view";
|
||
|
||
/** `@param` / `@return` lines for the PHP function declared in `text` (the code below the `/**`). */
|
||
export function phpDocTags(text: string): string[] {
|
||
const m =
|
||
/^\s*(?:#\[[^\]]*\]\s*)*(?:(?:abstract|final|public|protected|private|static)\s+)*function\s+&?\s*(\w+)\s*\(/.exec(
|
||
text,
|
||
);
|
||
if (!m) return [];
|
||
let depth = 1;
|
||
let i = m[0].length;
|
||
for (; i < text.length && depth > 0; i++) {
|
||
const c = text[i];
|
||
if (c === "(" || c === "[") depth++;
|
||
else if (c === ")" || c === "]") depth--;
|
||
}
|
||
if (depth > 0) return [];
|
||
const tags = splitTopLevel(text.slice(m[0].length, i - 1)).flatMap((param) => {
|
||
const p = /^(?:#\[[^\]]*\]\s*)*(?:(?:public|protected|private|readonly)\s+)*([^$=.]*?)\s*(\.\.\.)?\s*(\$\w+)/.exec(
|
||
param.trim(),
|
||
);
|
||
if (!p) return [];
|
||
// `&$x` passes by reference: the `&` belongs to no type (`Foo&Bar` is an intersection).
|
||
const type = p[1]!.replace(/&\s*$/, "").trim().replace(/\s+/g, " ");
|
||
const name = (p[2] ?? "") + p[3];
|
||
return [type ? `@param ${type} ${name}` : `@param ${name}`];
|
||
});
|
||
const ret = /^\s*:\s*([?\w\\|&() ]+?)\s*(?:\{|;|$)/.exec(text.slice(i));
|
||
const type = ret?.[1]?.trim();
|
||
if (type && type !== "void" && m[1] !== "__construct") tags.push(`@return ${type}`);
|
||
return tags;
|
||
}
|
||
|
||
function splitTopLevel(params: string): string[] {
|
||
const out: string[] = [];
|
||
let depth = 0;
|
||
let start = 0;
|
||
for (let i = 0; i < params.length; i++) {
|
||
const c = params[i];
|
||
if (c === "(" || c === "[") depth++;
|
||
else if (c === ")" || c === "]") depth--;
|
||
else if (c === "," && depth === 0) {
|
||
out.push(params.slice(start, i));
|
||
start = i + 1;
|
||
}
|
||
}
|
||
out.push(params.slice(start));
|
||
return out.filter((p) => p.trim());
|
||
}
|
||
|
||
/** The Enter command for documents of `language` (stub tags in PHP). */
|
||
export function enterInDocComment(language: string | undefined): Command {
|
||
return (view) => {
|
||
const { state } = view;
|
||
if (state.readOnly || state.selection.ranges.length !== 1) return false;
|
||
const r = state.selection.main;
|
||
if (!r.empty) return false;
|
||
const block = state.languageDataAt<{ block?: { open: string; close: string } }>("commentTokens", r.head)[0]?.block;
|
||
if (block?.open !== "/*" || block.close !== "*/") return false;
|
||
const line = state.doc.lineAt(r.head);
|
||
const before = line.text.slice(0, r.head - line.from);
|
||
if (before.trim() !== "/**" || line.text.slice(r.head - line.from).trim()) return false;
|
||
// A `/**` that already starts a doc block (the next line continues it) only breaks the line.
|
||
const next = line.number < state.doc.lines ? state.doc.line(line.number + 1).text.trimStart() : "";
|
||
if (next.startsWith("*")) return false;
|
||
const indent = /^[ \t]*/.exec(before)![0];
|
||
const br = state.lineBreak;
|
||
const below = line.number < state.doc.lines ? state.sliceDoc(state.doc.line(line.number + 1).from) : "";
|
||
const tags = language === "php" ? phpDocTags(below.slice(0, 4000)) : [];
|
||
const summary = `${br}${indent} * `;
|
||
const insert = summary + tags.map((t) => `${br}${indent} * ${t}`).join("") + `${br}${indent} */`;
|
||
view.dispatch(
|
||
state.update({
|
||
changes: { from: r.head, insert },
|
||
selection: EditorSelection.cursor(r.head + summary.length),
|
||
scrollIntoView: true,
|
||
userEvent: "input",
|
||
}),
|
||
);
|
||
return true;
|
||
};
|
||
}
|