VSCode 核心 API
Workspace、TreeView、Webview 与 LSP
目录
Workspace API
文档操作
typescript
// 获取当前文档
const document = vscode.window.activeTextEditor?.document;
// 打开文档
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// 创建新文档
const newDoc = await vscode.workspace.openTextDocument({
content: 'Hello World',
language: 'javascript'
});
// 读取文档内容
const text = document.getText();
const line = document.lineAt(0).text;
const range = document.getText(new vscode.Range(0, 0, 10, 0));编辑操作
typescript
const editor = vscode.window.activeTextEditor;
if (editor) {
await editor.edit((editBuilder) => {
// 插入
editBuilder.insert(new vscode.Position(0, 0), 'Header\n');
// 替换
editBuilder.replace(
new vscode.Range(1, 0, 1, 10),
'New content'
);
// 删除
editBuilder.delete(new vscode.Range(5, 0, 6, 0));
});
}文件系统
typescript
// 查找文件
const files = await vscode.workspace.findFiles(
'**/*.ts', // 包含
'**/node_modules/**' // 排除
);
// 读写文件
const uri = vscode.Uri.file('/path/to/file.txt');
const content = await vscode.workspace.fs.readFile(uri);
await vscode.workspace.fs.writeFile(uri, Buffer.from('content'));
// 监听文件变化
const watcher = vscode.workspace.createFileSystemWatcher('**/*.json');
watcher.onDidCreate(uri => console.log('Created:', uri.path));
watcher.onDidChange(uri => console.log('Changed:', uri.path));
watcher.onDidDelete(uri => console.log('Deleted:', uri.path));
context.subscriptions.push(watcher);工作区事件
typescript
// 文档变化
vscode.workspace.onDidChangeTextDocument((e) => {
console.log('Document changed:', e.document.fileName);
e.contentChanges.forEach(change => {
console.log('Change:', change.text, change.range);
});
});
// 文档保存
vscode.workspace.onDidSaveTextDocument((doc) => {
console.log('Saved:', doc.fileName);
});
// 编辑器切换
vscode.window.onDidChangeActiveTextEditor((editor) => {
console.log('Active editor:', editor?.document.fileName);
});TreeView
定义视图
json
// package.json
{
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "myExtensionContainer",
"title": "My Extension",
"icon": "resources/icon.svg"
}
]
},
"views": {
"myExtensionContainer": [
{
"id": "myTreeView",
"name": "My Tree View",
"icon": "resources/tree.svg"
}
]
}
}
}实现 TreeDataProvider
typescript
interface TreeItem {
id: string;
label: string;
children?: TreeItem[];
}
class MyTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
private data: TreeItem[] = [
{
id: '1',
label: 'Item 1',
children: [
{ id: '1-1', label: 'Child 1' },
{ id: '1-2', label: 'Child 2' }
]
},
{ id: '2', label: 'Item 2' }
];
getTreeItem(element: TreeItem): vscode.TreeItem {
const treeItem = new vscode.TreeItem(
element.label,
element.children
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None
);
treeItem.id = element.id;
treeItem.contextValue = 'myItem';
treeItem.iconPath = new vscode.ThemeIcon('file');
treeItem.command = {
command: 'myExtension.selectItem',
title: 'Select Item',
arguments: [element]
};
return treeItem;
}
getChildren(element?: TreeItem): TreeItem[] {
if (!element) {
return this.data;
}
return element.children || [];
}
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
}
// 注册
const treeDataProvider = new MyTreeDataProvider();
vscode.window.registerTreeDataProvider('myTreeView', treeDataProvider);
// 或使用 createTreeView(更多控制)
const treeView = vscode.window.createTreeView('myTreeView', {
treeDataProvider,
showCollapseAll: true,
canSelectMany: true
});Webview
创建 Webview Panel
typescript
function createWebviewPanel(context: vscode.ExtensionContext) {
const panel = vscode.window.createWebviewPanel(
'myWebview', // 类型标识
'My Webview', // 标题
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, 'media')
]
}
);
panel.webview.html = getWebviewContent(panel.webview, context.extensionUri);
return panel;
}
function getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri): string {
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(extensionUri, 'media', 'main.js')
);
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(extensionUri, 'media', 'style.css')
);
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src ${webview.cspSource};">
<link href="${styleUri}" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="${scriptUri}"></script>
</body>
</html>`;
}Webview 通信
typescript
// 插件 → Webview
panel.webview.postMessage({ type: 'update', data: { count: 10 } });
// Webview → 插件
panel.webview.onDidReceiveMessage(
(message) => {
switch (message.type) {
case 'alert':
vscode.window.showInformationMessage(message.text);
break;
case 'save':
saveData(message.data);
break;
}
},
undefined,
context.subscriptions
);
// Webview 端 (main.js)
const vscode = acquireVsCodeApi();
// 发送消息
vscode.postMessage({ type: 'alert', text: 'Hello!' });
// 接收消息
window.addEventListener('message', (event) => {
const message = event.data;
if (message.type === 'update') {
updateUI(message.data);
}
});
// 状态持久化
vscode.setState({ count: 10 });
const state = vscode.getState();Language Server Protocol
概述
┌─────────────────┐ LSP ┌─────────────────┐
│ VSCode │ ◄──────────────────► │ Language │
│ (Client) │ JSON-RPC │ Server │
│ │ │ │
│ • UI 展示 │ │ • 语言分析 │
│ • 用户交互 │ │ • 代码补全 │
│ │ │ • 诊断信息 │
└─────────────────┘ └─────────────────┘客户端配置
typescript
import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
export function activate(context: vscode.ExtensionContext) {
const serverModule = context.asAbsolutePath(
path.join('server', 'out', 'server.js')
);
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc }
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'myLanguage' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/.myrc')
}
};
const client = new LanguageClient(
'myLanguageServer',
'My Language Server',
serverOptions,
clientOptions
);
client.start();
context.subscriptions.push(client);
}服务端示例
typescript
import {
createConnection,
TextDocuments,
ProposedFeatures,
InitializeResult,
TextDocumentSyncKind,
CompletionItem,
CompletionItemKind
} from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);
connection.onInitialize((): InitializeResult => {
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: { resolveProvider: true }
}
};
});
connection.onCompletion((): CompletionItem[] => {
return [
{ label: 'TypeScript', kind: CompletionItemKind.Text },
{ label: 'JavaScript', kind: CompletionItemKind.Text }
];
});
documents.listen(connection);
connection.listen();高频面试题
Q1: TreeView 如何实现数据刷新?
typescript
// 使用 EventEmitter 通知变化
private _onDidChangeTreeData = new vscode.EventEmitter<T | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
refresh(item?: T): void {
this._onDidChangeTreeData.fire(item); // 传 undefined 刷新整棵树
}Q2: Webview 安全注意事项?
- CSP:设置 Content-Security-Policy
- localResourceRoots:限制可访问的本地资源
- 禁用远程脚本:不加载外部 JS
- postMessage 验证:验证消息来源
Q3: LSP vs 直接实现的优势?
| 特性 | LSP | 直接实现 |
|---|---|---|
| 复用性 | 可用于多个编辑器 | 仅限 VSCode |
| 性能 | 独立进程,不阻塞 UI | 在主进程运行 |
| 复杂度 | 需要额外服务端 | 较简单 |
Q4: 如何在 Webview 中使用 React?
typescript
// 1. 构建 React 应用到 media 目录
// 2. 加载构建产物
function getWebviewContent(webview, extensionUri) {
const scriptUri = webview.asWebviewUri(
Uri.joinPath(extensionUri, 'media', 'app.js')
);
return `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="script-src ${webview.cspSource};">
</head>
<body>
<div id="root"></div>
<script src="${scriptUri}"></script>
</body>
</html>`;
}