const vscode = require('vscode'); const { createInspection, range, uri } = require('./inspection.cjs'); const { at, relations } = require('./graph.cjs'); const { registerReading } = require('./reading.cjs'); const { registerHover } = require('./hover.cjs'); function registerNavigation(context) { const inspection = createInspection(context); const selector = [ { language: 'dever', scheme: 'file' }, { language: 'dever', scheme: 'dever-source' } ]; async function item(index, symbol, root) { const first = symbol.declarations[0]; if (!first) return undefined; await inspection.verify(index, first.file, root); const end = symbol.declarations.at(-1).end; const span = range({ start: first.start, end }); const selected = new vscode.Range(span.start, span.start); const value = new vscode.CallHierarchyItem(vscode.SymbolKind.Function, symbol.name, symbol.kind, uri(index, first.file, root), span, selected); return value; } async function lookup(document, position) { const state = await inspection.current(document); return state ? { ...state, symbols: at(state.index, state.file, position) } : undefined; } context.subscriptions.push(vscode.languages.registerDefinitionProvider(selector, { async provideDefinition(document, position) { const state = await lookup(document, position); if (!state) return []; return Promise.all(state.symbols.flatMap(symbol => symbol.declarations) .map(site => inspection.location(state.index, site, state.root))); } })); context.subscriptions.push(vscode.languages.registerReferenceProvider(selector, { async provideReferences(document, position, options) { const state = await lookup(document, position); if (!state) return []; const ids = new Set(state.symbols.map(symbol => symbol.id)); const sites = state.index.calls.filter(call => ids.has(call.callee)).map(call => call.site); if (options.includeDeclaration) sites.push(...state.symbols.flatMap(symbol => symbol.declarations)); const unique = [...new Map(sites.map(site => [JSON.stringify(site), site])).values()]; return Promise.all(unique.map(site => inspection.location(state.index, site, state.root))); } })); async function hierarchy(value, incoming) { const document = await vscode.workspace.openTextDocument(value.uri); const state = await inspection.current(document); if (!state) throw new Error('请保存源码后重新打开调用层级。'); const { root, index, file } = state; const start = value.selectionRange.start; const symbol = index.symbols.find(symbol => symbol.name === value.name && symbol.declarations.some(site => site.file === file && site.start.line === start.line && site.start.character === start.character)); if (!symbol) throw new Error('函数位置已变化,请重新打开调用层级。'); await inspection.verify(index, symbol.declarations[0].file, root); const result = []; for (const relation of relations(index, symbol.id, incoming)) { const target = await item(index, relation.symbol, root); if (!target) continue; for (const call of relation.calls) await inspection.verify(index, call.site.file, root); const ranges = relation.calls.map(call => range(call.site)); result.push(incoming ? new vscode.CallHierarchyIncomingCall(target, ranges) : new vscode.CallHierarchyOutgoingCall(target, ranges)); } return result; } context.subscriptions.push(vscode.languages.registerCallHierarchyProvider(selector, { async prepareCallHierarchy(document, position) { const state = await lookup(document, position); if (!state) return []; return (await Promise.all(state.symbols.map(symbol => item(state.index, symbol, state.root)))).filter(Boolean); }, provideCallHierarchyIncomingCalls(value) { return hierarchy(value, true); }, provideCallHierarchyOutgoingCalls(value) { return hierarchy(value, false); } })); context.subscriptions.push(vscode.languages.registerCodeLensProvider(selector, { onDidChangeCodeLenses: inspection.changed.event, async provideCodeLenses(document) { const state = await inspection.current(document); if (!state) return []; return state.index.symbols.filter(symbol => symbol.kind !== 'generated' && symbol.declarations[0]?.file === state.file).map(symbol => { const incoming = relations(state.index, symbol.id, true); const count = incoming.reduce((total, relation) => total + relation.calls.length, 0); const start = range(symbol.declarations[0]).start; return new vscode.CodeLens(new vscode.Range(start, start), { title: `${incoming.length} 个调用方 · ${count} 处调用 · 查看调用关系`, command: 'dever.showCalls', arguments: [state.root, symbol.name] }); }); } })); registerReading(context, inspection, lookup); registerHover(context, selector, inspection, lookup); } module.exports = { registerNavigation };