dsh-vscode-mode 0.5.3 → 0.7.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.
Files changed (77) hide show
  1. package/README.md +72 -2
  2. package/lib/client.js +12082 -6420
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +2750 -147
  5. package/lib/index.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/client/copyText.ts +28 -0
  8. package/src/client/dap/BpWidget.ts +262 -0
  9. package/src/client/dap/bpMenu.ts +91 -0
  10. package/src/client/dap/bpRowMenu.ts +53 -0
  11. package/src/client/dap/breakpoints.ts +222 -0
  12. package/src/client/dap/decorate.ts +128 -0
  13. package/src/client/dap/hintLine.ts +29 -0
  14. package/src/client/dap/hover.ts +166 -0
  15. package/src/client/dap/hoverMode.ts +159 -0
  16. package/src/client/dap/hoverTree.ts +722 -0
  17. package/src/client/dap/launchInsert.ts +229 -0
  18. package/src/client/dap/launchSnippetProvider.ts +206 -0
  19. package/src/client/dap/modelPath.ts +23 -0
  20. package/src/client/dap/panelSplit.ts +105 -0
  21. package/src/client/dap/pidPick.ts +24 -0
  22. package/src/client/dap/store.ts +571 -0
  23. package/src/client/dap/toolbarDrag.ts +51 -0
  24. package/src/client/dap/trace.ts +22 -0
  25. package/src/client/dap/variableTree.ts +62 -0
  26. package/src/client/fileClipboard.ts +46 -0
  27. package/src/client/index.ts +12 -3
  28. package/src/client/monaco/lsp/index.ts +15 -0
  29. package/src/client/monaco/lsp/providers.ts +2 -0
  30. package/src/client/monaco/theme.ts +15 -0
  31. package/src/client/openWithDialog.ts +66 -0
  32. package/src/client/searchSeed.ts +36 -0
  33. package/src/client/sidebar/contextMenu.ts +51 -10
  34. package/src/client/sidebar/menuItems.ts +473 -50
  35. package/src/client/sidebar/panels/DebugPanel.ts +613 -0
  36. package/src/client/sidebar/panels/SearchPanel.ts +24 -3
  37. package/src/client/sidebar/panels/SvnPanel.ts +167 -23
  38. package/src/client/sidebar/panels/index.ts +19 -0
  39. package/src/client/sidebar/types.ts +8 -0
  40. package/src/client/styles/editor.css +176 -0
  41. package/src/client/svnActions.ts +18 -1
  42. package/src/client/svnLog.ts +3 -3
  43. package/src/client/svnStatus.ts +140 -2
  44. package/src/client/tabActions.ts +22 -10
  45. package/src/client/ui/EditorView.ts +625 -23
  46. package/src/client/ui/OfficialSideTab.ts +1 -0
  47. package/src/client/ui/PromptDialog.ts +63 -0
  48. package/src/client/ui/SideBySideDiff.tsx +214 -29
  49. package/src/client/ui/SideEditorTab.ts +1 -0
  50. package/src/client/ui/SvnDiffPanel.ts +21 -8
  51. package/src/client/ui/SvnLogDialog.ts +44 -2
  52. package/src/client/ui/SvnPatchDialog.ts +63 -0
  53. package/src/client/ui/SvnSumDialog.ts +77 -0
  54. package/src/client/ui/commandCatalog.ts +63 -0
  55. package/src/client/ui/svnExport.ts +71 -0
  56. package/src/dap/configSnippets.ts +269 -0
  57. package/src/dap/discovery.ts +198 -0
  58. package/src/dap/launchConfig.ts +127 -0
  59. package/src/dap/manager.ts +588 -0
  60. package/src/dap/processList.ts +76 -0
  61. package/src/dap/protocol.ts +80 -0
  62. package/src/dap/provider.ts +49 -0
  63. package/src/dap/rpc.ts +192 -0
  64. package/src/dap/sourcePath.ts +82 -0
  65. package/src/index.ts +7 -3
  66. package/src/lsp/providers.ts +3 -2
  67. package/src/reveal.ts +31 -20
  68. package/src/rpc.ts +209 -5
  69. package/src/search/ripgrep.ts +127 -11
  70. package/src/shared/dap.ts +262 -0
  71. package/src/shared/fsNames.ts +115 -0
  72. package/src/shared/keybindings.ts +9 -0
  73. package/src/shared/rpc.ts +52 -2
  74. package/src/shared/svn.ts +147 -0
  75. package/src/shared/svnActions.ts +20 -0
  76. package/src/svn.ts +320 -11
  77. package/src/workspace.ts +0 -75
@@ -22,10 +22,12 @@ import {
22
22
  SVN_STATUS_LETTER,
23
23
  SVN_STATUS_TONE,
24
24
  isSvnDiffable,
25
+ pairMissingWithUnversioned,
25
26
  svnVisibleChanges,
26
27
  } from '../../../shared/svn.js'
27
28
  import { CACHE_KEY } from '../../paths.js'
28
- import { refreshSvnChanges, svnAdd, svnRevert } from '../../svnStatus.js'
29
+ import { refreshSvnChanges, svnAdd, svnConflictArtifacts, svnFileSizes, svnRemoteStatus, svnRevert } from '../../svnStatus.js'
30
+ import { csvOf, downloadText, htmlTableOf } from '../../ui/svnExport.js'
29
31
  import type { SidebarCtx } from '../types.js'
30
32
 
31
33
  /** 未分组条目的分组键(changelist 为空)。 */
@@ -44,7 +46,7 @@ const REVERT_CONFIRM_TAIL = ' 个文件的本地改动?新增(A)文件在
44
46
  * @returns 开关(默认:显示未版本控制、隐藏忽略项)
45
47
  */
46
48
  function loadFilter(scope) {
47
- const fallback = { unversioned: true, ignored: false }
49
+ const fallback = { unversioned: true, ignored: false, name: '' }
48
50
  if (!scope) return fallback
49
51
  try {
50
52
  const raw = window.localStorage.getItem(CACHE_KEY.svn + scope)
@@ -53,6 +55,8 @@ function loadFilter(scope) {
53
55
  return {
54
56
  unversioned: parsed?.unversioned !== false,
55
57
  ignored: parsed?.ignored === true,
58
+ // W1-2 名称过滤随开关一并持久化(旧数据缺字段 = 不过滤,向后兼容)
59
+ name: typeof parsed?.name === 'string' ? parsed.name : '',
56
60
  }
57
61
  } catch (error) {
58
62
  return fallback
@@ -121,16 +125,18 @@ export function groupByChangelist(entries) {
121
125
 
122
126
  /**
123
127
  * 单条变更行(状态字母 + 路径 + 行内动作;双击 = 与基线比较)。
124
- * @author ddj 2026年09月16号
128
+ * @author ddj 2026年09月16号 / 2026年09月20号
125
129
  * @param props.entry 变更条目
126
130
  * @param props.busy 批量动作进行中(按钮置灰)
127
131
  * @param props.onDiff 打开基线差异
128
132
  * @param props.onAdd 加入版本控制
129
133
  * @param props.onRevert 还原
134
+ * @param props.onConflict 冲突副本对比(W2-4;仅冲突行出现按钮)
135
+ * @param props.pairNote 疑似改名标记文案(W2-5;空 = 无配对)
130
136
  * @returns 行元素
131
137
  */
132
138
  function SvnRow(props) {
133
- const { entry, busy, onDiff, onAdd, onRevert } = props
139
+ const { entry, busy, onDiff, onAdd, onRevert, onConflict, pairNote } = props
134
140
  const diffable = isSvnDiffable(entry.path, entry.status)
135
141
  const act = (label, title, danger, run) => React.createElement('button', {
136
142
  className: 'edrv-svn-act' + (danger ? ' edrv-svn-act-danger' : ''),
@@ -145,7 +151,9 @@ function SvnRow(props) {
145
151
  },
146
152
  statusLetterEl(entry),
147
153
  React.createElement('span', { className: 'edrv-svn-path' }, entry.path),
154
+ (pairNote ? React.createElement('span', { className: 'edrv-svn-pair-mark', title: pairNote }, '⇄') : null),
148
155
  React.createElement('span', { className: 'edrv-svn-acts' },
156
+ (entry.status === 'conflicted' ? act('冲突对比', '与 .mine/.rN 冲突副本并排对比(解决指引见差异视图顶部)', false, () => onConflict(entry.path)) : null),
149
157
  (diffable ? act('比较', SVN_DIFF_BASE_LABEL + '(BASE 与工作区并排)', false, () => onDiff(entry.path)) : null),
150
158
  (!entry.versioned ? act('加入', SVN_ADD_LABEL, false, () => onAdd([entry.path])) : null),
151
159
  (entry.versioned ? act('还原', SVN_REVERT_LABEL + '(放弃本地改动)', true, () => onRevert([entry.path])) : null)))
@@ -161,7 +169,11 @@ function SvnRow(props) {
161
169
  function svnListBody(state, handlers) {
162
170
  if (!state.managed) return React.createElement('div', { className: 'edrv-tree-loading' }, '当前工作区不受 SVN 管理')
163
171
  if (state.entries === null) return React.createElement('div', { className: 'edrv-tree-loading' }, '读取变更中…')
164
- if (!state.visible.length) return React.createElement('div', { className: 'edrv-tree-loading' }, '无变更(工作副本干净)')
172
+ if (!state.visible.length) {
173
+ // W1-2:名称过滤生效时区分「无匹配」与「无变更」,提示清空过滤框
174
+ return React.createElement('div', { className: 'edrv-tree-loading' },
175
+ state.nameFiltered ? '无匹配条目(名称过滤生效中,清空过滤框可看全部)' : '无变更(工作副本干净)')
176
+ }
165
177
  const rows = []
166
178
  for (const group of state.groups) {
167
179
  if (state.groups.length > 1 || group.name !== NO_CHANGELIST) {
@@ -170,47 +182,64 @@ function svnListBody(state, handlers) {
170
182
  React.createElement('span', { className: 'edrv-svn-group-n' }, String(group.entries.length))))
171
183
  }
172
184
  for (const entry of group.entries) {
173
- rows.push(React.createElement(SvnRow, Object.assign({ key: entry.path, entry }, handlers)))
185
+ rows.push(React.createElement(SvnRow, Object.assign({ key: entry.path, entry, pairNote: handlers.pairNotes?.[entry.path] }, handlers)))
174
186
  }
175
187
  }
176
188
  return rows
177
189
  }
178
190
 
179
191
  /**
180
- * 工具条(显示开关 + 批量动作)。
181
- * @author ddj 2026年09月16号
182
- * @param props.filter 当前开关
183
- * @param props.count 可见条目数
184
- * @param props.paths 批量动作目标(add/revert 各自清单)
185
- * @param props.handlers 动作回调(toggle/add/revert/busy)
192
+ * 工具条(显示开关 + 名称过滤 + 批量动作 + 导出)。
193
+ * @author ddj 2026年09月16号 / 2026年09月20号
194
+ * @param props.filter 当前开关(含 W1-2 name)
195
+ * @param props.count 可见条目数(名称过滤后)
196
+ * @param props.countAll 全量条数(未按名称过滤,供「x/y 项」展示)
197
+ * @param props.paths 批量动作目标(add/revert 各自清单;按未过滤集合计算)
198
+ * @param props.handlers 动作回调(toggle/add/revert/onNameFilter/onExportCsv/onExportHtml/busy)
186
199
  * @returns 工具条元素数组
187
200
  */
188
201
  function svnToolbarEl(props) {
189
- const { filter, count, paths, handlers } = props
202
+ const { filter, count, countAll, paths, handlers } = props
190
203
  const check = (label, title, checked, onChange) => React.createElement('label', {
191
204
  className: 'edrv-svn-check', title,
192
205
  },
193
206
  React.createElement('input', { type: 'checkbox', checked, onChange }),
194
207
  React.createElement('span', null, label))
208
+ const nameFiltered = String(filter.name || '').trim() !== ''
195
209
  return [
196
210
  React.createElement('div', { key: 'tools', className: 'edrv-svn-toolbar' },
197
211
  check('未版本控制', '显示未纳入版本控制的文件', filter.unversioned, handlers.toggleUnversioned),
198
- check('忽略项', '显示被忽略的文件', filter.ignored, handlers.toggleIgnored)),
212
+ check('忽略项', '显示被忽略的文件', filter.ignored, handlers.toggleIgnored),
213
+ React.createElement('input', {
214
+ className: 'edrv-svn-name-filter',
215
+ placeholder: '名称过滤:* ? 通配或子串',
216
+ title: '按路径过滤条目(* 任意串、? 单字符;无通配符按子串包含,忽略大小写;只影响列表展示,不缩小批量动作范围)',
217
+ value: filter.name || '',
218
+ onChange: (event) => handlers.onNameFilter(event.target.value),
219
+ })),
199
220
  (count
200
221
  ? React.createElement('div', { key: 'bulk', className: 'edrv-svn-bulk' },
201
- React.createElement('span', { className: 'edrv-svn-count' }, String(count) + ' 项'),
222
+ React.createElement('span', { className: 'edrv-svn-count' }, String(count) + (nameFiltered ? '/' + countAll : '') + ' 项'),
202
223
  React.createElement('span', { style: { flex: 1 } }),
224
+ React.createElement('button', {
225
+ className: 'edrv-svn-act', title: '导出当前列表为 CSV(带 BOM,Excel 直开;状态/路径/changelist/修订)',
226
+ onClick: handlers.onExportCsv,
227
+ }, '导出CSV'),
228
+ React.createElement('button', {
229
+ className: 'edrv-svn-act', title: '导出当前列表为 HTML 报表(仅本地查看,不外发)',
230
+ onClick: handlers.onExportHtml,
231
+ }, '导出HTML'),
203
232
  (paths.unversioned.length
204
233
  ? React.createElement('button', {
205
234
  className: 'edrv-svn-act', disabled: handlers.busy,
206
- title: '把全部未版本控制文件加入版本控制',
235
+ title: '把全部未版本控制文件加入版本控制(不受名称过滤影响)',
207
236
  onClick: () => handlers.add(paths.unversioned),
208
237
  }, '全部加入')
209
238
  : null),
210
239
  (paths.revert.length
211
240
  ? React.createElement('button', {
212
241
  className: 'edrv-svn-act edrv-svn-act-danger', disabled: handlers.busy,
213
- title: '还原全部已受版本控制文件的本地改动',
242
+ title: '还原全部已受版本控制文件的本地改动(不受名称过滤影响)',
214
243
  onClick: () => handlers.revert(paths.revert),
215
244
  }, '全部还原')
216
245
  : null))
@@ -233,10 +262,17 @@ export function SvnPanel(props) {
233
262
  const managed = Boolean(ctx?.svn?.managed && ctx?.svn?.svnCli)
234
263
  const [filter, setFilter] = React.useState(() => loadFilter(scope))
235
264
  const [busy, setBusy] = React.useState(false)
265
+ // W2-3:远端检查结果(null = 未检查;{ failed: true } = 失败降级;否则 { outdated, againstRev })
266
+ const [remote, setRemote] = React.useState(null)
267
+ const [remoteBusy, setRemoteBusy] = React.useState(false)
268
+ // W2-5:配对候选的文件大小(path → size|null)与成对结果
269
+ const [sizes, setSizes] = React.useState({})
236
270
 
237
- // 作用域切换:重读该作用域自己的开关(同一工作区共享)
271
+ // 作用域切换:重读该作用域自己的开关(同一工作区共享)并清掉远端/配对的会话内状态
238
272
  React.useEffect(() => {
239
273
  setFilter(loadFilter(scope))
274
+ setRemote(null)
275
+ setSizes({})
240
276
  }, [scope])
241
277
 
242
278
  const applyFilter = (next) => {
@@ -264,40 +300,148 @@ export function SvnPanel(props) {
264
300
  }).finally(() => setBusy(false))
265
301
  }
266
302
 
267
- const visible = entries === null ? [] : svnVisibleChanges(entries, filter)
303
+ /**
304
+ * 远端更新检查(W2-3;host 侧 15s 短超时,离线/超时降级为提示条文案,不阻塞面板)。
305
+ * @author ddj 2026年09月20号
306
+ */
307
+ const checkRemote = () => {
308
+ if (remoteBusy) return
309
+ setRemoteBusy(true)
310
+ void svnRemoteStatus(sessionId, '').then((outcome) => {
311
+ if (!outcome.ok) {
312
+ setRemote({ failed: true })
313
+ ctx?.notify?.(outcome.message)
314
+ return
315
+ }
316
+ setRemote({ outdated: outcome.outdated ?? [], againstRev: outcome.againstRev ?? null })
317
+ }).finally(() => setRemoteBusy(false))
318
+ }
319
+
320
+ /**
321
+ * 冲突副本对比入口(W2-4,只读):扫描 .mine/.working/.rN → 默认首个 .rN ↔ .mine 并排;
322
+ * resolve 指引在差异视图顶部展示,本入口绝不自动执行 svn resolve(归 P2 破坏性批次)。
323
+ * @author ddj 2026年09月20号
324
+ * @param path 冲突文件的工作区相对路径
325
+ */
326
+ const openConflictDiff = (path) => {
327
+ void svnConflictArtifacts(sessionId, path).then((outcome) => {
328
+ if (!outcome.ok) { ctx?.notify?.(outcome.message); return }
329
+ const artifacts = outcome.artifacts ?? []
330
+ const revSide = artifacts.find((item) => item.kind === 'rev')
331
+ const mineSide = artifacts.find((item) => item.kind === 'mine')
332
+ if (!revSide || !mineSide) {
333
+ ctx?.notify?.('未发现冲突副本(.mine/.rN):' + artifacts.map((item) => item.name).join('、'))
334
+ return
335
+ }
336
+ if (!ctx?.openSvnLocalPair) { ctx?.notify?.('当前视图不支持冲突对比'); return }
337
+ ctx.openSvnLocalPair(revSide.path, mineSide.path, '.r' + (revSide.rev ?? '?'), '.mine', path)
338
+ })
339
+ }
340
+
341
+ // W1-2:展示集合(含名称过滤)与批量动作集合(不含名称过滤)分离,「全部加入/还原」不缩小范围
342
+ const shown = entries === null ? [] : svnVisibleChanges(entries, filter)
343
+ const actionable = entries === null ? [] : svnVisibleChanges(entries, { unversioned: filter.unversioned, ignored: filter.ignored })
344
+ const nameFiltered = String(filter.name || '').trim() !== ''
345
+
346
+ // W2-5:`!`(missing)与 `?`(unversioned)共存时才做大小查询(候选少,一次批量查完成对)
347
+ const missingEntries = entries === null ? [] : entries.filter((entry) => entry.status === 'missing')
348
+ const unversionedEntries = entries === null ? [] : entries.filter((entry) => entry.status === 'unversioned')
349
+ const candidates = [...missingEntries, ...unversionedEntries].map((entry) => entry.path)
350
+ const pairsKnown = missingEntries.length > 0 && unversionedEntries.length > 0
351
+ && candidates.every((path) => path in sizes)
352
+ React.useEffect(() => {
353
+ if (!pairsKnown && candidates.length) {
354
+ void svnFileSizes(sessionId, candidates).then((table) => setSizes(table))
355
+ }
356
+ }, [pairsKnown, candidates.join('\n'), sessionId]) // eslint-disable-line react-hooks/exhaustive-deps
357
+ const pairNoteOf = pairsKnown
358
+ ? (() => {
359
+ const notes = {}
360
+ for (const pair of pairMissingWithUnversioned(entries ?? [], sizes)) {
361
+ notes[pair.missingPath] = '疑似改名:原文件 → ' + pair.unversionedPath
362
+ notes[pair.unversionedPath] = '疑似改名:← 原文件 ' + pair.missingPath
363
+ }
364
+ return notes
365
+ })()
366
+ : {}
367
+
268
368
  const openSvnDiff = (p) => ctx?.openSvnDiff?.(p)
269
369
  const handlers = {
270
370
  busy,
271
371
  onDiff: openSvnDiff,
372
+ onConflict: openConflictDiff,
373
+ pairNotes: pairNoteOf,
272
374
  onAdd: (paths) => runBatch('add', paths),
273
375
  onRevert: (paths) => runBatch('revert', paths),
274
376
  }
275
- const listState = { managed, entries, visible, groups: groupByChangelist(visible) }
377
+
378
+ /**
379
+ * 导出当前展示列表(W1-4):CSV 带 BOM 供 Excel 直开;HTML 本地报表;失败经 notify 提示。
380
+ * @author ddj 2026年09月20号
381
+ * @param kind 导出格式('csv' | 'html')
382
+ */
383
+ const onExport = (kind) => {
384
+ const rows = shown.map((entry) => [
385
+ SVN_STATUS_LETTER[entry.status] || '',
386
+ entry.path,
387
+ entry.changelist || '',
388
+ entry.revision && entry.revision > 0 ? String(entry.revision) : '',
389
+ ])
390
+ const headers = ['状态', '路径', 'changelist', '修订']
391
+ const stamp = new Date().toISOString().slice(0, 10)
392
+ const done = kind === 'html'
393
+ ? downloadText('svn-changes-' + stamp + '.html', htmlTableOf(headers, rows, 'SVN 变更列表 ' + stamp), 'text/html')
394
+ : downloadText('svn-changes-' + stamp + '.csv', csvOf([headers, ...rows]), 'text/csv')
395
+ if (!done) ctx?.notify?.('导出失败:浏览器下载能力不可用')
396
+ }
397
+
398
+ const listState = { managed, entries, visible: shown, groups: groupByChangelist(shown), nameFiltered }
276
399
  const toolbar = svnToolbarEl({
277
400
  filter,
278
- count: visible.length,
401
+ count: shown.length,
402
+ countAll: actionable.length,
279
403
  paths: {
280
- unversioned: visible.filter((entry) => !entry.versioned).map((entry) => entry.path),
281
- revert: visible.filter((entry) => entry.versioned).map((entry) => entry.path),
404
+ unversioned: actionable.filter((entry) => !entry.versioned).map((entry) => entry.path),
405
+ revert: actionable.filter((entry) => entry.versioned).map((entry) => entry.path),
282
406
  },
283
407
  handlers: Object.assign({}, handlers, {
284
408
  toggleUnversioned: () => applyFilter({ ...filter, unversioned: !filter.unversioned }),
285
409
  toggleIgnored: () => applyFilter({ ...filter, ignored: !filter.ignored }),
410
+ onNameFilter: (name) => applyFilter({ ...filter, name }),
411
+ onExportCsv: () => onExport('csv'),
412
+ onExportHtml: () => onExport('html'),
286
413
  add: (paths) => runBatch('add', paths),
287
414
  revert: (paths) => runBatch('revert', paths),
288
415
  }),
289
416
  })
290
417
  const rootName = ctx?.svn?.wcRoot ? String(ctx.svn.wcRoot).split(/[\\/]/).pop() || ctx.svn.wcRoot : ''
291
418
 
419
+ // W2-3:远端更新提示条(null = 未检查;failed = 降级文案;否则展示落后数与 against 修订)
420
+ const remoteEl = remote === null
421
+ ? null
422
+ : React.createElement('div', { className: 'edrv-svn-hintbar' + (remote.failed ? ' edrv-svn-hintbar-warn' : '') },
423
+ React.createElement('span', { style: { flex: 1 } },
424
+ remote.failed
425
+ ? '远端检查失败(网络不可达或超时;可用 ⟳ 或「检查远端」重试)'
426
+ : (remote.outdated.length
427
+ ? '远端有 ' + remote.outdated.length + ' 个更新(against r' + (remote.againstRev ?? '?') + '),其中本地已改 ' + remote.outdated.filter((item) => item.path && entries?.some((entry) => entry.path === item.path && entry.versioned)).length + ' 个;更新前请先提交或还原'
428
+ : '远端无更新(against r' + (remote.againstRev ?? '?') + ')')),
429
+ React.createElement('button', { className: 'edrv-svn-act', title: '关闭提示条', onClick: () => setRemote(null) }, '✕'))
430
+
292
431
  return React.createElement('div', { className: 'edrv-side-panel' },
293
432
  React.createElement('div', { className: 'edrv-side-head' },
294
433
  React.createElement('span', { className: 'edrv-side-title' }, 'SVN 变更'),
295
434
  React.createElement('span', { className: 'edrv-side-root', title: ctx?.svn?.wcRoot || '' }, rootName),
296
435
  React.createElement('span', { style: { flex: 1 } }),
436
+ React.createElement('button', {
437
+ className: 'edrv-side-btn', title: '检查远端更新(svn status -u;约数秒,离线自动降级)',
438
+ onClick: checkRemote,
439
+ }, remoteBusy ? '…' : '⇅'),
297
440
  React.createElement('button', {
298
441
  className: 'edrv-side-btn', title: '刷新变更',
299
442
  onClick: () => { refreshSvnChanges(sessionId, scope); ctx?.refreshRecords?.() },
300
443
  }, refreshIconEl())),
444
+ remoteEl,
301
445
  ...toolbar,
302
446
  React.createElement('div', { className: 'edrv-svn-list' }, svnListBody(listState, handlers)))
303
447
  }
@@ -7,10 +7,12 @@
7
7
  */
8
8
  import React from 'react'
9
9
  import { IconBranchOutline16, IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
10
+ import { dapStore } from '../../dap/store.js'
10
11
  import { FileExplorer } from './FileExplorer.js'
11
12
  import { SearchPanel } from './SearchPanel.js'
12
13
  import { RulesPanel } from './RulesPanel.js'
13
14
  import { SvnPanel } from './SvnPanel.js'
15
+ import { DebugPanel } from './DebugPanel.js'
14
16
  import type { SidebarPanelDef, SidebarCtx } from '../types.js'
15
17
 
16
18
  /**
@@ -97,3 +99,20 @@ function svnVisibleCount(entries) {
97
99
  }
98
100
  return count
99
101
  }
102
+
103
+ /**
104
+ * 构造「调试」面板定义(VS Code 调试视图四段布局 + REPL)。
105
+ * 图标用文本回落(官方原语图标集未含调试类图标时由 SidebarView 文本渲染)。
106
+ * @author ddj 2026年09月29号
107
+ * @returns 面板定义(徽标 = 已暂停时显示 1,吸引注意)
108
+ */
109
+ export function createDebugPanel(): SidebarPanelDef {
110
+ return {
111
+ id: 'debug',
112
+ title: '调试',
113
+ icon: '🐞',
114
+ order: 22,
115
+ badge: () => (dapStore.getSnapshot().phase === 'paused' ? 1 : null),
116
+ render: (ctx: SidebarCtx) => React.createElement(DebugPanel, { ctx }),
117
+ }
118
+ }
@@ -41,6 +41,8 @@ export interface SidebarCtx {
41
41
  svnChangeMap?: Record<string, SvnChangeEntry>
42
42
  /** 打开 SVN 基线差异视图(变更面板行内「与基线比较」;缺省时面板降级提示)。 */
43
43
  openSvnDiff?: (path: string) => void
44
+ /** 打开双侧本地文件差异(W2-4 冲突副本 `.rN` ↔ `.mine`;缺省时面板降级提示)。 */
45
+ openSvnLocalPair?: (left: string, right: string, leftLabel: string, rightLabel: string, title: string) => void
44
46
  /** 重查 SVN 变更清单(CLI 更新/还原/加入版本控制后由菜单动作调用)。 */
45
47
  refreshSvnChanges?: () => void
46
48
  /** 打开 SVN 日志弹窗(P3;菜单/命令栏「查看日志」)。 */
@@ -49,6 +51,12 @@ export interface SidebarCtx {
49
51
  confirm?: (message: string) => boolean
50
52
  /** 面板动作反馈(如右键菜单操作结果 → 编辑区路径栏状态)。 */
51
53
  notify?: (message: string) => void
54
+ /** 名称输入弹窗(资源管理器右键的新建/重命名;取消/空输入返回 null,缺省时相关项降级提示)。 */
55
+ prompt?: (title: string, initial: string) => Promise<string | null>
56
+ /** 「打开方式…」:在已注册打开器间选择并打开(缺省时该项隐藏)。 */
57
+ openWith?: (path: string) => void
58
+ /** 「在文件夹中查找…」:限定目录并跳到搜索面板(缺省时该项隐藏)。 */
59
+ searchInFolder?: (dir: string) => void
52
60
  }
53
61
 
54
62
  /** 单个侧边栏面板定义。 */
@@ -330,6 +330,44 @@
330
330
  [data-edrv-view] .edrv-svndiff-tag { padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }
331
331
  [data-edrv-view] .edrv-svndiff-host { flex: 1 1 auto; min-height: 0; }
332
332
  [data-edrv-view] .edrv-svndiff-empty { display: flex; flex: 1 1 auto; align-items: center; justify-content: center; padding: 24px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 12px; text-align: center; }
333
+ /* 差异整行色块与行首 +/- 指示符:vendored Monaco(diffEditorWidget2)静态 CSS 无
334
+ .line-insert/.line-delete 背景规则,整行色块靠主题键 + 此处固定色兜底保证可见。
335
+ 注意不要用 --dsw-alias-interactive-bg-hover 类 token:实测为 92% 透明中性色,肉眼不可见。
336
+ 固定色取自研差异 UI 同色相(绿 #0f9d58 / 红 #d9534f),alpha 提到肉眼可辨。 */
337
+ [data-edrv-view] .monaco-editor .line-insert { background: rgba(15, 157, 88, 0.18); }
338
+ [data-edrv-view] .monaco-editor .line-delete { background: rgba(217, 83, 79, 0.16); }
339
+ [data-edrv-view] .monaco-editor .gutter-insert { background: rgba(15, 157, 88, 0.35); }
340
+ [data-edrv-view] .monaco-editor .gutter-delete { background: rgba(217, 83, 79, 0.35); }
341
+ [data-edrv-view] .monaco-editor .insert-sign { color: var(--dsw-alias-state-success-primary, #0f9d58); }
342
+ [data-edrv-view] .monaco-editor .delete-sign { color: var(--dsw-alias-state-error-primary, #d9534f); }
343
+ /* 差异条快速跳转簇(对齐自研审查条 ↑ i/N ↓ 的形态) */
344
+ [data-edrv-view] .edrv-svndiff-nav { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
345
+ [data-edrv-view] .edrv-svndiff-nav-count { min-width: 36px; text-align: center; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }
346
+ /* W1-3 差异统计徽标(+新增/修改 −删除/修改;色相与行块绿红一致) @author ddj 2026-09-20 */
347
+ [data-edrv-view] .edrv-svndiff-stat { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-variant-numeric: tabular-nums; }
348
+ [data-edrv-view] .edrv-svndiff-stat-add { color: var(--dsw-alias-state-success-primary, #0f9d58); }
349
+ [data-edrv-view] .edrv-svndiff-stat-del { color: var(--dsw-alias-state-error-primary, #d9534f); }
350
+ /* W1-1 差异视图选项条(右端开关组;复用 .edrv-svn-check 的 label 形态) */
351
+ [data-edrv-view] .edrv-svndiff-opts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
352
+ /* W1-2 变更面板名称过滤输入框(工具条行内,紧凑) @author ddj 2026-09-20 */
353
+ [data-edrv-view] .edrv-svn-name-filter { width: 168px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 14px; }
354
+ [data-edrv-view] .edrv-svn-name-filter:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #0f9d58); }
355
+ [data-edrv-view] .edrv-svn-name-filter::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
356
+ /* W2-3 远端更新提示条(工具条上方;warn = 失败降级) @author ddj 2026-09-20 */
357
+ [data-edrv-view] .edrv-svn-hintbar { display: flex; align-items: center; gap: 6px; margin: 0 10px 4px 12px; padding: 3px 8px; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; flex-shrink: 0; }
358
+ [data-edrv-view] .edrv-svn-hintbar-warn { background: rgba(217, 83, 79, 0.12); color: var(--dsw-alias-state-error-primary, #d9534f); }
359
+ /* W2-5 疑似改名标记(行内 ⇄;hover 见 tooltip) */
360
+ [data-edrv-view] .edrv-svn-pair-mark { flex-shrink: 0; color: var(--dsw-alias-state-warn-primary, #7c3aed); font-size: 11px; cursor: help; }
361
+ /* W2-1 补丁对话框 / W2-2 目录对比对话框 @author ddj 2026-09-20 */
362
+ [data-edrv-view] .edrv-svn-patch-dialog { max-width: calc(100vw - 48px); }
363
+ [data-edrv-view] .edrv-svn-patch-head { display: flex; align-items: center; gap: 8px; padding-bottom: 8px; }
364
+ [data-edrv-view] .edrv-svn-patch-notes { display: flex; flex-direction: column; gap: 4px; padding-bottom: 6px; }
365
+ [data-edrv-view] .edrv-svn-patch-warn { color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }
366
+ [data-edrv-view] .edrv-svn-patch-pre { max-height: 46vh; overflow: auto; margin: 0 0 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.5; white-space: pre; user-select: text; }
367
+ [data-edrv-view] .edrv-svn-patch-foot { display: flex; align-items: center; gap: 8px; }
368
+ [data-edrv-view] .edrv-svn-sum-form { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; }
369
+ [data-edrv-view] .edrv-svn-sum-list { max-height: 44vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 2px; margin-bottom: 8px; }
370
+ [data-edrv-view] .edrv-svnlog-path-kind { flex-shrink: 0; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; }
333
371
 
334
372
  /* ===== 通用弹窗外壳(ModalShell)+ SVN 日志弹窗 @author ddj 2026-09-16 ===== */
335
373
  /* z-index 190:落在既有阶梯内(命令栏 200 > 片段二级 197 > 片段一级 195 > 190),
@@ -623,3 +661,141 @@
623
661
 
624
662
 
625
663
 
664
+
665
+ /* 调试(DAP):断点圆点 / 停帧行 / 浮动调试工具条。
666
+ 圆点挂在 Monaco glyphMargin(glyphMarginClassName),行背景为 isWholeLine className;
667
+ 工具条绝对定位于编辑区右上(editorArea relative 由 .edrv-editor-area 自身定位)。 */
668
+ /* 断点圆点:装饰类占满整行,圆点经 ::before 在本行盒子内居中(行归属明确、不跨行界) */
669
+ [data-edrv-view] .edrv-dap-bp { cursor: pointer; }
670
+ [data-edrv-view] .edrv-dap-bp::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }
671
+ [data-edrv-view] .edrv-dap-bp-off { cursor: pointer; }
672
+ [data-edrv-view] .edrv-dap-bp-off::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }
673
+ /* 未验证断点(适配器真实回执 verified:false):空心红圈,对齐 VS Code 空心圆语义 */
674
+ [data-edrv-view] .edrv-dap-bp-unverified { cursor: pointer; }
675
+ [data-edrv-view] .edrv-dap-bp-unverified::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }
676
+ [data-edrv-view] .edrv-dap-paused-line { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .22)); }
677
+ [data-edrv-view] .edrv-dap-paused-glyph { cursor: pointer; }
678
+ [data-edrv-view] .edrv-dap-paused-glyph::before { content: ''; display: block; width: 5px; height: 12px; margin: 4px auto 0; border-radius: 2px; background: var(--dsw-alias-state-warn-primary, #e5b93f); }
679
+ [data-edrv-view] .edrv-editor-area { position: relative; }
680
+ /* launch.json 右下角「添加配置」按钮(VSCode 同款形态):仅 launch.json 打开时渲染 */
681
+ [data-edrv-view] .edrv-addcfg { position: absolute; right: 14px; bottom: 10px; z-index: 13; padding: 4px 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 18px; cursor: pointer; box-shadow: 0 1px 4px rgba(0, 0, 0, .1); }
682
+ [data-edrv-view] .edrv-addcfg:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, #1f2933); }
683
+ [data-edrv-view] .edrv-dapbar { position: absolute; top: 6px; right: 14px; z-index: 12; display: none; align-items: center; gap: 3px; padding: 3px 5px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #f7f9fa); box-shadow: 0 2px 8px rgba(0, 0, 0, .12); }
684
+ [data-edrv-view] .edrv-dapbar.on { display: flex; border-color: var(--dsw-alias-border-l2, #cdd6da); }
685
+ [data-edrv-view] .edrv-dapbar-grip { flex: 0 0 auto; width: 16px; height: 22px; display: inline-flex; align-items: center; justify-content: center; cursor: grab; user-select: none; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 12px; }
686
+ [data-edrv-view] .edrv-dapbar-grip:active, [data-edrv-view] .edrv-dapbar.dragging .edrv-dapbar-grip { cursor: grabbing; }
687
+ [data-edrv-view] .edrv-dapbar.dragging { user-select: none; }
688
+ [data-edrv-view] .edrv-dapbar-cfg { max-width: 220px; height: 22px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }
689
+ [data-edrv-view] .edrv-dapbar-btn { min-width: 24px; height: 22px; padding: 0 6px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 12px; line-height: 1; cursor: pointer; }
690
+ [data-edrv-view] .edrv-dapbar-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }
691
+ [data-edrv-view] .edrv-dapbar-btn:disabled { opacity: .35; cursor: default; }
692
+ [data-edrv-view] .edrv-dapbar-phase { padding: 0 4px; font-size: 11px; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }
693
+ [data-edrv-view] .edrv-dapbar-phase.paused { color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }
694
+ [data-edrv-view] .edrv-dapbar-err { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--dsw-alias-state-error-primary, #e05252); }
695
+
696
+ /* 调试面板(活动栏「调试」页签):四段折叠区 + 断点列表 + REPL。 */
697
+ [data-edrv-view] .edrv-dappanel { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; overflow: auto; padding: 2px 2px 8px; }
698
+ [data-edrv-view] .edrv-dappanel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; row-gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); }
699
+ [data-edrv-view] .edrv-dappanel-phase { padding: 2px 8px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }
700
+ [data-edrv-view] .edrv-dappanel-phase.paused { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .18)); color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }
701
+ [data-edrv-view] .edrv-dappanel-cfg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
702
+ [data-edrv-view] .edrv-dappanel-pid { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }
703
+ [data-edrv-view] .edrv-dappanel-err { color: var(--dsw-alias-state-error-primary, #e05252); }
704
+ [data-edrv-view] .edrv-dapseg { display: flex; flex-direction: column; gap: 2px; min-height: 0; }
705
+ [data-edrv-view] .edrv-dapseg-body { min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }
706
+ /* 段间分隔条(splitview sash):拖动调整相邻两段高度占比;各段高度由 DebugPanel 内联接管。 */
707
+ [data-edrv-view] .edrv-dapseg-sash { flex: 0 0 auto; height: 4px; margin: 2px 0; border-radius: 2px; background: transparent; cursor: row-resize; }
708
+ [data-edrv-view] .edrv-dapseg-sash:hover, [data-edrv-view] .edrv-dapseg-sash.dragging { background: var(--dsw-alias-border-l2, #cdd6da); }
709
+ /* 折叠段头:chevron + 标题 + 悬浮动作区(动作 hover 显隐;REPL 头不参与折叠)。 */
710
+ [data-edrv-view] .edrv-dapseg-head { display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #7b8794); padding: 2px 2px; cursor: pointer; border-radius: 4px; }
711
+ [data-edrv-view] .edrv-dapseg-head:hover { color: var(--dsw-alias-label-secondary, #52606d); }
712
+ [data-edrv-view] .edrv-daprepl .edrv-dapseg-head { cursor: default; padding: 2px 0; }
713
+ [data-edrv-view] .edrv-dapseg-chevron { flex: 0 0 auto; width: 14px; text-align: center; font-size: 10px; }
714
+ [data-edrv-view] .edrv-dapseg-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
715
+ [data-edrv-view] .edrv-dapseg-act { flex: 0 0 auto; min-width: 20px; height: 18px; padding: 0 4px; border: none; border-radius: 4px; background: transparent; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 11px; line-height: 1; cursor: pointer; opacity: 0; }
716
+ [data-edrv-view] .edrv-dapseg-head:hover .edrv-dapseg-act { opacity: 1; }
717
+ [data-edrv-view] .edrv-dapseg-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, inherit); }
718
+ [data-edrv-view] .edrv-dapempty { font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 4px 2px; }
719
+ [data-edrv-view] .edrv-dapframe { display: flex; align-items: baseline; gap: 8px; padding: 3px 4px; border-radius: 6px; cursor: pointer; font-size: 12px; }
720
+ [data-edrv-view] .edrv-dapframe:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }
721
+ [data-edrv-view] .edrv-dapframe-name { color: var(--dsw-alias-label-primary, inherit); font-weight: 600; }
722
+ [data-edrv-view] .edrv-dapframe-loc { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
723
+ [data-edrv-view] .edrv-dapscope { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); padding: 4px 2px 1px; }
724
+ [data-edrv-view] .edrv-dapvar { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; font-size: 12px; padding: 1px 2px; min-width: 0; }
725
+ [data-edrv-view] .edrv-dapvar-tw { flex: 0 0 auto; width: 14px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 10px; padding: 0; }
726
+ [data-edrv-view] span.edrv-dapvar-tw { cursor: default; }
727
+ [data-edrv-view] .edrv-dapvar-name { flex: 0 1 34%; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, inherit); font-family: var(--ds-font-family-code, ui-monospace, monospace); white-space: nowrap; }
728
+ [data-edrv-view] .edrv-dapvar-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }
729
+ [data-edrv-view] .edrv-dapvar-type { flex: 0 0 auto; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }
730
+ [data-edrv-view] .edrv-dapframe.selected { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .16)); outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }
731
+ [data-edrv-view] .edrv-dapwatch { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; min-width: 0; font-size: 12px; padding: 1px 2px; }
732
+ [data-edrv-view] .edrv-dapwatch-x { border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; width: 14px; }
733
+ [data-edrv-view] .edrv-dapwatch-x:hover { color: var(--dsw-alias-state-error-primary, #e05252); }
734
+ [data-edrv-view] .edrv-dapwatch-add { padding: 2px 0; }
735
+ [data-edrv-view] .edrv-dapbp { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 2px 2px; border-radius: 6px; cursor: pointer; }
736
+ [data-edrv-view] .edrv-dapbp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }
737
+ [data-edrv-view] .edrv-dapbp:active { background: var(--dsw-alias-interactive-bg-pressed, rgba(127, 127, 127, .2)); }
738
+ [data-edrv-view] .edrv-dapbp-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }
739
+ [data-edrv-view] .edrv-dapbp-dot.off { background: transparent; border: 1.5px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }
740
+ [data-edrv-view] .edrv-dapbp-file { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: none; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; padding: 0; font-size: 12px; }
741
+ [data-edrv-view] .edrv-dapbp-dir { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; text-align: right; }
742
+ [data-edrv-view] .edrv-dapbp-line { flex: 0 0 auto; font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }
743
+ [data-edrv-view] .edrv-dapbp-x { flex: 0 0 auto; border: none; background: transparent; color: transparent; cursor: pointer; width: 14px; }
744
+ [data-edrv-view] .edrv-dapbp:hover .edrv-dapbp-x { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
745
+ [data-edrv-view] .edrv-dapbp-x:hover { color: var(--dsw-alias-state-error-primary, #e05252) !important; }
746
+ [data-edrv-view] .edrv-daprepl { display: flex; flex-direction: column; gap: 4px; }
747
+ [data-edrv-view] .edrv-daprepl-log { display: flex; flex-direction: column; gap: 1px; max-height: 180px; overflow: auto; }
748
+ [data-edrv-view] .edrv-daprepl-line { font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); white-space: pre-wrap; word-break: break-all; display: flex; gap: 6px; }
749
+ [data-edrv-view] .edrv-daprepl-line.out { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
750
+ [data-edrv-view] .edrv-daprepl-in { width: 100%; box-sizing: border-box; height: 26px; padding: 0 8px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }
751
+ [data-edrv-view] .edrv-dappanel-run { flex: 0 0 auto; width: 24px; height: 24px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; line-height: 1; cursor: pointer; }
752
+ [data-edrv-view] .edrv-dappanel-run:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }
753
+ [data-edrv-view] .edrv-dappanel-run:disabled { opacity: .35; cursor: default; background: transparent; }
754
+ /* 面板顶部配置下拉(与编辑器浮动条 .edrv-dapbar-cfg 同视觉)。 */
755
+ [data-edrv-view] .edrv-dappanel-cfg-select { max-width: 190px; height: 24px; padding: 0 4px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }
756
+ /* 断点行内编辑行(面板内展开编辑,控件复用 .edrv-bpwidget-* 视觉;左缩进对齐断点行文字列)。 */
757
+ [data-edrv-view] .edrv-dapbp-edit { display: flex; align-items: center; gap: 6px; padding: 3px 2px 3px 20px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }
758
+ [data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-mode { height: 20px; }
759
+ [data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-input { height: 20px; }
760
+ /* 断点回执:未验证空心红圈(须排在 .off 之后,禁用态优先展示灰圈语义)。 */
761
+ [data-edrv-view] .edrv-dapbp-dot.unverified { background: transparent; border: 1.5px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }
762
+ /* 断点行内编辑浮层(Monaco content widget,锚定断点行上方;对齐 VS Code breakpointWidget 形态)。 */
763
+ /* View Zone 浮层:宽度/左移由 BpWidget.syncWidth 按 layout 内联设置
764
+ (Monaco .view-zones 仅内容区宽,需补偿 contentLeft 与滚动条),此处只管视觉。 */
765
+ [data-edrv-view] .edrv-bpwidget { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-top: 1px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-layer-1, #f7f9fa); }
766
+ /* 断点编辑中的目标行高亮(浮层打开期间) */
767
+ [data-edrv-view] .edrv-dap-editing-line { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }
768
+ [data-edrv-view] .edrv-bpwidget-mode { flex: 0 0 auto; height: 22px; padding: 0 4px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); cursor: pointer; }
769
+ [data-edrv-view] .edrv-bpwidget-input { flex: 1 1 auto !important; min-width: 0; width: auto !important; box-sizing: border-box; height: 22px; padding: 0 6px; font-size: 12px; font-family: var(--ds-font-family-code, ui-monospace, monospace); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }
770
+ [data-edrv-view] .edrv-bpwidget-input:focus { outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }
771
+
772
+ /* 断点 hover 预览(glyph 区):半透明小红点 + pointer 光标(对齐 VS Code .codicon-debug-hint)。 */
773
+ [data-edrv-view] .edrv-dap-bp-hint { cursor: pointer; }
774
+ [data-edrv-view] .edrv-dap-bp-hint::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); opacity: .4; }
775
+ /* 行号区也给出可点提示(与 VS Code 一致:glyph 区与行号区都可点/可 hover);
776
+ 不拦截折叠箭头等自有控件的光标。 */
777
+ [data-edrv-view] .edrv-editor-row .monaco-editor .margin-view-overlays .line-numbers:hover { cursor: pointer; }
778
+
779
+ /* 暂停态 hover 调试块 + 变量树(成员全量直显 + table 成员折叠展开)。
780
+ ⚠️ 选择器一律用 data-*:本仓 vendored Monaco 的 markdown→DOM 管线会剥掉我们 HTML 里的
781
+ class(supportHtml/isTrusted 都不豁免),只有 data-* 存活;用 class 会静默失效。 */
782
+ [data-edrv-view] [data-edrv-dap] { display: block; min-width: 0; }
783
+ [data-edrv-view] [data-edrv-head] { display: flex; align-items: baseline; gap: 2px; }
784
+ [data-edrv-view] [data-edrv-head] [data-edrv-expr] { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }
785
+ [data-edrv-view] [data-edrv-head] [data-edrv-val] { font-family: var(--ds-font-family-code, ui-monospace, monospace); word-break: break-all; }
786
+ [data-edrv-view] [data-edrv-type] { margin-top: 2px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #8a949b); }
787
+ [data-edrv-view] [data-edrv-tree] { margin: 2px 0 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }
788
+ [data-edrv-view] [data-edrv-line] { display: flex; align-items: baseline; gap: 4px; padding: 1px 2px; border-radius: 3px; white-space: nowrap; }
789
+ [data-edrv-view] [data-edrv-line]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }
790
+ /* 折叠按钮(对齐 CodeBuddy 调试浮窗变量树的 twist):热区 14px + hover 反馈,未可展开时留空占位 */
791
+ [data-edrv-view] [data-edrv-twist] { flex: 0 0 auto; box-sizing: border-box; width: 14px; height: 14px; line-height: 13px; text-align: center; border-radius: 3px; color: var(--dsw-alias-label-tertiary, #8a949b); }
792
+ [data-edrv-view] [data-edrv-twist]:not(:empty) { cursor: pointer; user-select: none; }
793
+ [data-edrv-view] [data-edrv-twist]:not(:empty):hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .2)); color: var(--dsw-alias-label-primary, #1f2933); }
794
+ [data-edrv-view] [data-edrv-name] { flex: 0 0 auto; color: var(--dsw-alias-label-primary, #1f2933); }
795
+ [data-edrv-view] [data-edrv-line] [data-edrv-eq] { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #8a949b); }
796
+ [data-edrv-view] [data-edrv-line] [data-edrv-val] { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }
797
+ [data-edrv-view] [data-edrv-note] { color: var(--dsw-alias-label-tertiary, #8a949b); font-style: italic; }
798
+ /* Alt 提示层:由 hoverTree 注入到 .monaco-hover(滚动区之外)贴底常驻;容器预留同高内边距避免遮挡末行。
799
+ 不再用 sticky / `:has()`:滚动链在 .monaco-hover-content 上,sticky 会被滚走。 */
800
+ [data-edrv-view] .monaco-hover[data-edrv-hint-host="1"] { padding-bottom: 22px; }
801
+ [data-edrv-view] [data-edrv-hint="1"] { position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box; height: 22px; padding: 2px 8px; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8a949b); background: var(--dsw-alias-bg-overlay, #ffffff); border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { SvnActionDef } from '../shared/svnActions.js'
13
13
  import { svnCleanup } from './svnLog.js'
14
- import { refreshSvnChanges, svnAdd, svnDiffBase, svnRevert, svnTortoise, svnUpdate } from './svnStatus.js'
14
+ import { refreshSvnChanges, svnAdd, svnDiffBase, svnPatchText, svnRevert, svnTortoise, svnUpdate } from './svnStatus.js'
15
15
 
16
16
  /** 动作执行上下文(各入口把自身能力投影进来)。 */
17
17
  export interface SvnActionRunCtx {
@@ -29,6 +29,10 @@ export interface SvnActionRunCtx {
29
29
  openSvnDiffRev?: (path: string, revision: number) => void
30
30
  /** 打开日志弹窗(log 动作)。 */
31
31
  openSvnLog?: (path?: string) => void
32
+ /** 打开补丁对话框(create-patch 动作;执行器完成 RPC 后把载荷交回 UI)。 */
33
+ openPatchDialog?: (payload: { path: string; text: string; truncated: boolean; binary: boolean }) => void
34
+ /** 打开目录对比对话框(diff-summarize 动作;目标目录相对路径,'' = 工作副本根)。 */
35
+ openSumDialog?: (path: string) => void
32
36
  /** 重查变更清单(写操作后)。 */
33
37
  refreshChanges?: () => void
34
38
  /** 就地打开文件(日志面板点击变更文件)。 */
@@ -92,6 +96,19 @@ export const SVN_ACTION_RUNNERS: Record<string, SvnActionRunner> = {
92
96
  }, log: (ctx) => {
93
97
  ctx.openSvnLog?.(ctx.path || undefined)
94
98
  },
99
+ // W2-1:补丁生成(只读,仅生成不落盘应用);RPC 后把载荷交给 UI 对话框
100
+ 'create-patch': (ctx) => {
101
+ if (!ctx.path) { ctx.notify?.('无活动文件'); return }
102
+ const target = ctx.path
103
+ void svnPatchText(ctx.sessionId, target).then((outcome) => {
104
+ if (!outcome.ok || typeof outcome.text !== 'string') { ctx.notify?.(outcome.message); return }
105
+ ctx.openPatchDialog?.({ path: target, text: outcome.text, truncated: outcome.truncated === true, binary: outcome.binary === true })
106
+ })
107
+ },
108
+ // W2-2:目录对比(RPC 在对话框内做,执行器只负责打开并传入默认目标目录)
109
+ 'diff-summarize': (ctx) => {
110
+ ctx.openSumDialog?.(ctx.path ?? '')
111
+ },
95
112
  cleanup: (ctx) => {
96
113
  const options = ctx.cleanupOptions ?? {}
97
114
  const destructive = options.removeUnversioned === true || options.removeIgnored === true