lsp本身不负责语法高亮,它仅定义编辑器与语言服务器的通信协议;真正实现高亮的是客户端的semantictokensprovider等api,需服务器主动返回结构化token数据并手动配置启用。

为什么LSP不是语法高亮的首选方案
直接说结论:Language Server Protocol 本身不负责语法高亮。它只定义了编辑器与语言服务器之间的通信协议,真正做高亮的是 VSCode 客户端侧的 semanticTokensProvider 或 documentHighlightProvider 等 API,而这些能力需要语言服务器主动注册并返回结构化 token 数据——这意味着你必须在 LSP 服务里额外实现语义分析逻辑,且客户端插件仍需配套注册 provider。
常见误解是“用了 LSP 就自动有高亮”,结果跑起来发现文件全是黑白的。根本原因在于:LSP 的 textDocument/semanticTokens/full 请求默认不触发,除非你在客户端扩展中显式调用 languages.registerSemanticTokensProvider,并在服务器端正确响应 token 类型、范围和修饰符。
- 纯 LSP 启动后,VSCode 不会自动启用语义高亮,必须手动开启设置:
"editor.semanticHighlighting.enabled": true -
semanticTokensProvider返回的 token 数组不能包含重叠范围,否则整个高亮会失效(VSCode 不报错,但静默跳过) - 性能敏感:每次编辑都会触发 full/delta token 请求,若服务器解析耗时 >100ms,用户会感知到高亮延迟或闪烁
TextMate 语法高亮才是快速落地的第一步
如果你的目标是让 GoSu 或类似 DSL 在 VSCode 里“先看起来像代码”,package.json 里声明 grammars + snippets,搭配一个 .tmLanguage.json 文件,5 分钟就能上线基础高亮。VSCode 内置的 TextMate 引擎只做词法匹配,不依赖进程通信,零延迟、零崩溃风险。
这个方案适合绝大多数内部 DSL 场景——比如 GoSu 的 program、function、property 关键字,或是自定义注释 ##@rule、变量插值 ${foo} 等模式,全靠正则就能覆盖。
- 正则中避免使用
.*这类贪婪匹配,TextMate 对嵌套结构支持弱,容易导致整行变色异常 - 作用域名(scope name)必须唯一且符合规范,例如
source.gosu,否则主题无法映射颜色 - 不要试图在 TextMate 里做语义判断(如“只有在 class 块内才高亮 method”),它没有上下文状态机
何时必须上 LSP 语义高亮
当你需要区分同名但不同含义的标识符时,TextMate 就不够用了。比如 GoSu 中的 policy 可能是类型名、变量名、或配置块关键字;又或者你想把所有被 @Deprecated 注解修饰的方法名标成灰色——这种依赖 AST 或符号表的判断,只能由 LSP 服务完成。
此时你要做的不是重写整个高亮逻辑,而是复用已有的 TextMate 基础高亮,再叠加一层语义层:TextMate 负责分词(tokenization),LSP 负责标注(annotation)。
- 服务器端需实现
semanticTokensProvider接口,返回TokenTypes(如function、type)和TokenModifiers(如deprecated、readonly) - 客户端扩展中必须用
vscode.languages.registerSemanticTokensProvider绑定文档语言和 provider 实例 - VSCode 主题需支持对应 scope,例如
support.function.gosu才能生效,否则 fallback 到 TextMate 颜色
调试高亮失效的三个关键检查点
高亮没反应?别急着重写 parser,先看这三处:
- 确认
package.json的contributes.languages.id和grammars.language完全一致,大小写、连字符都不能错(例如gosu≠GoSu) - 打开 VSCode 开发者工具(
Ctrl+Shift+P→Developer: Toggle Developer Tools),过滤关键词semanticTokens,看是否有Response rejected或空数组返回 - 运行
Developer: Inspect Editor Tokens and Scopes(快捷键Ctrl+Shift+P搜),点击目标文本,检查右下角显示的 scope 是否是你在.tmLanguage.json或 LSP 中定义的那个
最常被忽略的一点:VSCode 缓存了语法定义。改完 .tmLanguage.json 后,必须重启窗口(Developer: Reload Window),仅刷新扩展无效。











