import type { Location, LocationLink, Position } from 'vscode' import type { BlockKind, LanguageAdapter, RuleLocation } from '../contract' import type { TreeSitterRuntime } from '../runtime' import type { DefinitionTarget } from './definitionAction' import { commands, Range, Uri, window, workspace } from 'vscode' import { logger } from '../../logging/logger' import { readDefinitionKind } from '../blockView' /** 查到的一处定义:文件和定义处那个名字的范围。 */ export interface FoundLocation { uri: Uri, range: Range } /** VS Code 查到的一处定义,加上打开目标文件才知道的信息。 */ export interface Definition extends DefinitionTarget { location: FoundLocation /** 定义处在目标文件里的下标,预览靠它找到是哪块、哪一行属性 */ offset: number /** 定义所在那一行的源码,列在挑选框里 */ lineText: string } /** uri 里 position 处名字的定义:交给 VS Code 注册的定义提供者(Pylance、TypeScript 语言服务……),和普通编辑器里 ⌘ + 点击查的是同一套。 */ export async function languageDefinitions(uri: Uri, position: Position): Promise { let provided: (Location | LocationLink)[] try { provided = await commands.executeCommand<(Location | LocationLink)[] | undefined>('vscode.executeDefinitionProvider', uri, position) ?? [] } catch (err) { logger.add({ level: 'warn', source: 'codeReader', message: `查定义失败 ${uri.toString()}:${position.line + 1}:${err instanceof Error ? err.message : String(err)}` }) return [] } return provided.map(item => 'targetUri' in item ? { uri: item.targetUri, range: item.targetSelectionRange ?? item.targetRange } : { uri: item.uri, range: item.range }) } /** 库规则查到的定义(RuleLocation)换成 VS Code 的位置。 */ export function ruleDefinitions(locations: readonly RuleLocation[]): FoundLocation[] { return locations.map(({ uri, start, end }) => ({ uri: Uri.parse(uri), range: new Range(start.line, start.character, end.line, end.character) })) } /** 定义处那个名字定义的是哪种块:打开目标文件,用它那种语言的语言插件分块认;目标打不开、语言不支持、不在哪块的名字上时没有。 */ export async function definitionKind( location: FoundLocation, languageFor: (languageId: string) => LanguageAdapter | undefined, runtime: Pick, ): Promise { try { const document = await workspace.openTextDocument(location.uri) const adapter = languageFor(document.languageId) return adapter && await readDefinitionKind(runtime, adapter, document.languageId, document.getText(), document.offsetAt(location.range.start)) } catch (err) { logger.add({ level: 'warn', source: 'codeReader', message: `认不出 ${location.uri.toString()} 里定义的是哪种块:${err instanceof Error ? err.message : String(err)}` }) return undefined } } /** 查到的各处定义打开目标文件补上行文字、下标;目标打不开成文本的(例如 Python 命名空间包指向的目录)略过。 */ export async function findDefinitions(locations: readonly FoundLocation[], supports: (languageId: string) => boolean): Promise { const found = await Promise.all(locations.map(async (location): Promise => { const { start } = location.range try { const document = await workspace.openTextDocument(location.uri) return [{ uri: location.uri.toString(), line: start.line, character: start.character, supported: supports(document.languageId), location, offset: document.offsetAt(start), lineText: document.lineAt(start.line).text, }] } catch (err) { logger.add({ level: 'warn', source: 'codeReader', message: `定义所在的 ${location.uri.toString()} 打不开:${err instanceof Error ? err.message : String(err)}` }) return [] } })) return found.flat() } /** 几处定义里挑一处:每项是「相对路径:行号」,下面一行是那行源码。 */ export async function pickDefinition(definitions: T[]): Promise { const picked = await window.showQuickPick( definitions.map(definition => ({ label: `${workspace.asRelativePath(definition.location.uri)}:${definition.line + 1}`, detail: definition.lineText.trim(), definition, })), { placeHolder: '跳到哪一处定义', matchOnDetail: true }, ) return picked?.definition }