75 lines
3.5 KiB
TypeScript
75 lines
3.5 KiB
TypeScript
import type { Location, LocationLink, Position } from 'vscode'
|
||
import type { RuleLocation } from '../contract'
|
||
import type { DefinitionTarget } from './definitionAction'
|
||
import { commands, Range, Uri, window, workspace } from 'vscode'
|
||
import { logger } from '../../logging/logger'
|
||
|
||
/** 查到的一处定义:文件和定义处那个名字的范围。 */
|
||
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<FoundLocation[]> {
|
||
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) }))
|
||
}
|
||
|
||
/** 查到的各处定义打开目标文件补上行文字、下标;目标打不开成文本的(例如 Python 命名空间包指向的目录)略过。 */
|
||
export async function findDefinitions(locations: readonly FoundLocation[], supports: (languageId: string) => boolean): Promise<Definition[]> {
|
||
const found = await Promise.all(locations.map(async (location): Promise<Definition[]> => {
|
||
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<T extends Definition>(definitions: T[]): Promise<T | undefined> {
|
||
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
|
||
}
|