Файли
openidea/src/editor/core/cm/docComment.ts
T
l0ng-ai 2e9a637158 feat(editor): insert a documentation comment stub on Enter after /** (#1600)
Enter after a /** that opens a new comment 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, ... for variadics) and the declared return type (@return; none for void and constructors). Settings › Editor › General › Smart Keys › Enter › Insert documentation comment stub turns it off.
2026-11-06 01:14:38 +08:00

89 рядки
3.7 KiB
TypeScript
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
/**
* 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;
};
}