vscode调试自定义语言需实现符合dap协议的调试适配器,核心是处理initialize/launch/setbreakpoints等请求并严格遵循content-length+json-rpc通信规范,缺字段或配置错将导致静默失败。

要让 VSCode 支持自定义语言或运行时的调试,核心不是写个“调试器”,而是实现一个符合 DAP 协议的中间进程——调试适配器(Debug Adapter)。它不直接执行代码,只负责翻译:把 VSCode 的 setBreakpoints、continue、variables 等请求,转成你后端能理解的操作;再把变量值、栈帧、暂停事件等,按 DAP 格式塞回去。
为什么不能直接调用你的解释器?
VSCode 从不碰你的语言运行时。它只和标准输入输出打交道,且只认 Content-Length + JSON-RPC 格式的消息。如果你的解释器不支持 stdin/stdout 上收发带长度头的 JSON,VSCode 就会卡在 “Starting debugger…” 或报错 Cannot connect to the target。
- 所有通信必须以
Content-Length: N\r\n\r\n{...}开头,不能少\r\n\r\n分隔符 - 每条消息的
seq必须递增,响应里request_seq要匹配原始请求的seq - 首次消息必须是
initialize,且响应中需声明supportsConfigurationDoneRequest: true,否则后续launch不会被触发 - 不处理
initialize就直接返回空响应,VSCode 会静默退出调试会话
Node.js 实现适配器时最关键的三个钩子方法
用 vscode-debugadapter 库继承 DebugSession 是最快路径,但必须重写以下方法,否则断点不生效、F5 没反应:
-
initializeRequest:返回能力声明。至少设supportsConfigurationDoneRequest: true、supportsSetBreakpointsRequest: true、supportsThreadsRequest: true。漏掉supportsThreadsRequest,多线程调试面板就为空 -
launchRequest:真正启动目标程序的地方。别在这里阻塞;要用child_process.spawn启子进程,并监听其stdout/stderr,把输出转为output事件发给 VSCode -
setBreakpointsRequest:接收的是文件路径 + 行号数组,但你的语言可能没“源码行”概念。这时得做路径映射(比如把/src/main.mylang映射到内部 AST 节点 ID),否则断点永远“未绑定”
package.json 里最容易被忽略的注册细节
光写 debuggers 字段不够,VSCode 启动调试前会先查 onDebugResolve:xxx 激活事件。如果插件没响应这个事件,就不会调用 registerDebugAdapterDescriptorFactory,整个流程根本不会开始。
-
"activationEvents": ["onDebugResolve:my-debugger"]必须存在,且my-debugger要和debuggers.type完全一致 -
configurationAttributes.launch中的required字段要严格对应launch.json里实际填的字段名,比如你写了"program": { "type": "string", "description": "入口文件" },那用户就必须填"program",拼错或少填都会导致launchRequest的arguments为空对象 - 调试器进程路径(如
node out/debugAdapter.js)必须可执行;Windows 下若用.ts源码直跑,会因缺少ts-node报spawn ENOENT
真正卡住人的地方,往往不在“怎么实现单步”,而在于 initialize 响应缺字段、launch 没发 initialized 事件、或者 package.json 的激活事件和类型名对不上——这些错误不会抛异常,只会让调试面板一片空白,连日志都不出。











