dsh-vscode-mode 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/lib/client.debug.js +934 -0
- package/lib/client.debug.js.map +1 -0
- package/lib/client.js +848 -1676
- package/lib/client.js.map +1 -1
- package/lib/client.rules.js +793 -0
- package/lib/client.rules.js.map +1 -0
- package/lib/index.js +236 -42
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +9 -0
- package/src/client/lazyPanel.ts +68 -0
- package/src/client/sidebar/PanelBoundary.ts +57 -0
- package/src/client/sidebar/SidebarView.ts +58 -21
- package/src/client/sidebar/panels/FileExplorer.ts +80 -27
- package/src/client/sidebar/panels/SvnPanel.ts +24 -8
- package/src/client/sidebar/panels/index.ts +3 -4
- package/src/client/sidebar/panels/treeWindow.ts +106 -0
- package/src/client/sidebar/panels/useExplorerReveal.ts +5 -2
- package/src/client/sidebar/registry.ts +4 -1
- package/src/client/styles/editor.css +8 -0
- package/src/client/textPages.ts +135 -0
- package/src/client/ui/EditorView.ts +71 -11
- package/src/client/ui/LargeTextView.ts +94 -0
- package/src/rpc.ts +29 -7
- package/src/shared/rpc.ts +3 -1
- package/src/shared/textPages.ts +28 -0
- package/src/textPages.ts +149 -0
- package/src/treeIndex.ts +62 -37
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-vscode-mode",
|
|
3
|
+
chunk: "client.debug.js",
|
|
4
|
+
factory: (require) => {
|
|
5
|
+
var module = { exports: {} };
|
|
6
|
+
var exports = module.exports;
|
|
7
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
|
+
//#region \0rolldown/runtime.js
|
|
9
|
+
var __create = Object.create;
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
14
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
17
|
+
key = keys[i];
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
19
|
+
get: ((k) => from[k]).bind(null, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
26
|
+
value: mod,
|
|
27
|
+
enumerable: true
|
|
28
|
+
}) : target, mod));
|
|
29
|
+
//#endregion
|
|
30
|
+
let react = require("react");
|
|
31
|
+
react = __toESM(react, 1);
|
|
32
|
+
let dsh_vscode_mode = require("dsh-vscode-mode");
|
|
33
|
+
//#region src/shared/dap.ts
|
|
34
|
+
/** 调试配置文件(工作区相对路径;插件专属,与 VS Code 的 .vscode/launch.json 互不干扰)。 */
|
|
35
|
+
const DAP_LAUNCH_REL = ".dsh/launch.json";
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region \0panel-shared:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/dap/store.ts
|
|
38
|
+
const dapStore = dsh_vscode_mode.__lazyShared.dapStore;
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/client/dap/breakpoints.ts
|
|
41
|
+
/**
|
|
42
|
+
* 推导断点行的跳转目标(调试面板点击断点项 → 打开文件并定位到断点行)。
|
|
43
|
+
* 路径缺失或行号非 ≥1 的有限数时返回 null(提前返回,不发起无效导航)。
|
|
44
|
+
* @author ddj 2026年09月21号
|
|
45
|
+
* @param row 面板行视图(文件路径 + 断点条目)
|
|
46
|
+
* @returns 跳转目标;入参不合法时返回 null
|
|
47
|
+
*/
|
|
48
|
+
function bpJumpTargetOf(row) {
|
|
49
|
+
const path = typeof row?.path === "string" ? row.path : "";
|
|
50
|
+
const line = row?.entry?.line;
|
|
51
|
+
if (!path || typeof line !== "number" || !Number.isFinite(line) || line < 1) return null;
|
|
52
|
+
return {
|
|
53
|
+
path,
|
|
54
|
+
line: Math.floor(line),
|
|
55
|
+
column: 1
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/client/dap/variableTree.ts
|
|
60
|
+
/**
|
|
61
|
+
* 将变量树展平为 sibling rows;循环 ref 只展开一次,避免异常适配器数据造成递归。
|
|
62
|
+
* @author ddj 2026年09月29号
|
|
63
|
+
* @param roots 根变量
|
|
64
|
+
* @param expanded 已展开的 variablesReference
|
|
65
|
+
* @param cache ref → 子变量缓存
|
|
66
|
+
* @param options 行数/深度上限
|
|
67
|
+
*/
|
|
68
|
+
function flattenVariableRows(roots, expanded, cache, options = {}) {
|
|
69
|
+
const rows = [];
|
|
70
|
+
const maxRows = options.maxRows ?? 1e3;
|
|
71
|
+
const maxDepth = options.maxDepth ?? 32;
|
|
72
|
+
const walk = (items, depth, ancestry, prefix) => {
|
|
73
|
+
if (depth > maxDepth || rows.length >= maxRows) return;
|
|
74
|
+
for (let index = 0; index < items.length && rows.length < maxRows; index += 1) {
|
|
75
|
+
const variable = items[index];
|
|
76
|
+
const key = prefix + "/" + index + ":" + variable.name;
|
|
77
|
+
rows.push({
|
|
78
|
+
key,
|
|
79
|
+
variable,
|
|
80
|
+
depth
|
|
81
|
+
});
|
|
82
|
+
if (variable.ref <= 0 || !expanded.has(variable.ref) || ancestry.has(variable.ref)) continue;
|
|
83
|
+
const children = cache.get(variable.ref);
|
|
84
|
+
if (!children?.length) continue;
|
|
85
|
+
const nextAncestry = new Set(ancestry);
|
|
86
|
+
nextAncestry.add(variable.ref);
|
|
87
|
+
walk(children, depth + 1, nextAncestry, key);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
walk(roots, 0, /* @__PURE__ */ new Set(), "root");
|
|
91
|
+
return rows;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/client/dap/bpRowMenu.ts
|
|
95
|
+
/**
|
|
96
|
+
* 构造调试面板断点行右键菜单条目。
|
|
97
|
+
* @author ddj 2026年09月21号
|
|
98
|
+
* @param state 行内断点状态
|
|
99
|
+
* @returns 菜单条目列表(顺序即展示顺序)
|
|
100
|
+
*/
|
|
101
|
+
function buildBpRowMenu(state) {
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
id: "edit",
|
|
105
|
+
label: "编辑断点…"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "edit-condition",
|
|
109
|
+
label: "编辑条件…"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "edit-hit-count",
|
|
113
|
+
label: "编辑命中次数…"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "edit-logpoint",
|
|
117
|
+
label: "编辑记录点…"
|
|
118
|
+
},
|
|
119
|
+
state.enabled !== false ? {
|
|
120
|
+
id: "disable",
|
|
121
|
+
label: "禁用断点",
|
|
122
|
+
separator: true
|
|
123
|
+
} : {
|
|
124
|
+
id: "enable",
|
|
125
|
+
label: "启用断点",
|
|
126
|
+
separator: true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "remove",
|
|
130
|
+
label: "删除断点"
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/client/dap/modelPath.ts
|
|
136
|
+
/**
|
|
137
|
+
* 取当前 model 的文件路径(uri.path → 去首斜杠 → decode)。
|
|
138
|
+
* 无 model / 无 uri.path 时返回 null(调用方降级提示,不产生断点)。
|
|
139
|
+
* @author ddj 2026年09月21号
|
|
140
|
+
* @param editor Monaco 编辑器实例(或形状兼容对象)
|
|
141
|
+
* @returns 归一化路径;不可用时 null
|
|
142
|
+
*/
|
|
143
|
+
function editorModelPathOf(editor) {
|
|
144
|
+
const uriPath = editor?.getModel?.()?.uri?.path;
|
|
145
|
+
return uriPath ? decodeURIComponent(String(uriPath).replace(/^\//, "")) : null;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/client/dap/panelSplit.ts
|
|
149
|
+
/**
|
|
150
|
+
* dsh-vscode-mode client — 调试面板四段分栏高度(纯函数 + localStorage 持久)。
|
|
151
|
+
* 对齐 VS Code/CodeBuddy splitview 语义:段间 sash 拖动时相邻两段高度重分配
|
|
152
|
+
* (拖下 = 上段增高、下段减矮;任一段触到夹取界时拖动量被"吸收",分隔条停在可行位置)。
|
|
153
|
+
* 存储:`edrv.dap.split.<scope>` → { variables, watch, stack, breakpoints }。
|
|
154
|
+
* 作者 ddj 2026年09月21号
|
|
155
|
+
*/
|
|
156
|
+
/** 分栏段 key(与 DebugPanel section key 一致)。 */
|
|
157
|
+
const SPLIT_KEYS = [
|
|
158
|
+
"variables",
|
|
159
|
+
"watch",
|
|
160
|
+
"stack",
|
|
161
|
+
"breakpoints"
|
|
162
|
+
];
|
|
163
|
+
/** 默认高度(沿用重构前各段 max-height 值,改前改后视觉一致)。 */
|
|
164
|
+
const DEFAULT_HEIGHTS = {
|
|
165
|
+
variables: 360,
|
|
166
|
+
watch: 220,
|
|
167
|
+
stack: 180,
|
|
168
|
+
breakpoints: 260
|
|
169
|
+
};
|
|
170
|
+
/** 单段最大高度(防无限拖爆面板)。 */
|
|
171
|
+
const MAX_H = 1200;
|
|
172
|
+
/** localStorage key 前缀。 */
|
|
173
|
+
const KEY_PREFIX = "edrv.dap.split.";
|
|
174
|
+
/**
|
|
175
|
+
* 夹取单段高度到 [MIN_H, MAX_H](非有限数回退最小值)。
|
|
176
|
+
* @author ddj 2026年09月21号
|
|
177
|
+
* @param px 目标高度
|
|
178
|
+
*/
|
|
179
|
+
function clampSectionHeight(px) {
|
|
180
|
+
if (!Number.isFinite(px)) return 64;
|
|
181
|
+
return Math.min(MAX_H, Math.max(64, Math.round(px)));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 归一化高度表:非对象/缺 key 回退默认,非法值丢弃(损坏安全)。
|
|
185
|
+
* @author ddj 2026年09月21号
|
|
186
|
+
* @param input localStorage 解析结果
|
|
187
|
+
*/
|
|
188
|
+
function sanitizeHeights(input) {
|
|
189
|
+
const out = { ...DEFAULT_HEIGHTS };
|
|
190
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return out;
|
|
191
|
+
const data = input;
|
|
192
|
+
for (const key of SPLIT_KEYS) {
|
|
193
|
+
const value = data[key];
|
|
194
|
+
if (typeof value === "number" && Number.isFinite(value)) out[key] = clampSectionHeight(value);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 相邻两段高度重分配:above += dy、below −= dy,各自夹取;
|
|
200
|
+
* 一段被夹取吸收的量补给另一段(分隔条停在可行位置,拖动量尽量生效)。
|
|
201
|
+
* @author ddj 2026年09月21号
|
|
202
|
+
* @param heights 拖动起点高度表(不修改入参)
|
|
203
|
+
* @param above 上邻段 key
|
|
204
|
+
* @param below 下邻段 key
|
|
205
|
+
* @param dy 拖动位移(>0 向下拖 = 上段增高)
|
|
206
|
+
* @returns 新高度表
|
|
207
|
+
*/
|
|
208
|
+
function redistribute(heights, above, below, dy) {
|
|
209
|
+
const clean = Math.round(Number.isFinite(dy) ? dy : 0);
|
|
210
|
+
const next = { ...heights };
|
|
211
|
+
const startAbove = heights[above] ?? 64;
|
|
212
|
+
const startBelow = heights[below] ?? 64;
|
|
213
|
+
const targetAbove = clampSectionHeight(startAbove + clean);
|
|
214
|
+
const targetBelow = clampSectionHeight(startBelow - clean);
|
|
215
|
+
next[above] = targetAbove;
|
|
216
|
+
next[below] = targetBelow;
|
|
217
|
+
const lostAbove = startAbove + clean - targetAbove;
|
|
218
|
+
const lostBelow = startBelow - clean - targetBelow;
|
|
219
|
+
if (lostAbove > 0) next[below] = clampSectionHeight(targetBelow + lostAbove);
|
|
220
|
+
else if (lostBelow > 0) next[above] = clampSectionHeight(targetAbove + lostBelow);
|
|
221
|
+
return next;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 读高度表(缺失/损坏回退默认值)。
|
|
225
|
+
* @author ddj 2026年09月21号
|
|
226
|
+
* @param scope 状态作用域
|
|
227
|
+
*/
|
|
228
|
+
function loadSectionHeights(scope) {
|
|
229
|
+
if (!scope) return { ...DEFAULT_HEIGHTS };
|
|
230
|
+
try {
|
|
231
|
+
const raw = localStorage.getItem(KEY_PREFIX + scope);
|
|
232
|
+
return sanitizeHeights(raw ? JSON.parse(raw) : null);
|
|
233
|
+
} catch {
|
|
234
|
+
return { ...DEFAULT_HEIGHTS };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* 写高度表(写失败静默;写入前归一化)。
|
|
239
|
+
* @author ddj 2026年09月21号
|
|
240
|
+
* @param scope 状态作用域
|
|
241
|
+
* @param heights 高度表
|
|
242
|
+
*/
|
|
243
|
+
function saveSectionHeights(scope, heights) {
|
|
244
|
+
try {
|
|
245
|
+
localStorage.setItem(KEY_PREFIX + scope, JSON.stringify(sanitizeHeights(heights)));
|
|
246
|
+
} catch {}
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region \0panel-shared:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/dap/BpWidget.ts
|
|
250
|
+
const BP_MODE = dsh_vscode_mode.__lazyShared.BP_MODE;
|
|
251
|
+
const MODES = dsh_vscode_mode.__lazyShared.MODES;
|
|
252
|
+
const fieldOfMode = dsh_vscode_mode.__lazyShared.fieldOfMode;
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region \0panel-shared:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/ui/ContextMenu.ts
|
|
255
|
+
const ContextMenu = dsh_vscode_mode.__lazyShared.ContextMenu;
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region \0panel-shared:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/rpc.ts
|
|
258
|
+
const rpc = dsh_vscode_mode.__lazyShared.rpc;
|
|
259
|
+
dsh_vscode_mode.__lazyShared.dbg;
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/client/sidebar/panels/DebugPanel.ts
|
|
262
|
+
/**
|
|
263
|
+
* dsh-vscode-mode client — 侧边栏「调试」面板(对齐 CodeBuddy「运行和调试」视图)。
|
|
264
|
+
* 顶部工具条:配置下拉 + 启动/继续 + 暂停 + 单步组 + 停止(禁用逻辑镜像编辑器浮动条)。
|
|
265
|
+
* 四段折叠区(变量 / 监视 / 调用堆栈 / 断点,图2 顺序)+ 底部调试控制台(REPL);
|
|
266
|
+
* 每段头部可折叠(状态按 scope 持久化)且带悬浮动作按钮。
|
|
267
|
+
* 断点多方式管理(对齐 CodeBuddy 断点区):段头「新建断点」菜单(条件断点/记录点,
|
|
268
|
+
* 落在活动编辑器光标行)、禁用/启用所有、删除所有;断点行右键菜单(编辑条件/命中次数/
|
|
269
|
+
* 记录点/启停/删除)→ 行内展开编辑器(Enter 提交 / Escape 取消,字段语义同 BpWidget);
|
|
270
|
+
* 行内点展示适配器回执(未验证 = 空心点 + tooltip)。
|
|
271
|
+
* 数据自取:paused 时经 dapStore 拉栈/作用域/变量/监视求值;断点读写 dapStore 断点表;
|
|
272
|
+
* 输出流 = host poll 事件缓存(outputs)。host 不可达或未暂停时各段空态降级。
|
|
273
|
+
* 断点行整行可点:除启停 checkbox 与删除 × 外,点击任一处即打开并定位到该断点行。
|
|
274
|
+
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
275
|
+
*/
|
|
276
|
+
/** 变量展开缓存上限(ref → 子级;防长会话累积)。 */
|
|
277
|
+
const VAR_CACHE_CAP = 200;
|
|
278
|
+
/** REPL 历史行上限。 */
|
|
279
|
+
const REPL_CAP = 200;
|
|
280
|
+
/** 监视持久化 key 前缀(CACHE_KEY 无调试段,自管命名空间)。 */
|
|
281
|
+
const WATCH_KEY = "edrv.dap.watch.";
|
|
282
|
+
/** 段折叠持久化 key 前缀(存折叠段名数组;损坏安全,默认全展开)。 */
|
|
283
|
+
const FOLD_KEY = "edrv.dap.fold.";
|
|
284
|
+
/** 调试配置文件(工作区相对路径;⚙ 按钮跳转/新建目标;与 VS Code 的 .vscode/launch.json 互不干扰)。 */
|
|
285
|
+
const LAUNCH_REL = DAP_LAUNCH_REL;
|
|
286
|
+
/** 新建调试配置模板(JSONC;示例按本机已装的 EmmyLua 适配器写)。 */
|
|
287
|
+
const LAUNCH_TEMPLATE = `{
|
|
288
|
+
// DSH 调试配置(插件专属 .dsh/launch.json,支持注释;改完整页刷新生效)
|
|
289
|
+
// type 来自本机已装扩展的 contributes.debuggers(EmmyLua:emmylua_attach / emmylua_launch / emmylua_new)
|
|
290
|
+
"version": "0.2.0",
|
|
291
|
+
"configurations": [
|
|
292
|
+
{
|
|
293
|
+
"name": "附加 Unity (xLua)",
|
|
294
|
+
"type": "emmylua_attach",
|
|
295
|
+
"request": "attach",
|
|
296
|
+
"processName": "Unity",
|
|
297
|
+
"captureLog": true
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}`;
|
|
301
|
+
/** 相位徽标文案。 */
|
|
302
|
+
const PHASE_LABEL = {
|
|
303
|
+
idle: "未启动",
|
|
304
|
+
starting: "启动中…",
|
|
305
|
+
waiting: "等待目标…",
|
|
306
|
+
running: "运行中",
|
|
307
|
+
paused: "已暂停",
|
|
308
|
+
terminated: "已结束"
|
|
309
|
+
};
|
|
310
|
+
/** 读取监视表达式列表(损坏安全)。 */
|
|
311
|
+
function loadWatches(scope) {
|
|
312
|
+
if (!scope) return [];
|
|
313
|
+
try {
|
|
314
|
+
const raw = window.localStorage.getItem(WATCH_KEY + scope);
|
|
315
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
316
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string" && item.trim()) : [];
|
|
317
|
+
} catch {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** 写监视表达式列表。 */
|
|
322
|
+
function saveWatches(scope, list) {
|
|
323
|
+
try {
|
|
324
|
+
window.localStorage.setItem(WATCH_KEY + scope, JSON.stringify(list));
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
/** 读折叠段名列表(损坏安全;非字符串项丢弃)。 */
|
|
328
|
+
function loadFolds(scope) {
|
|
329
|
+
if (!scope) return [];
|
|
330
|
+
try {
|
|
331
|
+
const raw = window.localStorage.getItem(FOLD_KEY + scope);
|
|
332
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
333
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string" && item) : [];
|
|
334
|
+
} catch {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** 写折叠段名列表(写失败静默)。 */
|
|
339
|
+
function saveFolds(scope, list) {
|
|
340
|
+
try {
|
|
341
|
+
window.localStorage.setItem(FOLD_KEY + scope, JSON.stringify(list));
|
|
342
|
+
} catch {}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* 「调试」面板组件。
|
|
346
|
+
* @param ctx 侧栏上下文(openFileAt 跳转 / scope 持久化 / cwd / editor 光标)
|
|
347
|
+
*/
|
|
348
|
+
function DebugPanel({ ctx }) {
|
|
349
|
+
const scope = ctx?.scope || "";
|
|
350
|
+
const [, setTick] = react.default.useState(0);
|
|
351
|
+
react.default.useEffect(() => dapStore.subscribe(() => setTick((t) => t + 1)), []);
|
|
352
|
+
const snap = dapStore.getSnapshot();
|
|
353
|
+
const paused = snap.phase === "paused";
|
|
354
|
+
const dapActive = snap.phase !== "idle" && snap.phase !== "terminated";
|
|
355
|
+
const [stacks, setStacks] = react.default.useState([]);
|
|
356
|
+
const [scopes, setScopes] = react.default.useState([]);
|
|
357
|
+
const [varCache, setVarCache] = react.default.useState(() => /* @__PURE__ */ new Map());
|
|
358
|
+
const [expanded, setExpanded] = react.default.useState(() => /* @__PURE__ */ new Set());
|
|
359
|
+
const [watches, setWatches] = react.default.useState(() => loadWatches(scope));
|
|
360
|
+
const [watchHits, setWatchHits] = react.default.useState({});
|
|
361
|
+
const [watchDraft, setWatchDraft] = react.default.useState("");
|
|
362
|
+
const [replLog, setReplLog] = react.default.useState([]);
|
|
363
|
+
const [replDraft, setReplDraft] = react.default.useState("");
|
|
364
|
+
const [folds, setFolds] = react.default.useState(() => new Set(loadFolds(scope)));
|
|
365
|
+
const [heights, setHeights] = react.default.useState(() => loadSectionHeights(scope));
|
|
366
|
+
const [splitDragging, setSplitDragging] = react.default.useState(false);
|
|
367
|
+
const [bpMenu, setBpMenu] = react.default.useState(null);
|
|
368
|
+
const [newBpMenu, setNewBpMenu] = react.default.useState(null);
|
|
369
|
+
const [bpEdit, setBpEdit] = react.default.useState(null);
|
|
370
|
+
const [pickPid, setPickPid] = react.default.useState(0);
|
|
371
|
+
const watchInputRef = react.default.useRef(null);
|
|
372
|
+
const stackTopKey = paused && snap.topFrame ? snap.topFrame.file + ":" + snap.topFrame.line : "";
|
|
373
|
+
react.default.useEffect(() => {
|
|
374
|
+
if (!ctx?.cwd) return;
|
|
375
|
+
dapStore.setWorkspace(ctx.cwd);
|
|
376
|
+
dapStore.ensureConfigs();
|
|
377
|
+
}, [ctx?.cwd]);
|
|
378
|
+
const [reloadTick, setReloadTick] = react.default.useState(0);
|
|
379
|
+
react.default.useEffect(() => {
|
|
380
|
+
if (!paused) {
|
|
381
|
+
setStacks([]);
|
|
382
|
+
setScopes([]);
|
|
383
|
+
setVarCache(/* @__PURE__ */ new Map());
|
|
384
|
+
setExpanded(/* @__PURE__ */ new Set());
|
|
385
|
+
setWatchHits({});
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
let alive = true;
|
|
389
|
+
const load = async () => {
|
|
390
|
+
const frames = await dapStore.stackTrace();
|
|
391
|
+
if (!alive) return;
|
|
392
|
+
setStacks(frames);
|
|
393
|
+
const list = await dapStore.scopes(snap.selectedFrameId);
|
|
394
|
+
if (!alive) return;
|
|
395
|
+
setScopes(list);
|
|
396
|
+
const first = list[0];
|
|
397
|
+
if (first?.ref) {
|
|
398
|
+
const vars = await dapStore.variables(first.ref);
|
|
399
|
+
if (!alive) return;
|
|
400
|
+
setVarCache((prev) => new Map(prev).set(first.ref, vars));
|
|
401
|
+
setExpanded((prev) => new Set(prev).add(first.ref));
|
|
402
|
+
}
|
|
403
|
+
const hits = {};
|
|
404
|
+
for (const expr of watches) hits[expr] = await dapStore.evaluate(expr, snap.selectedFrameId);
|
|
405
|
+
if (alive) setWatchHits(hits);
|
|
406
|
+
};
|
|
407
|
+
load();
|
|
408
|
+
return () => {
|
|
409
|
+
alive = false;
|
|
410
|
+
};
|
|
411
|
+
}, [
|
|
412
|
+
paused,
|
|
413
|
+
stackTopKey,
|
|
414
|
+
snap.selectedFrameId,
|
|
415
|
+
watches,
|
|
416
|
+
reloadTick
|
|
417
|
+
]);
|
|
418
|
+
const expandVar = async (ref) => {
|
|
419
|
+
setExpanded((prev) => {
|
|
420
|
+
const next = new Set(prev);
|
|
421
|
+
if (next.has(ref)) next.delete(ref);
|
|
422
|
+
else next.add(ref);
|
|
423
|
+
return next;
|
|
424
|
+
});
|
|
425
|
+
if (!varCache.has(ref)) {
|
|
426
|
+
const vars = await dapStore.variables(ref);
|
|
427
|
+
setVarCache((prev) => {
|
|
428
|
+
const next = new Map(prev);
|
|
429
|
+
next.set(ref, vars);
|
|
430
|
+
if (next.size > VAR_CACHE_CAP) next.delete(next.keys().next().value);
|
|
431
|
+
return next;
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const addWatch = () => {
|
|
436
|
+
const expr = watchDraft.trim();
|
|
437
|
+
if (!expr || watches.includes(expr)) {
|
|
438
|
+
setWatchDraft("");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const next = [...watches, expr];
|
|
442
|
+
setWatches(next);
|
|
443
|
+
saveWatches(scope, next);
|
|
444
|
+
setWatchDraft("");
|
|
445
|
+
if (paused) dapStore.evaluate(expr, snap.selectedFrameId).then((r) => setWatchHits((prev) => ({
|
|
446
|
+
...prev,
|
|
447
|
+
[expr]: r
|
|
448
|
+
})));
|
|
449
|
+
};
|
|
450
|
+
const removeWatch = (expr) => {
|
|
451
|
+
const next = watches.filter((item) => item !== expr);
|
|
452
|
+
setWatches(next);
|
|
453
|
+
saveWatches(scope, next);
|
|
454
|
+
};
|
|
455
|
+
/** 移除全部监视(段头动作;持久化与命中缓存同步清)。 */
|
|
456
|
+
const clearWatches = () => {
|
|
457
|
+
setWatches([]);
|
|
458
|
+
saveWatches(scope, []);
|
|
459
|
+
setWatchHits({});
|
|
460
|
+
};
|
|
461
|
+
/** 聚焦监视新增输入(段折叠时先展开,二次点击聚焦)。 */
|
|
462
|
+
const focusWatchInput = () => {
|
|
463
|
+
if (folds.has("watch")) {
|
|
464
|
+
toggleFold("watch");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
watchInputRef.current?.focus?.();
|
|
468
|
+
};
|
|
469
|
+
const runRepl = async () => {
|
|
470
|
+
const expr = replDraft.trim();
|
|
471
|
+
if (!expr) return;
|
|
472
|
+
setReplDraft("");
|
|
473
|
+
const r = await dapStore.evaluate(expr, snap.selectedFrameId);
|
|
474
|
+
setReplLog((prev) => {
|
|
475
|
+
const next = [...prev, {
|
|
476
|
+
expr,
|
|
477
|
+
result: r.result,
|
|
478
|
+
type: r.type
|
|
479
|
+
}];
|
|
480
|
+
return next.length > REPL_CAP ? next.slice(next.length - REPL_CAP) : next;
|
|
481
|
+
});
|
|
482
|
+
};
|
|
483
|
+
/** 切换段折叠(持久化到 localStorage)。 */
|
|
484
|
+
const toggleFold = (key) => {
|
|
485
|
+
setFolds((prev) => {
|
|
486
|
+
const next = new Set(prev);
|
|
487
|
+
if (next.has(key)) next.delete(key);
|
|
488
|
+
else next.add(key);
|
|
489
|
+
saveFolds(scope, [...next]);
|
|
490
|
+
return next;
|
|
491
|
+
});
|
|
492
|
+
};
|
|
493
|
+
const splitCleanupRef = react.default.useRef(null);
|
|
494
|
+
react.default.useEffect(() => () => {
|
|
495
|
+
const cleanup = splitCleanupRef.current;
|
|
496
|
+
if (cleanup) cleanup();
|
|
497
|
+
}, []);
|
|
498
|
+
/** 段间 sash 拖动:以起点高度为基准按 dy 相邻重分配;pointerup 持久化(镜像 dapbar 拖动模式)。 */
|
|
499
|
+
const startSplitDrag = (above, below) => (e) => {
|
|
500
|
+
e.preventDefault();
|
|
501
|
+
const startY = e.clientY;
|
|
502
|
+
const startHeights = { ...heights };
|
|
503
|
+
const onMove = (ev) => {
|
|
504
|
+
setHeights(redistribute(startHeights, above, below, ev.clientY - startY));
|
|
505
|
+
};
|
|
506
|
+
const detach = () => {
|
|
507
|
+
window.removeEventListener("pointermove", onMove);
|
|
508
|
+
window.removeEventListener("pointerup", onUp);
|
|
509
|
+
splitCleanupRef.current = null;
|
|
510
|
+
setSplitDragging(false);
|
|
511
|
+
};
|
|
512
|
+
const onUp = () => {
|
|
513
|
+
detach();
|
|
514
|
+
setHeights((prev) => {
|
|
515
|
+
saveSectionHeights(scope, prev);
|
|
516
|
+
return prev;
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
splitCleanupRef.current = detach;
|
|
520
|
+
window.addEventListener("pointermove", onMove);
|
|
521
|
+
window.addEventListener("pointerup", onUp);
|
|
522
|
+
setSplitDragging(true);
|
|
523
|
+
};
|
|
524
|
+
const cwd = ctx?.cwd || dapStore.workspace();
|
|
525
|
+
const bpMap = dapStore.bpMapOf(scope);
|
|
526
|
+
const bpRows = Object.entries(bpMap).flatMap(([path, list]) => list.map((entry) => ({
|
|
527
|
+
path,
|
|
528
|
+
entry
|
|
529
|
+
}))).sort((a, b) => a.path.localeCompare(b.path) || a.entry.line - b.entry.line);
|
|
530
|
+
const allBpEnabled = bpRows.length > 0 && bpRows.every((row) => row.entry.enabled);
|
|
531
|
+
const toggleBpEnabled = (row) => dapStore.setBpEnabled(scope, cwd, row.path, row.entry.line, !row.entry.enabled);
|
|
532
|
+
const removeBp = (row) => dapStore.toggleAt(scope, cwd, row.path, row.entry.line);
|
|
533
|
+
/** 删除所有断点(confirm 守卫;逐文件同步空载荷清空 Monaco 装饰与 host 断点)。 */
|
|
534
|
+
const removeAllBps = () => {
|
|
535
|
+
if (ctx?.confirm && !ctx.confirm("删除所有断点?")) return;
|
|
536
|
+
dapStore.removeAllBps(scope, cwd);
|
|
537
|
+
setBpEdit(null);
|
|
538
|
+
};
|
|
539
|
+
const jumpBp = (row) => {
|
|
540
|
+
const target = bpJumpTargetOf(row);
|
|
541
|
+
if (target) ctx?.openFileAt?.(target.path, target.line, target.column);
|
|
542
|
+
};
|
|
543
|
+
const autoBpModeOf = (entry) => entry?.logMessage ? BP_MODE.LOG_MESSAGE : entry?.hitCondition ? BP_MODE.HIT_COUNT : BP_MODE.EXPRESSION;
|
|
544
|
+
const openBpRowEditor = (path, line, entry, mode) => {
|
|
545
|
+
setBpEdit({
|
|
546
|
+
path,
|
|
547
|
+
line,
|
|
548
|
+
mode: typeof mode === "number" ? mode : autoBpModeOf(entry),
|
|
549
|
+
drafts: {
|
|
550
|
+
condition: entry?.condition || "",
|
|
551
|
+
hitCondition: entry?.hitCondition || "",
|
|
552
|
+
logMessage: entry?.logMessage || ""
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
};
|
|
556
|
+
const commitBpEdit = () => {
|
|
557
|
+
if (!bpEdit) return;
|
|
558
|
+
dapStore.applyBpFields(scope, cwd, bpEdit.path, bpEdit.line, { ...bpEdit.drafts });
|
|
559
|
+
setBpEdit(null);
|
|
560
|
+
ctx?.notify?.("断点已更新");
|
|
561
|
+
};
|
|
562
|
+
/** 断点行右键菜单动作分发(菜单数据由 buildBpRowMenu 产出)。 */
|
|
563
|
+
const runBpRowAction = (action, row) => {
|
|
564
|
+
if (action === "remove") {
|
|
565
|
+
removeBp(row);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (action === "enable" || action === "disable") {
|
|
569
|
+
dapStore.setBpEnabled(scope, cwd, row.path, row.entry.line, action === "enable");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const mode = action === "edit-condition" ? BP_MODE.EXPRESSION : action === "edit-hit-count" ? BP_MODE.HIT_COUNT : action === "edit-logpoint" ? BP_MODE.LOG_MESSAGE : void 0;
|
|
573
|
+
openBpRowEditor(row.path, row.entry.line, row.entry, mode);
|
|
574
|
+
};
|
|
575
|
+
/** 段头「新建断点」:条件断点/记录点落在活动编辑器光标行(已有断点则直接编辑不翻转)。 */
|
|
576
|
+
const addBpAtCursor = (mode) => {
|
|
577
|
+
const ed = ctx?.editor?.();
|
|
578
|
+
const path = editorModelPathOf(ed);
|
|
579
|
+
const line = ed?.getPosition?.()?.lineNumber;
|
|
580
|
+
if (!path || !line || !Number.isFinite(line)) {
|
|
581
|
+
ctx?.notify?.("无活动文件,无法添加断点");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (!(dapStore.bpMapOf(scope)[path] ?? []).find((it) => it.line === line)) dapStore.toggleAt(scope, cwd, path, line);
|
|
585
|
+
const entry = (dapStore.bpMapOf(scope)[path] ?? []).find((it) => it.line === line);
|
|
586
|
+
if (entry) openBpRowEditor(path, line, entry, mode);
|
|
587
|
+
};
|
|
588
|
+
/** ⚙ 打开/新建调试配置:存在即跳转;缺失经确认后写模板(edrv.save 无 rev 仅用于新建,绝不覆盖已有文件)。 */
|
|
589
|
+
const openLaunchConfig = async () => {
|
|
590
|
+
if (!ctx?.cwd) {
|
|
591
|
+
ctx?.notify?.("无工作区,无法定位 " + LAUNCH_REL);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
if ((await rpc("edrv.read", {
|
|
596
|
+
sessionId: ctx?.sessionId,
|
|
597
|
+
path: LAUNCH_REL
|
|
598
|
+
}).catch(() => null))?.ok) {
|
|
599
|
+
ctx?.openFile?.(LAUNCH_REL);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
} catch {}
|
|
603
|
+
if (ctx?.confirm && !ctx.confirm("未找到 " + LAUNCH_REL + ",是否创建调试配置模板?")) return;
|
|
604
|
+
try {
|
|
605
|
+
const saved = await rpc("edrv.save", {
|
|
606
|
+
sessionId: ctx?.sessionId,
|
|
607
|
+
path: LAUNCH_REL,
|
|
608
|
+
content: LAUNCH_TEMPLATE
|
|
609
|
+
});
|
|
610
|
+
if (!saved?.ok) {
|
|
611
|
+
ctx?.notify?.(saved?.error || "创建 " + LAUNCH_REL + " 失败");
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
dapStore.ensureConfigs(true);
|
|
615
|
+
ctx?.openFile?.(LAUNCH_REL);
|
|
616
|
+
ctx?.notify?.("已创建 " + LAUNCH_REL);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
ctx?.notify?.("创建 " + LAUNCH_REL + " 失败:" + String(error));
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
/** 段头动作按钮(hover 显隐,样式见 .edrv-dapseg-act)。 */
|
|
622
|
+
const segAct = (key, label, title, onClick) => react.default.createElement("button", {
|
|
623
|
+
key,
|
|
624
|
+
className: "edrv-dapseg-act",
|
|
625
|
+
title,
|
|
626
|
+
onClick: (e) => {
|
|
627
|
+
e.stopPropagation();
|
|
628
|
+
onClick(e);
|
|
629
|
+
}
|
|
630
|
+
}, label);
|
|
631
|
+
/** 折叠段壳:chevron + 标题 + 悬浮动作区 + 可折叠主体(高度由分栏 state 内联接管)。 */
|
|
632
|
+
const section = (title, key, variant, actions, children) => {
|
|
633
|
+
const folded = folds.has(key);
|
|
634
|
+
return react.default.createElement("div", {
|
|
635
|
+
className: "edrv-dapseg edrv-dapseg-" + variant,
|
|
636
|
+
key
|
|
637
|
+
}, react.default.createElement("div", {
|
|
638
|
+
className: "edrv-dapseg-head",
|
|
639
|
+
onClick: () => toggleFold(key)
|
|
640
|
+
}, react.default.createElement("span", {
|
|
641
|
+
className: "edrv-dapseg-chevron",
|
|
642
|
+
title: folded ? "展开" : "折叠"
|
|
643
|
+
}, folded ? "▸" : "▾"), react.default.createElement("span", { className: "edrv-dapseg-title" }, title), react.default.createElement("span", { style: { flex: 1 } }), ...folded ? [] : actions || []), ...folded ? [] : [react.default.createElement("div", {
|
|
644
|
+
className: "edrv-dapseg-body",
|
|
645
|
+
style: { height: (heights[key] ?? 200) + "px" }
|
|
646
|
+
}, ...children)]);
|
|
647
|
+
};
|
|
648
|
+
/** 段间分隔条(两侧段都展开时才渲染;拖动调整相邻两段高度占比)。 */
|
|
649
|
+
const sash = (above, below) => folds.has(above) || folds.has(below) ? null : react.default.createElement("div", {
|
|
650
|
+
key: "sash-" + above + "-" + below,
|
|
651
|
+
className: "edrv-dapseg-sash" + (splitDragging ? " dragging" : ""),
|
|
652
|
+
title: "拖动调整上下两段高度占比",
|
|
653
|
+
onPointerDown: startSplitDrag(above, below)
|
|
654
|
+
});
|
|
655
|
+
const renderVarRow = (row) => {
|
|
656
|
+
const v = row.variable;
|
|
657
|
+
const depth = row.depth + 1;
|
|
658
|
+
return react.default.createElement("div", {
|
|
659
|
+
key: row.key,
|
|
660
|
+
className: "edrv-dapvar",
|
|
661
|
+
style: { paddingLeft: 8 + depth * 12 }
|
|
662
|
+
}, v.ref > 0 ? react.default.createElement("button", {
|
|
663
|
+
className: "edrv-dapvar-tw",
|
|
664
|
+
title: expanded.has(v.ref) ? "折叠" : "展开",
|
|
665
|
+
onClick: () => {
|
|
666
|
+
expandVar(v.ref);
|
|
667
|
+
}
|
|
668
|
+
}, expanded.has(v.ref) ? "▾" : "▸") : react.default.createElement("span", { className: "edrv-dapvar-tw" }, ""), react.default.createElement("span", {
|
|
669
|
+
className: "edrv-dapvar-name",
|
|
670
|
+
title: v.name
|
|
671
|
+
}, v.name), react.default.createElement("span", {
|
|
672
|
+
className: "edrv-dapvar-val",
|
|
673
|
+
title: v.value
|
|
674
|
+
}, v.value), v.type ? react.default.createElement("span", { className: "edrv-dapvar-type" }, v.type) : null);
|
|
675
|
+
};
|
|
676
|
+
const stackRows = stacks.length ? stacks.map((f) => react.default.createElement("div", {
|
|
677
|
+
key: f.id,
|
|
678
|
+
className: "edrv-dapframe" + (f.id === snap.selectedFrameId ? " selected" : ""),
|
|
679
|
+
title: f.rawFile,
|
|
680
|
+
onClick: () => {
|
|
681
|
+
dapStore.selectFrame(f.id);
|
|
682
|
+
const target = f.file || f.rawFile;
|
|
683
|
+
if (target) ctx?.openFileAt?.(target, f.line, 1);
|
|
684
|
+
}
|
|
685
|
+
}, react.default.createElement("span", { className: "edrv-dapframe-name" }, f.name || "(anonymous)"), react.default.createElement("span", { className: "edrv-dapframe-loc" }, (f.file || f.rawFile) + ":" + f.line))) : [react.default.createElement("div", {
|
|
686
|
+
key: "empty",
|
|
687
|
+
className: "edrv-dapempty"
|
|
688
|
+
}, paused ? "(无调用栈)" : "暂停时显示调用堆栈")];
|
|
689
|
+
const scopeRows = scopes.flatMap((s) => [react.default.createElement("div", {
|
|
690
|
+
key: "scope" + s.name,
|
|
691
|
+
className: "edrv-dapscope"
|
|
692
|
+
}, s.name), ...flattenVariableRows(varCache.get(s.ref) ?? [], expanded, varCache, { maxRows: 800 }).map(renderVarRow)]);
|
|
693
|
+
const varRows = scopes.length ? scopeRows : [react.default.createElement("div", {
|
|
694
|
+
key: "e",
|
|
695
|
+
className: "edrv-dapempty"
|
|
696
|
+
}, "暂停时显示变量")];
|
|
697
|
+
const watchRows = watches.flatMap((expr) => {
|
|
698
|
+
const hit = watchHits[expr];
|
|
699
|
+
const rows = [react.default.createElement("div", {
|
|
700
|
+
key: expr,
|
|
701
|
+
className: "edrv-dapwatch"
|
|
702
|
+
}, react.default.createElement("button", {
|
|
703
|
+
className: "edrv-dapwatch-x",
|
|
704
|
+
title: "移除监视",
|
|
705
|
+
onClick: () => removeWatch(expr)
|
|
706
|
+
}, "×"), hit?.ref > 0 ? react.default.createElement("button", {
|
|
707
|
+
className: "edrv-dapvar-tw",
|
|
708
|
+
title: expanded.has(hit.ref) ? "折叠 table" : "展开 table",
|
|
709
|
+
onClick: () => {
|
|
710
|
+
expandVar(hit.ref);
|
|
711
|
+
}
|
|
712
|
+
}, expanded.has(hit.ref) ? "▾" : "▸") : react.default.createElement("span", { className: "edrv-dapvar-tw" }, ""), react.default.createElement("span", {
|
|
713
|
+
className: "edrv-dapvar-name",
|
|
714
|
+
title: expr
|
|
715
|
+
}, expr), react.default.createElement("span", {
|
|
716
|
+
className: "edrv-dapvar-val",
|
|
717
|
+
title: hit?.result || ""
|
|
718
|
+
}, hit ? "= " + hit.result : "= 不可用"), hit?.type ? react.default.createElement("span", { className: "edrv-dapvar-type" }, hit.type) : null)];
|
|
719
|
+
if (hit?.ref > 0 && expanded.has(hit.ref)) rows.push(...flattenVariableRows(varCache.get(hit.ref) ?? [], expanded, varCache, { maxRows: 500 }).map(renderVarRow));
|
|
720
|
+
return rows;
|
|
721
|
+
});
|
|
722
|
+
const watchSection = section("监视", "watch", "watch", [segAct("watch-add", "+", "新增监视表达式", focusWatchInput), segAct("watch-clear", "🗑", "移除全部监视", clearWatches)], [...watchRows, react.default.createElement("div", {
|
|
723
|
+
key: "add",
|
|
724
|
+
className: "edrv-dapwatch-add"
|
|
725
|
+
}, react.default.createElement("input", {
|
|
726
|
+
ref: watchInputRef,
|
|
727
|
+
className: "edrv-daprepl-in",
|
|
728
|
+
placeholder: "新增监视表达式,回车确认",
|
|
729
|
+
value: watchDraft,
|
|
730
|
+
onChange: (e) => setWatchDraft(e.target.value),
|
|
731
|
+
onKeyDown: (e) => {
|
|
732
|
+
if (e.key === "Enter") addWatch();
|
|
733
|
+
}
|
|
734
|
+
}))]);
|
|
735
|
+
const bpSectionChildren = bpRows.length ? bpRows.flatMap((row) => {
|
|
736
|
+
const base = row.path.split("/").pop() || row.path;
|
|
737
|
+
const dir = row.path.includes("/") ? row.path.slice(0, row.path.lastIndexOf("/")) : "";
|
|
738
|
+
const ack = dapStore.ackOf(row.path, row.entry.line);
|
|
739
|
+
const unverified = Boolean(ack && !ack.verified);
|
|
740
|
+
const editing = bpEdit && bpEdit.path === row.path && bpEdit.line === row.entry.line;
|
|
741
|
+
const rows = [react.default.createElement("div", {
|
|
742
|
+
key: row.path + ":" + row.entry.line,
|
|
743
|
+
className: "edrv-dapbp",
|
|
744
|
+
title: "跳转到 " + row.path + ":" + row.entry.line + (unverified && ack.message ? "(" + ack.message + ")" : ""),
|
|
745
|
+
onClick: () => jumpBp(row),
|
|
746
|
+
onContextMenu: (e) => {
|
|
747
|
+
e.preventDefault();
|
|
748
|
+
setBpMenu({
|
|
749
|
+
x: e.clientX,
|
|
750
|
+
y: e.clientY,
|
|
751
|
+
row
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}, react.default.createElement("input", {
|
|
755
|
+
type: "checkbox",
|
|
756
|
+
checked: row.entry.enabled,
|
|
757
|
+
title: row.entry.enabled ? "禁用断点" : "启用断点",
|
|
758
|
+
onChange: () => toggleBpEnabled(row),
|
|
759
|
+
onClick: (e) => e.stopPropagation()
|
|
760
|
+
}), react.default.createElement("span", {
|
|
761
|
+
className: "edrv-dapbp-dot" + (row.entry.enabled ? "" : " off") + (unverified ? " unverified" : ""),
|
|
762
|
+
title: unverified && ack.message ? ack.message : void 0
|
|
763
|
+
}), react.default.createElement("button", {
|
|
764
|
+
className: "edrv-dapbp-file",
|
|
765
|
+
title: row.path
|
|
766
|
+
}, base), react.default.createElement("span", {
|
|
767
|
+
className: "edrv-dapbp-dir",
|
|
768
|
+
title: dir
|
|
769
|
+
}, dir), react.default.createElement("span", { className: "edrv-dapbp-line" }, row.entry.line), react.default.createElement("button", {
|
|
770
|
+
className: "edrv-dapbp-x",
|
|
771
|
+
title: "删除断点",
|
|
772
|
+
onClick: (e) => {
|
|
773
|
+
e.stopPropagation();
|
|
774
|
+
removeBp(row);
|
|
775
|
+
}
|
|
776
|
+
}, "×"))];
|
|
777
|
+
if (editing) {
|
|
778
|
+
const fieldKey = fieldOfMode(bpEdit.mode);
|
|
779
|
+
const modeItem = MODES.find((m) => m.mode === bpEdit.mode) ?? MODES[0];
|
|
780
|
+
rows.push(react.default.createElement("div", {
|
|
781
|
+
key: row.path + ":" + row.entry.line + ":edit",
|
|
782
|
+
className: "edrv-dapbp-edit"
|
|
783
|
+
}, react.default.createElement("select", {
|
|
784
|
+
className: "edrv-bpwidget-mode",
|
|
785
|
+
title: "断点类型",
|
|
786
|
+
value: String(bpEdit.mode),
|
|
787
|
+
onChange: (e) => setBpEdit({
|
|
788
|
+
...bpEdit,
|
|
789
|
+
mode: Number(e.target.value)
|
|
790
|
+
})
|
|
791
|
+
}, MODES.filter((m) => m.mode !== BP_MODE.WAIT).map((m) => react.default.createElement("option", {
|
|
792
|
+
key: m.mode,
|
|
793
|
+
value: String(m.mode)
|
|
794
|
+
}, m.label))), react.default.createElement("input", {
|
|
795
|
+
className: "edrv-bpwidget-input",
|
|
796
|
+
autoFocus: true,
|
|
797
|
+
spellCheck: false,
|
|
798
|
+
placeholder: modeItem?.placeholder ?? "",
|
|
799
|
+
hidden: fieldKey === null,
|
|
800
|
+
value: fieldKey ? bpEdit.drafts[fieldKey] || "" : "",
|
|
801
|
+
onChange: (e) => setBpEdit({
|
|
802
|
+
...bpEdit,
|
|
803
|
+
drafts: {
|
|
804
|
+
...bpEdit.drafts,
|
|
805
|
+
[fieldKey]: e.target.value
|
|
806
|
+
}
|
|
807
|
+
}),
|
|
808
|
+
onKeyDown: (e) => {
|
|
809
|
+
if (e.key === "Enter") {
|
|
810
|
+
e.preventDefault();
|
|
811
|
+
commitBpEdit();
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (e.key === "Escape") {
|
|
815
|
+
e.preventDefault();
|
|
816
|
+
setBpEdit(null);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
})));
|
|
820
|
+
}
|
|
821
|
+
return rows;
|
|
822
|
+
}) : [react.default.createElement("div", {
|
|
823
|
+
key: "e",
|
|
824
|
+
className: "edrv-dapempty"
|
|
825
|
+
}, "在编辑器行首点击或 F9 添加断点")];
|
|
826
|
+
const bpSection = section("断点", "breakpoints", "breakpoints", [
|
|
827
|
+
segAct("bp-new", "新建断点 ▾", "添加条件断点 / 记录点(落在当前编辑器光标行)", (e) => setNewBpMenu({
|
|
828
|
+
x: e.clientX,
|
|
829
|
+
y: e.clientY
|
|
830
|
+
})),
|
|
831
|
+
bpRows.length ? segAct("bp-toggle", allBpEnabled ? "⊘" : "◉", allBpEnabled ? "禁用所有断点" : "启用所有断点", () => dapStore.setAllBpEnabled(scope, cwd, !allBpEnabled)) : null,
|
|
832
|
+
bpRows.length ? segAct("bp-clear", "🗑", "删除所有断点", removeAllBps) : null
|
|
833
|
+
], bpSectionChildren);
|
|
834
|
+
const outputRows = dapStore.outputs().slice(-60).map((ev, idx) => react.default.createElement("div", {
|
|
835
|
+
key: ev.seq + ":" + idx,
|
|
836
|
+
className: "edrv-daprepl-line out"
|
|
837
|
+
}, ev.text?.trimEnd() || ""));
|
|
838
|
+
const configReady = snap.configs.some((c) => snap.adapters.length === 0 || snap.adapters.find((a) => a.type === c.type)?.available === true);
|
|
839
|
+
const toolbarBtn = (key, label, title, disabled, onClick) => react.default.createElement("button", {
|
|
840
|
+
key,
|
|
841
|
+
className: "edrv-dappanel-run",
|
|
842
|
+
title,
|
|
843
|
+
disabled: disabled === true,
|
|
844
|
+
onClick
|
|
845
|
+
}, label);
|
|
846
|
+
const bpMenuEl = bpMenu ? react.default.createElement(ContextMenu, {
|
|
847
|
+
x: bpMenu.x,
|
|
848
|
+
y: bpMenu.y,
|
|
849
|
+
onClose: () => setBpMenu(null),
|
|
850
|
+
entries: buildBpRowMenu({ enabled: bpMenu.row.entry.enabled }).map((item) => ({
|
|
851
|
+
id: item.id,
|
|
852
|
+
label: item.label,
|
|
853
|
+
separator: item.separator,
|
|
854
|
+
onClick: () => runBpRowAction(item.id, bpMenu.row)
|
|
855
|
+
}))
|
|
856
|
+
}) : null;
|
|
857
|
+
const newBpMenuEl = newBpMenu ? react.default.createElement(ContextMenu, {
|
|
858
|
+
x: newBpMenu.x,
|
|
859
|
+
y: newBpMenu.y,
|
|
860
|
+
onClose: () => setNewBpMenu(null),
|
|
861
|
+
entries: [{
|
|
862
|
+
id: "add-conditional",
|
|
863
|
+
label: "添加条件断点...",
|
|
864
|
+
onClick: () => addBpAtCursor(BP_MODE.EXPRESSION)
|
|
865
|
+
}, {
|
|
866
|
+
id: "add-logpoint",
|
|
867
|
+
label: "添加记录点...",
|
|
868
|
+
onClick: () => addBpAtCursor(BP_MODE.LOG_MESSAGE)
|
|
869
|
+
}]
|
|
870
|
+
}) : null;
|
|
871
|
+
return react.default.createElement("div", { className: "edrv-dappanel" }, react.default.createElement("div", { className: "edrv-dappanel-head" }, ...snap.candidates ? [
|
|
872
|
+
react.default.createElement("select", {
|
|
873
|
+
key: "pick",
|
|
874
|
+
className: "edrv-dappanel-cfg-select",
|
|
875
|
+
title: "选择附加目标进程(processName 命中多个候选)",
|
|
876
|
+
value: String(pickPid || snap.candidates[0]?.pid || ""),
|
|
877
|
+
onChange: (e) => setPickPid(Number(e.target.value))
|
|
878
|
+
}, snap.candidates.map((it) => react.default.createElement("option", {
|
|
879
|
+
key: it.pid,
|
|
880
|
+
value: String(it.pid)
|
|
881
|
+
}, it.pid + " : " + it.name + (it.title ? " — " + it.title.slice(0, 44) : "")))),
|
|
882
|
+
toolbarBtn("pick-ok", "✓", "附加到选中进程", false, () => dapStore.confirmPick(pickPid || snap.candidates[0]?.pid || 0)),
|
|
883
|
+
toolbarBtn("pick-cancel", "✕", "取消", false, () => dapStore.cancelPick())
|
|
884
|
+
] : [react.default.createElement("select", {
|
|
885
|
+
key: "cfg",
|
|
886
|
+
className: "edrv-dappanel-cfg-select",
|
|
887
|
+
title: "调试配置(读取工作区 .dsh/launch.json;适配器由扩展清单 contributes.debuggers 提供)",
|
|
888
|
+
value: snap.selectedConfig ?? "",
|
|
889
|
+
onChange: (e) => dapStore.selectConfig(e.target.value)
|
|
890
|
+
}, snap.configs.length ? snap.configs.map((c) => {
|
|
891
|
+
const adapter = snap.adapters.find((a) => a.type === c.type);
|
|
892
|
+
const ready = snap.adapters.length === 0 || adapter?.available === true;
|
|
893
|
+
const suffix = ready ? "" : "(" + (adapter?.reason || "未发现该类型适配器") + ")";
|
|
894
|
+
return react.default.createElement("option", {
|
|
895
|
+
key: c.name,
|
|
896
|
+
value: c.name,
|
|
897
|
+
disabled: !ready
|
|
898
|
+
}, c.name + suffix);
|
|
899
|
+
}) : react.default.createElement("option", { value: "" }, "无可用调试配置"))], toolbarBtn("run", "▶", paused ? "继续(F5)" : "启动调试(F5)", !paused && (!configReady || dapActive), () => dapStore.startOrContinue()), toolbarBtn("cfg-gear", "⚙", "打开/新建调试配置(" + LAUNCH_REL + ")", !ctx?.cwd, () => {
|
|
900
|
+
openLaunchConfig();
|
|
901
|
+
}), toolbarBtn("pause", "⏸", "暂停", !dapActive, () => {
|
|
902
|
+
dapStore.action("pause");
|
|
903
|
+
}), toolbarBtn("next", "⤵", "单步跳过(F10)", !paused, () => {
|
|
904
|
+
dapStore.action("next");
|
|
905
|
+
}), toolbarBtn("stepin", "↓", "单步步入(F11)", !paused, () => {
|
|
906
|
+
dapStore.action("stepIn");
|
|
907
|
+
}), toolbarBtn("stepout", "↑", "单步步出(Shift+F11)", !paused, () => {
|
|
908
|
+
dapStore.action("stepOut");
|
|
909
|
+
}), toolbarBtn("stop", "⏹", "停止(Shift+F5)", !dapActive, () => {
|
|
910
|
+
dapStore.stop();
|
|
911
|
+
}), react.default.createElement("span", { className: "edrv-dappanel-phase" + (paused ? " paused" : "") }, PHASE_LABEL[snap.phase] || snap.phase), snap.pid ? react.default.createElement("span", { className: "edrv-dappanel-pid" }, "pid " + snap.pid) : null, react.default.createElement("span", { style: { flex: 1 } }), snap.error ? react.default.createElement("span", {
|
|
912
|
+
className: "edrv-dappanel-err",
|
|
913
|
+
title: snap.error
|
|
914
|
+
}, "⚠") : null), section("变量", "variables", "variables", [segAct("var-reload", "⟳", "刷新变量", () => setReloadTick((t) => t + 1))], varRows), sash("variables", "watch"), watchSection, sash("watch", "stack"), section("调用堆栈", "stack", "stack", [], stackRows), sash("stack", "breakpoints"), bpSection, react.default.createElement("div", { className: "edrv-daprepl" }, react.default.createElement("div", { className: "edrv-dapseg-head" }, "调试控制台"), react.default.createElement("div", { className: "edrv-daprepl-log" }, ...replLog.map((row, idx) => react.default.createElement("div", {
|
|
915
|
+
key: idx,
|
|
916
|
+
className: "edrv-daprepl-line"
|
|
917
|
+
}, react.default.createElement("span", { className: "edrv-dapvar-name" }, row.expr), react.default.createElement("span", { className: "edrv-dapvar-val" }, "= " + row.result))), ...outputRows), react.default.createElement("input", {
|
|
918
|
+
className: "edrv-daprepl-in",
|
|
919
|
+
placeholder: paused ? "输入表达式求值,回车执行(如 self.itemId)" : "暂停时才可求值",
|
|
920
|
+
disabled: !paused,
|
|
921
|
+
value: replDraft,
|
|
922
|
+
onChange: (e) => setReplDraft(e.target.value),
|
|
923
|
+
onKeyDown: (e) => {
|
|
924
|
+
if (e.key === "Enter") runRepl();
|
|
925
|
+
}
|
|
926
|
+
})), bpMenuEl, newBpMenuEl);
|
|
927
|
+
}
|
|
928
|
+
//#endregion
|
|
929
|
+
exports.DebugPanel = DebugPanel;
|
|
930
|
+
return module.exports;
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
//# sourceMappingURL=client.debug.js.map
|