✨ feat(vscode): 代码阅读的函数和类可以看调用树

This commit is contained in:
cruldra committed 2026-10-11 00:37:24 -04:00
1 parent 8dc572bf94
commit 645542eb5c
17 files changed
+1679 -5

No files matched your search

+141
View File
@@ -0,0 +1,141 @@
/**
* 问编辑器的调用关系命令,收成调用边。
* 语言扩展不提供、或者这一轮已经作废,就当没有。
*/
import type { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, TextDocument } from 'vscode'
import type { CallEdge, CallRange, CallSymbol } from './model'
import { commands, SymbolKind, workspace } from 'vscode'
import { symbolKey } from './model'
import { collectCallEdges } from './walk'
/**
* 在 offset 这个名字起点上问调用关系。
* 先准备符号,再往上问谁调用了它;路径上的调用者再问它还调用了谁,用来收「另外 N 处」。
*/
export async function loadCallEdges(
document: TextDocument,
offset: number,
cancelled: () => boolean,
): Promise<{ symbol: CallSymbol, edges: CallEdge[] } | undefined> {
if (cancelled())
return undefined
const position = document.positionAt(offset)
let prepared: CallHierarchyItem | CallHierarchyItem[] | null | undefined
try {
prepared = await commands.executeCommand('vscode.prepareCallHierarchy', document.uri, position)
}
catch {
return undefined
}
if (cancelled() || prepared == null)
return undefined
const items = (Array.isArray(prepared) ? prepared : [prepared]).filter(item => item)
if (items.length === 0)
return undefined
const vscodeItems = new Map<string, CallHierarchyItem>()
const remember = (item: CallHierarchyItem): CallSymbol => {
const symbol = toSymbol(item)
vscodeItems.set(symbolKey(symbol), item)
return symbol
}
const starts: CallSymbol[] = []
const seen = new Set<string>()
for (const item of items) {
const symbol = remember(item)
const key = symbolKey(symbol)
if (seen.has(key))
continue
seen.add(key)
starts.push(symbol)
}
const symbol = starts.find(item => item.line === position.line && item.character === position.character) ?? starts[0]!
const edges: CallEdge[] = []
for (const start of starts) {
if (cancelled())
return undefined
edges.push(...await collectCallEdges(start, incoming, outgoing, cancelled))
}
return cancelled() ? undefined : { symbol, edges }
async function incoming(node: CallSymbol) {
return hits(node, 'vscode.provideIncomingCalls', (call: CallHierarchyIncomingCall) => ({
from: remember(call.from),
ranges: call.fromRanges.map(toRange),
}))
}
async function outgoing(node: CallSymbol) {
return hits(node, 'vscode.provideOutgoingCalls', (call: CallHierarchyOutgoingCall) => ({
to: remember(call.to),
ranges: call.fromRanges.map(toRange),
}))
}
async function hits<T, R>(node: CallSymbol, command: string, map: (call: T) => R): Promise<R[]> {
const item = vscodeItems.get(symbolKey(node))
if (!item || cancelled())
return []
try {
const calls = await commands.executeCommand<T[]>(command, item)
return (calls ?? []).map(map)
}
catch {
return []
}
}
}
function toSymbol(item: CallHierarchyItem): CallSymbol {
const start = item.selectionRange.start
return {
uri: item.uri.toString(),
line: start.line,
character: start.character,
name: item.name,
kind: kindName(item.kind),
path: workspace.asRelativePath(item.uri),
...(item.detail && { detail: item.detail }),
}
}
function toRange(range: CallHierarchyItem['range']): CallRange {
return {
line: range.start.line,
character: range.start.character,
endLine: range.end.line,
endCharacter: range.end.character,
}
}
/** 编辑器的符号种类换成块种类,只给图标用。 */
function kindName(kind: SymbolKind): string {
switch (kind) {
case SymbolKind.Method:
case SymbolKind.Function:
case SymbolKind.Operator:
return 'function'
case SymbolKind.Class:
case SymbolKind.Struct:
case SymbolKind.Constructor:
return 'class'
case SymbolKind.Interface:
case SymbolKind.TypeParameter:
return 'type'
case SymbolKind.Enum:
return 'enum'
case SymbolKind.Field:
return 'field'
case SymbolKind.Variable:
return 'variable'
case SymbolKind.Constant:
return 'const'
case SymbolKind.Module:
case SymbolKind.Namespace:
case SymbolKind.Package:
return 'module'
default:
return 'function'
}
}