Files
openidea/src/editor/core/copyReference.ts
T

101 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Edit › Copy Reference (⌥⇧⌘C): the fully qualified name of the symbol at
* the caret, built from the file's outline (symbols enclosing the caret) and
* the language's module conventions; `path:line` where there is no symbol.
*/
export interface ReferenceInput {
/** Backend language id (`java`, `rust`…). */
language: string;
path: string;
/** Project root, for project-relative paths and module names. */
root: string | null;
text: string;
/** Names of the outline symbols enclosing the caret, outermost first. */
chain: string[];
/** 0-based caret line. */
line: number;
}
export function relativePath(path: string, root: string | null): string {
return root && path.startsWith(root + "/") ? path.slice(root.length + 1) : path;
}
const stem = (path: string) => {
const name = path.slice(path.lastIndexOf("/") + 1);
const dot = name.lastIndexOf(".");
return dot > 0 ? name.slice(0, dot) : name;
};
/** `impl Display for Foo<T>` → `Foo`; generic arguments dropped. */
export function rustImplType(name: string): string {
const m = /^impl(?:<[^>]*>)?\s+(?:.*?\s+for\s+)?(.+)$/.exec(name);
const type = (m ? m[1]! : name).trim();
return type.replace(/<.*$/, "").replace(/^&(mut\s+)?/, "");
}
/** `crate::a::b` for `src/a/b.rs` (or `src/a/b/mod.rs`); `crate` for lib.rs / main.rs. */
export function rustModulePath(path: string): string {
const src = path.lastIndexOf("/src/");
const rel = src >= 0 ? path.slice(src + 5) : path.slice(path.lastIndexOf("/") + 1);
const parts = rel.replace(/\.rs$/, "").split("/");
const last = parts[parts.length - 1];
if (last === "mod" || ((last === "lib" || last === "main") && parts.length === 1)) parts.pop();
return ["crate", ...parts].join("::");
}
/** Dotted Python module path relative to the project (a leading `src/` dropped, `__init__` folded). */
export function pythonModulePath(path: string, root: string | null): string {
const parts = relativePath(path, root)
.replace(/\.pyi?$/, "")
.split("/");
if (parts[0] === "src" && parts.length > 1) parts.shift();
if (parts[parts.length - 1] === "__init__") parts.pop();
return parts.join(".");
}
const declared = (text: string, re: RegExp) => re.exec(text)?.[1] ?? null;
export function qualifiedReference(r: ReferenceInput): string {
const fallback = `${relativePath(r.path, r.root)}:${r.line + 1}`;
const chain = r.chain.filter(Boolean);
switch (r.language) {
case "java":
case "groovy": {
if (!chain.length) return fallback;
const pkg = declared(r.text, /^\s*package\s+([\w.]+)\s*;?/m);
const [type, ...rest] = chain;
const typePath = [pkg, type].filter(Boolean).join(".");
// `pkg.Outer.Inner#member`: the last symbol is the member unless the caret is in a type only.
return rest.length ? `${[typePath, ...rest.slice(0, -1)].join(".")}#${rest[rest.length - 1]}` : typePath;
}
case "kotlin":
case "scala": {
if (!chain.length) return fallback;
const pkg = declared(r.text, /^\s*package\s+([\w.]+)/m);
return [pkg, ...chain].filter(Boolean).join(".");
}
case "csharp": {
if (!chain.length) return fallback;
return chain.join(".");
}
case "rust": {
if (!chain.length) return rustModulePath(r.path);
return [rustModulePath(r.path), ...chain.map((n) => (n.startsWith("impl") ? rustImplType(n) : n))].join("::");
}
case "python":
return [pythonModulePath(r.path, r.root), ...chain].join(".");
case "go": {
const pkg = declared(r.text, /^\s*package\s+(\w+)/m) ?? stem(r.path);
return chain.length ? [pkg, ...chain].join(".") : fallback;
}
case "cpp":
case "c":
case "objective-cpp":
case "php":
return chain.length ? chain.join("::") : fallback;
default:
return chain.length ? `${relativePath(r.path, r.root)}:${chain.join(".")}` : fallback;
}
}