语言扩展是VS Code生态系统的核心,为不同编程语言提供智能编辑支持。本阶段将深入探讨如何开发完整的语言支持扩展,从基础的语法高亮到高级的Language Server Protocol实现,帮你构建专业级的编程语言工具。


🎯 学习目标

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

  • 📝 语法高亮开发:TextMate语法、语义高亮、嵌入语言支持
  • 🧩 代码片段系统:动态片段、条件片段、智能触发
  • ⚙️ 语言基础配置:文件关联、注释样式、括号匹配
  • 🔍 智能感知功能:代码补全、参数提示、悬停信息、错误诊断
  • 🌐 Language Server Protocol:架构设计、客户端/服务器开发
  • 🛠️ 代码操作:格式化、重构、快速修复、代码导航

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


📝 第一部分:语法高亮与基础配置

1.1 TextMate语法开发

语法文件结构

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
// syntaxes/mylang.tmLanguage.json
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "MyLang",
"scopeName": "source.mylang",
"fileTypes": ["ml", "mylang"],
"patterns": [
{
"include": "#comments"
},
{
"include": "#keywords"
},
{
"include": "#strings"
},
{
"include": "#numbers"
},
{
"include": "#functions"
},
{
"include": "#operators"
}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.mylang",
"begin": "//",
"end": "$",
"captures": {
"0": {
"name": "punctuation.definition.comment.mylang"
}
}
},
{
"name": "comment.block.mylang",
"begin": "/\\*",
"end": "\\*/",
"captures": {
"0": {
"name": "punctuation.definition.comment.mylang"
}
}
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.mylang",
"match": "\\b(if|else|while|for|return|break|continue)\\b"
},
{
"name": "keyword.other.mylang",
"match": "\\b(var|let|const|function|class|interface)\\b"
},
{
"name": "storage.type.mylang",
"match": "\\b(int|string|bool|float|void)\\b"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.mylang",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.mylang",
"match": "\\\\."
},
{
"include": "#string-interpolation"
}
]
},
{
"name": "string.quoted.single.mylang",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.mylang",
"match": "\\\\."
}
]
}
]
},
"string-interpolation": {
"patterns": [
{
"name": "meta.interpolation.mylang",
"begin": "\\$\\{",
"end": "\\}",
"patterns": [
{
"include": "$self"
}
],
"beginCaptures": {
"0": {
"name": "punctuation.definition.interpolation.begin.mylang"
}
},
"endCaptures": {
"0": {
"name": "punctuation.definition.interpolation.end.mylang"
}
}
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.float.mylang",
"match": "\\b\\d+\\.\\d+([eE][+-]?\\d+)?\\b"
},
{
"name": "constant.numeric.integer.mylang",
"match": "\\b\\d+\\b"
},
{
"name": "constant.numeric.hex.mylang",
"match": "\\b0[xX][0-9a-fA-F]+\\b"
},
{
"name": "constant.numeric.binary.mylang",
"match": "\\b0[bB][01]+\\b"
}
]
},
"functions": {
"patterns": [
{
"name": "meta.function.declaration.mylang",
"begin": "\\b(function)\\s+(\\w+)\\s*\\(",
"end": "\\)",
"beginCaptures": {
"1": {
"name": "keyword.other.mylang"
},
"2": {
"name": "entity.name.function.mylang"
}
},
"patterns": [
{
"include": "#function-parameters"
}
]
},
{
"name": "meta.function.call.mylang",
"begin": "\\b(\\w+)\\s*\\(",
"end": "\\)",
"beginCaptures": {
"1": {
"name": "entity.name.function.mylang"
}
},
"patterns": [
{
"include": "$self"
}
]
}
]
},
"function-parameters": {
"patterns": [
{
"name": "variable.parameter.mylang",
"match": "\\b\\w+\\b"
},
{
"include": "#strings"
},
{
"include": "#numbers"
}
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.arithmetic.mylang",
"match": "[+\\-*/%]"
},
{
"name": "keyword.operator.comparison.mylang",
"match": "(==|!=|<=|>=|<|>)"
},
{
"name": "keyword.operator.logical.mylang",
"match": "(&&|\\|\\||!)"
},
{
"name": "keyword.operator.assignment.mylang",
"match": "(=|\\+=|-=|\\*=|/=)"
}
]
}
}
}

语言配置文件

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
// language-configuration.json
{
"comments": {
"lineComment": "//",
"blockComment": [ "/*", "*/" ]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#?region\\b",
"end": "^\\s*//\\s*#?endregion\\b"
}
},
"wordPattern": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b",
"indentationRules": {
"increaseIndentPattern": "^((?!\\/\\*).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$"
}
}

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

export class MyLangSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider {

// 定义语义token类型和修饰符
private readonly legend: vscode.SemanticTokensLegend = new vscode.SemanticTokensLegend(
[
'namespace', 'type', 'class', 'enum', 'interface', 'struct',
'typeParameter', 'parameter', 'variable', 'property', 'enumMember',
'event', 'function', 'method', 'macro', 'keyword', 'modifier',
'comment', 'string', 'number', 'regexp', 'operator'
],
[
'declaration', 'definition', 'readonly', 'static', 'deprecated',
'abstract', 'async', 'modification', 'documentation', 'defaultLibrary'
]
);

async provideDocumentSemanticTokens(
document: vscode.TextDocument,
token: vscode.CancellationToken
): Promise<vscode.SemanticTokens> {

const tokensBuilder = new vscode.SemanticTokensBuilder(this.legend);
const text = document.getText();

// 解析文档并生成语义tokens
await this.parseDocument(document, tokensBuilder);

return tokensBuilder.build();
}

private async parseDocument(
document: vscode.TextDocument,
builder: vscode.SemanticTokensBuilder
): Promise<void> {

const text = document.getText();
const lines = text.split('\n');

for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex];

// 解析函数定义
const functionMatch = /function\s+(\w+)\s*\(/g;
let match;
while ((match = functionMatch.exec(line)) !== null) {
const startPos = match.index + match[0].indexOf(match[1]);
builder.push(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + match[1].length),
'function',
['declaration']
);
}

// 解析变量声明
const variableMatch = /(var|let|const)\s+(\w+)/g;
while ((match = variableMatch.exec(line)) !== null) {
const startPos = match.index + match[0].indexOf(match[2]);
builder.push(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + match[2].length),
'variable',
['declaration']
);
}

// 解析类型声明
const typeMatch = /(class|interface|enum)\s+(\w+)/g;
while ((match = typeMatch.exec(line)) !== null) {
const startPos = match.index + match[0].indexOf(match[2]);
const tokenType = match[1] === 'class' ? 'class' :
match[1] === 'interface' ? 'interface' : 'enum';
builder.push(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + match[2].length),
tokenType,
['declaration']
);
}

// 解析字符串字面量中的变量插值
const interpolationMatch = /\$\{(\w+)\}/g;
while ((match = interpolationMatch.exec(line)) !== null) {
const startPos = match.index + 2; // 跳过 ${
builder.push(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + match[1].length),
'variable',
['readonly']
);
}

// 解析注释中的TODO、FIXME等
const commentTodoMatch = /\/\/.*?(TODO|FIXME|NOTE|HACK):/g;
while ((match = commentTodoMatch.exec(line)) !== null) {
const startPos = match.index + match[0].indexOf(match[1]);
builder.push(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + match[1].length),
'keyword',
['documentation']
);
}
}
}

// 增量更新支持
async provideDocumentSemanticTokensEdits(
document: vscode.TextDocument,
previousResultId: string,
token: vscode.CancellationToken
): Promise<vscode.SemanticTokens | vscode.SemanticTokensEdits> {

// 对于简单情况,直接返回新的tokens
// 复杂的增量更新需要实现差异检测
return this.provideDocumentSemanticTokens(document, token);
}

getLegend(): vscode.SemanticTokensLegend {
return this.legend;
}
}

1.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
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
// snippets/mylang.code-snippets
{
"Function Declaration": {
"prefix": ["func", "function"],
"body": [
"function ${1:functionName}(${2:parameters}) {",
"\t${3:// function body}",
"\treturn ${4:undefined};",
"}"
],
"description": "创建函数声明",
"scope": "source.mylang"
},

"Class Declaration": {
"prefix": ["class"],
"body": [
"class ${1:ClassName} {",
"\tconstructor(${2:parameters}) {",
"\t\t${3:// constructor body}",
"\t}",
"",
"\t${4:methodName}() {",
"\t\t${5:// method body}",
"\t}",
"}"
],
"description": "创建类声明",
"scope": "source.mylang"
},

"If Statement": {
"prefix": ["if"],
"body": [
"if (${1:condition}) {",
"\t${2:// if body}",
"}"
],
"description": "创建if语句",
"scope": "source.mylang"
},

"If-Else Statement": {
"prefix": ["ifelse", "ife"],
"body": [
"if (${1:condition}) {",
"\t${2:// if body}",
"} else {",
"\t${3:// else body}",
"}"
],
"description": "创建if-else语句",
"scope": "source.mylang"
},

"For Loop": {
"prefix": ["for"],
"body": [
"for (let ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) {",
"\t${3:// loop body}",
"}"
],
"description": "创建for循环",
"scope": "source.mylang"
},

"While Loop": {
"prefix": ["while"],
"body": [
"while (${1:condition}) {",
"\t${2:// loop body}",
"}"
],
"description": "创建while循环",
"scope": "source.mylang"
},

"Try-Catch": {
"prefix": ["try", "tryc"],
"body": [
"try {",
"\t${1:// try body}",
"} catch (${2:error}) {",
"\t${3:// catch body}",
"}"
],
"description": "创建try-catch语句",
"scope": "source.mylang"
},

"Variable Declaration": {
"prefix": ["var"],
"body": [
"${1|var,let,const|} ${2:variableName} = ${3:value};"
],
"description": "创建变量声明",
"scope": "source.mylang"
},

"Console Log": {
"prefix": ["log", "console"],
"body": [
"console.log(${1:message});"
],
"description": "创建console.log语句",
"scope": "source.mylang"
},

"Import Statement": {
"prefix": ["import"],
"body": [
"import { ${2:exports} } from '${1:module}';"
],
"description": "创建import语句",
"scope": "source.mylang"
},

"Export Statement": {
"prefix": ["export"],
"body": [
"export ${1|default,|} ${2:exportName};"
],
"description": "创建export语句",
"scope": "source.mylang"
},

"Arrow Function": {
"prefix": ["arrow", "=>"],
"body": [
"const ${1:functionName} = (${2:parameters}) => {",
"\t${3:// function body}",
"\treturn ${4:undefined};",
"};"
],
"description": "创建箭头函数",
"scope": "source.mylang"
},

"Async Function": {
"prefix": ["async"],
"body": [
"async function ${1:functionName}(${2:parameters}) {",
"\t${3:// async function body}",
"\treturn ${4:undefined};",
"}"
],
"description": "创建异步函数",
"scope": "source.mylang"
},

"Interface Declaration": {
"prefix": ["interface"],
"body": [
"interface ${1:InterfaceName} {",
"\t${2:propertyName}: ${3:type};",
"\t${4:methodName}(${5:parameters}): ${6:returnType};",
"}"
],
"description": "创建接口声明",
"scope": "source.mylang"
},

"Enum Declaration": {
"prefix": ["enum"],
"body": [
"enum ${1:EnumName} {",
"\t${2:VALUE1},",
"\t${3:VALUE2},",
"\t${4:VALUE3}",
"}"
],
"description": "创建枚举声明",
"scope": "source.mylang"
}
}

🔍 第二部分:智能感知功能

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

export class MyLangCompletionProvider implements vscode.CompletionItemProvider {

private keywords = [
'function', 'var', 'let', 'const', 'if', 'else', 'while', 'for',
'return', 'break', 'continue', 'class', 'interface', 'enum',
'import', 'export', 'async', 'await', 'try', 'catch', 'finally'
];

private builtinTypes = [
'string', 'number', 'boolean', 'object', 'array', 'void', 'any'
];

async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
): Promise<vscode.CompletionItem[]> {

const lineText = document.lineAt(position).text;
const wordRange = document.getWordRangeAtPosition(position);
const currentWord = wordRange ? document.getText(wordRange) : '';

const completions: vscode.CompletionItem[] = [];

// 根据上下文提供不同的补全
if (this.isInStringContext(lineText, position.character)) {
// 字符串内的补全
completions.push(...this.getStringCompletions());
} else if (this.isInCommentContext(lineText, position.character)) {
// 注释内的补全
completions.push(...this.getCommentCompletions());
} else {
// 普通代码补全
completions.push(
...this.getKeywordCompletions(),
...this.getTypeCompletions(),
...this.getFunctionCompletions(document, position),
...this.getVariableCompletions(document, position),
...this.getSnippetCompletions()
);
}

return completions.filter(item =>
!currentWord || item.label.toString().toLowerCase().includes(currentWord.toLowerCase())
);
}

private getKeywordCompletions(): vscode.CompletionItem[] {
return this.keywords.map(keyword => {
const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword);
item.detail = `关键字 ${keyword}`;
item.documentation = new vscode.MarkdownString(`MyLang关键字 \`${keyword}\``);

// 为某些关键字添加代码片段
switch (keyword) {
case 'function':
item.insertText = new vscode.SnippetString('function ${1:name}(${2:params}) {\n\t$0\n}');
break;
case 'if':
item.insertText = new vscode.SnippetString('if (${1:condition}) {\n\t$0\n}');
break;
case 'for':
item.insertText = new vscode.SnippetString('for (let ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) {\n\t$0\n}');
break;
case 'class':
item.insertText = new vscode.SnippetString('class ${1:ClassName} {\n\tconstructor(${2:params}) {\n\t\t$0\n\t}\n}');
break;
}

return item;
});
}

private getTypeCompletions(): vscode.CompletionItem[] {
return this.builtinTypes.map(type => {
const item = new vscode.CompletionItem(type, vscode.CompletionItemKind.TypeParameter);
item.detail = `内置类型 ${type}`;
item.documentation = new vscode.MarkdownString(`MyLang内置类型 \`${type}\``);
return item;
});
}

private getFunctionCompletions(
document: vscode.TextDocument,
position: vscode.Position
): vscode.CompletionItem[] {
const functions: vscode.CompletionItem[] = [];
const text = document.getText();

// 解析文档中的函数定义
const functionRegex = /function\s+(\w+)\s*\(([^)]*)\)/g;
let match;

while ((match = functionRegex.exec(text)) !== null) {
const [, functionName, params] = match;

const item = new vscode.CompletionItem(functionName, vscode.CompletionItemKind.Function);
item.detail = `function ${functionName}(${params})`;
item.documentation = new vscode.MarkdownString(`用户定义的函数\n\n\`\`\`mylang\nfunction ${functionName}(${params})\n\`\`\``);

// 添加参数提示
const paramList = params.split(',').map((p, i) => `\${${i + 1}:${p.trim()}}`).join(', ');
item.insertText = new vscode.SnippetString(`${functionName}(${paramList})`);

functions.push(item);
}

// 添加内置函数
const builtinFunctions = [
{
name: 'console.log',
params: 'message',
description: '在控制台输出消息'
},
{
name: 'Math.abs',
params: 'x',
description: '返回数字的绝对值'
},
{
name: 'String.prototype.charAt',
params: 'index',
description: '返回指定位置的字符'
}
];

builtinFunctions.forEach(func => {
const item = new vscode.CompletionItem(func.name, vscode.CompletionItemKind.Function);
item.detail = `${func.name}(${func.params})`;
item.documentation = new vscode.MarkdownString(func.description);
item.insertText = new vscode.SnippetString(`${func.name}(\${1:${func.params}})`);
functions.push(item);
});

return functions;
}

private getVariableCompletions(
document: vscode.TextDocument,
position: vscode.Position
): vscode.CompletionItem[] {
const variables: vscode.CompletionItem[] = [];
const text = document.getText(new vscode.Range(new vscode.Position(0, 0), position));

// 解析变量声明
const variableRegex = /(var|let|const)\s+(\w+)/g;
let match;

while ((match = variableRegex.exec(text)) !== null) {
const [, type, variableName] = match;

const item = new vscode.CompletionItem(variableName, vscode.CompletionItemKind.Variable);
item.detail = `${type} ${variableName}`;
item.documentation = new vscode.MarkdownString(`变量声明:\`${type} ${variableName}\``);

variables.push(item);
}

return variables;
}

private getSnippetCompletions(): vscode.CompletionItem[] {
const snippets = [
{
label: 'log',
detail: 'console.log语句',
snippet: 'console.log(${1:message});'
},
{
label: 'func',
detail: '函数声明',
snippet: 'function ${1:name}(${2:params}) {\n\t${3:// body}\n\treturn ${4:undefined};\n}'
},
{
label: 'tryc',
detail: 'try-catch语句',
snippet: 'try {\n\t${1:// try body}\n} catch (${2:error}) {\n\t${3:// catch body}\n}'
}
];

return snippets.map(snippet => {
const item = new vscode.CompletionItem(snippet.label, vscode.CompletionItemKind.Snippet);
item.detail = snippet.detail;
item.insertText = new vscode.SnippetString(snippet.snippet);
item.documentation = new vscode.MarkdownString(`代码片段:${snippet.detail}`);
return item;
});
}

private getStringCompletions(): vscode.CompletionItem[] {
// 字符串内的特殊补全(如模板字符串变量)
return [
new vscode.CompletionItem('${variable}', vscode.CompletionItemKind.Text)
];
}

private getCommentCompletions(): vscode.CompletionItem[] {
// 注释内的补全(如TODO、FIXME)
const commentKeywords = ['TODO', 'FIXME', 'NOTE', 'HACK', 'XXX'];
return commentKeywords.map(keyword => {
const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword);
item.detail = `注释关键字 ${keyword}`;
return item;
});
}

private isInStringContext(lineText: string, position: number): boolean {
const beforeCursor = lineText.substring(0, position);
const quoteCount = (beforeCursor.match(/"/g) || []).length;
return quoteCount % 2 === 1;
}

private isInCommentContext(lineText: string, position: number): boolean {
const beforeCursor = lineText.substring(0, position);
return beforeCursor.includes('//') || beforeCursor.includes('/*');
}

resolveCompletionItem(
item: vscode.CompletionItem,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.CompletionItem> {

// 为补全项添加更详细的信息
if (item.kind === vscode.CompletionItemKind.Function) {
item.documentation = new vscode.MarkdownString(
`函数:${item.label}\n\n` +
`**详细信息:**\n` +
`- 参数:${item.detail}\n` +
`- 返回值:根据实现而定\n\n` +
`**示例:**\n` +
`\`\`\`mylang\n${item.label}(参数)\n\`\`\``
);
}

return item;
}
}

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

export class MyLangHoverProvider implements vscode.HoverProvider {

async provideHover(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): Promise<vscode.Hover | undefined> {

const wordRange = document.getWordRangeAtPosition(position);
if (!wordRange) {
return undefined;
}

const word = document.getText(wordRange);
const line = document.lineAt(position.line).text;

// 获取悬停信息
const hoverInfo = await this.getHoverInfo(document, word, position, line);

if (hoverInfo) {
return new vscode.Hover(hoverInfo, wordRange);
}

return undefined;
}

private async getHoverInfo(
document: vscode.TextDocument,
word: string,
position: vscode.Position,
line: string
): Promise<vscode.MarkdownString | undefined> {

// 检查是否是关键字
if (this.isKeyword(word)) {
return this.getKeywordHover(word);
}

// 检查是否是函数调用
if (this.isFunctionCall(line, word)) {
return this.getFunctionHover(document, word);
}

// 检查是否是变量
if (this.isVariable(document, word, position)) {
return this.getVariableHover(document, word, position);
}

// 检查是否是类型
if (this.isType(word)) {
return this.getTypeHover(word);
}

// 检查是否是内置函数或对象
if (this.isBuiltinFunction(word)) {
return this.getBuiltinFunctionHover(word);
}

return undefined;
}

private isKeyword(word: string): boolean {
const keywords = [
'function', 'var', 'let', 'const', 'if', 'else', 'while', 'for',
'return', 'break', 'continue', 'class', 'interface', 'enum',
'import', 'export', 'async', 'await', 'try', 'catch', 'finally'
];
return keywords.includes(word);
}

private getKeywordHover(keyword: string): vscode.MarkdownString {
const descriptions: Record<string, string> = {
'function': '用于声明函数的关键字',
'var': '声明变量(函数作用域)',
'let': '声明变量(块作用域)',
'const': '声明常量(块作用域)',
'if': '条件语句',
'else': '条件语句的否则分支',
'while': '循环语句',
'for': '循环语句',
'return': '函数返回语句',
'break': '跳出循环或switch语句',
'continue': '跳过当前循环迭代',
'class': '声明类',
'interface': '声明接口',
'enum': '声明枚举',
'import': '导入模块',
'export': '导出模块成员',
'async': '声明异步函数',
'await': '等待异步操作完成',
'try': '异常处理语句',
'catch': '捕获异常',
'finally': '异常处理的最终执行块'
};

const description = descriptions[keyword] || '关键字';

const markdown = new vscode.MarkdownString();
markdown.appendCodeblock(`${keyword}`, 'mylang');
markdown.appendMarkdown(`**关键字:** ${keyword}\n\n`);
markdown.appendMarkdown(`**描述:** ${description}\n\n`);

// 添加使用示例
const examples: Record<string, string> = {
'function': 'function myFunction(param1, param2) {\n return param1 + param2;\n}',
'if': 'if (condition) {\n // 执行代码\n}',
'for': 'for (let i = 0; i < 10; i++) {\n // 循环体\n}',
'class': 'class MyClass {\n constructor() {\n // 构造函数\n }\n}'
};

if (examples[keyword]) {
markdown.appendMarkdown(`**示例:**\n`);
markdown.appendCodeblock(examples[keyword], 'mylang');
}

return markdown;
}

private isFunctionCall(line: string, word: string): boolean {
const functionCallPattern = new RegExp(`\\b${word}\\s*\\(`);
return functionCallPattern.test(line);
}

private getFunctionHover(document: vscode.TextDocument, functionName: string): vscode.MarkdownString {
const text = document.getText();

// 查找函数定义
const functionDefPattern = new RegExp(`function\\s+${functionName}\\s*\\(([^)]*)\\)\\s*\\{`, 'g');
const match = functionDefPattern.exec(text);

const markdown = new vscode.MarkdownString();

if (match) {
const params = match[1];
markdown.appendCodeblock(`function ${functionName}(${params})`, 'mylang');
markdown.appendMarkdown(`**函数:** ${functionName}\n\n`);
markdown.appendMarkdown(`**参数:** ${params || '无'}\n\n`);

// 尝试提取函数注释
const functionComment = this.extractFunctionComment(text, match.index);
if (functionComment) {
markdown.appendMarkdown(`**描述:** ${functionComment}\n\n`);
}
} else {
// 检查是否是内置函数
const builtinInfo = this.getBuiltinFunctionInfo(functionName);
if (builtinInfo) {
markdown.appendCodeblock(`${functionName}(${builtinInfo.params})`, 'mylang');
markdown.appendMarkdown(`**内置函数:** ${functionName}\n\n`);
markdown.appendMarkdown(`**描述:** ${builtinInfo.description}\n\n`);
markdown.appendMarkdown(`**返回值:** ${builtinInfo.returnType}\n\n`);
} else {
markdown.appendCodeblock(functionName, 'mylang');
markdown.appendMarkdown(`**函数:** ${functionName}\n\n`);
markdown.appendMarkdown(`*未找到函数定义*`);
}
}

return markdown;
}

private isVariable(document: vscode.TextDocument, word: string, position: vscode.Position): boolean {
const text = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
const variablePattern = new RegExp(`\\b(var|let|const)\\s+${word}\\b`);
return variablePattern.test(text);
}

private getVariableHover(
document: vscode.TextDocument,
variableName: string,
position: vscode.Position
): vscode.MarkdownString {

const text = document.getText(new vscode.Range(new vscode.Position(0, 0), position));

// 查找变量声明
const variablePattern = new RegExp(`\\b(var|let|const)\\s+${variableName}(\\s*=\\s*([^;\\n]+))?`, 'g');
const matches = Array.from(text.matchAll(variablePattern));

const markdown = new vscode.MarkdownString();

if (matches.length > 0) {
const lastMatch = matches[matches.length - 1];
const [, declarationType, , initialValue] = lastMatch;

markdown.appendCodeblock(`${declarationType} ${variableName}${initialValue ? ` = ${initialValue}` : ''}`, 'mylang');
markdown.appendMarkdown(`**变量:** ${variableName}\n\n`);
markdown.appendMarkdown(`**类型:** ${declarationType}\n\n`);

if (initialValue) {
markdown.appendMarkdown(`**初始值:** ${initialValue.trim()}\n\n`);

// 尝试推断类型
const inferredType = this.inferType(initialValue.trim());
if (inferredType) {
markdown.appendMarkdown(`**推断类型:** ${inferredType}\n\n`);
}
}
} else {
markdown.appendCodeblock(variableName, 'mylang');
markdown.appendMarkdown(`**变量:** ${variableName}\n\n`);
markdown.appendMarkdown(`*未找到变量声明*`);
}

return markdown;
}

private isType(word: string): boolean {
const types = ['string', 'number', 'boolean', 'object', 'array', 'void', 'any'];
return types.includes(word);
}

private getTypeHover(typeName: string): vscode.MarkdownString {
const typeDescriptions: Record<string, string> = {
'string': '字符串类型 - 表示文本数据',
'number': '数字类型 - 表示整数或浮点数',
'boolean': '布尔类型 - 表示true或false',
'object': '对象类型 - 表示复合数据结构',
'array': '数组类型 - 表示有序的元素集合',
'void': 'void类型 - 表示无返回值',
'any': 'any类型 - 表示任意类型'
};

const markdown = new vscode.MarkdownString();
markdown.appendCodeblock(typeName, 'mylang');
markdown.appendMarkdown(`**类型:** ${typeName}\n\n`);
markdown.appendMarkdown(`**描述:** ${typeDescriptions[typeName]}\n\n`);

return markdown;
}

private isBuiltinFunction(word: string): boolean {
const builtinFunctions = ['console', 'Math', 'String', 'Array', 'Object', 'Date'];
return builtinFunctions.some(builtin => word.startsWith(builtin));
}

private getBuiltinFunctionHover(word: string): vscode.MarkdownString {
const builtinInfo = this.getBuiltinFunctionInfo(word);

const markdown = new vscode.MarkdownString();
markdown.appendCodeblock(word, 'mylang');
markdown.appendMarkdown(`**内置对象/方法:** ${word}\n\n`);

if (builtinInfo) {
markdown.appendMarkdown(`**描述:** ${builtinInfo.description}\n\n`);
if (builtinInfo.params) {
markdown.appendMarkdown(`**参数:** ${builtinInfo.params}\n\n`);
}
if (builtinInfo.returnType) {
markdown.appendMarkdown(`**返回值:** ${builtinInfo.returnType}\n\n`);
}
}

return markdown;
}

private getBuiltinFunctionInfo(name: string): {
description: string;
params?: string;
returnType?: string;
} | undefined {

const builtins: Record<string, any> = {
'console.log': {
description: '在控制台输出信息',
params: 'message: any',
returnType: 'void'
},
'Math.abs': {
description: '返回数字的绝对值',
params: 'x: number',
returnType: 'number'
},
'Math.max': {
description: '返回最大值',
params: '...values: number[]',
returnType: 'number'
},
'String.prototype.charAt': {
description: '返回指定位置的字符',
params: 'index: number',
returnType: 'string'
},
'Array.prototype.push': {
description: '向数组末尾添加元素',
params: '...items: any[]',
returnType: 'number'
}
};

return builtins[name];
}

private extractFunctionComment(text: string, functionIndex: number): string | undefined {
// 向前查找注释
const beforeFunction = text.substring(0, functionIndex);
const lines = beforeFunction.split('\n');

// 从函数定义行往前查找注释
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trim();

if (line.startsWith('//')) {
return line.substring(2).trim();
} else if (line.includes('/*') && line.includes('*/')) {
const commentMatch = line.match(/\/\*(.*?)\*\//);
if (commentMatch) {
return commentMatch[1].trim();
}
} else if (line && !line.startsWith('//') && !line.includes('/*')) {
// 如果遇到非注释的非空行,停止查找
break;
}
}

return undefined;
}

private inferType(value: string): string | undefined {
value = value.trim();

if (value.startsWith('"') && value.endsWith('"')) {
return 'string';
}

if (value.startsWith("'") && value.endsWith("'")) {
return 'string';
}

if (/^\d+$/.test(value)) {
return 'number';
}

if (/^\d+\.\d+$/.test(value)) {
return 'number';
}

if (value === 'true' || value === 'false') {
return 'boolean';
}

if (value.startsWith('[') && value.endsWith(']')) {
return 'array';
}

if (value.startsWith('{') && value.endsWith('}')) {
return 'object';
}

return undefined;
}
}

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

export class MyLangDiagnosticProvider {
private diagnosticCollection: vscode.DiagnosticCollection;

constructor() {
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('mylang');
}

async provideDiagnostics(document: vscode.TextDocument): Promise<void> {
const diagnostics: vscode.Diagnostic[] = [];
const text = document.getText();
const lines = text.split('\n');

for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex];

// 检查语法错误
diagnostics.push(...this.checkSyntaxErrors(line, lineIndex));

// 检查未使用的变量
diagnostics.push(...this.checkUnusedVariables(document, lineIndex));

// 检查类型错误
diagnostics.push(...this.checkTypeErrors(line, lineIndex));

// 检查代码风格问题
diagnostics.push(...this.checkStyleIssues(line, lineIndex));

// 检查最佳实践
diagnostics.push(...this.checkBestPractices(line, lineIndex));
}

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

private checkSyntaxErrors(line: string, lineIndex: number): vscode.Diagnostic[] {
const diagnostics: vscode.Diagnostic[] = [];

// 检查未闭合的括号
const openParens = (line.match(/\(/g) || []).length;
const closeParens = (line.match(/\)/g) || []).length;

if (openParens > closeParens) {
const lastOpenParen = line.lastIndexOf('(');
if (lastOpenParen !== -1) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, lastOpenParen, lineIndex, lastOpenParen + 1),
'缺少闭合括号',
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'missing-close-paren';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}
}

// 检查未闭合的大括号
const openBraces = (line.match(/\{/g) || []).length;
const closeBraces = (line.match(/\}/g) || []).length;

if (openBraces > closeBraces && !line.trim().endsWith('{')) {
const lastOpenBrace = line.lastIndexOf('{');
if (lastOpenBrace !== -1) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, lastOpenBrace, lineIndex, lastOpenBrace + 1),
'缺少闭合大括号',
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'missing-close-brace';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}
}

// 检查未终止的字符串
const quotes = (line.match(/"/g) || []).length;
if (quotes % 2 === 1 && !line.trim().endsWith('\\')) {
const lastQuote = line.lastIndexOf('"');
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, lastQuote, lineIndex, line.length),
'未终止的字符串',
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'unterminated-string';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

// 检查无效的关键字使用
const invalidKeywordUsage = /\b(function|class|interface)\s*\(/g;
let match;
while ((match = invalidKeywordUsage.exec(line)) !== null) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, match.index, lineIndex, match.index + match[0].length),
`关键字 ${match[1]} 使用错误`,
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'invalid-keyword-usage';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

return diagnostics;
}

private checkUnusedVariables(document: vscode.TextDocument, lineIndex: number): vscode.Diagnostic[] {
const diagnostics: vscode.Diagnostic[] = [];
const line = document.lineAt(lineIndex).text;
const text = document.getText();

// 查找变量声明
const variableDeclaration = /(var|let|const)\s+(\w+)/g;
let match;

while ((match = variableDeclaration.exec(line)) !== null) {
const [, , variableName] = match;

// 检查变量是否在其他地方被使用
const usagePattern = new RegExp(`\\b${variableName}\\b`, 'g');
const allMatches = Array.from(text.matchAll(usagePattern));

// 如果只有一个匹配(就是声明本身),则认为未使用
if (allMatches.length === 1) {
const startPos = match.index + match[0].indexOf(variableName);
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + variableName.length),
`变量 '${variableName}' 已声明但未使用`,
vscode.DiagnosticSeverity.Warning
);
diagnostic.code = 'unused-variable';
diagnostic.source = 'mylang';
diagnostic.tags = [vscode.DiagnosticTag.Unnecessary];
diagnostics.push(diagnostic);
}
}

return diagnostics;
}

private checkTypeErrors(line: string, lineIndex: number): vscode.Diagnostic[] {
const diagnostics: vscode.Diagnostic[] = [];

// 检查类型不匹配(简化版)
const numberAssignment = /(\w+)\s*=\s*"[^"]*"/g;
let match;

while ((match = numberAssignment.exec(line)) !== null) {
// 假设变量名包含 'num' 或 'count' 意味着应该是数字
const variableName = match[1];
if (variableName.includes('num') || variableName.includes('count') || variableName.includes('index')) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, match.index, lineIndex, match.index + match[0].length),
`可能的类型错误:将字符串赋值给数字变量 '${variableName}'`,
vscode.DiagnosticSeverity.Warning
);
diagnostic.code = 'type-mismatch';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}
}

return diagnostics;
}

private checkStyleIssues(line: string, lineIndex: number): vscode.Diagnostic[] {
const diagnostics: vscode.Diagnostic[] = [];

// 检查缩进问题
if (line.match(/^\s*\t/)) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, 0, lineIndex, line.search(/[^\s]/)),
'建议使用空格而不是制表符进行缩进',
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'tab-indentation';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

// 检查行尾空格
if (line.match(/\s+$/)) {
const trimmedLength = line.trimEnd().length;
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, trimmedLength, lineIndex, line.length),
'行尾有多余的空格',
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'trailing-whitespace';
diagnostic.source = 'mylang';
diagnostic.tags = [vscode.DiagnosticTag.Unnecessary];
diagnostics.push(diagnostic);
}

// 检查过长的行
if (line.length > 100) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, 100, lineIndex, line.length),
'行过长,建议不超过100个字符',
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'line-too-long';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

// 检查命名约定
const variableDeclaration = /(var|let|const)\s+([A-Z]\w*)/g;
let match;

while ((match = variableDeclaration.exec(line)) !== null) {
const variableName = match[2];
const startPos = match.index + match[0].indexOf(variableName);

const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, startPos, lineIndex, startPos + variableName.length),
`变量名 '${variableName}' 应该使用驼峰命名法`,
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'naming-convention';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

return diagnostics;
}

private checkBestPractices(line: string, lineIndex: number): vscode.Diagnostic[] {
const diagnostics: vscode.Diagnostic[] = [];

// 检查console.log的使用
if (line.includes('console.log')) {
const index = line.indexOf('console.log');
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, index, lineIndex, index + 'console.log'.length),
'生产代码中应避免使用console.log',
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'console-log-usage';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

// 检查var的使用
if (line.includes('var ')) {
const index = line.indexOf('var ');
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, index, lineIndex, index + 3),
'建议使用let或const替代var',
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'var-usage';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}

// 检查魔法数字
const magicNumbers = /\b\d{2,}\b/g;
let match;

while ((match = magicNumbers.exec(line)) !== null) {
const number = match[0];
// 排除常见的非魔法数字
if (!['100', '200', '404', '500'].includes(number)) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(lineIndex, match.index, lineIndex, match.index + number.length),
`考虑将魔法数字 ${number} 定义为常量`,
vscode.DiagnosticSeverity.Information
);
diagnostic.code = 'magic-number';
diagnostic.source = 'mylang';
diagnostics.push(diagnostic);
}
}

return diagnostics;
}

dispose(): void {
this.diagnosticCollection.dispose();
}
}

🌐 第三部分:Language Server Protocol (LSP)

3.1 LSP客户端开发

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
// src/languageClient.ts
import * as vscode from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
RevealOutputChannelOn
} from 'vscode-languageclient/node';

export class MyLangLanguageClient {
private client: LanguageClient | undefined;

async activate(context: vscode.ExtensionContext): Promise<void> {
// Language Server的可执行文件路径
const serverModule = context.asAbsolutePath('out/server/server.js');

// 调试选项
const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };

// 服务器选项
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: debugOptions
}
};

// 客户端选项
const clientOptions: LanguageClientOptions = {
// 注册要处理的文档选择器
documentSelector: [
{ scheme: 'file', language: 'mylang' },
{ scheme: 'untitled', language: 'mylang' }
],

// 同步配置
synchronize: {
// 监听配置变化
configurationSection: 'mylang',
// 监听文件变化
fileEvents: vscode.workspace.createFileSystemWatcher('**/.mylangrc')
},

// 输出通道
outputChannelName: 'MyLang Language Server',
revealOutputChannelOn: RevealOutputChannelOn.Never,

// 错误处理
errorHandler: {
error: (error, message, count) => {
console.error('Language server error:', error, message, count);
return { action: vscode.languages.ErrorAction.Continue };
},
closed: () => {
console.log('Language server connection closed');
return { action: vscode.languages.CloseAction.Restart };
}
},

// 初始化选项
initializationOptions: {
settings: vscode.workspace.getConfiguration('mylang')
},

// 工作区文件夹
workspaceFolder: vscode.workspace.workspaceFolders?.[0]
};

// 创建Language Client
this.client = new LanguageClient(
'mylang-language-server',
'MyLang Language Server',
serverOptions,
clientOptions
);

// 注册自定义通知和请求处理器
this.registerCustomHandlers();

// 启动客户端
await this.client.start();

// 注册客户端命令
this.registerCommands(context);

console.log('MyLang Language Server启动成功');
}

private registerCustomHandlers(): void {
if (!this.client) return;

// 注册自定义通知处理器
this.client.onNotification('mylang/status', (params: any) => {
console.log('Language Server状态:', params);
vscode.window.showInformationMessage(`Language Server: ${params.message}`);
});

// 注册自定义请求处理器
this.client.onRequest('mylang/getWorkspaceInfo', async () => {
const workspaceFolders = vscode.workspace.workspaceFolders;
return {
folders: workspaceFolders?.map(folder => ({
name: folder.name,
uri: folder.uri.toString()
})) || [],
configuration: vscode.workspace.getConfiguration('mylang')
};
});
}

private registerCommands(context: vscode.ExtensionContext): void {
// 重启Language Server命令
const restartCommand = vscode.commands.registerCommand('mylang.restartServer', async () => {
if (this.client) {
await this.client.stop();
await this.client.start();
vscode.window.showInformationMessage('MyLang Language Server已重启');
}
});

// 显示服务器状态命令
const statusCommand = vscode.commands.registerCommand('mylang.showServerStatus', async () => {
if (this.client) {
const result = await this.client.sendRequest('mylang/getStatus');
vscode.window.showInformationMessage(`服务器状态: ${JSON.stringify(result)}`);
}
});

// 格式化文档命令
const formatCommand = vscode.commands.registerCommand('mylang.formatDocument', async () => {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.languageId === 'mylang') {
await vscode.commands.executeCommand('editor.action.formatDocument');
}
});

context.subscriptions.push(restartCommand, statusCommand, formatCommand);
}

async deactivate(): Promise<void> {
if (this.client) {
await this.client.stop();
}
}

// 发送自定义请求到服务器
async sendCustomRequest<T>(method: string, params?: any): Promise<T> {
if (!this.client) {
throw new Error('Language Client未初始化');
}

return this.client.sendRequest(method, params);
}

// 发送通知到服务器
sendNotification(method: string, params?: any): void {
if (this.client) {
this.client.sendNotification(method, params);
}
}

// 获取客户端状态
isReady(): boolean {
return this.client?.isRunning() || false;
}
}

3.2 LSP服务器开发

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
396
397
398
399
400
401
// src/server/server.ts
import {
createConnection,
TextDocuments,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
TextDocumentSyncKind,
InitializeResult,
DocumentDiagnosticReportKind,
type DocumentDiagnosticReport
} from 'vscode-languageserver/node';

import { TextDocument } from 'vscode-languageserver-textdocument';

// 创建连接
const connection = createConnection(ProposedFeatures.all);

// 创建文档管理器
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);

// 配置缓存
interface MyLangSettings {
maxNumberOfProblems: number;
enableLinting: boolean;
enableFormatting: boolean;
tabSize: number;
insertSpaces: boolean;
}

const defaultSettings: MyLangSettings = {
maxNumberOfProblems: 1000,
enableLinting: true,
enableFormatting: true,
tabSize: 4,
insertSpaces: true
};

let globalSettings: MyLangSettings = defaultSettings;
const documentSettings: Map<string, Thenable<MyLangSettings>> = new Map();

// 初始化处理
connection.onInitialize((params: InitializeParams) => {
const capabilities = params.capabilities;

// 检查客户端是否支持工作区文件夹
const hasWorkspaceFolderCapability = !!(
capabilities.workspace && !!capabilities.workspace.workspaceFolders
);

// 检查客户端是否支持配置变化
const hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration
);

// 检查客户端是否支持诊断刷新
const hasDiagnosticRefreshSupport = !!(
capabilities.workspace && !!capabilities.workspace.diagnostics && !!capabilities.workspace.diagnostics.refreshSupport
);

const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,

// 代码补全
completionProvider: {
resolveProvider: true,
triggerCharacters: ['.', ':', '<']
},

// 悬停信息
hoverProvider: true,

// 签名帮助
signatureHelpProvider: {
triggerCharacters: ['(', ',']
},

// 定义跳转
definitionProvider: true,

// 引用查找
referencesProvider: true,

// 文档高亮
documentHighlightProvider: true,

// 符号查找
documentSymbolProvider: true,
workspaceSymbolProvider: true,

// 代码操作
codeActionProvider: true,

// 重命名
renameProvider: {
prepareProvider: true
},

// 格式化
documentFormattingProvider: true,
documentRangeFormattingProvider: true,

// 诊断
diagnosticProvider: {
interFileDependencies: false,
workspaceDiagnostics: false
}
}
};

if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true
}
};
}

return result;
});

// 初始化完成处理
connection.onInitialized(() => {
connection.client.register(DidChangeConfigurationNotification.type, undefined);

// 发送初始化完成通知
connection.sendNotification('mylang/status', {
message: 'Language Server初始化完成'
});
});

// 配置变化处理
connection.onDidChangeConfiguration(change => {
if (change.settings) {
globalSettings = <MyLangSettings>(change.settings.mylang || defaultSettings);
} else {
globalSettings = defaultSettings;
}

// 清除文档设置缓存
documentSettings.clear();

// 重新验证所有打开的文档
documents.all().forEach(validateTextDocument);
});

// 获取文档设置
function getDocumentSettings(resource: string): Thenable<MyLangSettings> {
let result = documentSettings.get(resource);
if (!result) {
result = connection.workspace.getConfiguration({
scopeUri: resource,
section: 'mylang'
});
documentSettings.set(resource, result);
}
return result;
}

// 文档变化处理
documents.onDidChangeContent(change => {
validateTextDocument(change.document);
});

// 文档验证
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
const settings = await getDocumentSettings(textDocument.uri);

if (!settings.enableLinting) {
return;
}

const text = textDocument.getText();
const diagnostics: Diagnostic[] = [];

// 语法检查
diagnostics.push(...checkSyntax(text, textDocument));

// 语义检查
diagnostics.push(...checkSemantics(text, textDocument));

// 样式检查
diagnostics.push(...checkStyle(text, textDocument, settings));

// 限制诊断数量
const limitedDiagnostics = diagnostics.slice(0, settings.maxNumberOfProblems);

// 发送诊断信息
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: limitedDiagnostics });
}

// 语法检查
function checkSyntax(text: string, document: TextDocument): Diagnostic[] {
const diagnostics: Diagnostic[] = [];
const lines = text.split('\n');

for (let i = 0; i < lines.length; i++) {
const line = lines[i];

// 检查未闭合的括号
const openParens = (line.match(/\(/g) || []).length;
const closeParens = (line.match(/\)/g) || []).length;

if (openParens > closeParens) {
const diagnostic: Diagnostic = {
severity: DiagnosticSeverity.Error,
range: {
start: { line: i, character: line.lastIndexOf('(') },
end: { line: i, character: line.lastIndexOf('(') + 1 }
},
message: '缺少闭合括号',
source: 'mylang'
};
diagnostics.push(diagnostic);
}

// 检查未终止的字符串
const quotes = (line.match(/"/g) || []).length;
if (quotes % 2 === 1) {
const diagnostic: Diagnostic = {
severity: DiagnosticSeverity.Error,
range: {
start: { line: i, character: line.lastIndexOf('"') },
end: { line: i, character: line.length }
},
message: '未终止的字符串',
source: 'mylang'
};
diagnostics.push(diagnostic);
}
}

return diagnostics;
}

// 语义检查
function checkSemantics(text: string, document: TextDocument): Diagnostic[] {
const diagnostics: Diagnostic[] = [];

// 检查未定义的变量
const variableUsage = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
const variableDeclarations = new Set<string>();
const usedVariables = new Map<string, Array<{ line: number, character: number }>>();

const lines = text.split('\n');

// 收集变量声明
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const declarationMatch = /(var|let|const)\s+(\w+)/g;
let match;

while ((match = declarationMatch.exec(line)) !== null) {
variableDeclarations.add(match[2]);
}
}

// 检查变量使用
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
let match;

while ((match = variableUsage.exec(line)) !== null) {
const variable = match[1];

// 跳过关键字
if (['function', 'var', 'let', 'const', 'if', 'else', 'while', 'for'].includes(variable)) {
continue;
}

if (!usedVariables.has(variable)) {
usedVariables.set(variable, []);
}

usedVariables.get(variable)!.push({
line: i,
character: match.index
});
}
}

// 报告未定义的变量
for (const [variable, positions] of usedVariables) {
if (!variableDeclarations.has(variable)) {
for (const pos of positions) {
const diagnostic: Diagnostic = {
severity: DiagnosticSeverity.Warning,
range: {
start: { line: pos.line, character: pos.character },
end: { line: pos.line, character: pos.character + variable.length }
},
message: `未定义的变量 '${variable}'`,
source: 'mylang'
};
diagnostics.push(diagnostic);
}
}
}

return diagnostics;
}

// 样式检查
function checkStyle(text: string, document: TextDocument, settings: MyLangSettings): Diagnostic[] {
const diagnostics: Diagnostic[] = [];
const lines = text.split('\n');

for (let i = 0; i < lines.length; i++) {
const line = lines[i];

// 检查缩进
if (settings.insertSpaces) {
if (line.match(/^\t/)) {
const diagnostic: Diagnostic = {
severity: DiagnosticSeverity.Information,
range: {
start: { line: i, character: 0 },
end: { line: i, character: line.search(/[^\t]/) }
},
message: '建议使用空格而不是制表符',
source: 'mylang'
};
diagnostics.push(diagnostic);
}
}

// 检查行尾空格
if (line.match(/\s+$/)) {
const trimmedLength = line.trimEnd().length;
const diagnostic: Diagnostic = {
severity: DiagnosticSeverity.Information,
range: {
start: { line: i, character: trimmedLength },
end: { line: i, character: line.length }
},
message: '行尾有多余的空格',
source: 'mylang'
};
diagnostics.push(diagnostic);
}
}

return diagnostics;
}

// 代码补全
connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
return [
{
label: 'function',
kind: CompletionItemKind.Keyword,
data: 1
},
{
label: 'variable',
kind: CompletionItemKind.Variable,
data: 2
},
{
label: 'class',
kind: CompletionItemKind.Class,
data: 3
}
];
});

// 补全项解析
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
if (item.data === 1) {
item.detail = 'Function keyword';
item.documentation = 'Declares a function';
} else if (item.data === 2) {
item.detail = 'Variable';
item.documentation = 'A variable declaration';
} else if (item.data === 3) {
item.detail = 'Class keyword';
item.documentation = 'Declares a class';
}
return item;
});

// 自定义请求处理
connection.onRequest('mylang/getStatus', () => {
return {
status: 'running',
documentsCount: documents.all().length,
diagnosticsEnabled: globalSettings.enableLinting
};
});

// 监听文档
documents.listen(connection);

// 监听连接
connection.listen();

📚 本阶段总结

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

语法高亮技术:TextMate语法、语义高亮、嵌入语言支持
代码片段系统:动态片段、条件触发、智能补全
智能感知功能:代码补全、悬停信息、参数提示、错误诊断
Language Server Protocol:LSP架构、客户端/服务器开发
代码操作能力:格式化、重构、快速修复、代码导航

核心技术回顾

  • 语法文件开发:使用TextMate语法规则和正则表达式
  • 语义tokens:基于语言理解的智能高亮
  • LSP通信:JSON-RPC协议、增量同步、错误处理
  • 诊断系统:语法检查、语义分析、样式建议
  • 智能补全:上下文感知、类型推断、代码片段

🚀 下一步学习方向

在下一阶段(高级主题),我们将学习:

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

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

  1. 实践开发一个简单语言的完整支持
  2. 研究现有优秀语言扩展的实现
  3. 理解LSP协议的高级特性
  4. 优化语言服务器的性能

相关资源

恭喜你完成了VS Code扩展开发的语言扩展阶段!现在你已经具备了开发专业级编程语言工具的能力。