语义着色 SemanticTokens
语义着色(Semantic Tokens)让代码高亮超越「关键字/字符串」的语法层面,实现基于语义的着色:类型名、变量名、参数名各有专属颜色。这是现代语言体验的重要能力。
语法高亮 vs 语义着色
| 对比 | 语法高亮 | 语义着色 |
|---|---|---|
| 依据 | 正则/文本模式 | 代码语义(AST/分析) |
| 准确性 | 依赖语法规则 | 精确到符号 |
| 覆盖 | 关键字/字符串/注释 | 变量/类型/参数/属性 |
| 实现 | TextMate 语法 | Provider + 分析器 |
注册语义 Tokens Provider
typescript
import * as vscode from 'vscode';
const legend = new vscode.SemanticTokensLegend(
['type', 'parameter', 'variable', 'property'], // token 类型
['declaration', 'readonly', 'modification'] // 修饰符
);
vscode.languages.registerDocumentSemanticTokensProvider(
'plaintext',
{
provideDocumentSemanticTokens(
document,
token
): vscode.ProviderResult<vscode.SemanticTokens> {
// 返回语义 tokens
const builder = new vscode.SemanticTokensBuilder();
// ... 构建 tokens
return builder.build();
}
},
legend
);SemanticTokensLegend 定义
Legend 声明 token 类型与修饰符:
typescript
const legend = new vscode.SemanticTokensLegend(
[
'namespace', // 命名空间
'type', // 类型
'class', // 类
'enum', // 枚举
'interface', // 接口
'struct', // 结构体
'typeParameter',// 类型参数
'parameter', // 参数
'variable', // 变量
'property', // 属性
'function', // 函数
'method', // 方法
'keyword', // 关键字
'comment', // 注释
'string', // 字符串
'number', // 数字
'operator' // 运算符
],
[
'declaration', // 声明
'definition', // 定义
'readonly', // 只读
'static', // 静态
'deprecated', // 弃用
'modification' // 修改
]
);SemanticTokensBuilder 构建 tokens
用 Builder 添加 token 段:
typescript
const builder = new vscode.SemanticTokensBuilder(legend);
// 添加 token:位置 + 类型索引 + 修饰符位掩码
builder.push(
new vscode.Range(0, 0, 0, 10), // 范围
'type', // 类型名
['declaration'] // 修饰符数组
);push 参数
| 参数 | 说明 |
|---|---|
range | token 所在范围 |
tokenType | 类型名(legend 中定义) |
tokenModifiers | 修饰符数组 |
| 可选第五参数 | 嵌入索引(嵌套) |
provideDocumentSemanticTokens 返回值
typescript
async provideDocumentSemanticTokens(document, token) {
// 分析文档生成 tokens
const tokens = await analyzeDocument(document);
const builder = new vscode.SemanticTokensBuilder();
tokens.forEach((t) => builder.push(t.range, t.type, t.modifiers));
return builder.build();
}返回 null
不需要语义着色时返回 undefined:
typescript
provideDocumentSemanticTokens(document, token) {
if (document.isClosed) {
return undefined;
}
return this.buildTokens(document);
}构建增量更新
provideDocumentSemanticTokensEdits 提供增量更新,性能更好:
typescript
const provider = {
provideDocumentSemanticTokens(document, token) {
// 全量构建(首次调用)
return this.buildAll(document);
},
// 后续编辑时增量构建
provideDocumentSemanticTokensEdits(
document,
previousResultId: string,
token
) {
// 版本号变化时返回增量 edits
const edits = this.computeEdits(document, previousResultId);
return edits;
}
};SemanticTokensEdits
typescript
const edits: vscode.SemanticTokensEdit[] = [
{
start: 0, // 起始 token 索引
deleteCount: 5,// 删除数量
data: Uint32Array.from([...]) // 新增数据
}
];
return new vscode.SemanticTokensEdits(edits);完整示例:关键字与类型着色
typescript
import * as vscode from 'vscode';
const legend = new vscode.SemanticTokensLegend(
['keyword', 'type', 'variable', 'string'],
['declaration']
);
// 关键字集合
const KEYWORDS = new Set([
'function', 'return', 'const', 'let', 'if', 'else',
'for', 'while', 'class', 'import', 'export'
]);
// 类型集合
const TYPES = new Set([
'string', 'number', 'boolean', 'array', 'object'
]);
export function activate(context: vscode.ExtensionContext) {
const provider = {
provideDocumentSemanticTokens(document) {
const builder = new vscode.SemanticTokensBuilder();
const text = document.getText();
const wordRegex = /[a-zA-Z_]\w*/g;
let match: RegExpExecArray | null;
while ((match = wordRegex.exec(text))) {
const word = match[0];
const position = document.positionAt(match.index);
if (KEYWORDS.has(word)) {
builder.push(
new vscode.Range(
position,
document.positionAt(match.index + word.length)
),
'keyword'
);
} else if (TYPES.has(word)) {
builder.push(
new vscode.Range(
position,
document.positionAt(match.index + word.length)
),
'type'
);
}
}
return builder.build();
}
};
context.subscriptions.push(
vscode.languages.registerDocumentSemanticTokensProvider(
'plaintext',
provider,
legend
)
);
}颜色主题映射
token 类型通过主题定义颜色:
package.json 或主题文件:
json
{
"semanticTokenColors": {
"type": { "foreground": "#4ec9b0" },
"parameter": { "foreground": "#9cdcfe" },
"variable.declaration": { "foreground": "#4fc1ff" },
"keyword": { "foreground": "#c586c0" }
}
}修饰符组合
类型.修饰符:variable.declaration| 组合 | 用途 |
|---|---|
type | 所有类型 |
variable.declaration | 声明变量 |
parameter.readonly | 只读参数 |
method.deprecated | 弃用方法 |
性能优化
| 场景 | 处理 |
|---|---|
| 大文件 | 提供增量 edits |
| 频繁输入 | 防抖分析 |
| 全量重算 | 按变更区域局部更新 |
| 内存 | 及时释放分析结果 |
常见问题
| 问题 | 处理 |
|---|---|
| 无颜色变化 | 检查 legend 与主题映射 |
| 范围错误 | 精确计算 token 范围 |
| 性能差 | 用增量更新 |
| 类型不生效 | 确认主题支持 semanticTokenColors |
语义着色让代码高亮「懂语义」,变量/类型/参数各归其位,是提升代码可读性的高级能力。