调试器 UI 自定义
调试器扩展的 UI 定制围绕三个层面:launch.json 的配置体验、调试控制台与内联值显示、以及调试过程的诊断输出。合理的 UI 设计能让调试器"开箱即用"。
launch.json 配置体验
contributes.debuggers.configurationAttributes 定义了每种请求的配置结构,它是一份 JSON Schema 子集,VS Code 据此提供自动补全与校验:
{
"contributes": {
"debuggers": [
{
"type": "my-script",
"label": "MyScript Debugger",
"languages": ["myscript"],
"configurationAttributes": {
"launch": {
"required": ["program"],
"properties": {
"program": {
"type": "string",
"description": "要调试的脚本文件",
"default": "${workspaceFolder}/main.ms"
},
"args": {
"type": "array",
"items": { "type": "string" },
"description": "命令行参数"
},
"env": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "环境变量"
},
"stopOnEntry": {
"type": "boolean",
"default": false,
"description": "是否在入口处暂停"
}
}
},
"attach": {
"required": ["port"],
"properties": {
"port": {
"type": "number",
"description": "调试服务端口"
},
"host": {
"type": "string",
"default": "127.0.0.1"
}
}
}
},
"configurationSnippets": [
{
"label": "MyScript: Launch Script",
"body": {
"type": "my-script",
"request": "launch",
"name": "Launch Script",
"program": "${workspaceFolder}/${1:main.ms}"
}
}
]
}
]
}
}常用配置字段
| 字段 | 类型 | 用途 |
|---|---|---|
program | string | 目标程序/脚本路径 |
args | array | 传给程序的命令行参数 |
env | object | 设置的环境变量 |
runtimeArgs | array | 传给运行时的参数(非程序) |
cwd | string | 程序工作目录 |
stopOnEntry | boolean | 启动后是否立即暂停在入口 |
console | string | 输出目标(internalConsole/terminal) |
trace | boolean | 是否开启 DAP 日志 |
runtimeArgs 与 args 的区别:runtimeArgs 是运行时自身的参数(如 Node 的 --experimental-vm-modules),args 是传给被调试程序的参数。
DebugConfigurationProvider 动态配置
DebugConfigurationProvider 可以在解析阶段动态注入配置,常用于"一键调试当前文件":
import * as vscode from 'vscode';
import * as path from 'path';
export class MyScriptConfigProvider
implements vscode.DebugConfigurationProvider
{
provideDebugConfigurations(
folder: vscode.WorkspaceFolder | undefined
): vscode.ProviderResult<vscode.DebugConfiguration[]> {
return [
{
type: 'my-script',
name: 'Launch Current File',
request: 'launch',
program: '${file}',
stopOnEntry: false
}
];
}
resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
config: vscode.DebugConfiguration
): vscode.ProviderResult<vscode.DebugConfiguration> {
// 未配置 program 时使用当前活动文件
if (!config.program) {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.languageId === 'myscript') {
config.program = editor.document.uri.fsPath;
}
}
// 默认工作目录
if (!config.cwd) {
config.cwd = folder ? folder.uri.fsPath : undefined;
}
// 校验
if (!config.program) {
return undefined; // 放弃启动
}
return config;
}
}动态调试会话
插件可以用 startDebugging 配合动态配置,实现菜单/按钮一键调试:
vscode.commands.registerCommand('myExt.debugAndProfile', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const config: vscode.DebugConfiguration = {
type: 'my-script',
name: 'Debug & Profile',
request: 'launch',
program: editor.document.uri.fsPath,
profiling: true // 自定义字段,适配器可读取
};
await vscode.debug.startDebugging(undefined, config);
});调试控制台输出
调试控制台是用户观察程序运行的主要窗口,output 事件控制所有输出:
输出分类
// 程序标准输出
sendEvent(socket, 'output', {
category: 'stdout',
output: data.toString(),
source: { path: sourcePath }
});
// 程序错误输出(红色样式)
sendEvent(socket, 'output', {
category: 'stderr',
output: errorText
});
// 调试器自身消息
sendEvent(socket, 'output', {
category: 'console',
output: '正在启动调试会话...\n'
});
// 结构化数据(如 JSON 响应)
sendEvent(socket, 'output', {
category: 'console',
output: JSON.stringify(response, null, 2) + '\n'
});输出分组
group 参数可以把输出折叠成可展开的分组,适合大段日志:
// 开启分组
sendEvent(socket, 'output', {
category: 'console',
output: '=== 请求日志 ===\n',
group: 'start'
});
// ...中间输出
// 结束分组
sendEvent(socket, 'output', {
category: 'console',
output: '',
group: 'end'
});内联值显示
调试时编辑器中变量名下方直接显示当前值,这是 VS Code 的"内联值"功能。适配器通过 variables 和 evaluate 数据驱动,无需额外配置,但可以通过变量返回的元数据优化显示:
// 为变量返回 presentationHint 控制渲染
function handleVariables(msg: DAPMessage, socket: any) {
...
const variables = container.map((v) => ({
name: v.name,
value: v.value,
type: v.type,
variablesReference: v.variablesReference,
presentationHint: {
kind: v.isFunction ? 'method' : 'data',
attributes: v.readOnly ? ['readOnly'] : []
},
// 变量排序优先级,负数表示内置变量排后
sortText: v.isInternal ? 'zz' : 'aa'
}));
}编辑器中内联值的开关由用户控制(设置项 debug.inlineValues),适配器只需保证 supportsEvaluateForHovers 开启。
断点与异常断点
断点分类图标
通过 setExceptionBreakpoints 请求管理异常断点(如"遇到异常时暂停"):
function handleSetExceptionBreakpoints(msg: DAPMessage, socket: any) {
const filters = msg.arguments.filters; // 如 ['uncaught', 'all']
exceptionBreakpointFilters = filters;
sendResponse(socket, msg, { breakpoints: [] });
}在 initialize 响应中声明支持的过滤器:
capabilities = {
supportsExceptionBreakpoints: true,
exceptionBreakpointFilters: [
{ filter: 'all', label: '所有异常', default: false },
{ filter: 'uncaught', label: '未捕获异常', default: true }
]
};适配器日志与诊断
调试问题排查靠日志。VS Code 提供三层日志:
DAP 通信日志
用户设置 "trace": true 时,VS Code 自动记录 DAP 消息到输出面板的"Debug Adapter"频道,无需适配器实现。适配器也可主动输出日志:
// 适配器侧日志(写到 stderr,自动进入调试控制台)
console.error(`[DAP] ${msg.command} seq=${msg.seq}`);
// 或写日志文件
const fs = require('fs');
const logStream = fs.createWriteStream('/tmp/dap.log', { flags: 'a' });
logStream.write(`${Date.now()} ${msg.command}\n`);会话诊断命令
插件侧可以注册诊断命令,读取会话状态:
vscode.commands.registerCommand('myExt.debugSessionInfo', () => {
const session = vscode.debug.activeDebugSession;
if (!session) {
vscode.window.showInformationMessage('当前没有活动调试会话');
return;
}
vscode.window.showInformationMessage(
`类型: ${session.type}, 名称: ${session.name}, ID: ${session.id}`
);
});
// 向适配器发送自定义请求
async function askAdapterStatus(session: vscode.DebugSession) {
const response = await session.customRequest('status');
vscode.window.showInformationMessage(JSON.stringify(response));
}调试会话事件监听
export function activate(context: vscode.ExtensionContext) {
// 会话启动
vscode.debug.onDidStartDebugSession((session) => {
console.log(`调试会话开始: ${session.name}`);
});
// 会话终止
vscode.debug.onDidTerminateDebugSession((session) => {
console.log(`调试会话结束: ${session.name}`);
});
// 程序暂停(断点/单步)
vscode.debug.onDidChangeActiveDebugSession(() => {});
// 自定义事件(适配器通过 DAP 事件触发)
vscode.debug.onDidReceiveDebugSessionCustomEvent((event) => {
console.log(`自定义事件: ${event.event}`);
});
}自定义调试 UI 数据
适配器可以把自定义数据暴露给插件侧:适配器发送自定义事件 → 插件监听并更新状态栏/视图:
// 适配器侧:发送自定义事件(DAP 的非标准事件)
sendEvent(socket, 'myExt:memoryUsage', { heapUsed: 12345678 });
// 插件侧:接收并展示
vscode.debug.onDidReceiveDebugSessionCustomEvent((event) => {
if (event.event === 'myExt:memoryUsage') {
const heap = event.body.heapUsed;
const item = vscode.window.createStatusBarItem();
item.text = `$(database) ${Math.round(heap / 1024 / 1024)}MB`;
item.show();
}
});调试器 UI 的定制到此覆盖了配置体验、输出展示与诊断手段,最后一篇将把这些能力组装成一个完整的自定义脚本调试器。