104 lines
5.0 KiB
JavaScript
104 lines
5.0 KiB
JavaScript
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 };
|