我不知道的 VSCode 扩展(04)— API 全景与实战技巧
VSCode 扩展 API 的官方文档列了几百个接口,很多人第一次打开就晕了。其实这些 API 并不是平铺的,而是按职责域组织成几个大模块。理解这张地图,比记住每个方法名重要得多。
一、API 不是一个列表,而是一张地图
VSCode 扩展 API 的官方文档列了几百个接口,很多人第一次打开就晕了。其实这些 API 并不是平铺的,而是按职责域组织成几个大模块。理解这张地图,比记住每个方法名重要得多。
核心模块一共六个:
| 模块 | 职责 | 典型场景 |
|---|---|---|
vscode.window | 用户界面交互 | 弹框、选择器、状态栏、Webview、终端 |
vscode.workspace | 工作区与文件系统 | 读写文件、监听文件变化、读取配置 |
vscode.commands | 命令系统 | 注册命令、执行命令 |
vscode.languages | 语言特性 | 代码补全、悬浮提示、跳转定义、诊断 |
vscode.debug | 调试器 | 启动调试、管理断点 |
vscode.tasks | 任务系统 | 注册构建任务、运行脚本 |
下面按照”实际开发中最常用”的优先级,逐一拆解每个模块的核心用法。
二、vscode.window — 和用户对话
vscode.window 是使用频率最高的模块,因为几乎所有用户可见的操作都经过它。
消息框:三种级别
vscode.window.showInformationMessage('操作完成');
vscode.window.showWarningMessage('配置缺失,使用默认值');
vscode.window.showErrorMessage('连接失败');
消息框还可以带按钮,点击后返回选中的按钮文本:
const choice = await vscode.window.showWarningMessage(
'文件已修改,是否保存?',
'保存',
'不保存',
'取消',
);
if (choice === '保存') {
// ...
}
Quick Pick:下拉选择
const lang = await vscode.window.showQuickPick(['JavaScript', 'TypeScript', 'Python', 'Go'], {
placeHolder: '选择目标语言',
});
Quick Pick 还支持多选(canPickMany: true)和动态搜索(传入 Promise 或实现 QuickPickItem 接口)。
Input Box:文本输入
const name = await vscode.window.showInputBox({
prompt: '输入项目名称',
validateInput: (value) => {
return /^[a-z0-9-]+$/.test(value) ? null : '只允许小写字母、数字和连字符';
},
});
validateInput 回调实时校验输入,返回 null 表示合法,返回字符串表示错误信息——会直接显示在输入框下方。
终端操作
const terminal = vscode.window.createTerminal('Build');
terminal.show();
terminal.sendText('npm run build');
这里有一个很多人会忽略的细节——sendText 只是把文本”输入”到终端,相当于用户在终端里打字然后按回车。它不会等命令执行完毕,也拿不到命令的退出码。如果需要知道命令是否执行成功,得用 vscode.tasks 模块(后面会讲)。
三、vscode.workspace — 和文件系统打交道
获取工作区信息
const folders = vscode.workspace.workspaceFolders;
if (folders) {
const rootPath = folders[0].uri.fsPath;
}
VSCode 支持多根工作区(Multi-root Workspace),所以 workspaceFolders 是一个数组,不是单个路径。很多扩展写死了 workspaceFolders[0],在多根工作区下会出问题。
读写文件
const uri = vscode.Uri.file('/path/to/file.json');
// 读取
const content = await vscode.workspace.fs.readFile(uri);
const text = Buffer.from(content).toString('utf-8');
// 写入
const data = Buffer.from(JSON.stringify({ key: 'value' }), 'utf-8');
await vscode.workspace.fs.writeFile(uri, data);
vscode.workspace.fs 是 VSCode 提供的虚拟文件系统 API,不仅支持本地文件,还支持远程文件(比如 SSH Remote 场景)。用 Node.js 的 fs 模块虽然也能读写本地文件,但在远程开发场景下会失效。
监听文件变化
const watcher = vscode.workspace.createFileSystemWatcher('**/*.json');
watcher.onDidChange((uri) => {
console.log(`Changed: ${uri.fsPath}`);
});
watcher.onDidCreate((uri) => {
console.log(`Created: ${uri.fsPath}`);
});
watcher.onDidDelete((uri) => {
console.log(`Deleted: ${uri.fsPath}`);
});
Glob 模式 **/*.json 表示监听工作区内所有 JSON 文件的变化。
读取和监听配置
// 读取配置
const config = vscode.workspace.getConfiguration('myExtension');
const autoSave = config.get<boolean>('autoSave', true);
// 监听配置变化
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('myExtension.autoSave')) {
const newValue = config.get<boolean>('autoSave');
// 重新应用配置
}
});
配置项需要先在 package.json 的 contributes.configuration 中声明,然后才能通过 getConfiguration 读取。
四、TextEditor — 操作编辑器内容
vscode.window.activeTextEditor 返回当前激活的编辑器实例。通过它可以读取内容、修改文本、设置选择区域。
获取选中文本
const editor = vscode.window.activeTextEditor;
if (editor) {
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
}
插入或替换文本
editor.edit((editBuilder) => {
// 在光标位置插入
editBuilder.insert(editor.selection.active, 'inserted text');
// 替换选中区域
editBuilder.replace(editor.selection, 'replaced text');
// 删除指定范围
editBuilder.delete(new vscode.Range(0, 0, 0, 10));
});
editor.edit() 接收一个回调,回调参数 editBuilder 提供 insert、replace、delete 三个操作。所有修改在回调执行完后作为一次原子操作应用——要么全部成功,要么全部失败,不会出现修改到一半的中间状态。
文本装饰器(Decorations)
文本装饰器可以给编辑器中的文本添加视觉效果——高亮、下划线、背景色、边框等。
const decorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(255, 255, 0, 0.2)',
border: '1px solid rgba(255, 255, 0, 0.5)',
});
function highlightKeyword(editor: vscode.TextEditor, keyword: string) {
const text = editor.document.getText();
const decorations: vscode.DecorationOptions[] = [];
const regex = new RegExp(keyword, 'gi');
let match;
while ((match = regex.exec(text))) {
const start = editor.document.positionAt(match.index);
const end = editor.document.positionAt(match.index + match[0].length);
decorations.push({
range: new vscode.Range(start, end),
hoverMessage: `Found: **${keyword}**`,
});
}
editor.setDecorations(decorationType, decorations);
}
说白了,Decorations 就是给编辑器文本”贴标签”的能力。GitLens 的行内 blame 信息、错误波浪线、搜索高亮,底层都是 Decorations。
五、vscode.languages — 语言智能
vscode.languages 模块是实现代码智能的入口——补全、悬浮提示、跳转定义、诊断、格式化。
代码补全
vscode.languages.registerCompletionItemProvider('typescript', {
provideCompletionItems(document, position) {
const item = new vscode.CompletionItem('mySnippet');
item.kind = vscode.CompletionItemKind.Snippet;
item.insertText = new vscode.SnippetString('console.log("${1:message}", ${2:value});');
item.documentation = '插入一条带参数的 console.log';
return [item];
},
});
SnippetString 支持占位符语法(${1:default}),和 VSCode 内置 Snippet 的语法一致。
诊断信息(波浪线)
const diagnostics = vscode.languages.createDiagnosticCollection('myLinter');
function lint(document: vscode.TextDocument) {
const problems: vscode.Diagnostic[] = [];
const text = document.getText();
// 查找 console.log 并标记为警告
const regex = /console\.log/g;
let match;
while ((match = regex.exec(text))) {
const start = document.positionAt(match.index);
const end = document.positionAt(match.index + match[0].length);
problems.push(
new vscode.Diagnostic(
new vscode.Range(start, end),
'生产代码中不建议保留 console.log',
vscode.DiagnosticSeverity.Warning,
),
);
}
diagnostics.set(document.uri, problems);
}
DiagnosticCollection 是编辑器中波浪线(红色 = Error、黄色 = Warning、蓝色 = Information)的来源。ESLint 扩展在编辑器里标记的问题,底层就是通过这个 API 注入的。
Language Server Protocol(LSP)
当语言智能的逻辑比较复杂(比如需要解析整个项目的类型信息),在扩展进程里直接跑会阻塞 UI。这时候应该把语言分析逻辑拆到一个独立的 Language Server 进程中,通过 LSP 协议与 VSCode 通信。
import { LanguageClient, TransportKind } from 'vscode-languageclient/node';
const serverModule = context.asAbsolutePath('server/out/server.js');
const client = new LanguageClient(
'myLanguageServer',
'My Language Server',
{
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc },
},
{
documentSelector: [{ scheme: 'file', language: 'myLang' }],
},
);
client.start();
问题的关键在于——LSP 不是”高级功能”,而是”正确做法”。如果语言分析逻辑超过几百毫秒,就应该走 LSP,否则会让整个扩展进程卡住,影响所有已安装扩展的响应速度。
六、vscode.tasks — 运行构建任务
和 terminal.sendText 不同,vscode.tasks 提供了结构化的任务执行能力——可以追踪任务状态、解析输出中的错误信息、在”问题”面板中显示。
const task = new vscode.Task(
{ type: 'myBuild' },
vscode.TaskScope.Workspace,
'Build',
'my-ext',
new vscode.ShellExecution('npm run build'),
);
task.problemMatchers = ['$tsc'];
await vscode.tasks.executeTask(task);
problemMatchers 是任务系统的精华——它告诉 VSCode 如何从任务输出中提取错误和警告信息。$tsc 是 TypeScript 编译器的内置匹配器,能把 error TS2304: Cannot find name 'foo' 这样的输出解析成编辑器中的红色波浪线。
换句话说,终端 API 是”发射后不管”,任务 API 是”发射后跟踪”。
七、API 地图总结
如果你只记住一句话:window 管 UI,workspace 管文件,languages 管智能,commands 管命令,tasks 管构建,debug 管调试——六个模块,覆盖 95% 的扩展场景。
| 要做什么 | 用哪个模块 |
|---|---|
| 弹框、选择、输入 | vscode.window.show* |
| 操作编辑器文本 | vscode.window.activeTextEditor.edit() |
| 读写文件 | vscode.workspace.fs |
| 监听文件变化 | vscode.workspace.createFileSystemWatcher |
| 读取用户配置 | vscode.workspace.getConfiguration |
| 代码补全/悬浮/跳转 | vscode.languages.register*Provider |
| 标记代码问题 | vscode.languages.createDiagnosticCollection |
| 运行命令 | vscode.commands.executeCommand |
| 运行构建任务 | vscode.tasks.executeTask |
| 创建终端 | vscode.window.createTerminal |
本系列其他文章:
- 上一篇:Webview 与 Output Channel
- 下一篇:生命周期与激活策略
延伸阅读: