mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 17:02:17 +00:00
Run oxfmt once over the whole repository (formatting only, no logic changes) and add `pnpm format:check` to the frontend CI job so unformatted code no longer builds up. `.oxfmtrc.json` now skips the generated theme docs, test fixtures and code blocks inside Markdown. Co-authored-by: jaw52 <[email protected]>
65 lines
2.8 KiB
TypeScript
65 lines
2.8 KiB
TypeScript
/**
|
|
* Find Usages without a language server (IDEA's dumb mode): a certain local's
|
|
* same-scope references, otherwise identifier occurrences of the name across
|
|
* the project (comments and strings excluded) — labelled as text occurrences.
|
|
*/
|
|
import type { Location, Range } from "@/bindings";
|
|
import { nextRequestId } from "@/platform/ipc";
|
|
import { editorsFor, whenSynced } from "@/editor/api";
|
|
import { structure } from "@/editor/structure";
|
|
import { filesMentioning } from "./candidates";
|
|
import type { CaretContext } from "./ipc";
|
|
|
|
export const LOCAL_NOTE = "Based on syntax (language server not ready)";
|
|
export const TEXT_NOTE = "Text occurrences (language server not ready)";
|
|
|
|
export interface SyntaxUsages {
|
|
locs: Location[];
|
|
/** Shown with the results: where they came from and how far to trust them. */
|
|
note: string;
|
|
}
|
|
|
|
const IDENT = /^[\p{L}_$][\p{L}\p{N}_$]*$/u;
|
|
|
|
function inside(range: Range, pos: Range["start"]) {
|
|
const after = (a: Range["start"], b: Range["start"]) => a.line - b.line || a.col - b.col;
|
|
return after(range.start, pos) <= 0 && after(pos, range.end) <= 0;
|
|
}
|
|
|
|
/** Null when the structure service cannot answer for this file (no grammar, no editor). */
|
|
export async function syntaxUsages(c: CaretContext): Promise<SyntaxUsages | null> {
|
|
const language = c.view
|
|
? editorsFor(c.path).find((e) => e.view === c.view)?.ctx.language
|
|
: editorsFor(c.path)[0]?.ctx.language;
|
|
if (!language) return null;
|
|
const support = await structure.support(language);
|
|
if (!support.tree) return null;
|
|
if (c.view) await whenSynced(c.view);
|
|
const pos = { line: c.line, col: c.col };
|
|
let name = c.word;
|
|
if (support.locals) {
|
|
const locals = await structure.locals(c.doc, pos);
|
|
if (locals.status === "noIdentifier") return { locs: [], note: LOCAL_NOTE };
|
|
if (locals.status === "local") {
|
|
const { symbol } = locals;
|
|
if (symbol.certain)
|
|
return { locs: symbol.references.map((range) => ({ path: c.path, range })), note: LOCAL_NOTE };
|
|
// Re-declared in the same scope: every plain occurrence in that scope, as text.
|
|
const occ = await structure.identifiers(c.doc, symbol.name);
|
|
return {
|
|
locs: occ
|
|
.filter((o) => !o.qualified && inside(symbol.scope, o.range.start))
|
|
.map((o) => ({ path: c.path, range: o.range })),
|
|
note: TEXT_NOTE,
|
|
};
|
|
}
|
|
name = locals.name;
|
|
}
|
|
if (!name || !IDENT.test(name)) return null;
|
|
const { paths, limitHit } = await filesMentioning(nextRequestId(), name);
|
|
if (!paths.includes(c.path)) paths.push(c.path);
|
|
const files = await structure.identifiersInFiles(paths, name);
|
|
const locs = files.flatMap((f) => f.occurrences.map((o) => ({ path: f.path, range: o.range })));
|
|
return { locs, note: limitHit ? `${TEXT_NOTE} · search limit reached, results may be incomplete` : TEXT_NOTE };
|
|
}
|