dsh-vscode-mode 0.5.2 → 0.6.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 (78) hide show
  1. package/README.md +78 -5
  2. package/lib/client.js +5579 -236
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +2358 -126
  5. package/lib/index.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/client/compat.ts +1 -1
  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/editorLimit.ts +31 -0
  27. package/src/client/index.ts +13 -0
  28. package/src/client/markdownPreview.ts +24 -0
  29. package/src/client/md/componentType.ts +28 -0
  30. package/src/client/md/mdPanel.ts +98 -0
  31. package/src/client/monaco/lsp/index.ts +15 -0
  32. package/src/client/monaco/lsp/providers.ts +2 -0
  33. package/src/client/monaco/theme.ts +15 -0
  34. package/src/client/searchSeed.ts +64 -0
  35. package/src/client/sidebar/panels/DebugPanel.ts +613 -0
  36. package/src/client/sidebar/panels/SearchPanel.ts +70 -15
  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 +2 -0
  40. package/src/client/styles/editor.css +186 -1
  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 +54 -0
  45. package/src/client/ui/EditorView.ts +711 -29
  46. package/src/client/ui/McpSettings.ts +29 -0
  47. package/src/client/ui/SideBySideDiff.tsx +214 -29
  48. package/src/client/ui/SvnDiffPanel.ts +21 -8
  49. package/src/client/ui/SvnLogDialog.ts +44 -2
  50. package/src/client/ui/SvnPatchDialog.ts +63 -0
  51. package/src/client/ui/SvnSumDialog.ts +77 -0
  52. package/src/client/ui/commandCatalog.ts +54 -0
  53. package/src/client/ui/horizontalWheel.ts +85 -0
  54. package/src/client/ui/svnExport.ts +71 -0
  55. package/src/client-primitives.d.ts +34 -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/fileOpenSettings.ts +3 -0
  66. package/src/index.ts +7 -3
  67. package/src/lsp/providers.ts +3 -2
  68. package/src/reveal.ts +31 -20
  69. package/src/rpc.ts +11 -3
  70. package/src/search/ripgrep.ts +127 -11
  71. package/src/shared/dap.ts +262 -0
  72. package/src/shared/editorLimit.ts +46 -0
  73. package/src/shared/keybindings.ts +10 -0
  74. package/src/shared/rpc.ts +40 -2
  75. package/src/shared/svn.ts +147 -0
  76. package/src/shared/svnActions.ts +20 -0
  77. package/src/svn.ts +320 -11
  78. package/src/workspace.ts +0 -75
@@ -0,0 +1,62 @@
1
+ /**
2
+ * dsh-vscode-mode client — DAP 变量树纯函数。
3
+ * 把 DAP variablesReference 缓存展平成可渲染 sibling rows,避免把子行嵌套进父级
4
+ * 横向 flex 行导致变量重叠;同时限制递归深度与总行数,保护侧栏性能。
5
+ * 作者 ddj 2026年09月29号
6
+ */
7
+
8
+ /** DAP 变量最小视图。 */
9
+ export interface VariableLike {
10
+ name: string
11
+ value: string
12
+ type?: string
13
+ ref: number
14
+ }
15
+
16
+ /** 展平后的变量行。 */
17
+ export interface VariableRow {
18
+ key: string
19
+ variable: VariableLike
20
+ depth: number
21
+ }
22
+
23
+ /** 展平选项。 */
24
+ export interface VariableTreeOptions {
25
+ maxRows?: number
26
+ maxDepth?: number
27
+ }
28
+
29
+ /**
30
+ * 将变量树展平为 sibling rows;循环 ref 只展开一次,避免异常适配器数据造成递归。
31
+ * @author ddj 2026年09月29号
32
+ * @param roots 根变量
33
+ * @param expanded 已展开的 variablesReference
34
+ * @param cache ref → 子变量缓存
35
+ * @param options 行数/深度上限
36
+ */
37
+ export function flattenVariableRows(
38
+ roots: readonly VariableLike[],
39
+ expanded: ReadonlySet<number>,
40
+ cache: ReadonlyMap<number, readonly VariableLike[]>,
41
+ options: VariableTreeOptions = {},
42
+ ): VariableRow[] {
43
+ const rows: VariableRow[] = []
44
+ const maxRows = options.maxRows ?? 1000
45
+ const maxDepth = options.maxDepth ?? 32
46
+ const walk = (items: readonly VariableLike[], depth: number, ancestry: ReadonlySet<number>, prefix: string): void => {
47
+ if (depth > maxDepth || rows.length >= maxRows) return
48
+ for (let index = 0; index < items.length && rows.length < maxRows; index += 1) {
49
+ const variable = items[index]
50
+ const key = prefix + '/' + index + ':' + variable.name
51
+ rows.push({ key, variable, depth })
52
+ if (variable.ref <= 0 || !expanded.has(variable.ref) || ancestry.has(variable.ref)) continue
53
+ const children = cache.get(variable.ref)
54
+ if (!children?.length) continue
55
+ const nextAncestry = new Set(ancestry)
56
+ nextAncestry.add(variable.ref)
57
+ walk(children, depth + 1, nextAncestry, key)
58
+ }
59
+ }
60
+ walk(roots, 0, new Set(), 'root')
61
+ return rows
62
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * dsh-vscode-mode client — 页签数量上限共享状态。
3
+ * 设置来源:dsh-vscode-mode 命名空间的 maxOpenEditors 字段(通用设置页可调);
4
+ * 客户端 settings 订阅经 editorLimitApply 写入,编辑器经 getMaxOpenEditors 读取,
5
+ * 变更时由 client/index.ts 派发 edrv:max-open-editors 窗口事件通知编辑器复算淘汰。
6
+ * 形态与 sidebarMin.ts 一致(同一设置同步链路,便于统一维护)。
7
+ * 作者 ddj 2026年09月18号
8
+ */
9
+ import { EDITOR_LIMIT_DEFAULT, normalizeMaxOpenEditors } from '../shared/editorLimit.js'
10
+
11
+ let currentLimit = EDITOR_LIMIT_DEFAULT
12
+
13
+ /**
14
+ * 应用设置值(client/index.ts 设置订阅同步调用)。
15
+ * @author ddj 2026年09月18号
16
+ * @param value 设置文档中的 maxOpenEditors
17
+ * @returns 归一化后的生效值(0 = 不限制)
18
+ */
19
+ export function editorLimitApply(value: unknown): number {
20
+ currentLimit = normalizeMaxOpenEditors(value)
21
+ return currentLimit
22
+ }
23
+
24
+ /**
25
+ * 读取当前生效的页签上限(编辑器初始/事件回调共用;0 = 不限制)。
26
+ * @author ddj 2026年09月18号
27
+ * @returns 当前上限
28
+ */
29
+ export function getMaxOpenEditors(): number {
30
+ return currentLimit
31
+ }
@@ -41,6 +41,7 @@ import { createFilePanel } from './sidebar/panels/index.js'
41
41
  import { createSearchPanel } from './sidebar/panels/index.js'
42
42
  import { createRulesPanel } from './sidebar/panels/index.js'
43
43
  import { createSvnPanel } from './sidebar/panels/index.js'
44
+ import { createDebugPanel } from './sidebar/panels/index.js'
44
45
  import { createTreeMenuRegistry } from './sidebar/contextMenu.js'
45
46
  import { createDefaultFileMenuItems } from './sidebar/menuItems.js'
46
47
  import { createOutlinePanel } from './outline/index.js'
@@ -50,11 +51,14 @@ import { keybindingsApply } from './keybindings.js'
50
51
  import { createCommandBridge } from './commandBridge.js'
51
52
  import { REGISTRY_GLOBAL } from './commandGlobals.js'
52
53
  import { sidebarMinApply } from './sidebarMin.js'
54
+ import { editorLimitApply } from './editorLimit.js'
53
55
  import { log } from './log.js'
54
56
  import { setupLsp, setSession, disposeLsp } from './monaco/lsp/index.js'
55
57
  import { disposeSnippets } from './snippets/provider.js'
58
+ import { disposeLaunchJson } from './dap/launchSnippetProvider.js'
56
59
  import { disposeAiInline } from './ai/inlineProvider.js'
57
60
  import { readSessionScope, subscribeScope } from './sessionScope.js'
61
+ import { dapStore } from './dap/store.js'
58
62
  import type { CompatAdapter } from '../shared/compat.js'
59
63
 
60
64
  // ⚠️ inject 只列必需服务:webUiSettings 是 @linxin666/dsh-client-ui-web-ui-settings 提供的
@@ -193,6 +197,11 @@ export function apply(ctx: any): void {
193
197
  ctx.effect(() => sidebarPanels.register(createRulesPanel()), 'vscode-mode: sidebar panel rules')
194
198
  // SVN 变更面板:活动栏「SVN 变更」页签(工作副本状态列表;visible 守卫使非 SVN 工作区不出现)
195
199
  ctx.effect(() => sidebarPanels.register(createSvnPanel()), 'vscode-mode: sidebar panel svn')
200
+ // 调试面板:活动栏「调试」页签(VS Code 调试视图四段布局 + REPL;DAP 桥接)
201
+ ctx.effect(() => sidebarPanels.register(createDebugPanel()), 'vscode-mode: sidebar panel debug')
202
+ // 客户端(重)装配对账:host 的 DapSession 常驻,而本模块状态随刷新/HMR 归零,
203
+ // 不对账则调试面板与暂停态 hover 静默失效(见 dapStore.resync)。
204
+ void dapStore.resync()
196
205
  // 文件右键菜单项注册表(对外 provide,供本插件/第三方注册;内置「在文件浏览器中打开」)
197
206
  const fileMenuItems = createTreeMenuRegistry()
198
207
  ctx.provide('edrvFileContextMenuItems', fileMenuItems)
@@ -246,6 +255,7 @@ export function apply(ctx: any): void {
246
255
  }, 'vscode-mode: file opener setting sync')
247
256
  // 快捷键配置同步:设置提交后立即刷新键位匹配(编辑器/QuickOpen 按事件时读取)
248
257
  // 同一订阅里顺带同步侧边栏最小宽度(sidebarMinWidth)→ 派发 edrv:sidebar-min-width 通知编辑器重夹
258
+ // 以及页签上限(maxOpenEditors)→ 派发 edrv:max-open-editors 通知编辑器复算淘汰
249
259
  ctx.effect(() => {
250
260
  if (!settings) return undefined
251
261
  const sync = (): void => {
@@ -254,6 +264,8 @@ export function apply(ctx: any): void {
254
264
  keybindingsApply(snapshot.value?.keybindings)
255
265
  const minW = sidebarMinApply(snapshot.value?.sidebarMinWidth)
256
266
  window.dispatchEvent(new CustomEvent('edrv:sidebar-min-width', { detail: { value: minW } }))
267
+ const limit = editorLimitApply(snapshot.value?.maxOpenEditors)
268
+ window.dispatchEvent(new CustomEvent('edrv:max-open-editors', { detail: { value: limit } }))
257
269
  }
258
270
  sync()
259
271
  return settings.subscribe(sync)
@@ -444,6 +456,7 @@ export function apply(ctx: any): void {
444
456
  // 不做会导致重载后重复注册(补全/跳转/hover 各翻倍)与陈旧开关残留。
445
457
  ctx.effect(() => () => {
446
458
  try { disposeSnippets() } catch { /* 卸载异常不得阻断其余清理 */ }
459
+ try { disposeLaunchJson() } catch { /* 同上 */ }
447
460
  try { disposeAiInline() } catch { /* 同上 */ }
448
461
  try { disposeLsp() } catch { /* 同上 */ }
449
462
  }, 'vscode-mode: monaco providers teardown')
@@ -0,0 +1,24 @@
1
+ /**
2
+ * dsh-vscode-mode client — Markdown 文件判定(编辑区预览用)。
3
+ * 纯函数、无 DOM/React 依赖,便于单测。
4
+ * @author ddj 2026年09月18号
5
+ */
6
+
7
+ /** 可进入预览的 Markdown 扩展名(小写、无点)。 */
8
+ const MARKDOWN_EXT_SET: Set<string> = new Set(['md', 'markdown'])
9
+
10
+ /**
11
+ * 判断路径是否为可预览的 Markdown 文件(取 basename 扩展名,大小写不敏感)。
12
+ *
13
+ * 为什么不含 `mdx`:官方 MarkdownText 只走 GFM+KaTeX 语法,不解析 JSX;
14
+ * 对 .mdx 做「预览」会把 JSX 当字面文本渲染出来,比文本编辑更容易误导,故排除。
15
+ * @author ddj 2026年09月18号
16
+ * @param path 文件路径(`/` 或 `\` 分隔均可)
17
+ * @returns 是否 Markdown 文件
18
+ */
19
+ export function isMarkdownPath(path: string): boolean {
20
+ const base = String(path || '').split(/[\\/]/).pop() || ''
21
+ const dot = base.lastIndexOf('.')
22
+ if (dot <= 0) return false
23
+ return MARKDOWN_EXT_SET.has(base.slice(dot + 1).toLowerCase())
24
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * dsh-vscode-mode client — React 元素类型判定(纯函数,无 React/primitives 依赖)。
3
+ *
4
+ * 用途:编辑区 Markdown 预览要判断官方 `MarkdownText` 原语是否可用(旧版 DSH 可能缺该导出)。
5
+ *
6
+ * ⚠️ 为什么不能只判 `typeof value === 'function'`:
7
+ * 官方 `MarkdownText` 是 `React.memo(...)` 的产物(`MemoExoticComponent`),
8
+ * 其 `typeof` 是 **'object'** 而非 'function'。函数判据恒为 false,会让「兼容降级」分支在
9
+ * **所有新版 DSH 上永久生效** —— 表现是预览只显示纯文本降级提示。
10
+ * 该缺陷由 GUI 端到端验证捕获:纯函数单测不覆盖该守卫,手写垫片又把它声明得很宽松,
11
+ * 故 tsc 与既有测试都无法发现。抽出本模块正是为了让它有单测守约。
12
+ *
13
+ * 判定口径与 React 自身一致:函数(函数组件 / 类组件),或带 `$$typeof` 标记的非 null
14
+ * 对象(memo / forwardRef / lazy 等「外部对象类型」)。
15
+ * 作者 ddj 2026年09月18号
16
+ */
17
+
18
+ /**
19
+ * 判断值是否可作为 React 元素类型传给 `React.createElement`。
20
+ * @author ddj 2026年09月18号
21
+ * @param value 候选组件(undefined/null 表示该导出不存在)
22
+ * @returns 是否可作为元素类型使用
23
+ */
24
+ export function isComponentType(value: unknown): boolean {
25
+ if (typeof value === 'function') return true
26
+ if (typeof value !== 'object' || value === null) return false
27
+ return (value as { $$typeof?: unknown }).$$typeof !== undefined
28
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * dsh-vscode-mode client — 编辑区 Markdown 预览面板。
3
+ *
4
+ * 需求 4:.md 文件支持预览。渲染复用官方 UI 原语 MarkdownText(GFM + KaTeX,走 --dsw-*
5
+ * 令牌自动跟随 DSH 主题与皮肤),不引入任何第三方 Markdown 依赖。
6
+ *
7
+ * 与既有图片 / PDF 面板的形态保持一致:工具条(meta + 切换按钮 + 刷新)+ 内容区。
8
+ * 本面板为**只读**视图(编辑仍在 Monaco 文本态完成),故不接收编辑回调、不参与脏标记。
9
+ *
10
+ * ⚠️ 兼容降级:旧版 DSH 的 primitives 可能没有 MarkdownText。此时渲染只读 <pre> 并给出
11
+ * 提示,绝不抛错白屏(与 pdf.js / Monaco 加载失败的降级口径一致)。
12
+ * 作者 ddj 2026年09月18号
13
+ */
14
+ import React from 'react'
15
+ import type { ComponentType, ReactNode } from 'react'
16
+ import { MarkdownDelegateProvider, MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'
17
+ import type { MarkdownLabels } from '@deepseek-ai/dsh-client-ui-primitives'
18
+ import { isComponentType } from './componentType.js'
19
+
20
+ /**
21
+ * 渲染 chrome 常量。必须是模块级稳定引用:MarkdownText 的 memo 以 labels 身份
22
+ * 参与缓存判定,每次渲染新建对象会让渲染缓存在流式场景反复失效。
23
+ */
24
+ const MD_LABELS: MarkdownLabels = {
25
+ code: { copyLabel: '复制', copiedLabel: '已复制' },
26
+ footnotes: '脚注',
27
+ }
28
+
29
+ /**
30
+ * 官方原语声明在 client-primitives.d.ts 的手写垫片里,组件返回值只能声明为 unknown
31
+ * (该虚拟模块不入本地 node_modules,无法在环境声明中引用 react 类型)。
32
+ * 故在此把垫片面收敛成 react 可接受的组件类型,仅本文件承担这层转换;
33
+ * 原语在运行时缺失时由下方 hasMarkdownPrimitive 守卫降级。
34
+ */
35
+ type MdTextProps = { text: string; streaming?: boolean; labels: MarkdownLabels; variant?: 'body' | 'compact' }
36
+ type MdProviderProps = { children?: ReactNode; openFile?: (path: string, options?: { line?: number }) => void }
37
+
38
+ const MdText = MarkdownText as unknown as ComponentType<MdTextProps>
39
+ const MdProvider = MarkdownDelegateProvider as unknown as ComponentType<MdProviderProps>
40
+
41
+ /** 官方 Markdown 原语是否可用(旧版 DSH 缺该导出时为 false)。 */
42
+ const hasMarkdownPrimitive = isComponentType(MarkdownText)
43
+
44
+ /** 文本行数(meta 展示用;空文本计 0 行)。 */
45
+ function lineCountOf(text: string): number {
46
+ const value = String(text ?? '')
47
+ return value ? value.split(/\r?\n/).length : 0
48
+ }
49
+
50
+ /** 预览面板 props(面板由 EditorView 装配,故用宽松形状避免跨文件类型耦合)。 */
51
+ interface MarkdownPanelProps {
52
+ /** Markdown 原文(取自编辑器已加载内容,无需额外 RPC)。 */
53
+ text?: string
54
+ /** 切回源码编辑(工具条按钮)。 */
55
+ onToggleSource?: () => void
56
+ /** 重新读取文件(工具条刷新按钮)。 */
57
+ onReload?: () => void
58
+ /** 打开 Markdown 内的本地文件链接(可空:不给则链接保持纯文本)。 */
59
+ onOpenFile?: (path: string, options?: { line?: number }) => void
60
+ }
61
+
62
+ /**
63
+ * Markdown 预览面板。
64
+ * @author ddj 2026年09月18号
65
+ * @param props 面板输入(原文与三个动作回调)
66
+ * @returns 预览面板元素
67
+ */
68
+ export function MarkdownPanel(props: MarkdownPanelProps) {
69
+ const text = String(props?.text ?? '')
70
+ const onToggleSource = props?.onToggleSource
71
+ const onReload = props?.onReload
72
+ const onOpenFile = props?.onOpenFile
73
+ const chars = text.length
74
+ const lines = lineCountOf(text)
75
+
76
+ const body = hasMarkdownPrimitive
77
+ ? React.createElement(MdProvider, {
78
+ openFile: typeof onOpenFile === 'function' ? onOpenFile : undefined,
79
+ }, React.createElement(MdText, { text, streaming: false, labels: MD_LABELS, variant: 'body' }))
80
+ : React.createElement('div', { className: 'edrv-mdview-fallback' },
81
+ React.createElement('div', null, '当前 DSH 版本不含 Markdown 渲染原语,已降级为纯文本预览。'),
82
+ React.createElement('pre', { className: 'edrv-mdview-pre' }, text))
83
+
84
+ return React.createElement('div', { className: 'edrv-mdview' },
85
+ React.createElement('div', { className: 'edrv-mdview-bar' },
86
+ React.createElement('span', { className: 'edrv-mdview-meta' }, lines + ' 行 · ' + chars + ' 字符'),
87
+ React.createElement('span', { style: { flex: 1 } }),
88
+ React.createElement('button', {
89
+ className: 'edrv-pill edrv-pill-ghost', title: '切回源码编辑',
90
+ onClick: () => onToggleSource?.(),
91
+ }, '以源码打开'),
92
+ React.createElement('button', {
93
+ className: 'edrv-pill edrv-pill-ghost', title: '重新读取文件',
94
+ onClick: () => onReload?.(),
95
+ }, '⟳ 刷新')),
96
+ React.createElement('div', { className: 'edrv-mdview-stage' },
97
+ React.createElement('div', { className: 'edrv-mdview-doc' }, body)))
98
+ }
@@ -6,6 +6,9 @@
6
6
  * 作者 ddj 2026-08-27
7
7
  */
8
8
  import { registerLspProviders, disposeLspProviders, hideReferencesOverlay } from './providers.js'
9
+ import { registerDapHover, disposeDapHover } from '../../dap/hover.js'
10
+ import { installHoverTree, disposeHoverTree } from '../../dap/hoverTree.js'
11
+ import { installHoverMode, disposeHoverMode } from '../../dap/hoverMode.js'
9
12
  import { setLspSession, refreshStatus, lspStatusFor, onLspProgress, bindLspSession } from './lspClient.js'
10
13
 
11
14
  let monacoRef = null
@@ -19,7 +22,16 @@ const LSP_SESSION_GLOBAL = '__edrvLspSessionUnbind__'
19
22
  /** Monaco 加载后装配(幂等;重复调用仅刷新会话)。 */
20
23
  export function setupLsp(monaco) {
21
24
  monacoRef = monaco
25
+ // 注册顺序决定同分 provider 的 ordinal:LanguageFeatureRegistry._compareByScoreAndTime 对
26
+ // 同分选择器按 _time 倒序(后注册者排前),hover 部件再按 ordinal 升序渲染 —— 因此 DAP
27
+ // 必须**最后**注册,暂停态的调试值行才会稳定渲染在 LSP 文档行之前;反序会让调试值被
28
+ // LSP 长文档挤到浮窗下方(超出 maxHeight 需滚动,等同看不到)。
22
29
  registerLspProviders(monaco)
30
+ registerDapHover(monaco)
31
+ // hover 变量树的 DOM 绑定(观察 hover 面板插入/重渲染;幂等)
32
+ installHoverTree()
33
+ // Alt 跟踪:暂停态默认调试值浮窗、按住 Alt 切 LSP 信息(对齐 CodeBuddy)
34
+ installHoverMode()
23
35
  // 会话广播订阅只装一次:跨重载时上一代已订阅(取消函数落 window,模块级状态会复位),
24
36
  // 只判 sessionBound 会重复订阅 → 同一 LSP 会话事件被处理多次。
25
37
  const host = /* @__PURE__ */ (typeof window === 'undefined' ? undefined : window)
@@ -52,6 +64,9 @@ export function disposeLspOverlay() {
52
64
  */
53
65
  export function disposeLsp() {
54
66
  disposeLspProviders()
67
+ disposeDapHover()
68
+ disposeHoverTree()
69
+ disposeHoverMode()
55
70
  const unbind = sessionUnbind
56
71
  if (typeof unbind === 'function') {
57
72
  try { unbind() } catch { /* 已解绑 */ }
@@ -136,6 +136,8 @@ export function registerLspProviders(monaco) {
136
136
  }),
137
137
  monaco.languages.registerHoverProvider(LSP_LANGS, {
138
138
  provideHover: async (model, position, token) => {
139
+ // 暂停态的 LSP 行由调试 hover 层默认隐藏、按住 Alt 时显示(见 dap/hoverTree.ts),
140
+ // 因此这里照常返回内容,不再按位置让位。
139
141
  const path = pathOfModel(model)
140
142
  if (!path) return null
141
143
  const hover = await fetchHover(path, model.getValue(), position)
@@ -137,6 +137,14 @@ const DARK_COLORS = {
137
137
  'scrollbarSlider.background': '#79797966',
138
138
  'scrollbarSlider.hoverBackground': '#646464b3',
139
139
  'scrollbarSlider.activeBackground': '#bfbfbf66',
140
+ // 差异视图色块(本仓库 vendored Monaco 无 .line-insert 静态背景规则,靠主题键保证;
141
+ // 色相取自研差异 UI 同源:绿 #0f9d58 / 红 #d9534f,alpha 提到肉眼可辨)
142
+ 'diffEditor.insertedLineBackground': '#0f9d5830',
143
+ 'diffEditor.removedLineBackground': '#d9534f2e',
144
+ 'diffEditor.insertedTextBackground': '#0f9d5833',
145
+ 'diffEditor.removedTextBackground': '#d9534f40',
146
+ 'diffEditorGutter.insertedLineBackground': '#0f9d5859',
147
+ 'diffEditorGutter.removedLineBackground': '#d9534f59',
140
148
  }
141
149
 
142
150
  const LIGHT_COLORS = {
@@ -157,6 +165,13 @@ const LIGHT_COLORS = {
157
165
  'scrollbarSlider.background': '#64646466',
158
166
  'scrollbarSlider.hoverBackground': '#646464b3',
159
167
  'scrollbarSlider.activeBackground': '#00000099',
168
+ // 差异视图色块(同上:主题键保证整行背景可见;色相/透明度与暗色一致)
169
+ 'diffEditor.insertedLineBackground': '#0f9d5830',
170
+ 'diffEditor.removedLineBackground': '#d9534f2e',
171
+ 'diffEditor.insertedTextBackground': '#0f9d5833',
172
+ 'diffEditor.removedTextBackground': '#d9534f40',
173
+ 'diffEditorGutter.insertedLineBackground': '#0f9d5859',
174
+ 'diffEditorGutter.removedLineBackground': '#d9534f59',
160
175
  }
161
176
 
162
177
  /**
@@ -0,0 +1,64 @@
1
+ /**
2
+ * dsh-vscode-mode client — 工作区搜索的「选区种子」。
3
+ *
4
+ * 需求:Ctrl+Shift+F 时若编辑器有选中,把选中文本自动填入搜索框。
5
+ * 链路:EditorView 从 Monaco 取选区文本 → seedQueryOf 归一 → setSearchSeed 存入一次性槽
6
+ * → 派发 edrv:search-focus → SearchPanel 消费(挂载时或已挂载时),填入并立即搜索。
7
+ *
8
+ * 为什么用一次性槽而不是事件 detail:
9
+ * 侧边栏原本收起时搜索面板**尚未挂载**,派发瞬间没有监听者;事件 detail 会丢。
10
+ * 槽在 EditorView 与 SearchPanel 之间充当交接点,挂载后由消费方自行取走。
11
+ *
12
+ * 为什么取首行:搜索框是单行 input,ripgrep 按字面连续匹配。多行选区(整段/整函数)
13
+ * 作为单条 query 永远无匹配,故按项目既有口径取首行(与 VS Code 单行选区种子行为一致)。
14
+ *
15
+ * 纯逻辑 + 模块级槽,不依赖 React/DOM,可 node 单测。
16
+ * 作者 ddj 2026年09月18号
17
+ */
18
+
19
+ /** 种子最大长度(超长选区只取前 N 字符,防一次性把巨型 query 送进搜索)。 */
20
+ export const SEED_MAX = 200
21
+
22
+ /**
23
+ * 由选区文本推导搜索种子。
24
+ * @author ddj 2026年09月18号
25
+ * @param text 选区原文(可能含多行、前后空白;null/undefined 视为空)
26
+ * @returns 归一后的搜索词;无效输入返回空串(调用方据此保持原搜索词)
27
+ */
28
+ export function seedQueryOf(text: unknown): string {
29
+ const raw = String(text ?? '')
30
+ if (!raw) return ''
31
+ const firstLine = raw.split(/\r?\n/)[0] ?? ''
32
+ const trimmed = firstLine.trim()
33
+ if (!trimmed) return ''
34
+ return trimmed.length > SEED_MAX ? trimmed.slice(0, SEED_MAX) : trimmed
35
+ }
36
+
37
+ /** 一次性种子槽(字符串或 null;null 表示无待消费种子)。 */
38
+ let pendingSeed: string | null = null
39
+
40
+ /**
41
+ * 写入待消费种子(空词等价于清除)。
42
+ * @author ddj 2026年09月18号
43
+ * @param text 选区文本(经 seedQueryOf 归一)
44
+ */
45
+ export function setSearchSeed(text: unknown): void {
46
+ const seed = seedQueryOf(text)
47
+ pendingSeed = seed ? seed : null
48
+ }
49
+
50
+ /**
51
+ * 取走待消费种子(取后即清空,保证挂载路径与焦点路径不会重复应用同一个种子)。
52
+ * @author ddj 2026年09月18号
53
+ * @returns 种子搜索词;无待消费种子返回空串
54
+ */
55
+ export function takeSearchSeed(): string {
56
+ const seed = pendingSeed
57
+ pendingSeed = null
58
+ return seed ?? ''
59
+ }
60
+
61
+ /** 清空待消费种子(测试隔离用)。 */
62
+ export function clearSearchSeed(): void {
63
+ pendingSeed = null
64
+ }