const vscode = require('vscode'); const path = require('node:path'); const { relations } = require('./graph.cjs'); const { uri, projectRoot } = require('./inspection.cjs'); const { openFlowPreview } = require('./dist/flow-preview.cjs'); const { description, command } = require('./hover.cjs'); function registerReading(context, inspection, lookup) { const changed = new vscode.EventEmitter(); const previews = new Map(); context.subscriptions.push(changed, inspection.changed.event(() => { for (const uri of previews.values()) changed.fire(uri); })); context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(document => { previews.delete(document.uri.toString()); })); const provider = { onDidChange: changed.event, async provideTextDocumentContent(documentUri) { previews.set(documentUri.toString(), documentUri); const { root, name } = JSON.parse(documentUri.query); const index = await inspection.load(root); const symbols = index.symbols.filter(symbol => symbol.name === name); if (!symbols.length) throw new Error(`函数已不存在:${name}`); const lines = [`# ${name}`, '', '静态源码关系;分支可能只执行一部分,任务派发不表示同步执行。', '', index.coverage, '']; if (index.documentation_errors.length) lines.push('**说明校验未通过。** 详见“Dever”输出。', ''); function link(site) { const file = index.files[site.file]; const target = uri(index, site.file, root).with({ fragment: `L${site.start.line + 1},${site.start.character + 1}` }); return `[${file.path}:${site.start.line + 1}](${target.toString()})`; } for (const symbol of symbols) { for (const signature of symbol.signatures) lines.push('```dever', signature, '```', ''); const detail = await description(inspection, index, symbol, root); if (detail) lines.push(detail.markdown, '', `[主题说明](${detail.uri.toString()})`, ''); for (const site of symbol.declarations) { await inspection.verify(index, site.file, root); lines.push('## 定义', '', link(site), ''); } for (const incoming of [true, false]) { lines.push(incoming ? '## 谁调用它' : '## 它调用谁', ''); const related = relations(index, symbol.id, incoming); if (!related.length) lines.push('当前已检查源码中没有此类关系。', ''); else lines.push('| 函数 | 领域 | 关系 | 调用位置 | 定义 / 上下游 |', '| --- | --- | --- | --- | --- |'); for (const relation of related) { for (const call of relation.calls) { await inspection.verify(index, call.site.file, root); const definitions = relation.symbol.declarations.map(link).join('、') || '内建操作'; const chain = command('dever.showCalls', [root, relation.symbol.name]); lines.push(`| ${relation.symbol.name} | ${relation.symbol.name.split('.').slice(0, 2).join('.')} | ${call.kind} | ${link(call.site)} | ${definitions} · [上下游](${chain}) |`); } } lines.push(''); } } return lines.join('\n'); } }; context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('dever-calls', provider)); context.subscriptions.push(vscode.commands.registerCommand('dever.showCalls', async (root, name) => { try { if (!root || !name) { const editor = vscode.window.activeTextEditor; if (!editor) return; const state = await lookup(editor.document, editor.selection.active); if (!state) throw new Error('请先保存源码,再查看调用关系。'); if (!state.symbols.length) throw new Error('请把光标放在函数或调用位置。'); root = state.root; name = state.symbols[0].name; } const target = vscode.Uri.from({ scheme: 'dever-calls', path: '/calls.md', query: JSON.stringify({ root, name }) }); await openFlowPreview(context, target, root, { name, changed: inspection.changed, read: name => provider.provideTextDocumentContent(target.with({ query: JSON.stringify({ root, name }) })) }); } catch (error) { vscode.window.showErrorMessage(error.message); } })); context.subscriptions.push(vscode.commands.registerCommand('dever.previewDocumentation', async (root, name) => { try { const document = vscode.window.activeTextEditor?.document; if (!root || !name) { if (!document) return; const state = await lookup(document, vscode.window.activeTextEditor.selection.active); root = state?.root || await projectRoot(document); name = state?.symbols[0]?.name; } const index = await inspection.load(root); const symbol = index.symbols.find(value => value.name === name); const detail = symbol && await description(inspection, index, symbol, root); if (!detail) throw new Error('该符号尚未提供 doc/ 说明。'); await vscode.commands.executeCommand('markdown.showPreviewToSide', detail.uri.with({ fragment: `L${detail.line + 1}` })); } catch (error) { vscode.window.showErrorMessage(error.message); } })); context.subscriptions.push(vscode.commands.registerCommand('dever.openDomainLinks', async (root, name) => { try { const document = vscode.window.activeTextEditor?.document; if (!root && !document) return; root ||= await projectRoot(document); const index = await inspection.load(root); const symbol = index.symbols.find(value => value.name === name); const source = symbol?.declarations[0]; const filename = source ? index.files[source.file].path : document.uri.fsPath; const relative = path.relative(path.join(root, 'module'), filename).split(path.sep); if (relative.length < 3 || relative[0] === '..') { throw new Error('请在 module/<组件>/<领域>/ 内的源码中执行此命令。'); } const target = vscode.Uri.file(path.join(root, 'module', ...relative.slice(0, 2), 'link.md')); await inspection.checkLinks(root); const panel = await openFlowPreview(context, target, root); const changed = inspection.changed.event(() => panel.dispose()); panel.onDidDispose(() => changed.dispose()); } catch (error) { vscode.window.showErrorMessage(`${error.message} 可在项目根运行 dever inspect . --links 生成索引。`); } })); } module.exports = { registerReading };