import type { ExtensionContext, Uri } from 'vscode' import type { CodeReaderApi, RuleLocation, RuleReference } from './contract' import type { IndexedName } from './nameResolution' import { commands, window, workspace } from 'vscode' import { logger } from '../logging/logger' import { BlockFilter } from './editor/blockFilter' import { NEXT_OCCURRENCE, PREVIOUS_OCCURRENCE } from './editor/occurrenceNavigation' import { PREVIEW_VIEW_TYPE, PreviewEditorProvider } from './editor/previewEditor' import { showPreview, showSource } from './editor/switchEditor' import { registerCopyCommands } from './explorer/copyCommands' import { FileTreeProvider } from './explorer/fileTree' import { createPythonAdapter } from './languages/python' import { createTypeScriptAdapter } from './languages/typescript' import { resolveReference } from './libraryRules' import { indexedNames } from './nameResolution' import { registerNoteCommands } from './notes/commands' import { FileNotes } from './notes/fileNotes' import { CodeQa } from './qa/codeQa' import { CodeReaderRegistry } from './registry' import { celeryRule } from './rules/celery' import { dataclassRule } from './rules/dataclass' import { enumRule } from './rules/enum' import { sqlalchemyRule } from './rules/sqlalchemy' import { bundledWasmDir, TreeSitterRuntime } from './runtime' import { pythonZhTranslator } from './translators/pythonZh' import { typescriptZhTranslator } from './translators/typescriptZh' import { workspaceIndexHost } from './workspaceFiles' import { WorkspaceIndexes } from './workspaceIndex' /** 当前语言插件覆盖的 languageId,给 editor/title 的 when 子句判断要不要显示「预览」。 */ const LANGUAGE_IDS_CONTEXT = 'superpowers.codeReader.languageIds' const FILES_VIEW = 'superpowers.codeReader.files' const OPEN_COMMAND = 'superpowers.codeReader.open' /** 按 VS Code 的判断(含 files.associations)取文件的 languageId;图片等二进制文件打不开成文本,返回 undefined。 */ async function languageOf(uri: Uri): Promise { try { return (await workspace.openTextDocument(uri)).languageId } catch { return undefined } } /** 侧栏点文件:语言受支持就用预览打开,否则用普通编辑器。 */ async function openFile(uri: Uri, registry: CodeReaderRegistry): Promise { const languageId = await languageOf(uri) if (languageId !== undefined && registry.languageFor(languageId)) // 和 vscode.open 一样按「预览标签」打开(用户关了 workbench.editor.enablePreview 时 VS Code 自会固定) await commands.executeCommand('vscode.openWith', uri, PREVIEW_VIEW_TYPE, { preview: true }) else await commands.executeCommand('vscode.open', uri) } /** * 接入代码阅读:注册表、侧栏文件树(复制、文件注释)、预览编辑器(含问 AI)、编辑器右上角的「预览 / 编辑」切换。 * 内置的语言插件、翻译插件和库规则与其他扩展一样通过返回的 API 注册。 */ export function registerCodeReader(context: ExtensionContext): CodeReaderApi { const registry = new CodeReaderRegistry(context.globalState) const runtime = new TreeSitterRuntime(bundledWasmDir()) const report = (message: string): void => logger.add({ level: 'warn', source: 'codeReader', message }) const indexes = new WorkspaceIndexes(workspaceIndexHost(async (languageId, source) => { const adapter = registry.languageFor(languageId) if (!adapter) throw new Error(`没有「${languageId}」的语言插件`) return runtime.parse(adapter, languageId, source) }, report)) const resolve = (reference: RuleReference): Promise => resolveReference(reference, registry.libraryRules(), indexes, report) const qa = new CodeQa(context.globalState) const namesIn = async (languageId: string, name: string): Promise => { const adapter = registry.languageFor(languageId) return adapter ? indexedNames((index, key) => indexes.find(index, key), adapter, name) : [] } const previews = new PreviewEditorProvider(context.extensionUri, registry, runtime, new BlockFilter(context.workspaceState), resolve, qa, namesIn) const notes = new FileNotes() const files = new FileTreeProvider(OPEN_COMMAND, notes) context.subscriptions.push( qa, notes, indexes, registerNoteCommands(notes), registerCopyCommands(), registry.onDidChange(() => { void commands.executeCommand('setContext', LANGUAGE_IDS_CONTEXT, registry.languageIds()) previews.refresh() }), window.registerCustomEditorProvider(PREVIEW_VIEW_TYPE, previews), files, window.createTreeView(FILES_VIEW, { treeDataProvider: files, showCollapseAll: true }), commands.registerCommand('superpowers.codeReader.refresh', () => files.refresh()), commands.registerCommand(OPEN_COMMAND, (uri: Uri) => openFile(uri, registry)), // 标题栏按钮传来的是 (文件 Uri, { groupId }),原样转给 VS Code 定位是哪一组的哪个标签 commands.registerCommand('superpowers.codeReader.showPreview', (...args: unknown[]) => showPreview(previews, args)), commands.registerCommand('superpowers.codeReader.showSource', (...args: unknown[]) => showSource(previews, args)), commands.registerCommand(NEXT_OCCURRENCE, () => previews.moveOccurrence(1)), commands.registerCommand(PREVIOUS_OCCURRENCE, () => previews.moveOccurrence(-1)), ) const api: CodeReaderApi = registry const wasmDir = bundledWasmDir() context.subscriptions.push( api.registerLanguage(createTypeScriptAdapter(wasmDir)), api.registerLanguage(createPythonAdapter(wasmDir)), api.registerTranslator(typescriptZhTranslator), api.registerTranslator(pythonZhTranslator), api.registerLibraryRule(sqlalchemyRule), api.registerLibraryRule(celeryRule), api.registerLibraryRule(enumRule), api.registerLibraryRule(dataclassRule), ) return api }