在掌握了扩展开发基础后,本阶段将深入探索VS Code扩展的核心能力。我们将学习如何创建主题、扩展工作台界面、操作编辑器内容,以及构建丰富的用户交互界面。这些技能将使你能够开发功能完整、用户体验优秀的扩展。


🎯 学习目标

完成本阶段学习后,你将能够:

  • 🎨 主题开发:创建颜色主题、文件图标主题和产品图标主题
  • 🔧 工作台扩展:自定义侧边栏视图、状态栏项目和菜单系统
  • 💻 编辑器功能:操作文本内容、管理选择和光标、添加装饰器
  • 🎛️ 用户界面:构建QuickPick、InputBox、WebView等交互组件
  • 🔔 消息系统:设计完善的通知和进度指示

预计学习时间:2-3周(每天1-2小时)


🎨 第一部分:主题和外观定制

1.1 颜色主题开发

创建颜色主题扩展

1
2
3
4
5
# 使用Yeoman生成颜色主题扩展
yo code

# 选择:New Color Theme
# 按提示填写主题信息

主题文件结构

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
// themes/my-theme-color-theme.json
{
"name": "My Custom Theme",
"type": "dark", // "dark" | "light" | "hc"
"colors": {
// 工作台颜色
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4",
"activityBar.background": "#333333",
"statusBar.background": "#0e7eff",
"sideBar.background": "#252526",

// 编辑器颜色
"editor.lineHighlightBackground": "#2d2d30",
"editor.selectionBackground": "#404040",
"editor.inactiveSelectionBackground": "#3a3d41",

// 语法高亮基础颜色
"editorCursor.foreground": "#aeafad",
"editor.findMatchBackground": "#515c6a",
"editor.findMatchHighlightBackground": "#ea5c004d"
},
"tokenColors": [
{
"name": "Comments",
"scope": [
"comment",
"punctuation.definition.comment"
],
"settings": {
"fontStyle": "italic",
"foreground": "#6A9955"
}
},
{
"name": "Keywords",
"scope": [
"keyword",
"storage.type",
"storage.modifier"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "Strings",
"scope": "string",
"settings": {
"foreground": "#CE9178"
}
},
{
"name": "Numbers",
"scope": "constant.numeric",
"settings": {
"foreground": "#B5CEA8"
}
},
{
"name": "Built-in constants",
"scope": "constant.language",
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "User-defined constants",
"scope": [
"constant.character",
"constant.other"
],
"settings": {
"foreground": "#569CD6"
}
}
]
}

package.json配置

1
2
3
4
5
6
7
8
9
{
"contributes": {
"themes": [{
"label": "My Custom Theme",
"uiTheme": "vs-dark",
"path": "./themes/my-theme-color-theme.json"
}]
}
}

1.2 文件图标主题

图标主题结构

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
// icons/file-icons.json
{
"iconDefinitions": {
"_file": {
"iconPath": "./icons/file.svg"
},
"_folder": {
"iconPath": "./icons/folder.svg"
},
"_folder_open": {
"iconPath": "./icons/folder-open.svg"
},
"_javascript": {
"iconPath": "./icons/javascript.svg"
},
"_typescript": {
"iconPath": "./icons/typescript.svg"
},
"_json": {
"iconPath": "./icons/json.svg"
}
},
"file": "_file",
"folder": "_folder",
"folderExpanded": "_folder_open",
"fileExtensions": {
"js": "_javascript",
"ts": "_typescript",
"json": "_json"
},
"fileNames": {
"package.json": "_json",
"tsconfig.json": "_json"
},
"folderNames": {
"node_modules": "_folder",
".vscode": "_folder"
},
"languageIds": {
"javascript": "_javascript",
"typescript": "_typescript"
}
}

package.json贡献点

1
2
3
4
5
6
7
8
9
{
"contributes": {
"iconThemes": [{
"id": "my-icon-theme",
"label": "My Icon Theme",
"path": "./icons/file-icons.json"
}]
}
}

1.3 产品图标主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// icons/product-icon-theme.json
{
"iconDefinitions": {
"explorer-view-icon": {
"iconPath": "./icons/explorer.svg"
},
"search-view-icon": {
"iconPath": "./icons/search.svg"
},
"debug-view-icon": {
"iconPath": "./icons/debug.svg"
}
}
}

🔧 第二部分:工作台扩展

2.1 侧边栏视图扩展

创建自定义视图

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
// src/treeDataProvider.ts
import * as vscode from 'vscode';

export class CustomTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {

private _onDidChangeTreeData: vscode.EventEmitter<TreeItem | undefined | null | void> = new vscode.EventEmitter<TreeItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<TreeItem | undefined | null | void> = this._onDidChangeTreeData.event;

constructor() {}

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

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

getChildren(element?: TreeItem): Thenable<TreeItem[]> {
if (!element) {
// 根级别项目
return Promise.resolve([
new TreeItem('项目文件', vscode.TreeItemCollapsibleState.Expanded),
new TreeItem('配置文件', vscode.TreeItemCollapsibleState.Expanded),
new TreeItem('工具脚本', vscode.TreeItemCollapsibleState.Collapsed)
]);
} else {
// 子项目
switch (element.label) {
case '项目文件':
return Promise.resolve([
new TreeItem('src/index.ts', vscode.TreeItemCollapsibleState.None),
new TreeItem('src/utils.ts', vscode.TreeItemCollapsibleState.None)
]);
case '配置文件':
return Promise.resolve([
new TreeItem('package.json', vscode.TreeItemCollapsibleState.None),
new TreeItem('tsconfig.json', vscode.TreeItemCollapsibleState.None)
]);
default:
return Promise.resolve([]);
}
}
}
}

export class TreeItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly command?: vscode.Command
) {
super(label, collapsibleState);

this.tooltip = `${this.label} - 自定义树视图项目`;
this.description = this.label.includes('.') ? '文件' : '文件夹';

// 设置图标
if (this.label.includes('.ts')) {
this.iconPath = new vscode.ThemeIcon('file-code');
} else if (this.label.includes('.json')) {
this.iconPath = new vscode.ThemeIcon('json');
} else {
this.iconPath = new vscode.ThemeIcon('folder');
}

// 设置上下文值,用于控制右键菜单
this.contextValue = this.label.includes('.') ? 'file' : 'folder';
}
}

注册视图提供者

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
// src/extension.ts
import * as vscode from 'vscode';
import { CustomTreeDataProvider } from './treeDataProvider';

export function activate(context: vscode.ExtensionContext) {

// 创建树视图数据提供者
const treeDataProvider = new CustomTreeDataProvider();

// 注册树视图
const treeView = vscode.window.createTreeView('myCustomView', {
treeDataProvider: treeDataProvider,
showCollapseAll: true
});

// 刷新命令
let refreshCommand = vscode.commands.registerCommand('myCustomView.refresh', () => {
treeDataProvider.refresh();
});

// 打开文件命令
let openCommand = vscode.commands.registerCommand('myCustomView.openFile', (item) => {
if (item.label.includes('.')) {
vscode.window.showInformationMessage(`打开文件: ${item.label}`);
}
});

context.subscriptions.push(treeView, refreshCommand, openCommand);
}

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
{
"contributes": {
"views": {
"explorer": [{
"id": "myCustomView",
"name": "我的自定义视图",
"when": "workspaceHasFile:package.json"
}]
},
"commands": [
{
"command": "myCustomView.refresh",
"title": "刷新",
"icon": "$(refresh)"
},
{
"command": "myCustomView.openFile",
"title": "打开文件"
}
],
"menus": {
"view/title": [{
"command": "myCustomView.refresh",
"when": "view == myCustomView",
"group": "navigation"
}],
"view/item/context": [{
"command": "myCustomView.openFile",
"when": "view == myCustomView && viewItem == file"
}]
}
}
}

2.2 状态栏扩展

创建状态栏项目

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
import * as vscode from 'vscode';

export class StatusBarManager {
private statusBarItems: vscode.StatusBarItem[] = [];

constructor(context: vscode.ExtensionContext) {
this.createStatusBarItems(context);
this.updateStatusBarItems();

// 监听活动编辑器变化
vscode.window.onDidChangeActiveTextEditor(() => {
this.updateStatusBarItems();
});

// 监听文档变化
vscode.workspace.onDidChangeTextDocument(() => {
this.updateStatusBarItems();
});
}

private createStatusBarItems(context: vscode.ExtensionContext) {
// 创建行数统计状态栏
const lineCountItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
100
);
lineCountItem.command = 'extension.showFileStats';
this.statusBarItems.push(lineCountItem);

// 创建字符数统计状态栏
const charCountItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
99
);
charCountItem.command = 'extension.showCharacterCount';
this.statusBarItems.push(charCountItem);

// 创建编码显示状态栏
const encodingItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100
);
this.statusBarItems.push(encodingItem);

// 将状态栏项目添加到订阅列表
context.subscriptions.push(...this.statusBarItems);
}

private updateStatusBarItems() {
const editor = vscode.window.activeTextEditor;

if (editor) {
const document = editor.document;
const text = document.getText();

// 更新行数统计
this.statusBarItems[0].text = `$(file-text) ${document.lineCount} 行`;
this.statusBarItems[0].tooltip = '点击查看详细文件统计';
this.statusBarItems[0].show();

// 更新字符数统计
this.statusBarItems[1].text = `$(symbol-string) ${text.length} 字符`;
this.statusBarItems[1].tooltip = `包含空格的总字符数:${text.length}`;
this.statusBarItems[1].show();

// 更新编码信息
this.statusBarItems[2].text = `$(file-binary) ${document.uri.scheme}`;
this.statusBarItems[2].tooltip = `文件协议:${document.uri.scheme}`;
this.statusBarItems[2].show();
} else {
// 隐藏所有状态栏项目
this.statusBarItems.forEach(item => item.hide());
}
}
}

// 在extension.ts中使用
export function activate(context: vscode.ExtensionContext) {
const statusBarManager = new StatusBarManager(context);

// 注册状态栏点击命令
let showStatsCommand = vscode.commands.registerCommand('extension.showFileStats', () => {
const editor = vscode.window.activeTextEditor;
if (editor) {
const document = editor.document;
const text = document.getText();
const wordCount = text.split(/\s+/).filter(word => word.length > 0).length;

const message = `文件统计信息:
文件名:${document.fileName.split('/').pop()}
行数:${document.lineCount}
字符数:${text.length}
单词数:${wordCount}
语言:${document.languageId}`;

vscode.window.showInformationMessage(message, { modal: true });
}
});

context.subscriptions.push(showStatsCommand);
}

2.3 菜单系统扩展

上下文菜单配置

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
{
"contributes": {
"commands": [
{
"command": "extension.formatSelectedText",
"title": "格式化选中文本"
},
{
"command": "extension.copyFilePath",
"title": "复制文件路径"
},
{
"command": "extension.openInBrowser",
"title": "在浏览器中打开"
}
],
"menus": {
"editor/context": [
{
"command": "extension.formatSelectedText",
"when": "editorHasSelection",
"group": "modification"
}
],
"explorer/context": [
{
"command": "extension.copyFilePath",
"when": "resourceExtname == .html",
"group": "5_cutcopypaste"
},
{
"command": "extension.openInBrowser",
"when": "resourceExtname == .html",
"group": "navigation"
}
],
"editor/title": [
{
"command": "extension.openInBrowser",
"when": "resourceExtname == .html",
"group": "navigation"
}
]
}
}
}

菜单命令实现

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
// 格式化选中文本
let formatCommand = vscode.commands.registerCommand('extension.formatSelectedText', () => {
const editor = vscode.window.activeTextEditor;
if (editor && !editor.selection.isEmpty) {
const selectedText = editor.document.getText(editor.selection);

// 简单的格式化逻辑(实际可以更复杂)
const formattedText = selectedText
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
.join('\n');

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

vscode.window.showInformationMessage('文本格式化完成');
}
});

// 复制文件路径
let copyPathCommand = vscode.commands.registerCommand('extension.copyFilePath', (uri: vscode.Uri) => {
vscode.env.clipboard.writeText(uri.fsPath);
vscode.window.showInformationMessage(`文件路径已复制:${uri.fsPath}`);
});

// 在浏览器中打开HTML文件
let openBrowserCommand = vscode.commands.registerCommand('extension.openInBrowser', (uri: vscode.Uri) => {
vscode.env.openExternal(vscode.Uri.parse(`file://${uri.fsPath}`));
});

💻 第三部分:编辑器功能开发

3.1 文本操作和编辑

文本插入和替换

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
import * as vscode from 'vscode';

export class TextEditor {

// 在光标位置插入文本
static async insertTextAtCursor(text: string) {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

await editor.edit(editBuilder => {
editBuilder.insert(editor.selection.active, text);
});
}

// 替换选中文本
static async replaceSelectedText(newText: string) {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.selection.isEmpty) return;

await editor.edit(editBuilder => {
editBuilder.replace(editor.selection, newText);
});
}

// 在指定行插入文本
static async insertTextAtLine(lineNumber: number, text: string) {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const position = new vscode.Position(lineNumber, 0);
await editor.edit(editBuilder => {
editBuilder.insert(position, text + '\n');
});
}

// 删除当前行
static async deleteCurrentLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const currentLine = editor.selection.active.line;
const lineRange = editor.document.lineAt(currentLine).rangeIncludingLineBreak;

await editor.edit(editBuilder => {
editBuilder.delete(lineRange);
});
}

// 复制当前行
static async duplicateCurrentLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const currentLine = editor.selection.active.line;
const lineText = editor.document.lineAt(currentLine).text;
const endOfLine = new vscode.Position(currentLine, lineText.length);

await editor.edit(editBuilder => {
editBuilder.insert(endOfLine, '\n' + lineText);
});
}
}

// 实际使用示例
export function activate(context: vscode.ExtensionContext) {

// 插入当前时间戳
let insertTimeCommand = vscode.commands.registerCommand('extension.insertTimestamp', () => {
const timestamp = new Date().toISOString();
TextEditor.insertTextAtCursor(timestamp);
});

// 包围选中文本
let wrapTextCommand = vscode.commands.registerCommand('extension.wrapWithQuotes', () => {
const editor = vscode.window.activeTextEditor;
if (editor && !editor.selection.isEmpty) {
const selectedText = editor.document.getText(editor.selection);
TextEditor.replaceSelectedText(`"${selectedText}"`);
}
});

// 插入代码模板
let insertTemplateCommand = vscode.commands.registerCommand('extension.insertFunctionTemplate', () => {
const template = `function ${1:functionName}(${2:parameters}) {
${3:// TODO: 实现函数逻辑}
return ${4:undefined};
}`;
TextEditor.insertTextAtCursor(template);
});

context.subscriptions.push(insertTimeCommand, wrapTextCommand, insertTemplateCommand);
}

3.2 代码片段和智能插入

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
// 使用SnippetString创建智能代码片段
export class SnippetManager {

// 插入函数模板
static insertFunctionSnippet() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const snippet = new vscode.SnippetString(`function \${1:functionName}(\${2:param}) {
\${3:// 函数实现}
return \${4:result};
}`);

editor.insertSnippet(snippet);
}

// 插入类模板
static insertClassSnippet() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const snippet = new vscode.SnippetString(`class \${1:ClassName} {
constructor(\${2:params}) {
\${3:// 构造函数}
}

\${4:methodName}() {
\${5:// 方法实现}
}
}`);

editor.insertSnippet(snippet);
}

// 插入注释块
static insertCommentBlock() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const snippet = new vscode.SnippetString(`/**
* \${1:函数描述}
* @param {\${2:type}} \${3:paramName} - \${4:参数描述}
* @returns {\${5:returnType}} \${6:返回值描述}
*/`);

editor.insertSnippet(snippet);
}
}

3.3 选择和光标管理

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
export class SelectionManager {

// 选择当前单词
static selectCurrentWord() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const position = editor.selection.active;
const wordRange = editor.document.getWordRangeAtPosition(position);

if (wordRange) {
editor.selection = new vscode.Selection(wordRange.start, wordRange.end);
}
}

// 选择当前行
static selectCurrentLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const lineNumber = editor.selection.active.line;
const lineStart = new vscode.Position(lineNumber, 0);
const lineEnd = editor.document.lineAt(lineNumber).range.end;

editor.selection = new vscode.Selection(lineStart, lineEnd);
}

// 扩展选择到括号
static expandSelectionToBrackets() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

// 简化的括号匹配逻辑
const document = editor.document;
const position = editor.selection.active;
const text = document.getText();

// 向前找到开括号
let openBracket = -1;
let closeBracket = -1;

// 实际实现需要更复杂的括号匹配算法
// 这里仅作示例
}

// 多光标选择
static addCursorBelow() {
vscode.commands.executeCommand('editor.action.insertCursorBelow');
}

static addCursorAbove() {
vscode.commands.executeCommand('editor.action.insertCursorAbove');
}
}

3.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
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
export class EditorDecorations {
private static highlightDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(255, 255, 0, 0.3)',
border: '1px solid yellow'
});

private static errorDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(255, 0, 0, 0.2)',
border: '1px solid red',
overviewRulerColor: 'red',
overviewRulerLane: vscode.OverviewRulerLane.Right
});

// 高亮显示特定文本
static highlightText(text: string) {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const document = editor.document;
const fullText = document.getText();
const decorations: vscode.DecorationOptions[] = [];

let index = 0;
while ((index = fullText.indexOf(text, index)) !== -1) {
const startPos = document.positionAt(index);
const endPos = document.positionAt(index + text.length);
const range = new vscode.Range(startPos, endPos);

decorations.push({
range,
hoverMessage: `高亮文本: ${text}`
});

index += text.length;
}

editor.setDecorations(this.highlightDecorationType, decorations);
}

// 标记错误行
static markErrorLines(lineNumbers: number[]) {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const decorations: vscode.DecorationOptions[] = lineNumbers.map(lineNumber => {
const line = editor.document.lineAt(lineNumber);
return {
range: line.range,
hoverMessage: '这行有错误',
renderOptions: {
after: {
contentText: ' ❌',
color: 'red'
}
}
};
});

editor.setDecorations(this.errorDecorationType, decorations);
}

// 清除所有装饰
static clearDecorations() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

editor.setDecorations(this.highlightDecorationType, []);
editor.setDecorations(this.errorDecorationType, []);
}
}

🎛️ 第四部分:用户界面组件

4.1 QuickPick 快速选择器

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
export class QuickPickManager {

// 基础快速选择
static async showBasicQuickPick() {
const items = [
{ label: '选项1', description: '这是第一个选项' },
{ label: '选项2', description: '这是第二个选项' },
{ label: '选项3', description: '这是第三个选项' }
];

const selected = await vscode.window.showQuickPick(items, {
placeHolder: '请选择一个选项',
canPickMany: false
});

if (selected) {
vscode.window.showInformationMessage(`你选择了:${selected.label}`);
}
}

// 多选快速选择
static async showMultiSelectQuickPick() {
const items = [
{ label: 'JavaScript', picked: true },
{ label: 'TypeScript', picked: false },
{ label: 'Python', picked: false },
{ label: 'Java', picked: false }
];

const selected = await vscode.window.showQuickPick(items, {
placeHolder: '选择你喜欢的编程语言(可多选)',
canPickMany: true
});

if (selected && selected.length > 0) {
const labels = selected.map(item => item.label).join(', ');
vscode.window.showInformationMessage(`你选择了:${labels}`);
}
}

// 自定义QuickPick
static async showCustomQuickPick() {
const quickPick = vscode.window.createQuickPick();
quickPick.title = '文件操作选择器';
quickPick.placeholder = '输入文件名或选择操作';

quickPick.items = [
{
label: '$(file-add) 新建文件',
description: '创建一个新文件'
},
{
label: '$(folder) 新建文件夹',
description: '创建一个新文件夹'
},
{
label: '$(search) 搜索文件',
description: '在工作区中搜索文件'
}
];

quickPick.onDidChangeSelection(selection => {
if (selection[0]) {
const action = selection[0].label;
quickPick.hide();

switch (action) {
case '$(file-add) 新建文件':
this.createNewFile();
break;
case '$(folder) 新建文件夹':
this.createNewFolder();
break;
case '$(search) 搜索文件':
vscode.commands.executeCommand('workbench.action.quickOpen');
break;
}
}
});

quickPick.onDidHide(() => quickPick.dispose());
quickPick.show();
}

private static async createNewFile() {
const fileName = await vscode.window.showInputBox({
prompt: '请输入文件名',
placeHolder: '例如:new-file.ts'
});

if (fileName) {
vscode.window.showInformationMessage(`创建文件:${fileName}`);
}
}

private static async createNewFolder() {
const folderName = await vscode.window.showInputBox({
prompt: '请输入文件夹名',
placeHolder: '例如:new-folder'
});

if (folderName) {
vscode.window.showInformationMessage(`创建文件夹:${folderName}`);
}
}
}

4.2 InputBox 输入框

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
113
export class InputManager {

// 基础输入框
static async getBasicInput() {
const input = await vscode.window.showInputBox({
prompt: '请输入你的名字',
placeHolder: '例如:张三',
value: '',
ignoreFocusOut: true
});

if (input) {
vscode.window.showInformationMessage(`你好,${input}!`);
}
}

// 带验证的输入框
static async getValidatedInput() {
const input = await vscode.window.showInputBox({
prompt: '请输入邮箱地址',
placeHolder: 'example@domain.com',
validateInput: (value: string) => {
if (!value) {
return '邮箱地址不能为空';
}

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return '请输入有效的邮箱地址';
}

return null; // 验证通过
}
});

if (input) {
vscode.window.showInformationMessage(`邮箱设置成功:${input}`);
}
}

// 密码输入框
static async getPasswordInput() {
const password = await vscode.window.showInputBox({
prompt: '请输入密码',
placeHolder: '密码',
password: true,
ignoreFocusOut: true,
validateInput: (value: string) => {
if (!value || value.length < 6) {
return '密码长度至少6位';
}
return null;
}
});

if (password) {
vscode.window.showInformationMessage('密码设置成功');
}
}

// 多步骤输入
static async getMultiStepInput() {
// 第一步:输入项目名
const projectName = await vscode.window.showInputBox({
prompt: '第1步:输入项目名称',
placeHolder: '例如:my-awesome-project',
validateInput: (value: string) => {
if (!value) return '项目名称不能为空';
if (!/^[a-zA-Z0-9-_]+$/.test(value)) return '项目名称只能包含字母、数字、连字符和下划线';
return null;
}
});

if (!projectName) return;

// 第二步:选择模板
const template = await vscode.window.showQuickPick([
{ label: 'TypeScript', description: 'TypeScript项目模板' },
{ label: 'JavaScript', description: 'JavaScript项目模板' },
{ label: 'React', description: 'React应用模板' },
{ label: 'Vue', description: 'Vue应用模板' }
], {
placeHolder: '第2步:选择项目模板'
});

if (!template) return;

// 第三步:输入描述
const description = await vscode.window.showInputBox({
prompt: '第3步:输入项目描述(可选)',
placeHolder: '简要描述你的项目...'
});

// 显示配置结果
const config = {
name: projectName,
template: template.label,
description: description || '暂无描述'
};

const configText = JSON.stringify(config, null, 2);
vscode.window.showInformationMessage('项目配置完成', '查看配置').then(selection => {
if (selection === '查看配置') {
vscode.workspace.openTextDocument({
content: configText,
language: 'json'
}).then(doc => {
vscode.window.showTextDocument(doc);
});
}
});
}
}

4.3 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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
export class WebViewManager {

// 创建基础WebView
static createBasicWebView(context: vscode.ExtensionContext) {
const panel = vscode.window.createWebviewPanel(
'basicWebview',
'我的自定义界面',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true
}
);

panel.webview.html = this.getWebviewContent();

// 处理WebView消息
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
case 'alert':
vscode.window.showInformationMessage(message.text);
return;
case 'openFile':
this.handleOpenFile(message.path);
return;
}
},
undefined,
context.subscriptions
);

return panel;
}

private static getWebviewContent(): string {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义WebView</title>
<style>
body {
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
padding: 20px;
}

.container {
max-width: 800px;
margin: 0 auto;
}

button {
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
padding: 8px 16px;
margin: 5px;
border-radius: 3px;
cursor: pointer;
}

button:hover {
background-color: var(--vscode-button-hoverBackground);
}

input {
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
padding: 6px;
margin: 5px;
border-radius: 3px;
}

.section {
margin: 20px 0;
padding: 15px;
border: 1px solid var(--vscode-panel-border);
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>VS Code 扩展 WebView 示例</h1>

<div class="section">
<h3>消息测试</h3>
<input type="text" id="messageInput" placeholder="输入消息内容...">
<button onclick="sendMessage()">发送消息</button>
</div>

<div class="section">
<h3>文件操作</h3>
<input type="text" id="filePathInput" placeholder="文件路径...">
<button onclick="openFile()">打开文件</button>
</div>

<div class="section">
<h3>快捷操作</h3>
<button onclick="showDateTime()">显示当前时间</button>
<button onclick="clearInputs()">清空输入</button>
</div>

<div class="section">
<h3>当前状态</h3>
<p id="statusText">准备就绪</p>
</div>
</div>

<script>
const vscode = acquireVsCodeApi();

function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();

if (message) {
vscode.postMessage({
command: 'alert',
text: message
});

updateStatus('消息已发送:' + message);
input.value = '';
}
}

function openFile() {
const input = document.getElementById('filePathInput');
const path = input.value.trim();

if (path) {
vscode.postMessage({
command: 'openFile',
path: path
});

updateStatus('尝试打开文件:' + path);
}
}

function showDateTime() {
const now = new Date().toLocaleString();
updateStatus('当前时间:' + now);
}

function clearInputs() {
document.getElementById('messageInput').value = '';
document.getElementById('filePathInput').value = '';
updateStatus('输入已清空');
}

function updateStatus(text) {
document.getElementById('statusText').textContent = text;
}

// 监听VS Code主题变化
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'themeChanged':
updateStatus('主题已切换');
break;
}
});
</script>
</body>
</html>`;
}

private static handleOpenFile(path: string) {
vscode.workspace.openTextDocument(path).then(
document => {
vscode.window.showTextDocument(document);
vscode.window.showInformationMessage(`文件已打开:${path}`);
},
error => {
vscode.window.showErrorMessage(`无法打开文件:${error.message}`);
}
);
}
}

🔔 第五部分:消息和通知系统

5.1 消息类型和层级

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
export class NotificationManager {

// 信息提示
static showInfo(message: string, ...actions: string[]) {
return vscode.window.showInformationMessage(message, ...actions);
}

// 警告提示
static showWarning(message: string, ...actions: string[]) {
return vscode.window.showWarningMessage(message, ...actions);
}

// 错误提示
static showError(message: string, ...actions: string[]) {
return vscode.window.showErrorMessage(message, ...actions);
}

// 模态对话框
static showModal(message: string, ...actions: string[]) {
return vscode.window.showInformationMessage(
message,
{ modal: true },
...actions
);
}

// 带详情的通知
static async showDetailedNotification(
title: string,
detail: string,
type: 'info' | 'warning' | 'error' = 'info'
) {
const showDetailAction = '查看详情';

let result;
switch (type) {
case 'warning':
result = await vscode.window.showWarningMessage(title, showDetailAction);
break;
case 'error':
result = await vscode.window.showErrorMessage(title, showDetailAction);
break;
default:
result = await vscode.window.showInformationMessage(title, showDetailAction);
}

if (result === showDetailAction) {
// 在新的文档中显示详情
const doc = await vscode.workspace.openTextDocument({
content: detail,
language: 'plaintext'
});
vscode.window.showTextDocument(doc);
}
}

// 进度通知
static async showProgressNotification<T>(
title: string,
task: (progress: vscode.Progress<{ message?: string; increment?: number }>) => Promise<T>
): Promise<T> {
return vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: title,
cancellable: true
},
async (progress, token) => {
return task(progress);
}
);
}
}

// 使用示例
export function activate(context: vscode.ExtensionContext) {

// 简单通知
let simpleNotificationCommand = vscode.commands.registerCommand('extension.simpleNotification', () => {
NotificationManager.showInfo('这是一个简单的信息提示');
});

// 带操作的通知
let actionNotificationCommand = vscode.commands.registerCommand('extension.actionNotification', async () => {
const result = await NotificationManager.showInfo(
'文件已保存',
'打开文件',
'查看位置'
);

switch (result) {
case '打开文件':
vscode.commands.executeCommand('workbench.action.files.openFile');
break;
case '查看位置':
vscode.commands.executeCommand('workbench.files.action.showActiveFileInExplorer');
break;
}
});

// 进度通知示例
let progressCommand = vscode.commands.registerCommand('extension.progressNotification', async () => {
await NotificationManager.showProgressNotification(
'正在处理文件...',
async (progress) => {
// 模拟长时间任务
for (let i = 0; i < 10; i++) {
progress.report({
message: `步骤 ${i + 1}/10`,
increment: 10
});

// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 500));
}

return '处理完成';
}
);

NotificationManager.showInfo('文件处理完成!');
});

context.subscriptions.push(
simpleNotificationCommand,
actionNotificationCommand,
progressCommand
);
}

📋 实践项目

项目:多功能文本编辑器增强扩展

创建一个综合性的扩展,集成本阶段学到的所有功能:

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
// src/extension.ts
import * as vscode from 'vscode';
import { CustomTreeDataProvider } from './treeDataProvider';
import { StatusBarManager } from './statusBarManager';
import { TextEditor } from './textEditor';
import { EditorDecorations } from './editorDecorations';
import { QuickPickManager } from './quickPickManager';
import { WebViewManager } from './webViewManager';

export function activate(context: vscode.ExtensionContext) {

// 状态栏管理器
const statusBarManager = new StatusBarManager(context);

// 树视图提供者
const treeDataProvider = new CustomTreeDataProvider();
const treeView = vscode.window.createTreeView('textEditorTools', {
treeDataProvider: treeDataProvider,
showCollapseAll: true
});

// 注册所有命令
const commands = [
// 文本操作命令
vscode.commands.registerCommand('textEditor.insertTimestamp', () => {
const timestamp = new Date().toLocaleString();
TextEditor.insertTextAtCursor(`[${timestamp}] `);
}),

vscode.commands.registerCommand('textEditor.duplicateLine', () => {
TextEditor.duplicateCurrentLine();
}),

vscode.commands.registerCommand('textEditor.wrapWithQuotes', () => {
const editor = vscode.window.activeTextEditor;
if (editor && !editor.selection.isEmpty) {
const selectedText = editor.document.getText(editor.selection);
TextEditor.replaceSelectedText(`"${selectedText}"`);
}
}),

// 装饰器命令
vscode.commands.registerCommand('textEditor.highlightTODO', () => {
EditorDecorations.highlightText('TODO');
}),

vscode.commands.registerCommand('textEditor.clearDecorations', () => {
EditorDecorations.clearDecorations();
}),

// UI组件命令
vscode.commands.registerCommand('textEditor.showQuickActions', () => {
QuickPickManager.showCustomQuickPick();
}),

vscode.commands.registerCommand('textEditor.openWebView', () => {
WebViewManager.createBasicWebView(context);
}),

// 树视图命令
vscode.commands.registerCommand('textEditorTools.refresh', () => {
treeDataProvider.refresh();
})
];

// 添加到订阅列表
context.subscriptions.push(treeView, ...commands);

// 欢迎消息
vscode.window.showInformationMessage('文本编辑器增强扩展已激活!', '查看功能').then(selection => {
if (selection === '查看功能') {
vscode.commands.executeCommand('textEditor.showQuickActions');
}
});
}

export function deactivate() {
// 清理资源
}

对应的 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
{
"contributes": {
"commands": [
{
"command": "textEditor.insertTimestamp",
"title": "插入时间戳",
"category": "文本编辑器"
},
{
"command": "textEditor.duplicateLine",
"title": "复制当前行",
"category": "文本编辑器"
},
{
"command": "textEditor.wrapWithQuotes",
"title": "用引号包围",
"category": "文本编辑器"
},
{
"command": "textEditor.highlightTODO",
"title": "高亮TODO",
"category": "文本编辑器"
},
{
"command": "textEditor.showQuickActions",
"title": "快速操作",
"category": "文本编辑器"
},
{
"command": "textEditor.openWebView",
"title": "打开控制面板",
"category": "文本编辑器"
}
],
"views": {
"explorer": [{
"id": "textEditorTools",
"name": "文本编辑工具",
"when": "true"
}]
},
"menus": {
"editor/context": [
{
"command": "textEditor.wrapWithQuotes",
"when": "editorHasSelection",
"group": "modification"
},
{
"command": "textEditor.insertTimestamp",
"group": "modification"
}
],
"view/title": [{
"command": "textEditorTools.refresh",
"when": "view == textEditorTools",
"group": "navigation"
}]
},
"keybindings": [
{
"command": "textEditor.duplicateLine",
"key": "ctrl+shift+d"
},
{
"command": "textEditor.showQuickActions",
"key": "ctrl+shift+a"
}
]
}
}

📚 本阶段总结

通过本阶段学习,你已经掌握了:

主题开发技能:颜色主题、图标主题、产品图标主题
工作台扩展能力:自定义视图、状态栏、菜单系统
编辑器操作技术:文本编辑、选择管理、装饰器
用户界面组件:QuickPick、InputBox、WebView
消息通知系统:各类通知、进度指示

核心API回顾

  • vscode.window.createTreeView() - 创建自定义树视图
  • vscode.window.createStatusBarItem() - 创建状态栏项目
  • vscode.window.createWebviewPanel() - 创建WebView面板
  • vscode.window.showQuickPick() - 显示快速选择器
  • vscode.window.showInputBox() - 显示输入框
  • vscode.window.withProgress() - 显示进度指示

🚀 下一步学习方向

在下一阶段(扩展指南专题),我们将学习:

  • 🤖 AI集成开发:Chat Participant、Language Model Tool
  • 🌐 WebView进阶:React/Vue集成、复杂交互
  • 📓 Notebook扩展:自定义渲染器、内核集成
  • ✏️ 自定义编辑器:可视化编辑器、二进制编辑器

继续深入学习之前,建议多练习本阶段的实践项目,熟悉各种API的使用方法。


相关资源

恭喜你完成了VS Code扩展开发的核心能力阶段!现在你已经具备了开发功能丰富、用户体验优秀的扩展的能力。