Visual Studio Code因其强大的扩展生态系统而深受开发者喜爱。通过创建自定义扩展,可以进一步提升开发效率。本文将详细介绍VS Code扩展开发的全过程。

1. 开发环境搭建

基础工具安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 安装Node.js (推荐v16+)
node --version

# 安装Yeoman和VS Code扩展生成器
npm install -g yo generator-code

# 安装TypeScript编译器
npm install -g typescript

# 安装vsce (VS Code扩展打包工具)
npm install -g vsce

创建扩展项目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 使用生成器创建新扩展
yo code

# 选择扩展类型:
# ? What type of extension do you want to create?
# ❯ New Extension (TypeScript)
#   New Extension (JavaScript)
#   New Color Theme
#   New Language Support
#   New Code Snippets
#   New Keymap
#   New Extension Pack

生成的项目结构:

my-extension/
├── .vscode/
│   ├── launch.json          # 调试配置
│   ├── tasks.json           # 构建任务
│   └── settings.json        # VS Code设置
├── src/
│   ├── extension.ts         # 主扩展文件
│   └── test/
│       └── extension.test.ts # 测试文件
├── package.json             # 扩展配置
├── tsconfig.json           # TypeScript配置
├── README.md                # 扩展说明
└── CHANGELOG.md             # 更新日志

2. 扩展配置文件

package.json详解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
{
  "name": "my-awesome-extension",
  "displayName": "My Awesome Extension",
  "description": "A helpful description of what this extension does",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.75.0"
  },
  "categories": [
    "Other",
    "Snippets",
    "Linters"
  ],
  "activationEvents": [
    "onCommand:extension.myCommand",
    "onLanguage:javascript"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "extension.myCommand",
        "title": "My Command",
        "category": "My Extension"
      }
    ],
    "keybindings": [
      {
        "command": "extension.myCommand",
        "key": "ctrl+shift+u",
        "mac": "cmd+shift+u"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "extension.myCommand",
          "group": "navigation"
        }
      ]
    },
    "configuration": {
      "title": "My Extension",
      "properties": {
        "myExtension.enable": {
          "type": "boolean",
          "default": true,
          "description": "Enable my extension"
        }
      }
    }
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "npm run compile && npm run lint",
    "lint": "eslint src --ext ts",
    "test": "node ./out/test/runTest.js"
  },
  "devDependencies": {
    "@types/vscode": "^1.75.0",
    "@types/mocha": "^10.0.1",
    "@types/node": "16.x",
    "typescript": "^4.9.4",
    "eslint": "^8.28.0",
    "@typescript-eslint/eslint-plugin": "^5.45.0",
    "@typescript-eslint/parser": "^5.45.0",
    "mocha": "^10.1.0"
  },
  "publisher": "your-publisher-name"
}

TypeScript配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2020",
    "outDir": "out",
    "lib": ["ES2020"],
    "sourceMap": true,
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "exclude": ["node_modules", ".vscode-test"]
}

3. 扩展核心API

基础命令注册

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// src/extension.ts
import * as vscode from 'vscode';

// 激活函数
export function activate(context: vscode.ExtensionContext) {
    console.log('Extension "my-extension" is now active!');

    // 注册命令
    const disposable = vscode.commands.registerCommand(
        'extension.helloWorld',
        () => {
            vscode.window.showInformationMessage('Hello World from My Extension!');
        }
    );

    context.subscriptions.push(disposable);

    // 注册复杂命令
    const advancedCommand = vscode.commands.registerCommand(
        'extension.openFile',
        async (uri?: vscode.Uri) => {
            if (uri) {
                const document = await vscode.workspace.openTextDocument(uri);
                await vscode.window.showTextDocument(document);
            } else {
                const fileUri = await vscode.window.showOpenDialog({
                    canSelectMany: false,
                    openLabel: 'Select a file to open'
                });

                if (fileUri) {
                    const document = await vscode.workspace.openTextDocument(fileUri[0]);
                    await vscode.window.showTextDocument(document);
                }
            }
        }
    );

    context.subscriptions.push(advancedCommand);
}

// 停用函数
export function deactivate() {}

状态栏和状态项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 添加状态栏项
const statusBarItem = vscode.window.createStatusBarItem(
    vscode.StatusBarAlignment.Right,
    100
);

statusBarItem.text = '$(sync~spin) Loading...';
statusBarItem.tooltip = 'My Extension Status';
statusBarItem.command = 'extension.showStatus';

context.subscriptions.push(statusBarItem);

// 更新状态栏
function updateStatus(text: string, tooltip?: string) {
    statusBarItem.text = text;
    if (tooltip) {
        statusBarItem.tooltip = tooltip;
    }
}

// 定时更新状态
let updateInterval: NodeJS.Timer;

function startStatusUpdates() {
    updateInterval = setInterval(() => {
        const time = new Date().toLocaleTimeString();
        updateStatus(`$(clock) ${time}`, `当前时间: ${time}`);
    }, 1000);
}

// 清理资源
function stopStatusUpdates() {
    if (updateInterval) {
        clearInterval(updateInterval);
    }
}

输出通道

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 创建输出通道
const outputChannel = vscode.window.createOutputChannel(
    'My Extension',
    { log: true }
);

context.subscriptions.push(outputChannel);

// 写入输出
outputChannel.appendLine('Extension started');
outputChannel.appendLine(`Timestamp: ${new Date().toISOString()}`);

// 显示输出通道
vscode.commands.registerCommand('extension.showOutput', () => {
    outputChannel.show();
});

// 不同级别的日志
function log(level: 'info' | 'warn' | 'error', message: string) {
    const timestamp = new Date().toISOString();
    const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;

    outputChannel.appendLine(logMessage);

    if (level === 'error') {
        vscode.window.showErrorMessage(message);
    } else if (level === 'warn') {
        vscode.window.showWarningMessage(message);
    }
}

配置和设置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取扩展配置
function getConfig<T>(key: string): T | undefined {
    const config = vscode.workspace.getConfiguration('myExtension');
    return config.get<T>(key);
}

// 监听配置变化
vscode.workspace.onDidChangeConfiguration(event => {
    if (event.affectsConfiguration('myExtension')) {
        const newConfig = getConfig('enable');
        vscode.window.showInformationMessage(
            `Extension ${newConfig ? 'enabled' : 'disabled'}`
        );
    }
});

// 设置配置
function setConfig(key: string, value: any) {
    const config = vscode.workspace.getConfiguration('myExtension');
    return config.update(key, value, vscode.ConfigurationTarget.Global);
}

4. 编辑器功能扩展

文档编辑器修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 注册文本编辑器命令
vscode.commands.registerCommand(
    'extension.insertCurrentTime',
    () => {
        const editor = vscode.window.activeTextEditor;
        if (editor) {
            const currentTime = new Date().toLocaleString();
            editor.edit(editBuilder => {
                const position = editor.selection.active;
                editBuilder.insert(position, currentTime);
            });
        }
    }
);

// 文档内容修改
vscode.commands.registerCommand(
    'extension.formatDocument',
    async () => {
        const editor = vscode.window.activeTextEditor;
        if (editor) {
            const document = editor.document;
            const fullRange = new vscode.Range(
                document.positionAt(0),
                document.positionAt(document.getText().length)
            );

            const formattedText = formatCode(document.getText());

            await editor.edit(editBuilder => {
                editBuilder.replace(fullRange, formattedText);
            });
        }
    }
);

function formatCode(code: string): string {
    // 简单的格式化逻辑
    return code
        .split('\n')
        .map(line => line.trim())
        .join('\n');
}

装饰器(Decorations)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 创建装饰类型
const decorationType = vscode.window.createTextEditorDecorationType({
    backgroundColor: new vscode.ThemeColor('editor.wordHighlightBackground'),
    border: '1px solid',
    borderColor: new vscode.ThemeColor('editor.wordHighlightBorder'),
    borderRadius: '3px'
});

// 应用装饰
function highlightWords(editor: vscode.TextEditor, words: string[]) {
    const text = editor.document.getText();
    const decorations: vscode.DecorationOptions[] = [];

    words.forEach(word => {
        const regex = new RegExp(word, 'gi');
        let match;

        while ((match = regex.exec(text)) !== null) {
            const startPos = editor.document.positionAt(match.index);
            const endPos = editor.document.positionAt(match.index + match[0].length);

            decorations.push({
                range: new vscode.Range(startPos, endPos),
                hoverMessage: `Found word: ${word}`,
                isWholeLine: false
            });
        }
    });

    editor.setDecorations(decorationType, decorations);
}

// 清除装饰
function clearDecorations(editor: vscode.TextEditor) {
    editor.setDecorations(decorationType, []);
}

代码完成提供者

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 注册代码完成提供者
const completionProvider = vscode.languages.registerCompletionItemProvider(
    { scheme: 'file', language: 'javascript' },
    {
        provideCompletionItems(
            document: vscode.TextDocument,
            position: vscode.Position,
            token: vscode.CancellationToken,
            context: vscode.CompletionContext
        ): vscode.CompletionItem[] {
            const completions: vscode.CompletionItem[] = [];

            // 自定义代码片段
            const snippet = new vscode.CompletionItem(
                'myFunction',
                vscode.CompletionItemKind.Snippet
            );

            snippet.insertText = new vscode.SnippetString(
                'function ${1:functionName}(${2:parameters}) {\n\t${3:// function body}\n}'
            );

            snippet.documentation = 'Creates a new function';
            snippet.detail = 'Custom function template';

            completions.push(snippet);

            // 根据上下文提供不同的完成项
            const linePrefix = document.getText(
                new vscode.Range(position.line, 0, position.line, position.character)
            );

            if (linePrefix.includes('console.')) {
                completions.push(
                    new vscode.CompletionItem(
                        'logCustom',
                        vscode.CompletionItemKind.Method
                    )
                );
            }

            return completions;
        }
    }
);

context.subscriptions.push(completionProvider);

悬停提示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 注册悬停提示提供者
const hoverProvider = vscode.languages.registerHoverProvider(
    { scheme: 'file', language: 'typescript' },
    {
        provideHover(
            document: vscode.TextDocument,
            position: vscode.Position,
            token: vscode.CancellationToken
        ): vscode.Hover | null {
            const word = document.getText(
                document.getWordRangeAtPosition(position)
            );

            if (word && ['API', 'HTTP', 'JSON'].includes(word)) {
                const markdown = new vscode.MarkdownString();
                markdown.appendMarkdown(`### ${word}\n\n`);

                switch (word) {
                    case 'API':
                        markdown.appendMarkdown(
                            'Application Programming Interface\n\n' +
                            '应用程序编程接口,定义了软件组件之间的通信方式'
                        );
                        break;
                    case 'HTTP':
                        markdown.appendMarkdown(
                            'HyperText Transfer Protocol\n\n' +
                            '超文本传输协议,用于传输超媒体文档'
                        );
                        break;
                    case 'JSON':
                        markdown.appendMarkdown(
                            'JavaScript Object Notation\n\n' +
                            '轻量级的数据交换格式'
                        );
                        break;
                }

                return new vscode.Hover(markdown);
            }

            return null;
        }
    }
);

context.subscriptions.push(hoverProvider);

5. WebView扩展

创建WebView

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
// WebView Panel类
class MyWebView {
    private readonly _panel: vscode.WebviewPanel;
    private readonly _extensionUri: vscode.Uri;

    constructor(extensionUri: vscode.Uri) {
        this._extensionUri = extensionUri;

        // 创建Webview面板
        this._panel = vscode.window.createWebviewPanel(
            'myWebView',
            'My WebView',
            vscode.ViewColumn.One,
            {
                enableScripts: true,
                retainContextWhenHidden: true
            }
        );

        // 设置HTML内容
        this._panel.webview.html = this._getHtmlForWebview();

        // 监听消息
        this._panel.webview.onDidReceiveMessage(
            message => this._onMessage(message)
        );
    }

    private _getHtmlForWebview(): string {
        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>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
        }
        button {
            background: #007acc;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #005a9e;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>My WebView</h1>
        <p>This is a custom WebView panel.</p>
        <button onclick="sendMessage()">Send Message</button>
        <div id="response"></div>
    </div>

    <script>
        const vscode = acquireVsCodeApi();

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

        window.addEventListener('message', event => {
            const message = event.data;
            switch (message.command) {
                case 'update':
                    document.getElementById('response').textContent = message.text;
                    break;
            }
        });
    </script>
</body>
</html>`;
    }

    private _onMessage(message: any) {
        switch (message.command) {
            case 'hello':
                vscode.window.showInformationMessage(
                    `WebView says: ${message.text}`
                );

                // 发送响应到WebView
                this._panel.webview.postMessage({
                    command: 'update',
                    text: 'Extension received your message!'
                });
                break;
        }
    }
}

// 注册WebView命令
vscode.commands.registerCommand(
    'extension.openWebView',
    () => {
        const webView = new MyWebView(context.extensionUri);
    }
);

WebView与扩展通信

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 更复杂的WebView通信
class AdvancedWebView {
    private _panel: vscode.WebviewPanel;

    constructor(extensionUri: vscode.Uri) {
        this._panel = vscode.window.createWebviewPanel(
            'advancedWebView',
            'Advanced WebView',
            vscode.ViewColumn.One,
            {
                enableScripts: true,
                retainContextWhenHidden: true
            }
        );

        this._panel.webview.html = this._getHtml();
        this._setupMessageHandlers();
    }

    private _setupMessageHandlers() {
        this._panel.webview.onDidReceiveMessage(
            async message => {
                switch (message.command) {
                    case 'openFile':
                        const fileUri = await vscode.window.showOpenDialog({
                            canSelectMany: false,
                            openLabel: 'Select a file'
                        });

                        if (fileUri) {
                            this._panel.webview.postMessage({
                                command: 'fileOpened',
                                path: fileUri[0].fsPath
                            });
                        }
                        break;

                    case 'saveData':
                        try {
                            await vscode.workspace.fs.writeFile(
                                vscode.Uri.file(message.path),
                                Buffer.from(message.content, 'utf8')
                            );

                            this._panel.webview.postMessage({
                                command: 'dataSaved',
                                success: true
                            });
                        } catch (error) {
                            this._panel.webview.postMessage({
                                command: 'dataSaved',
                                success: false,
                                error: error.message
                            });
                        }
                        break;
                }
            }
        );
    }

    // 公开方法供外部调用
    public updateContent(data: any) {
        this._panel.webview.postMessage({
            command: 'updateContent',
            data
        });
    }
}

6. 文件系统操作

工作区文件操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 读取文件内容
async function readFile(uri: vscode.Uri): Promise<string> {
    const content = await vscode.workspace.fs.readFile(uri);
    return Buffer.from(content).toString('utf8');
}

// 写入文件
async function writeFile(uri: vscode.Uri, content: string): Promise<void> {
    await vscode.workspace.fs.writeFile(
        uri,
        Buffer.from(content, 'utf8')
    );
}

// 创建文件
vscode.commands.registerCommand(
    'extension.createFile',
    async () => {
        const fileName = await vscode.window.showInputBox({
            prompt: 'Enter file name',
            placeHolder: 'example.txt'
        });

        if (fileName) {
            const workspaceFolders = vscode.workspace.workspaceFolders;
            if (workspaceFolders) {
                const fileUri = vscode.Uri.joinPath(
                    workspaceFolders[0].uri,
                    fileName
                );

                await writeFile(fileUri, '');

                const document = await vscode.workspace.openTextDocument(fileUri);
                await vscode.window.showTextDocument(document);
            }
        }
    }
);

// 批量文件操作
async function processFilesInDirectory(directoryUri: vscode.Uri) {
    const entries = await vscode.workspace.fs.readDirectory(directoryUri);

    for (const [name, type] of entries) {
        const entryUri = vscode.Uri.joinPath(directoryUri, name);

        if (type === vscode.FileType.File) {
            // 处理文件
            const content = await readFile(entryUri);
            console.log(`Processing file: ${name}, size: ${content.length}`);
        } else if (type === vscode.FileType.Directory) {
            // 递归处理目录
            await processFilesInDirectory(entryUri);
        }
    }
}

工作区监听

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 监听文件变化
const fileWatcher = vscode.workspace.createFileSystemWatcher(
    '**/*.{js,ts,json}'
);

fileWatcher.onDidCreate(uri => {
    console.log(`File created: ${uri.fsPath}`);
});

fileWatcher.onDidChange(uri => {
    console.log(`File changed: ${uri.fsPath}`);
});

fileWatcher.onDidDelete(uri => {
    console.log(`File deleted: ${uri.fsPath}`);
});

// 监听文档保存
vscode.workspace.onDidSaveTextDocument(
    (document) => {
        if (document.languageId === 'javascript') {
            // 自动格式化JavaScript文件
            formatDocument(document);
        }
    }
);

// 监听文本编辑器变化
vscode.window.onDidChangeActiveTextEditor(
    (editor) => {
        if (editor) {
            console.log(`Active editor: ${editor.document.fileName}`);
        }
    }
);

7. 测试和调试

单元测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// src/test/extension.test.ts
import * as assert from 'assert';
import * as vscode from 'vscode';
import * as extension from '../extension';

suite('Extension Test Suite', () => {
    vscode.window.showInformationMessage('Start all tests.');

    test('Extension should be present', () => {
        assert.ok(extension);
    });

    test('Command should register', async () => {
        const command = 'extension.helloWorld';
        const result = await vscode.commands.executeCommand(command);

        assert.strictEqual(result, undefined);
    });

    test('Command should show message', async () => {
        let messageShown = false;

        const disposable = vscode.window.onDidShowInformationMessage(() => {
            messageShown = true;
        });

        await vscode.commands.executeCommand('extension.helloWorld');

        assert.ok(messageShown);
        disposable.dispose();
    });
});

// 集成测试
suite('Integration Tests', () => {
    test('Should create and open file', async () => {
        const fileName = 'test-file.txt';
        const fileContent = 'Hello, World!';

        const workspaceFolders = vscode.workspace.workspaceFolders;
        assert.ok(workspaceFolders, 'No workspace folder found');

        const fileUri = vscode.Uri.joinPath(
            workspaceFolders[0].uri,
            fileName
        );

        // 写入文件
        await vscode.workspace.fs.writeFile(
            fileUri,
            Buffer.from(fileContent)
        );

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

        assert.strictEqual(document.getText(), fileContent);
    });
});

调试配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}"
            ],
            "outFiles": [
                "${workspaceFolder}/out/**/*.js"
            ],
            "preLaunchTask": "${workspaceFolder}/npm: compile"
        },
        {
            "name": "Extension Tests",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}",
                "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
            ],
            "outFiles": [
                "${workspaceFolder}/out/test/**/*.js"
            ],
            "preLaunchTask": "${workspaceFolder}/npm: compile"
        }
    ]
}

8. 发布和打包

准备发布

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 安装vsce工具
npm install -g vsce

# 更新package.json版本
npm version patch  # 或 minor, major

# 构建扩展
npm run vscode:prepublish

# 检查包
vsce ls --publish

# 发布到市场
vsce publish

# 打包为.vsix文件
vsce package

发布配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// package.json中的发布相关配置
{
  "name": "my-extension",
  "displayName": "My Awesome Extension",
  "description": "A helpful description",
  "version": "1.0.0",
  "publisher": "your-publisher-name",
  "repository": {
    "type": "git",
    "url": "https://github.com/your-username/my-extension.git"
  },
  "bugs": {
    "url": "https://github.com/your-username/my-extension/issues"
  },
  "homepage": "https://github.com/your-username/my-extension#readme",
  "keywords": [
    "vscode",
    "extension",
    "tool"
  ],
  "engines": {
    "vscode": "^1.75.0"
  },
  "categories": [
    "Other"
  ],
  "icon": "images/icon.png",
  "gallery": [
    {
      "feature": "images/screenshot1.png",
      "label": "Feature 1"
    },
    {
      "feature": "images/screenshot2.png",
      "label": "Feature 2"
    }
  ]
}

9. 最佳实践

代码组织

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 模块化命令
class CommandManager {
    private commands: vscode.Disposable[] = [];

    constructor(private context: vscode.ExtensionContext) {}

    register(command: string, callback: (...args: any[]) => any) {
        const disposable = vscode.commands.registerCommand(command, callback);
        this.commands.push(disposable);
        return disposable;
    }

    dispose() {
        this.commands.forEach(disposable => disposable.dispose());
        this.commands = [];
    }
}

// 模块化配置
class ConfigManager {
    constructor(private configSection: string) {}

    get<T>(key: string): T | undefined {
        return vscode.workspace.getConfiguration(this.configSection).get<T>(key);
    }

    async set(key: string, value: any): Promise<void> {
        const config = vscode.workspace.getConfiguration(this.configSection);
        await config.update(key, value, vscode.ConfigurationTarget.Global);
    }
}

// 使用示例
export function activate(context: vscode.ExtensionContext) {
    const commandManager = new CommandManager(context);
    const configManager = new ConfigManager('myExtension');

    commandManager.register('extension.myCommand', () => {
        const setting = configManager.get<boolean>('enable');
        vscode.window.showInformationMessage(`Setting: ${setting}`);
    });
}

性能优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 懒加载功能
let featureModule: any;

async function loadFeatureModule() {
    if (!featureModule) {
        featureModule = await import('./feature-module');
    }
    return featureModule;
}

vscode.commands.registerCommand(
    'extension.feature',
    async () => {
        const module = await loadFeatureModule();
        module.execute();
    }
);

// 缓存计算结果
const cache = new Map<string, any>();

function expensiveCalculation(input: string): any {
    if (cache.has(input)) {
        return cache.get(input);
    }

    const result = performExpensiveCalculation(input);
    cache.set(input, result);
    return result;
}

// 事件防抖
function debounce<T extends (...args: any[]) => void>(
    fn: T,
    delay: number
): T {
    let timeoutId: NodeJS.Timeout;

    return ((...args: any[]) => {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn(...args), delay);
    }) as T;
}

// 使用示例
const debouncedCommand = debounce(
    (args: string[]) => {
        vscode.window.showInformationMessage(args.join(', '));
    },
    300
);

10. 常见问题和解决方案

常见错误处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 处理扩展激活失败
export function activate(context: vscode.ExtensionContext) {
    try {
        // 扩展初始化逻辑
        initializeExtension(context);
    } catch (error) {
        console.error('Extension activation failed:', error);
        vscode.window.showErrorMessage(
            `Extension activation failed: ${error.message}`
        );
    }
}

// 安全的类型检查
function getActiveEditor(): vscode.TextEditor | undefined {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showWarningMessage('No active editor');
        return undefined;
    }
    return editor;
}

// 版本兼容性检查
function checkVSCodeVersion() {
    const vscodeVersion = vscode.version;
    const requiredVersion = '1.75.0';

    if (vscode.version < requiredVersion) {
        vscode.window.showErrorMessage(
            `This extension requires VS Code ${requiredVersion} or higher. ` +
            `Current version: ${vscodeVersion}`
        );
        return false;
    }
    return true;
}

总结

VS Code扩展开发为开发者提供了强大的定制化能力。通过本文介绍的技术和最佳实践,你可以:

核心技能:

  • 掌握扩展API的使用
  • 理解扩展生命周期管理
  • 实现丰富的编辑器功能

高级特性:

  • 开发自定义WebView
  • 创建复杂的语言支持
  • 实现高效的文件操作

开发流程:

  • 建立完整的测试体系
  • 实现持续集成/部署
  • 遵循发布最佳实践

记住,好的扩展应该解决实际问题、提供良好的用户体验,并且保持稳定和高效。开始你的VS Code扩展开发之旅吧!