/** * 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` → `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; } }