✨ feat(vscode): 代码阅读的函数和类可以看调用树
This commit is contained in:
1 parent
8dc572bf94
commit
645542eb5c
17 files changed
+1679
-5
No files matched your search
@@ -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'
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user