本阶段将深入探讨VS Code扩展开发的高级主题,学习如何开发调试器扩展、集成构建任务系统、实现源代码管理功能,以及支持远程开发环境。这些高级技能将使你能够创建完整的开发工具链扩展。


🎯 学习目标

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

  • 🐛 调试器扩展开发:Debug Adapter Protocol、断点管理、调试会话控制
  • ⚙️ 任务系统集成:自定义构建任务、工具链集成、任务发现
  • 📂 源代码管理:SCM Provider开发、版本控制集成、文件状态管理
  • 🌐 远程开发支持:虚拟文件系统、远程工作区、协议扩展
  • 🔧 工作区增强:多根工作区、文件监听、配置管理

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


🐛 第一部分:调试器扩展开发

1.1 Debug Adapter Protocol (DAP)

调试配置提供者

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

export class MyLangDebugConfigurationProvider implements vscode.DebugConfigurationProvider {

/**
* 解析调试配置
*/
resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
config: vscode.DebugConfiguration,
token?: vscode.CancellationToken
): vscode.ProviderResult<vscode.DebugConfiguration> {

// 如果没有launch.json文件,提供默认配置
if (!config.type && !config.request && !config.name) {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.languageId === 'mylang') {
config.type = 'mylang';
config.name = 'Launch MyLang';
config.request = 'launch';
config.program = '${file}';
config.console = 'integratedTerminal';
}
}

// 验证必需的属性
if (!config.program) {
return vscode.window.showInformationMessage("请指定要调试的程序文件").then(_ => {
return undefined; // 取消调试
});
}

// 解析变量
config.program = this.resolveVariables(config.program, folder);

return config;
}

/**
* 提供初始调试配置
*/
provideDebugConfigurations(
folder: vscode.WorkspaceFolder | undefined,
token?: vscode.CancellationToken
): vscode.ProviderResult<vscode.DebugConfiguration[]> {

return [
{
type: 'mylang',
request: 'launch',
name: 'Launch MyLang Program',
program: '${workspaceFolder}/main.ml',
console: 'integratedTerminal',
args: [],
env: {},
cwd: '${workspaceFolder}'
},
{
type: 'mylang',
request: 'attach',
name: 'Attach to MyLang Process',
port: 9229,
address: 'localhost',
localRoot: '${workspaceFolder}',
remoteRoot: '.'
}
];
}

private resolveVariables(value: string, folder?: vscode.WorkspaceFolder): string {
const variables: Record<string, string> = {
'${workspaceFolder}': folder?.uri.fsPath || '',
'${file}': vscode.window.activeTextEditor?.document.fileName || '',
'${fileBasename}': vscode.window.activeTextEditor?.document.fileName.split('/').pop() || '',
'${fileDirname}': vscode.window.activeTextEditor?.document.fileName.split('/').slice(0, -1).join('/') || ''
};

return value.replace(/\${(\w+)}/g, (match, varName) => {
return variables[match] || match;
});
}
}

Debug Adapter实现

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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
// src/debugAdapter.ts
import {
DebugSession,
InitializedEvent,
TerminatedEvent,
StoppedEvent,
OutputEvent,
Thread,
StackFrame,
Scope,
Variable,
Breakpoint
} from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import * as path from 'path';

interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
program: string;
args?: string[];
env?: { [key: string]: string };
cwd?: string;
console?: string;
}

interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
port: number;
address: string;
localRoot: string;
remoteRoot: string;
}

export class MyLangDebugSession extends DebugSession {

private static THREAD_ID = 1;
private _configurationDone = new Promise<void>((resolve, reject) => {
this._configurationDoneResolve = resolve;
});
private _configurationDoneResolve!: () => void;

// 调试状态
private _breakpoints = new Map<string, DebugProtocol.Breakpoint[]>();
private _runtime: MyLangRuntime;

constructor() {
super();

this.setDebuggerLinesStartAt1(false);
this.setDebuggerColumnsStartAt1(false);

this._runtime = new MyLangRuntime();

// 设置运行时事件监听器
this._runtime.on('stopOnEntry', () => {
this.sendEvent(new StoppedEvent('entry', MyLangDebugSession.THREAD_ID));
});

this._runtime.on('stopOnStep', () => {
this.sendEvent(new StoppedEvent('step', MyLangDebugSession.THREAD_ID));
});

this._runtime.on('stopOnBreakpoint', () => {
this.sendEvent(new StoppedEvent('breakpoint', MyLangDebugSession.THREAD_ID));
});

this._runtime.on('stopOnException', (exception) => {
this.sendEvent(new StoppedEvent('exception', MyLangDebugSession.THREAD_ID, exception));
});

this._runtime.on('output', (text, category) => {
const e: DebugProtocol.OutputEvent = new OutputEvent(`${text}\n`);
if (category) {
e.body.category = category;
}
this.sendEvent(e);
});

this._runtime.on('end', () => {
this.sendEvent(new TerminatedEvent());
});
}

/**
* 初始化请求
*/
protected initializeRequest(
response: DebugProtocol.InitializeResponse,
args: DebugProtocol.InitializeRequestArguments
): void {

response.body = response.body || {};

// 支持的功能
response.body.supportsConfigurationDoneRequest = true;
response.body.supportsEvaluateForHovers = true;
response.body.supportsStepBack = false;
response.body.supportsSetVariable = true;
response.body.supportsRestartFrame = false;
response.body.supportsGotoTargetsRequest = false;
response.body.supportsStepInTargetsRequest = false;
response.body.supportsCompletionsRequest = true;
response.body.supportsModulesRequest = false;
response.body.supportsRestartRequest = true;
response.body.supportsExceptionOptions = true;
response.body.supportsValueFormattingOptions = true;
response.body.supportsExceptionInfoRequest = true;
response.body.supportTerminateDebuggee = true;
response.body.supportSuspendDebuggee = true;
response.body.supportsDelayedStackTraceLoading = true;
response.body.supportsLoadedSourcesRequest = false;
response.body.supportsLogPoints = true;
response.body.supportsTerminateThreadsRequest = false;
response.body.supportsSetExpression = false;
response.body.supportsTerminateRequest = true;
response.body.completionTriggerCharacters = ['.', '['];
response.body.supportsBreakpointLocationsRequest = true;

this.sendResponse(response);
this.sendEvent(new InitializedEvent());
}

/**
* 配置完成请求
*/
protected configurationDoneRequest(
response: DebugProtocol.ConfigurationDoneResponse,
args: DebugProtocol.ConfigurationDoneArguments
): void {
super.configurationDoneRequest(response, args);
this._configurationDoneResolve();
}

/**
* 启动请求
*/
protected async launchRequest(
response: DebugProtocol.LaunchResponse,
args: LaunchRequestArguments
): Promise<void> {

// 等待配置完成
await this._configurationDone;

// 启动程序
await this._runtime.start(args.program, !!args.stopOnEntry);

this.sendResponse(response);
}

/**
* 附加请求
*/
protected async attachRequest(
response: DebugProtocol.AttachResponse,
args: AttachRequestArguments
): Promise<void> {

await this._configurationDone;

// 附加到运行中的进程
await this._runtime.attach(args.port, args.address);

this.sendResponse(response);
}

/**
* 设置断点请求
*/
protected setBreakPointsRequest(
response: DebugProtocol.SetBreakpointsResponse,
args: DebugProtocol.SetBreakpointsArguments
): void {

const path = args.source.path!;
const clientLines = args.lines || [];

// 清除之前的断点
this._runtime.clearBreakpoints(path);

// 设置新的断点
const actualBreakpoints = clientLines.map(line => {
const verified = this._runtime.setBreakpoint(path, this.convertClientLineToDebugger(line));
const bp = new Breakpoint(verified, this.convertDebuggerLineToClient(line));
bp.id = this._runtime.getBreakpointId(path, line);
return bp;
});

// 缓存断点
this._breakpoints.set(path, actualBreakpoints);

response.body = {
breakpoints: actualBreakpoints
};
this.sendResponse(response);
}

/**
* 获取线程请求
*/
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
response.body = {
threads: [
new Thread(MyLangDebugSession.THREAD_ID, "main")
]
};
this.sendResponse(response);
}

/**
* 获取堆栈跟踪请求
*/
protected stackTraceRequest(
response: DebugProtocol.StackTraceResponse,
args: DebugProtocol.StackTraceArguments
): void {

const startFrame = typeof args.startFrame === 'number' ? args.startFrame : 0;
const maxLevels = typeof args.levels === 'number' ? args.levels : 1000;
const endFrame = startFrame + maxLevels;

const stack = this._runtime.getStack(startFrame, endFrame);

response.body = {
stackFrames: stack.frames.map(f => {
const filename = path.basename(f.file);
return new StackFrame(
f.index,
f.name,
this.createSource(filename, f.file),
this.convertDebuggerLineToClient(f.line),
this.convertDebuggerColumnToClient(f.column)
);
}),
totalFrames: stack.count
};
this.sendResponse(response);
}

/**
* 获取作用域请求
*/
protected scopesRequest(
response: DebugProtocol.ScopesResponse,
args: DebugProtocol.ScopesArguments
): void {

response.body = {
scopes: [
new Scope("Local", this._runtime.getLocalScopeId(args.frameId), false),
new Scope("Global", this._runtime.getGlobalScopeId(), true)
]
};
this.sendResponse(response);
}

/**
* 获取变量请求
*/
protected variablesRequest(
response: DebugProtocol.VariablesResponse,
args: DebugProtocol.VariablesArguments
): void {

const variables = this._runtime.getVariables(args.variablesReference);

response.body = {
variables: variables.map(v => {
const variable = new Variable(v.name, v.value);
variable.type = v.type;
variable.variablesReference = v.reference;
if (v.indexedVariables) {
variable.indexedVariables = v.indexedVariables;
}
if (v.namedVariables) {
variable.namedVariables = v.namedVariables;
}
return variable;
})
};
this.sendResponse(response);
}

/**
* 继续执行请求
*/
protected continueRequest(
response: DebugProtocol.ContinueResponse,
args: DebugProtocol.ContinueArguments
): void {
this._runtime.continue();
this.sendResponse(response);
}

/**
* 下一步请求
*/
protected nextRequest(
response: DebugProtocol.NextResponse,
args: DebugProtocol.NextArguments
): void {
this._runtime.step();
this.sendResponse(response);
}

/**
* 步入请求
*/
protected stepInRequest(
response: DebugProtocol.StepInResponse,
args: DebugProtocol.StepInArguments
): void {
this._runtime.stepIn();
this.sendResponse(response);
}

/**
* 步出请求
*/
protected stepOutRequest(
response: DebugProtocol.StepOutResponse,
args: DebugProtocol.StepOutArguments
): void {
this._runtime.stepOut();
this.sendResponse(response);
}

/**
* 表达式求值请求
*/
protected evaluateRequest(
response: DebugProtocol.EvaluateResponse,
args: DebugProtocol.EvaluateArguments
): void {

let result = '';
let variablesReference = 0;

try {
const evaluation = this._runtime.evaluate(args.expression, args.frameId);
result = evaluation.result;
variablesReference = evaluation.variablesReference;
} catch (error) {
result = `Error: ${error}`;
}

response.body = {
result: result,
variablesReference: variablesReference
};
this.sendResponse(response);
}

/**
* 终止请求
*/
protected terminateRequest(
response: DebugProtocol.TerminateResponse,
args: DebugProtocol.TerminateArguments
): void {
this._runtime.terminate();
this.sendResponse(response);
}

private createSource(name: string, path: string): DebugProtocol.Source {
return {
name: name,
path: path,
sourceReference: 0
};
}
}

// 调试运行时模拟类
class MyLangRuntime extends EventEmitter {
// 这里需要实现具体的调试运行时逻辑
// 包括程序执行、断点管理、变量查看等

async start(program: string, stopOnEntry: boolean): Promise<void> {
// 启动程序执行
}

async attach(port: number, address: string): Promise<void> {
// 附加到运行中的进程
}

setBreakpoint(path: string, line: number): boolean {
// 设置断点
return true;
}

clearBreakpoints(path: string): void {
// 清除断点
}

// ... 其他运行时方法
}

⚙️ 第二部分:任务系统集成

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
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// src/taskProvider.ts
import * as vscode from 'vscode';
import * as path from 'path';

export class MyLangTaskProvider implements vscode.TaskProvider {
static MyLangType = 'mylang';
private tasks: vscode.Task[] | undefined;

constructor() {}

public async provideTasks(): Promise<vscode.Task[]> {
return this.getTasks();
}

public resolveTask(_task: vscode.Task): vscode.Task | undefined {
const task = _task.definition.task;
if (task) {
const definition: MyLangTaskDefinition = <any>_task.definition;
return this.getTask(
definition.task,
definition.file ? definition.file : '',
definition
);
}
return undefined;
}

private getTasks(): vscode.Task[] {
if (this.tasks !== undefined) {
return this.tasks;
}

this.tasks = [];

const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
return this.tasks;
}

for (const workspaceFolder of workspaceFolders) {
const tasks = this.getTasksForWorkspace(workspaceFolder);
this.tasks.push(...tasks);
}

return this.tasks;
}

private getTasksForWorkspace(workspaceFolder: vscode.WorkspaceFolder): vscode.Task[] {
const result: vscode.Task[] = [];

// 构建任务
result.push(this.getBuildTask(workspaceFolder));

// 运行任务
result.push(this.getRunTask(workspaceFolder));

// 测试任务
result.push(this.getTestTask(workspaceFolder));

// 清理任务
result.push(this.getCleanTask(workspaceFolder));

// 检查任务
result.push(this.getLintTask(workspaceFolder));

// 格式化任务
result.push(this.getFormatTask(workspaceFolder));

return result;
}

private getBuildTask(workspaceFolder: vscode.WorkspaceFolder): vscode.Task {
const definition: MyLangTaskDefinition = {
type: MyLangTaskProvider.MyLangType,
task: 'build'
};

const task = new vscode.Task(
definition,
workspaceFolder,
'build',
'mylang',
new vscode.ShellExecution('mylang build'),
['$mylang-compile']
);

task.group = vscode.TaskGroup.Build;
task.detail = '编译MyLang项目';
task.presentationOptions = {
echo: true,
reveal: vscode.TaskRevealKind.Always,
focus: false,
panel: vscode.TaskPanelKind.Shared,
showReuseMessage: true,
clear: false
};

return task;
}

private getRunTask(workspaceFolder: vscode.WorkspaceFolder): vscode.Task {
const definition: MyLangTaskDefinition = {
type: MyLangTaskProvider.MyLangType,
task: 'run'
};

// 查找主文件
const mainFile = this.findMainFile(workspaceFolder);
const command = mainFile ? `mylang run ${mainFile}` : 'mylang run';

const task = new vscode.Task(
definition,
workspaceFolder,
'run',
'mylang',
new vscode.ShellExecution(command),
[]
);

task.group = vscode.TaskGroup.Test;
task.detail = '运行MyLang程序';
task.presentationOptions = {
echo: true,
reveal: vscode.TaskRevealKind.Always,
focus: true,
panel: vscode.TaskPanelKind.Dedicated,
showReuseMessage: false,
clear: true
};

return task;
}

private getTestTask(workspaceFolder: vscode.WorkspaceFolder): vscode.Task {
const definition: MyLangTaskDefinition = {
type: MyLangTaskProvider.MyLangType,
task: 'test'
};

const task = new vscode.Task(
definition,
workspaceFolder,
'test',
'mylang',
new vscode.ShellExecution('mylang test'),
['$mylang-test']
);

task.group = vscode.TaskGroup.Test;
task.detail = '运行MyLang测试';

return task;
}

private getCleanTask(workspaceFolder: vscode.WorkspaceFolder): vscode.Task {
const definition: MyLangTaskDefinition = {
type: MyLangTaskProvider.MyLangType,
task: 'clean'
};

const task = new vscode.Task(
definition,
workspaceFolder,
'clean',
'mylang',
new vscode.ShellExecution('mylang clean'),
[]
);

task.detail = '清理构建输出';

return task;
}

private getLintTask(workspaceFolder: vscode.WorkspaceFolder): vscode.Task {
const definition: MyLangTaskDefinition = {
type: MyLangTaskProvider.MyLangType,
task: 'lint'
};

const task = new vscode.Task(
definition,
workspaceFolder,
'lint',
'mylang',
new vscode.ShellExecution('mylang lint'),
['$mylang-lint']
);

task.detail = '检查代码质量';

return task;
}

private getFormatTask(workspaceFolder: vscode.WorkspaceFolder): vscode.Task {
const definition: MyLangTaskDefinition = {
type: MyLangTaskProvider.MyLangType,
task: 'format'
};

const task = new vscode.Task(
definition,
workspaceFolder,
'format',
'mylang',
new vscode.ShellExecution('mylang format'),
[]
);

task.detail = '格式化代码';

return task;
}

private getTask(
task: string,
file: string,
definition: MyLangTaskDefinition
): vscode.Task | undefined {

const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
return undefined;
}

switch (task) {
case 'build':
return this.getBuildTask(workspaceFolder);
case 'run':
return this.getRunTask(workspaceFolder);
case 'test':
return this.getTestTask(workspaceFolder);
case 'clean':
return this.getCleanTask(workspaceFolder);
case 'lint':
return this.getLintTask(workspaceFolder);
case 'format':
return this.getFormatTask(workspaceFolder);
default:
return undefined;
}
}

private findMainFile(workspaceFolder: vscode.WorkspaceFolder): string | undefined {
const possibleMainFiles = ['main.ml', 'app.ml', 'index.ml'];

for (const fileName of possibleMainFiles) {
const filePath = path.join(workspaceFolder.uri.fsPath, fileName);
try {
if (require('fs').existsSync(filePath)) {
return fileName;
}
} catch (error) {
// 忽略错误
}
}

return undefined;
}
}

interface MyLangTaskDefinition extends vscode.TaskDefinition {
task: string;
file?: string;
args?: string[];
cwd?: string;
env?: { [key: string]: string };
}

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
// package.json中的问题匹配器配置
{
"contributes": {
"problemMatchers": [
{
"name": "mylang-compile",
"owner": "mylang",
"fileLocation": "relative",
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
},
{
"name": "mylang-test",
"owner": "mylang",
"fileLocation": "relative",
"pattern": [
{
"regexp": "^FAIL\\s+(.*)$",
"file": 1
},
{
"regexp": "^\\s+(.*):(\\d+):(\\d+):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"loop": true
}
]
},
{
"name": "mylang-lint",
"owner": "mylang",
"fileLocation": "relative",
"severity": "warning",
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)\\s+\\[(.*)\\]$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
}
]
}
}

📂 第三部分:源代码管理集成

3.1 SCM Provider开发

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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// src/scmProvider.ts
import * as vscode from 'vscode';
import * as path from 'path';

export class MyLangSCMProvider implements vscode.Disposable {
private disposables: vscode.Disposable[] = [];
private _sourceControl: vscode.SourceControl;
private _index: vscode.SourceControlResourceGroup;
private _workingTree: vscode.SourceControlResourceGroup;

constructor(private workspaceRoot: string) {
// 创建源代码管理控件
this._sourceControl = vscode.scm.createSourceControl(
'mylang-scm',
'MyLang SCM'
);

this._sourceControl.acceptInputCommand = {
command: 'mylang.commitAll',
title: 'Commit All',
arguments: [this._sourceControl]
};

this._sourceControl.quickDiffProvider = this;

// 创建资源组
this._index = this._sourceControl.createResourceGroup(
'index',
'Staged Changes'
);
this._index.hideWhenEmpty = true;

this._workingTree = this._sourceControl.createResourceGroup(
'workingTree',
'Changes'
);

// 注册命令
this.registerCommands();

// 初始状态
this.updateStatus();

// 监听文件变化
this.watchFiles();
}

private registerCommands(): void {
// 提交所有命令
this.disposables.push(
vscode.commands.registerCommand('mylang.commitAll', async (sourceControl) => {
const message = sourceControl.inputBox.value;
await this.commitAll(message);
sourceControl.inputBox.value = '';
})
);

// 暂存文件命令
this.disposables.push(
vscode.commands.registerCommand('mylang.stage', async (resource) => {
await this.stage(resource.resourceUri);
})
);

// 取消暂存命令
this.disposables.push(
vscode.commands.registerCommand('mylang.unstage', async (resource) => {
await this.unstage(resource.resourceUri);
})
);

// 丢弃更改命令
this.disposables.push(
vscode.commands.registerCommand('mylang.discard', async (resource) => {
const result = await vscode.window.showWarningMessage(
`确定要丢弃 ${path.basename(resource.resourceUri.fsPath)} 的更改吗?`,
{ modal: true },
'丢弃'
);
if (result === '丢弃') {
await this.discard(resource.resourceUri);
}
})
);

// 刷新命令
this.disposables.push(
vscode.commands.registerCommand('mylang.refresh', () => {
this.updateStatus();
})
);

// 查看文件历史命令
this.disposables.push(
vscode.commands.registerCommand('mylang.viewHistory', (resource) => {
this.viewHistory(resource.resourceUri);
})
);

// 比较文件命令
this.disposables.push(
vscode.commands.registerCommand('mylang.openDiff', (resource) => {
this.openDiff(resource.resourceUri);
})
);
}

private async updateStatus(): Promise<void> {
const changes = await this.getChanges();

// 更新工作树资源
this._workingTree.resourceStates = changes.workingTree.map(change => ({
resourceUri: change.uri,
decorations: {
strikeThrough: change.status === 'deleted',
faded: false,
tooltip: this.getStatusText(change.status),
iconPath: this.getStatusIcon(change.status)
},
command: {
command: 'mylang.openDiff',
title: 'Open Diff',
arguments: [{ resourceUri: change.uri }]
},
contextValue: 'modified'
}));

// 更新暂存区资源
this._index.resourceStates = changes.index.map(change => ({
resourceUri: change.uri,
decorations: {
strikeThrough: change.status === 'deleted',
faded: false,
tooltip: this.getStatusText(change.status),
iconPath: this.getStatusIcon(change.status)
},
command: {
command: 'mylang.openDiff',
title: 'Open Diff',
arguments: [{ resourceUri: change.uri }]
},
contextValue: 'staged'
}));

// 更新状态栏计数
this._sourceControl.count = changes.workingTree.length + changes.index.length;
}

private async getChanges(): Promise<{
workingTree: FileChange[],
index: FileChange[]
}> {
// 这里需要实现实际的文件状态检查逻辑
// 模拟返回一些变更
return {
workingTree: [
{
uri: vscode.Uri.file(path.join(this.workspaceRoot, 'main.ml')),
status: 'modified'
},
{
uri: vscode.Uri.file(path.join(this.workspaceRoot, 'utils.ml')),
status: 'added'
}
],
index: []
};
}

private getStatusText(status: FileStatus): string {
switch (status) {
case 'modified': return '已修改';
case 'added': return '新增';
case 'deleted': return '已删除';
case 'renamed': return '重命名';
case 'copied': return '复制';
default: return '未知';
}
}

private getStatusIcon(status: FileStatus): vscode.ThemeIcon {
switch (status) {
case 'modified': return new vscode.ThemeIcon('diff-modified');
case 'added': return new vscode.ThemeIcon('diff-added');
case 'deleted': return new vscode.ThemeIcon('diff-removed');
case 'renamed': return new vscode.ThemeIcon('diff-renamed');
default: return new vscode.ThemeIcon('diff');
}
}

private async stage(uri: vscode.Uri): Promise<void> {
// 实现暂存逻辑
console.log(`暂存文件: ${uri.fsPath}`);
await this.updateStatus();
}

private async unstage(uri: vscode.Uri): Promise<void> {
// 实现取消暂存逻辑
console.log(`取消暂存文件: ${uri.fsPath}`);
await this.updateStatus();
}

private async discard(uri: vscode.Uri): Promise<void> {
// 实现丢弃更改逻辑
console.log(`丢弃更改: ${uri.fsPath}`);
await this.updateStatus();
}

private async commitAll(message: string): Promise<void> {
if (!message.trim()) {
vscode.window.showErrorMessage('请输入提交消息');
return;
}

// 实现提交逻辑
console.log(`提交消息: ${message}`);

await vscode.window.withProgress({
location: vscode.ProgressLocation.SourceControl,
title: '正在提交...'
}, async () => {
// 模拟提交过程
await new Promise(resolve => setTimeout(resolve, 1000));
});

await this.updateStatus();
vscode.window.showInformationMessage('提交成功');
}

private async viewHistory(uri: vscode.Uri): Promise<void> {
// 实现查看文件历史
console.log(`查看历史: ${uri.fsPath}`);
}

private async openDiff(uri: vscode.Uri): Promise<void> {
// 打开差异比较
const originalUri = uri.with({ scheme: 'mylang-original' });

await vscode.commands.executeCommand(
'vscode.diff',
originalUri,
uri,
`${path.basename(uri.fsPath)} (Working Tree)`
);
}

private watchFiles(): void {
// 监听文件变化
const watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this.workspaceRoot, '**/*.ml')
);

watcher.onDidChange(() => this.updateStatus());
watcher.onDidCreate(() => this.updateStatus());
watcher.onDidDelete(() => this.updateStatus());

this.disposables.push(watcher);
}

// 实现QuickDiffProvider接口
async provideOriginalResource(uri: vscode.Uri): Promise<vscode.Uri | undefined> {
if (uri.scheme === 'file') {
return uri.with({ scheme: 'mylang-original' });
}
return undefined;
}

dispose(): void {
this.disposables.forEach(d => d.dispose());
this._sourceControl.dispose();
}
}

interface FileChange {
uri: vscode.Uri;
status: FileStatus;
}

type FileStatus = 'modified' | 'added' | 'deleted' | 'renamed' | 'copied';

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// src/fileSystemProvider.ts
import * as vscode from 'vscode';

export class MyLangFileSystemProvider implements vscode.FileSystemProvider {

private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event;

watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
// 实现文件监听
return new vscode.Disposable(() => {});
}

stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
// 获取文件状态
return {
type: vscode.FileType.File,
ctime: Date.now(),
mtime: Date.now(),
size: 0
};
}

readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
// 读取目录
return [];
}

createDirectory(uri: vscode.Uri): void | Thenable<void> {
// 创建目录
}

readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
// 读取文件
if (uri.scheme === 'mylang-original') {
// 返回文件的原始版本
return this.getOriginalContent(uri);
}

return new Uint8Array();
}

writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
// 写入文件
}

delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable<void> {
// 删除文件
}

rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
// 重命名文件
}

private async getOriginalContent(uri: vscode.Uri): Promise<Uint8Array> {
// 获取文件的原始版本(如从版本控制系统)
const originalUri = uri.with({ scheme: 'file' });

// 这里需要实现从版本控制系统获取文件原始内容的逻辑
// 模拟返回空内容
return new Uint8Array();
}
}

🌐 第四部分:远程开发支持

4.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
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// src/remoteExtensionHost.ts
import * as vscode from 'vscode';

export class RemoteExtensionManager {

static async activate(context: vscode.ExtensionContext): Promise<void> {
// 检查是否在远程环境中运行
if (vscode.env.remoteName) {
console.log(`运行在远程环境: ${vscode.env.remoteName}`);

// 远程环境特定的初始化
await this.initializeRemoteFeatures(context);
}

// 注册远程相关命令
this.registerRemoteCommands(context);
}

private static async initializeRemoteFeatures(context: vscode.ExtensionContext): Promise<void> {
// 配置远程文件系统
this.setupRemoteFileSystem(context);

// 配置远程调试
this.setupRemoteDebugging(context);

// 配置远程任务
this.setupRemoteTasks(context);
}

private static setupRemoteFileSystem(context: vscode.ExtensionContext): void {
// 注册远程文件系统提供者
const remoteFileSystemProvider = new RemoteFileSystemProvider();

context.subscriptions.push(
vscode.workspace.registerFileSystemProvider(
'mylang-remote',
remoteFileSystemProvider,
{ isCaseSensitive: true }
)
);
}

private static setupRemoteDebugging(context: vscode.ExtensionContext): void {
// 远程调试配置
const remoteDebugConfigProvider = new RemoteDebugConfigurationProvider();

context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
'mylang-remote',
remoteDebugConfigProvider
)
);
}

private static setupRemoteTasks(context: vscode.ExtensionContext): void {
// 远程任务提供者
const remoteTaskProvider = new RemoteTaskProvider();

context.subscriptions.push(
vscode.tasks.registerTaskProvider(
'mylang-remote',
remoteTaskProvider
)
);
}

private static registerRemoteCommands(context: vscode.ExtensionContext): void {
// 连接到远程服务器
context.subscriptions.push(
vscode.commands.registerCommand('mylang.connectRemote', async () => {
await this.connectToRemoteServer();
})
);

// 断开远程连接
context.subscriptions.push(
vscode.commands.registerCommand('mylang.disconnectRemote', async () => {
await this.disconnectFromRemoteServer();
})
);

// 同步远程文件
context.subscriptions.push(
vscode.commands.registerCommand('mylang.syncRemoteFiles', async () => {
await this.syncRemoteFiles();
})
);

// 执行远程命令
context.subscriptions.push(
vscode.commands.registerCommand('mylang.executeRemoteCommand', async () => {
await this.executeRemoteCommand();
})
);
}

private static async connectToRemoteServer(): Promise<void> {
const serverConfig = await vscode.window.showInputBox({
prompt: '输入远程服务器地址',
placeholder: 'user@hostname:port'
});

if (!serverConfig) {
return;
}

await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: '正在连接远程服务器...',
cancellable: true
}, async (progress, token) => {
// 实现远程连接逻辑
progress.report({ increment: 50, message: '建立连接...' });

// 模拟连接过程
await new Promise(resolve => setTimeout(resolve, 2000));

if (token.isCancellationRequested) {
throw new Error('连接已取消');
}

progress.report({ increment: 100, message: '连接成功' });
});

vscode.window.showInformationMessage('已连接到远程服务器');
}

private static async disconnectFromRemoteServer(): Promise<void> {
// 实现断开连接逻辑
vscode.window.showInformationMessage('已断开远程连接');
}

private static async syncRemoteFiles(): Promise<void> {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: '正在同步远程文件...'
}, async (progress) => {
// 实现文件同步逻辑
progress.report({ increment: 50, message: '下载文件...' });
await new Promise(resolve => setTimeout(resolve, 1000));

progress.report({ increment: 100, message: '同步完成' });
});
}

private static async executeRemoteCommand(): Promise<void> {
const command = await vscode.window.showInputBox({
prompt: '输入要执行的远程命令',
placeholder: 'mylang --version'
});

if (!command) {
return;
}

// 创建终端执行远程命令
const terminal = vscode.window.createTerminal({
name: 'Remote MyLang',
env: {
'REMOTE_HOST': vscode.env.remoteName || 'localhost'
}
});

terminal.sendText(command);
terminal.show();
}
}

class RemoteFileSystemProvider implements vscode.FileSystemProvider {
private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile = this._emitter.event;

// 实现远程文件系统接口
watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
return new vscode.Disposable(() => {});
}

stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
// 获取远程文件状态
return {
type: vscode.FileType.File,
ctime: Date.now(),
mtime: Date.now(),
size: 0
};
}

readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
// 读取远程目录
return [];
}

createDirectory(uri: vscode.Uri): void | Thenable<void> {
// 在远程创建目录
}

readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
// 读取远程文件
return new Uint8Array();
}

writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
// 写入远程文件
}

delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable<void> {
// 删除远程文件
}

rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
// 重命名远程文件
}
}

class RemoteDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
config: vscode.DebugConfiguration,
token?: vscode.CancellationToken
): vscode.ProviderResult<vscode.DebugConfiguration> {

// 配置远程调试
config.type = 'mylang-remote';
config.request = config.request || 'attach';
config.remoteHost = vscode.env.remoteName;

return config;
}
}

class RemoteTaskProvider implements vscode.TaskProvider {
provideTasks(token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task[]> {
// 提供远程任务
return [];
}

resolveTask(task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task> {
// 解析远程任务
return task;
}
}

📚 本阶段总结

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

调试器扩展开发:Debug Adapter Protocol、断点管理、调试会话控制
任务系统集成:自定义构建任务、问题匹配器、工具链集成
源代码管理:SCM Provider开发、文件状态管理、版本控制集成
远程开发支持:虚拟文件系统、远程调试、协议扩展
工作区增强:多根工作区、文件监听、配置管理

核心技术回顾

  • DAP协议:调试适配器协议的实现和扩展
  • 任务系统:自动发现、执行和监控构建任务
  • SCM集成:版本控制状态展示和操作
  • 远程开发:跨网络的开发环境支持
  • 文件系统:虚拟文件系统和自定义协议

🚀 下一步学习方向

在下一阶段(测试与发布),我们将学习:

  • 🧪 扩展测试:单元测试、集成测试、端到端测试
  • 📦 打包优化:Webpack配置、资源压缩、依赖管理
  • 🚀 发布流程:市场发布、CI/CD集成、版本管理
  • 📊 监控和分析:遥测数据、错误报告、性能监控

继续深入学习之前,建议:

  1. 实践开发一个包含调试功能的扩展
  2. 研究现有SCM扩展的实现方式
  3. 理解远程开发的架构和协议
  4. 测试扩展在不同环境下的兼容性

相关资源

恭喜你完成了VS Code扩展开发的高级主题阶段!现在你已经具备了开发企业级、全功能扩展的能力。