最有效的办法是让LSP服务器独立运行并输出完整日志:找到入口文件(如out/server.js),用node out/server.js --stdio手动启动,配合模拟初始化请求验证;禁用VSCode自动捕获,改用connection.console.log()和process.env.VSCODE_LOG_LEVEL=3输出可读日志。

调试 LSP 插件最有效的办法,不是加断点,而是让服务器进程独立运行并输出完整日志。 VSCode 的 LanguageClient 默认会把语言服务器作为子进程启动并捕获 stdout/stderr,这导致错误被吞掉、堆栈被截断、初始化失败原因难定位——尤其在 onInitialize 阶段出错时,VSCode 往往只显示“Language server crashed”,不告诉你 crash 在哪一行。
如何让 LSP 服务器脱离 VSCode 独立运行
这是排查初始化失败、环境变量缺失、路径错误等问题的第一步。关键不是改客户端代码,而是绕过 LanguageClient 启动逻辑,手动运行服务器。
- 找到服务器入口文件(通常是
out/server.js或dist/server.js),确认它能直接执行:node out/server.js - 用
--stdio模式启动,并重定向输入输出:node out/server.js --stdio < /dev/tty >&1 2>&1(Linux/macOS)或node out/server.js --stdio(Windows 下直接运行,观察控制台) - 用另一个终端模拟客户端发一条最简初始化请求(可借助
curl或nc),验证服务器是否响应:{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":null,"rootPath":null,"capabilities":{}}} - 若服务器立即退出,说明
createConnection()初始化失败——常见原因是未传入ProposedFeatures.all或process.stdin不可用(比如在非 stdio 模式下误用了 IPC)
为什么 console.log 在 LSP 服务器里不显示
LSP 服务器的标准输出(stdout)是 JSON-RPC 通信通道,所有 console.log 输出都会被当成非法 JSON 消息,导致协议解析失败、连接中断。这不是“没打印”,是“一打就崩”。
- 必须使用
connection.console.log()或connection.tracer.log(),它们会走 LSP 定义的window/logMessage通知通道 - 在服务器启动前,可临时加一句
process.env.VSCODE_LOG_LEVEL = '3';(数字越大越详细),让vscode-languageserver库自动启用内部 tracer - 若用
vscode-languageclient启动客户端,记得在clientOptions中开启日志:outputChannel: window.createOutputChannel('MyLang'),否则日志无处落脚
调试 textDocument/completion 返回空结果
补全没反应,但服务器没报错,大概率是能力声明、触发字符、文档同步三者不匹配,而不是逻辑写错了。
- 检查
onInitialize返回的capabilities.completionProvider是否包含triggerCharacters,比如['.', ':', '"'];漏掉.就不会响应obj.这类补全 - 确认文档已正确加入
TextDocuments实例:调用documents.listen(connection)必须在connection.listen()之前,否则didOpen事件不会被监听到 - 在
onCompletion处理函数开头加connection.console.log('completion triggered for:', params.textDocument.uri),先确认请求是否真的到达服务器——很多问题其实卡在客户端没发出去 - 返回的
CompletionItem数组不能为null或undefined,必须是[](空数组)才能被客户端识别为“有响应但无建议”
本地调试时 gopls 或 clangd 干扰怎么办
VSCode 可能同时激活多个同类型语言服务器(比如你写了 Go 服务器,但 gopls 也在运行),造成端口冲突、诊断覆盖、补全混杂。这不是 bug,是设计使然——LSP 允许多个服务器共存,但调试时你需要隔离。
- 在
clientOptions.documentSelector中避免泛化匹配,例如不用language: 'go',而用自定义语言 ID:{ scheme: 'file', language: 'mygo' },并在package.json的contributes.languages里注册该 ID - 临时禁用其他 LSP 扩展:在调试期间,关闭
golang.go、clangd等插件,或在settings.json中设"go.useLanguageServer": false - 检查
textDocument/publishDiagnostics推送的uri是否与客户端打开的文件uri完全一致(包括大小写、file://前缀、末尾斜杠),不一致会导致诊断不显示
真正卡住的往往不是协议细节,而是服务器进程是否真正在跑、日志是否真正在吐、URI 是否真的对得上——这些地方没有魔法,只有路径、权限和协议头里的 Content-Length 字段是否准确。


















