Files
IC-Coder-Plugin/docs/VSCode-Extension-API-Guide.md
2026-03-04 18:58:18 +08:00

19 KiB
Raw Blame History

VS Code Extension API 核心知识点

目录


1. Extension 生命周期

1.1 核心函数 🔥必考

// extension.ts
import * as vscode from 'vscode';

// 插件激活时调用(只调用一次)
export function activate(context: vscode.ExtensionContext) {
    console.log('Extension is now active!');

    // 注册命令、视图、事件监听等
    // 使用 context.subscriptions 管理资源
}

// 插件停用时调用(清理资源)
export function deactivate() {
    console.log('Extension is deactivated');
    // 清理资源、关闭连接等
}

1.2 ExtensionContext 重要属性 🔥必考

interface ExtensionContext {
    // 插件订阅管理(自动清理)
    subscriptions: { dispose(): any }[];

    // 工作区存储路径
    storageUri: vscode.Uri | undefined;
    globalStorageUri: vscode.Uri;

    // 插件路径
    extensionUri: vscode.Uri;
    extensionPath: string;

    // 状态存储
    workspaceState: Memento;  // 工作区级别
    globalState: Memento;     // 全局级别
    secrets: SecretStorage;   // 敏感信息存储

    // 环境变量
    environmentVariableCollection: EnvironmentVariableCollection;
}

1.3 资源管理最佳实践 🔥必考

export function activate(context: vscode.ExtensionContext) {
    // ✅ 推荐:使用 context.subscriptions 自动管理
    context.subscriptions.push(
        vscode.commands.registerCommand('extension.command', () => {})
    );

    // ❌ 不推荐:手动管理容易忘记清理
    const disposable = vscode.commands.registerCommand('extension.command', () => {});
    // 需要在 deactivate 中手动调用 disposable.dispose()
}

2. 激活事件 (Activation Events)

2.1 常用激活事件 📌重要

// package.json
{
    "activationEvents": [
        // 启动时激活
        "onStartupFinished",

        // 执行命令时激活
        "onCommand:extension.helloWorld",

        // 打开特定语言文件时激活
        "onLanguage:javascript",
        "onLanguage:verilog",

        // 打开特定文件类型时激活
        "onFileSystem:sftp",

        // 打开特定视图时激活
        "onView:myCustomView",

        // 调试时激活
        "onDebug",

        // 打开特定 URI 时激活
        "onUri",

        // Webview 恢复时激活
        "onWebviewPanel:myWebview",

        // 任务执行时激活
        "onTaskType:npm"
    ]
}

2.2 延迟激活策略 🔥必考

// ✅ 推荐:使用 onStartupFinished 延迟激活
"activationEvents": ["onStartupFinished"]

// ❌ 不推荐:使用 * 会拖慢启动速度
"activationEvents": ["*"]

3. 命令系统 (Commands)

3.1 注册命令

// 注册简单命令
const disposable = vscode.commands.registerCommand(
    'extension.helloWorld',
    () => {
        vscode.window.showInformationMessage('Hello World!');
    }
);
context.subscriptions.push(disposable);

// 注册带参数的命令
vscode.commands.registerCommand(
    'extension.openFile',
    (filePath: string) => {
        vscode.workspace.openTextDocument(filePath).then(doc => {
            vscode.window.showTextDocument(doc);
        });
    }
);

3.2 执行命令

// 执行内置命令
await vscode.commands.executeCommand('workbench.action.files.save');

// 执行自定义命令
await vscode.commands.executeCommand('extension.openFile', '/path/to/file');

// 获取所有可用命令
const commands = await vscode.commands.getCommands();

3.3 常用内置命令

// 文件操作
'workbench.action.files.save'
'workbench.action.files.saveAll'
'workbench.action.closeActiveEditor'

// 编辑器操作
'editor.action.formatDocument'
'editor.action.commentLine'
'editor.action.selectAll'

// 窗口操作
'workbench.action.toggleSidebarVisibility'
'workbench.action.terminal.new'
'workbench.action.quickOpen'

// Git 操作
'git.commit'
'git.push'
'git.pull'

4. Webview API 面试重点

4.1 创建 Webview Panel 🔥必考

const panel = vscode.window.createWebviewPanel(
    'myWebview',              // viewType唯一标识
    'My Webview',             // 标题
    vscode.ViewColumn.One,    // 显示位置
    {
        enableScripts: true,  // 启用 JavaScript
        retainContextWhenHidden: true,  // 隐藏时保留状态
        localResourceRoots: [  // 允许访问的本地资源路径
            vscode.Uri.joinPath(context.extensionUri, 'media')
        ]
    }
);

4.2 设置 Webview 内容

panel.webview.html = getWebviewContent();

function getWebviewContent() {
    return `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>My Webview</title>
    </head>
    <body>
        <h1>Hello from Webview!</h1>
        <button onclick="sendMessage()">Send Message</button>

        <script>
            const vscode = acquireVsCodeApi();

            function sendMessage() {
                vscode.postMessage({
                    command: 'alert',
                    text: 'Hello from Webview!'
                });
            }

            // 接收来自 Extension 的消息
            window.addEventListener('message', event => {
                const message = event.data;
                console.log('Received:', message);
            });
        </script>
    </body>
    </html>`;
}

4.3 Webview 消息通信 🔥必考(项目核心)

// Extension → Webview
panel.webview.postMessage({
    command: 'update',
    data: 'some data'
});

// Webview → Extension
panel.webview.onDidReceiveMessage(
    message => {
        switch (message.command) {
            case 'alert':
                vscode.window.showInformationMessage(message.text);
                break;
            case 'getData':
                // 处理数据请求
                panel.webview.postMessage({
                    command: 'dataResponse',
                    data: fetchData()
                });
                break;
        }
    },
    undefined,
    context.subscriptions
);

4.4 Webview 生命周期管理 📌重要

// 监听 Webview 关闭事件
panel.onDidDispose(
    () => {
        // 清理资源
        console.log('Webview disposed');
    },
    null,
    context.subscriptions
);

// 监听 Webview 可见性变化
panel.onDidChangeViewState(
    e => {
        if (e.webviewPanel.visible) {
            console.log('Webview is now visible');
        }
    },
    null,
    context.subscriptions
);

4.5 加载本地资源 📌重要

// 获取本地资源 URI
const scriptUri = panel.webview.asWebviewUri(
    vscode.Uri.joinPath(context.extensionUri, 'media', 'script.js')
);

const styleUri = panel.webview.asWebviewUri(
    vscode.Uri.joinPath(context.extensionUri, 'media', 'style.css')
);

// 在 HTML 中使用
const html = `
    <link href="${styleUri}" rel="stylesheet">
    <script src="${scriptUri}"></script>
`;

4.6 Webview 状态持久化 📌重要

// Webview 中保存状态
const vscode = acquireVsCodeApi();
const state = vscode.getState() || { count: 0 };

// 更新状态
state.count++;
vscode.setState(state);

// Extension 中序列化状态
panel.webview.options = {
    enableScripts: true,
    retainContextWhenHidden: true
};

// 恢复 Webview
vscode.window.registerWebviewPanelSerializer('myWebview', {
    async deserializeWebviewPanel(webviewPanel, state) {
        webviewPanel.webview.html = getWebviewContent();
        // 恢复状态
        webviewPanel.webview.postMessage({ command: 'restore', state });
    }
});

5. TreeView 和自定义视图

5.1 创建 TreeView Provider

class MyTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {
    private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
    readonly onDidChangeTreeData = this._onDidChangeTreeData.event;

    refresh(): void {
        this._onDidChangeTreeData.fire(undefined);
    }

    getTreeItem(element: TreeItem): vscode.TreeItem {
        return element;
    }

    getChildren(element?: TreeItem): Thenable<TreeItem[]> {
        if (!element) {
            // 返回根节点
            return Promise.resolve([
                new TreeItem('Item 1', vscode.TreeItemCollapsibleState.None),
                new TreeItem('Item 2', vscode.TreeItemCollapsibleState.Collapsed)
            ]);
        }
        // 返回子节点
        return Promise.resolve([]);
    }
}

class TreeItem extends vscode.TreeItem {
    constructor(
        public readonly label: string,
        public readonly collapsibleState: vscode.TreeItemCollapsibleState
    ) {
        super(label, collapsibleState);
        this.tooltip = `Tooltip for ${label}`;
        this.command = {
            command: 'extension.itemClicked',
            title: 'Click Item',
            arguments: [this]
        };
    }
}

5.2 注册 TreeView

const treeDataProvider = new MyTreeDataProvider();
const treeView = vscode.window.createTreeView('myTreeView', {
    treeDataProvider,
    showCollapseAll: true
});

context.subscriptions.push(treeView);

// 刷新视图
treeDataProvider.refresh();

5.3 WebviewView Provider侧边栏 Webview

class MyWebviewViewProvider implements vscode.WebviewViewProvider {
    resolveWebviewView(
        webviewView: vscode.WebviewView,
        context: vscode.WebviewViewResolveContext,
        token: vscode.CancellationToken
    ) {
        webviewView.webview.options = {
            enableScripts: true
        };

        webviewView.webview.html = getWebviewContent();

        webviewView.webview.onDidReceiveMessage(message => {
            // 处理消息
        });
    }
}

// 注册
vscode.window.registerWebviewViewProvider(
    'myWebviewView',
    new MyWebviewViewProvider()
);

6. 文件系统操作

6.1 读取文件 📌重要

// 读取文本文件
const uri = vscode.Uri.file('/path/to/file.txt');
const content = await vscode.workspace.fs.readFile(uri);
const text = Buffer.from(content).toString('utf8');

// 使用 TextDocument API
const document = await vscode.workspace.openTextDocument(uri);
const text = document.getText();

6.2 写入文件

// 写入文件
const uri = vscode.Uri.file('/path/to/file.txt');
const content = Buffer.from('Hello World', 'utf8');
await vscode.workspace.fs.writeFile(uri, content);

// 使用 WorkspaceEdit
const edit = new vscode.WorkspaceEdit();
edit.createFile(uri, { overwrite: true });
edit.insert(uri, new vscode.Position(0, 0), 'Hello World');
await vscode.workspace.applyEdit(edit);

6.3 文件监听

// 监听文件变化
const watcher = vscode.workspace.createFileSystemWatcher('**/*.js');

watcher.onDidCreate(uri => {
    console.log('File created:', uri.fsPath);
});

watcher.onDidChange(uri => {
    console.log('File changed:', uri.fsPath);
});

watcher.onDidDelete(uri => {
    console.log('File deleted:', uri.fsPath);
});

context.subscriptions.push(watcher);

6.4 工作区操作

// 获取工作区文件夹
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders) {
    const rootPath = workspaceFolders[0].uri.fsPath;
}

// 查找文件
const files = await vscode.workspace.findFiles(
    '**/*.ts',      // include pattern
    '**/node_modules/**'  // exclude pattern
);

// 打开文件
const document = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(document);

7. 配置和存储 面试重点

7.1 读取配置 📌重要

// 读取配置
const config = vscode.workspace.getConfiguration('myExtension');
const value = config.get<string>('settingName', 'defaultValue');

// 监听配置变化
vscode.workspace.onDidChangeConfiguration(e => {
    if (e.affectsConfiguration('myExtension.settingName')) {
        console.log('Configuration changed');
    }
});

7.2 更新配置

const config = vscode.workspace.getConfiguration('myExtension');

// 更新用户配置(全局)
await config.update('settingName', 'newValue', vscode.ConfigurationTarget.Global);

// 更新工作区配置
await config.update('settingName', 'newValue', vscode.ConfigurationTarget.Workspace);

7.3 状态存储 🔥必考

// 工作区状态(仅当前工作区)
await context.workspaceState.update('key', 'value');
const value = context.workspaceState.get('key');

// 全局状态(跨工作区)
await context.globalState.update('key', 'value');
const value = context.globalState.get('key');

// 存储对象
await context.globalState.update('userData', { name: 'John', age: 30 });

7.4 敏感信息存储 🔥必考Token 管理)

// 存储密码、Token 等敏感信息
await context.secrets.store('apiToken', 'secret-token-value');

// 读取
const token = await context.secrets.get('apiToken');

// 删除
await context.secrets.delete('apiToken');

// 监听变化
context.secrets.onDidChange(e => {
    console.log('Secret changed:', e.key);
});

8. 消息通知

8.1 信息提示

// 普通信息
vscode.window.showInformationMessage('Operation completed!');

// 警告
vscode.window.showWarningMessage('This action may cause issues');

// 错误
vscode.window.showErrorMessage('Operation failed!');

8.2 带按钮的提示

const result = await vscode.window.showInformationMessage(
    'Do you want to continue?',
    'Yes',
    'No',
    'Cancel'
);

if (result === 'Yes') {
    // 用户点击了 Yes
}

8.3 输入框

// 简单输入
const input = await vscode.window.showInputBox({
    prompt: 'Enter your name',
    placeHolder: 'John Doe',
    validateInput: (value) => {
        return value.length < 3 ? 'Name too short' : null;
    }
});

// 快速选择
const selected = await vscode.window.showQuickPick(
    ['Option 1', 'Option 2', 'Option 3'],
    {
        placeHolder: 'Select an option',
        canPickMany: false
    }
);

8.4 进度提示

await vscode.window.withProgress(
    {
        location: vscode.ProgressLocation.Notification,
        title: 'Processing...',
        cancellable: true
    },
    async (progress, token) => {
        token.onCancellationRequested(() => {
            console.log('User canceled');
        });

        progress.report({ increment: 0, message: 'Starting...' });
        await doWork1();

        progress.report({ increment: 50, message: 'Half done...' });
        await doWork2();

        progress.report({ increment: 100, message: 'Complete!' });
    }
);

9. 语言特性支持

9.1 代码补全

const provider = vscode.languages.registerCompletionItemProvider(
    'javascript',
    {
        provideCompletionItems(document, position) {
            const item = new vscode.CompletionItem('myFunction');
            item.kind = vscode.CompletionItemKind.Function;
            item.detail = 'My custom function';
            item.documentation = 'This is a custom function';
            item.insertText = new vscode.SnippetString('myFunction($1)$0');

            return [item];
        }
    },
    '.'  // 触发字符
);

context.subscriptions.push(provider);

9.2 悬停提示

const provider = vscode.languages.registerHoverProvider('javascript', {
    provideHover(document, position) {
        const range = document.getWordRangeAtPosition(position);
        const word = document.getText(range);

        return new vscode.Hover([
            `**${word}**`,
            'This is a hover tooltip'
        ]);
    }
});

9.3 诊断(错误提示)

const diagnosticCollection = vscode.languages.createDiagnosticCollection('myExtension');
context.subscriptions.push(diagnosticCollection);

function updateDiagnostics(document: vscode.TextDocument) {
    const diagnostics: vscode.Diagnostic[] = [];

    const text = document.getText();
    const regex = /TODO/g;
    let match;

    while ((match = regex.exec(text))) {
        const range = new vscode.Range(
            document.positionAt(match.index),
            document.positionAt(match.index + match[0].length)
        );

        const diagnostic = new vscode.Diagnostic(
            range,
            'TODO found',
            vscode.DiagnosticSeverity.Warning
        );

        diagnostics.push(diagnostic);
    }

    diagnosticCollection.set(document.uri, diagnostics);
}

10. 调试和诊断

10.1 输出通道

const outputChannel = vscode.window.createOutputChannel('My Extension');
context.subscriptions.push(outputChannel);

outputChannel.appendLine('Extension activated');
outputChannel.show();  // 显示输出面板

10.2 日志记录

// 使用 LogOutputChannel带时间戳
const logger = vscode.window.createOutputChannel('My Extension', { log: true });

logger.trace('Trace message');
logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

10.3 错误处理

try {
    await riskyOperation();
} catch (error) {
    if (error instanceof Error) {
        vscode.window.showErrorMessage(`Error: ${error.message}`);
        logger.error(error.stack || error.message);
    }
}

最佳实践总结

推荐做法

  1. 资源管理:所有 disposable 对象都放入 context.subscriptions
  2. 延迟激活:使用 onStartupFinished 而不是 *
  3. 异步操作:使用 async/await 处理异步操作
  4. 错误处理:捕获异常并给用户友好提示
  5. 类型安全:充分利用 TypeScript 类型系统
  6. 状态持久化:使用 globalState/workspaceState 保存状态
  7. 敏感信息:使用 secrets API 存储 Token、密码等

避免做法

  1. 不要在 activate 中执行耗时操作
  2. 不要忘记清理资源监听器、Webview 等)
  3. 不要在 Webview 中直接访问文件系统
  4. 不要在配置中存储敏感信息
  5. 不要阻塞主线程(使用 Worker 或异步操作)

参考资源