定义与引用跳转
F12 跳到定义、Shift+F12 查找引用,是编辑器最核心的导航能力。通过语言服务 Provider,插件可以为任意语言实现「点一下跳到实现」的体验。
定义跳转 registerDefinitionProvider
languages.registerDefinitionProvider 注册定义跳转:
typescript
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerDefinitionProvider(
'plaintext',
{
provideDefinition(document, position, token) {
// 返回定义位置
return new vscode.Location(
document.uri, // 目标文件
new vscode.Position(0, 0) // 目标位置
);
}
}
)
);
}Location 返回值
Location 描述「文件 + 位置」:
typescript
// 返回当前文件中的位置
return new vscode.Location(
document.uri,
new vscode.Position(10, 5)
);
// 返回其他文件中的位置
return new vscode.Location(
vscode.Uri.file('C:\\project\\defs.ts'),
new vscode.Range(20, 0, 20, 10)
);Location 与 LocationLink
| 类型 | 作用 |
|---|---|
Location | 简单定义位置(文件 + 位置) |
LocationLink | 高级定义(含预览范围) |
DefinitionLink 多定义
LocationLink 支持更丰富的定义信息:
typescript
provideDefinition(document, position) {
const link = new vscode.LocationLink(
vscode.Uri.file('/path/to/impl.ts'), // targetUri
new vscode.Range(5, 0, 5, 20), // targetRange:整个定义范围
new vscode.Range(5, 10, 5, 15), // targetSelectionRange:定义名称
new vscode.Range(0, 0, 0, 5) // originSelectionRange:触发范围
);
return [link]; // 支持数组(多个定义)
}LocationLink 属性
| 属性 | 说明 |
|---|---|
targetUri | 目标文件 |
targetRange | 定义所在范围 |
targetSelectionRange | 定义名称高亮范围 |
originSelectionRange | 触发位置范围 |
多定义场景
typescript
// 返回多个定义(如接口与实现)
provideDefinition(document, position) {
const locations: vscode.Location[] = [
new vscode.Location(
vscode.Uri.file('/path/interface.ts'),
new vscode.Position(3, 10)
),
new vscode.Location(
vscode.Uri.file('/path/impl.ts'),
new vscode.Position(30, 15)
)
];
return locations;
}引用查找 registerReferenceProvider
languages.registerReferenceProvider 查找所有引用位置:
typescript
vscode.languages.registerReferenceProvider(
'plaintext',
{
provideReferences(document, position, context, token) {
// 查找当前单词的所有引用
const word = getWordAtPosition(document, position);
if (!word) {
return [];
}
const references: vscode.Location[] = [];
// 遍历文档所有行
for (let line = 0; line < document.lineCount; line++) {
const lineText = document.lineAt(line).text;
let index = lineText.indexOf(word);
while (index !== -1) {
references.push(
new vscode.Location(
document.uri,
new vscode.Range(
line, index, line, index + word.length
)
)
);
index = lineText.indexOf(word, index + 1);
}
}
return references;
}
}
);完整示例:符号表跳转
实现一个「函数名 → 定义」跳转器:
typescript
import * as vscode from 'vscode';
// 解析函数定义行
interface SymbolDef {
name: string;
line: number;
column: number;
}
function parseSymbols(document: vscode.TextDocument): SymbolDef[] {
const symbols: SymbolDef[] = [];
for (let i = 0; i < document.lineCount; i++) {
const text = document.lineAt(i).text;
// 匹配 "function 名称(" 或 "名称 = ("
const match = text.match(/(?:function\s+|const\s+)(\w+)\s*[=(]/);
if (match) {
const column = text.indexOf(match[1]);
symbols.push({ name: match[1], line: i, column });
}
}
return symbols;
}
export function activate(context: vscode.ExtensionContext) {
// 定义跳转
context.subscriptions.push(
vscode.languages.registerDefinitionProvider('plaintext', {
provideDefinition(document, position) {
const word = getWordAtPosition(document, position);
if (!word) {
return undefined;
}
// 查找符号定义
const symbols = parseSymbols(document);
const def = symbols.find((s) => s.name === word);
if (!def) {
return undefined;
}
return new vscode.Location(
document.uri,
new vscode.Range(
def.line, def.column, def.line, def.column + def.name.length
)
);
}
})
);
// 引用查找
context.subscriptions.push(
vscode.languages.registerReferenceProvider('plaintext', {
provideReferences(document, position) {
const word = getWordAtPosition(document, position);
if (!word) {
return [];
}
const locations: vscode.Location[] = [];
for (let i = 0; i < document.lineCount; i++) {
const lineText = document.lineAt(i).text;
const idx = lineText.indexOf(word);
if (idx !== -1) {
locations.push(
new vscode.Location(
document.uri,
new vscode.Range(i, idx, i, idx + word.length)
)
);
}
}
return locations;
}
})
);
}
// 工具函数
function getWordAtPosition(
document: vscode.TextDocument,
position: vscode.Position
): string | undefined {
const range = document.getWordRangeAtPosition(position);
return range ? document.getText(range) : undefined;
}跨文件跳转
真实场景中定义常在其他文件:
typescript
async provideDefinition(document, position) {
const word = getWordAtPosition(document, position);
if (!word) {
return undefined;
}
// 搜索工作区中的定义文件
const files = await vscode.workspace.findFiles(
'**/definitions/**/*.json',
'**/node_modules/**'
);
const locations: vscode.Location[] = [];
for (const file of files) {
const text = await readFileText(file);
const lines = text.split('\n');
lines.forEach((line, index) => {
if (line.includes(`"${word}"`)) {
locations.push(
new vscode.Location(
file,
new vscode.Position(index, line.indexOf(word))
)
);
}
});
}
return locations;
}导航体验优化
| 场景 | 处理 |
|---|---|
| 找不到定义 | 返回 undefined 或空数组 |
| 多定义展示 | 返回 Location 数组弹出选择 |
| 跳转精度 | 用 targetSelectionRange 定位名称 |
| 性能 | 缓存符号表,避免重复解析 |
常见问题
| 问题 | 处理 |
|---|---|
| F12 无响应 | 检查 provideDefinition 返回位置 |
| 跳转位置不准 | 精确计算 Range |
| 跨文件失败 | 确认 URI 正确 |
| 引用不完整 | 遍历所有行匹配 |
定义与引用跳转打通了「代码导航」链路,是构建语言理解能力的关键特性。