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,198 @@
1
+ /**
2
+ * dsh-vscode-mode host — 调试适配器发现(清单驱动,VSCode contributes.debuggers 同源语义)。
3
+ * 扫描全部扩展根(复用 lsp 侧 vscodeExtensionsDirs:~/.vscode/extensions、
4
+ * ~/.vscode-server/extensions、插件扩展目录),读各扩展 package.json 的
5
+ * contributes.debuggers[] 声明,解析为 DapAdapterDecl:
6
+ * - program 按平台取 windows/linux/osx 覆盖,相对扩展根解析;
7
+ * - runtime='node' → 由 node 跑 js 入口;无 runtime → program 即可执行(args 为清单声明);
8
+ * - 同 type 多扩展并存 → 按「清单版本降序(numeric) → 路径升序」取唯一(与 LSP 候选口径一致);
9
+ * - 入口不存在 / runtime 不支持 → available=false + reason(如 DotRush 按需下载的 clrdbg,
10
+ * 诚实标注不假成功,调用方据此在下拉置灰并提示原因)。
11
+ * 作者 ddj 2026年09月21号
12
+ */
13
+ import { existsSync, readdirSync, readFileSync, type Dirent } from 'node:fs'
14
+ import { join } from 'node:path'
15
+ import { LSP_LANG_EXT, manifestVersionOf, vscodeExtensionsDirs } from '../lsp/providers.js'
16
+ import { dshHome } from '../paths.js'
17
+ import type { DapAdapterDecl } from '../shared/dap.js'
18
+
19
+ /** package.json contributes.debuggers 条目(本插件消费的子集)。 */
20
+ interface DebuggerEntry {
21
+ type?: unknown
22
+ label?: unknown
23
+ program?: unknown
24
+ args?: unknown
25
+ runtime?: unknown
26
+ languages?: unknown
27
+ windows?: { program?: unknown }
28
+ linux?: { program?: unknown }
29
+ osx?: { program?: unknown }
30
+ darwin?: { program?: unknown }
31
+ configurationAttributes?: {
32
+ attach?: { properties?: { ext?: { default?: unknown } } }
33
+ launch?: { properties?: { ext?: { default?: unknown } } }
34
+ }
35
+ }
36
+
37
+ /** 发现缓存(扩展清单会话内基本不变;测试用 force 重扫)。 */
38
+ let cacheReady = false
39
+ let cacheHome = ''
40
+ let cache: DapAdapterDecl[] = []
41
+
42
+ /**
43
+ * 发现全部调试适配器声明(按 type 去重、type 字典序稳定输出)。
44
+ * @author ddj 2026年09月21号
45
+ * @param force 强制重扫(扩展安装后 / 测试隔离用)
46
+ * @param home DSH home(缺省真实;测试可注入)
47
+ */
48
+ export function debuggerDecls(force = false, home = dshHome()): DapAdapterDecl[] {
49
+ if (cacheReady && !force && home === cacheHome) return cache
50
+ cache = scanDecls(home)
51
+ cacheHome = home
52
+ cacheReady = true
53
+ return cache
54
+ }
55
+
56
+ /** 清空发现缓存(扩展目录变更时;测试 afterEach 复位)。 */
57
+ export function clearDiscoveryCache(): void {
58
+ cacheReady = false
59
+ cacheHome = ''
60
+ cache = []
61
+ }
62
+
63
+ /** 扫描全部扩展根并收集声明(同 type 去重后返回)。 */
64
+ function scanDecls(home: string): DapAdapterDecl[] {
65
+ const hits: { decl: DapAdapterDecl; version: string }[] = []
66
+ for (const root of vscodeExtensionsDirs(home)) {
67
+ for (const extDir of subDirsOf(root)) {
68
+ for (const entry of debuggersOf(extDir)) {
69
+ const decl = declOf(extDir, entry)
70
+ if (decl) hits.push({ decl, version: manifestVersionOf(extDir) ?? '' })
71
+ }
72
+ }
73
+ }
74
+ return pickPerType(hits)
75
+ }
76
+
77
+ /** 同 type 去重:清单版本降序(numeric)→ 路径升序;输出按 type 字典序。 */
78
+ function pickPerType(hits: { decl: DapAdapterDecl; version: string }[]): DapAdapterDecl[] {
79
+ const byType = new Map<string, { decl: DapAdapterDecl; version: string }>()
80
+ for (const hit of hits) {
81
+ const prev = byType.get(hit.decl.type)
82
+ if (!betterOf(hit, prev)) continue
83
+ byType.set(hit.decl.type, hit)
84
+ }
85
+ return [...byType.values()].map((h) => h.decl).sort((a, b) => a.type.localeCompare(b.type))
86
+ }
87
+
88
+ /** 候选比较:版本降序优先,平版本路径升序(无前任直接胜出)。 */
89
+ function betterOf(hit: { decl: DapAdapterDecl; version: string }, prev?: { decl: DapAdapterDecl; version: string }): boolean {
90
+ if (!prev) return true
91
+ const byVersion = compareVersion(hit.version, prev.version)
92
+ if (byVersion !== 0) return byVersion > 0
93
+ return hit.decl.extensionPath.localeCompare(prev.decl.extensionPath) < 0
94
+ }
95
+
96
+ /** 清单版本比较(numeric 感知,与 LSP 候选排序同口径):>0 表示 a 更新。 */
97
+ function compareVersion(a: string, b: string): number {
98
+ return String(a || '').localeCompare(String(b || ''), undefined, { numeric: true })
99
+ }
100
+
101
+ /** 枚举扩展根下的扩展子目录(根不存在返回空)。 */
102
+ function subDirsOf(root: string): string[] {
103
+ let entries: Dirent[]
104
+ try {
105
+ entries = readdirSync(root, { withFileTypes: true })
106
+ } catch {
107
+ return []
108
+ }
109
+ return entries.filter((e) => e.isDirectory()).map((e) => join(root, e.name))
110
+ }
111
+
112
+ /** 读某扩展的 contributes.debuggers 声明(无清单/无声明/解析失败返回空)。 */
113
+ function debuggersOf(extDir: string): DebuggerEntry[] {
114
+ try {
115
+ const text = readFileSync(join(extDir, 'package.json'), 'utf8').replace(/^\uFEFF/, '')
116
+ const manifest = JSON.parse(text) as { contributes?: { debuggers?: unknown } }
117
+ const list = manifest.contributes?.debuggers
118
+ return Array.isArray(list) ? (list as DebuggerEntry[]) : []
119
+ } catch {
120
+ return []
121
+ }
122
+ }
123
+
124
+ /** 清单条目 → 声明(缺 type/program 视为无效条目跳过)。 */
125
+ function declOf(extDir: string, entry: DebuggerEntry): DapAdapterDecl | null {
126
+ const type = typeof entry.type === 'string' ? entry.type : ''
127
+ if (!type || typeof entry.program !== 'string' || !entry.program) return null
128
+ const languages = stringsOf(entry.languages)
129
+ const resolved = resolveProgram(extDir, entry)
130
+ const runtime = typeof entry.runtime === 'string' && entry.runtime ? entry.runtime : undefined
131
+ const runtimeBad = runtime !== undefined && runtime !== 'node'
132
+ const available = resolved.available && !runtimeBad
133
+ const reason = !resolved.available
134
+ ? resolved.reason
135
+ : runtimeBad
136
+ ? '暂不支持运行时 "' + runtime + '"(仅支持 node 与原生可执行)'
137
+ : undefined
138
+ return {
139
+ type,
140
+ label: typeof entry.label === 'string' && entry.label ? entry.label : type,
141
+ extensionId: extensionIdOf(extDir),
142
+ extensionPath: extDir,
143
+ program: resolved.program,
144
+ args: stringsOf(entry.args),
145
+ runtime,
146
+ languages,
147
+ exts: extsOf(entry, languages),
148
+ available,
149
+ reason,
150
+ }
151
+ }
152
+
153
+ /** 平台 program 解析:windows/linux/osx(darwin) 覆盖优先,相对扩展根(剥 ./)。 */
154
+ function resolveProgram(extDir: string, entry: DebuggerEntry): { program: string; available: boolean; reason?: string } {
155
+ const plat = process.platform
156
+ const override = plat === 'win32'
157
+ ? entry.windows?.program
158
+ : plat === 'darwin'
159
+ ? (entry.osx?.program ?? entry.darwin?.program)
160
+ : entry.linux?.program
161
+ const rel = typeof override === 'string' && override ? override : entry.program
162
+ if (typeof rel !== 'string' || !rel) return { program: '', available: false, reason: '清单未声明本平台 program' }
163
+ const program = join(extDir, ...rel.replace(/^\.\//, '').split(/[\\/]/))
164
+ if (!existsSync(program)) {
165
+ return { program, available: false, reason: '适配器入口不存在(扩展可能需按需下载):' + rel }
166
+ }
167
+ return { program, available: true }
168
+ }
169
+
170
+ /** 来源扩展 id(目录名 publisher.name-version 取 version 前段;解析失败回退目录名)。 */
171
+ function extensionIdOf(extDir: string): string {
172
+ const base = extDir.split(/[\\/]/).pop() ?? ''
173
+ const dot = base.lastIndexOf('-')
174
+ return dot > 0 ? base.slice(0, dot) : base
175
+ }
176
+
177
+ /** 可下断点扩展名:优先清单 configurationAttributes 的 ext 缺省(emmylua 的 .lua 家族),
178
+ * 否则按声明 languages 映射(LSP 侧同源);两者皆空 = 不限(不过滤断点)。 */
179
+ function extsOf(entry: DebuggerEntry, languages: string[]): string[] {
180
+ const attrs = entry.configurationAttributes
181
+ const declared = attrs?.attach?.properties?.ext?.default ?? attrs?.launch?.properties?.ext?.default
182
+ const fromManifest = stringsOf(declared)
183
+ if (fromManifest.length) return fromManifest
184
+ const out: string[] = []
185
+ for (const lang of languages) {
186
+ for (const ext of LSP_LANG_EXT[lang] ?? []) {
187
+ const dotted = '.' + ext.replace(/^\./, '')
188
+ if (!out.includes(dotted)) out.push(dotted)
189
+ }
190
+ }
191
+ return out
192
+ }
193
+
194
+ /** unknown → 字符串数组(非字符串元素剔除)。 */
195
+ function stringsOf(value: unknown): string[] {
196
+ if (!Array.isArray(value)) return []
197
+ return value.filter((v): v is string => typeof v === 'string' && v.length > 0)
198
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * dsh-vscode-mode host — 调试配置文件通用解析(JSONC 清洗 + 全量配置透传)。
3
+ * 纯函数,node 可测。读插件专属 `.dsh/launch.json`(格式与 VS Code launch.json 兼容;
4
+ * 与 `.vscode/launch.json` 互不干扰,旧共用文件仅作首读迁移源):
5
+ * - 不再限定 emmylua:所有带 name/type 的 configuration 都保留(类型可用性由 discovery 裁决);
6
+ * - 未识别字段原样进 raw(attach/launch 请求参数透传给适配器,对齐 VS Code 语义);
7
+ * - ${workspaceFolder}/${cwd} 按工作区根替换;${command:...} 是 VS Code 命令占位,
8
+ * 本插件无法解析——检出后由调用方明确报错(不做静默假解析)。
9
+ * 作者 ddj 2026年09月29号 / 2026年09月21号
10
+ */
11
+ import type { DapDebugConfig } from '../shared/dap.js'
12
+
13
+ /**
14
+ * 清洗 JSONC 为可 JSON.parse 文本:去 // 与 /* *\/ 注释、去尾逗号(字符串字面量感知)。
15
+ * @author ddj 2026年09月29号
16
+ * @param text 原始 launch.json 文本
17
+ * @returns 清洗后文本
18
+ */
19
+ export function stripJsonc(text: string): string {
20
+ let out = ''
21
+ let i = 0
22
+ const n = text.length
23
+ let state: 'code' | 'string' = 'code'
24
+ while (i < n) {
25
+ const ch = text[i]
26
+ const next = i + 1 < n ? text[i + 1] : ''
27
+ if (state === 'string') {
28
+ out += ch
29
+ if (ch === '\\') { out += next; i += 2; continue }
30
+ if (ch === '"') state = 'code'
31
+ i += 1
32
+ continue
33
+ }
34
+ if (ch === '"') { state = 'string'; out += ch; i += 1; continue }
35
+ if (ch === '/' && next === '/') { while (i < n && text[i] !== '\n') i += 1; continue }
36
+ if (ch === '/' && next === '*') { i += 2; while (i + 1 < n && !(text[i] === '*' && text[i + 1] === '/')) i += 1; i += 2; continue }
37
+ out += ch
38
+ i += 1
39
+ }
40
+ return out.replace(/,(\s*[}\]])/g, '$1')
41
+ }
42
+
43
+ /**
44
+ * 解析 launch.json 文本为全量调试配置列表(非法/缺 name/type 的条目跳过)。
45
+ * @author ddj 2026年09月29号 / 2026年09月21号
46
+ * @param text launch.json 文本(JSONC)
47
+ * @param workspacePath 工作区根(${workspaceFolder}/${cwd} 替换基准;空 = 不替换)
48
+ * @returns 全量配置(raw 原样透传)
49
+ */
50
+ export function parseLaunchConfigs(text: string, workspacePath = ''): DapDebugConfig[] {
51
+ let data: unknown
52
+ try { data = JSON.parse(stripJsonc(text)) } catch { return [] }
53
+ const list = (data as { configurations?: unknown })?.configurations
54
+ if (!Array.isArray(list)) return []
55
+ const out: DapDebugConfig[] = []
56
+ for (const item of list) {
57
+ if (!item || typeof item !== 'object' || Array.isArray(item)) continue
58
+ const cfg = item as Record<string, unknown>
59
+ const type = asString(cfg.type)
60
+ const name = asString(cfg.name)
61
+ if (!type || !name) continue
62
+ out.push({
63
+ name,
64
+ type,
65
+ request: asString(cfg.request),
66
+ processName: asString(cfg.processName),
67
+ pid: asPositive(cfg.pid),
68
+ processId: asPositive(cfg.processId),
69
+ host: asString(cfg.host),
70
+ port: asPositive(cfg.port),
71
+ sourcePaths: stringListOf(cfg.sourcePaths),
72
+ ext: stringListOf(cfg.ext),
73
+ captureLog: typeof cfg.captureLog === 'boolean' ? cfg.captureLog : undefined,
74
+ raw: expandRaw(cfg, workspacePath),
75
+ })
76
+ }
77
+ return out
78
+ }
79
+
80
+ /**
81
+ * 配置是否含 ${command:...} 占位(VS Code 命令,本插件无法解析,需用户改为具体值)。
82
+ * @author ddj 2026年09月21号
83
+ * @param config 调试配置
84
+ */
85
+ export function hasCmdPlaceholder(config: DapDebugConfig): boolean {
86
+ return JSON.stringify(config.raw ?? {}).includes('${command:')
87
+ }
88
+
89
+ /** ${workspaceFolder}/${cwd} → 工作区根(大小写不敏感;其余占位保持原样交适配器)。 */
90
+ function expandLaunchVars(text: string, workspacePath: string): string {
91
+ if (!workspacePath) return text
92
+ return text
93
+ .replace(/\$\{workspaceFolder\}/gi, workspacePath)
94
+ .replace(/\$\{cwd\}/gi, workspacePath)
95
+ }
96
+
97
+ /** raw 逐字段递归展开(字符串内占位替换;数组/嵌套对象同口径)。 */
98
+ function expandRaw(raw: Record<string, unknown>, workspacePath: string): Record<string, unknown> {
99
+ const out: Record<string, unknown> = {}
100
+ for (const [key, value] of Object.entries(raw)) out[key] = expandValue(value, workspacePath)
101
+ return out
102
+ }
103
+
104
+ /** 单值展开。 */
105
+ function expandValue(value: unknown, workspacePath: string): unknown {
106
+ if (typeof value === 'string') return expandLaunchVars(value, workspacePath)
107
+ if (Array.isArray(value)) return value.map((v) => expandValue(v, workspacePath))
108
+ if (value && typeof value === 'object') return expandRaw(value as Record<string, unknown>, workspacePath)
109
+ return value
110
+ }
111
+
112
+ /** unknown → 字符串(非字符串返回 undefined)。 */
113
+ function asString(value: unknown): string | undefined {
114
+ return typeof value === 'string' && value ? value : undefined
115
+ }
116
+
117
+ /** unknown → 正数(number 且 >0;其余 undefined)。 */
118
+ function asPositive(value: unknown): number | undefined {
119
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined
120
+ }
121
+
122
+ /** 字符串数组字段清洗(非字符串元素剔除;空数组返回 undefined)。 */
123
+ function stringListOf(raw: unknown): string[] | undefined {
124
+ if (!Array.isArray(raw)) return undefined
125
+ const items = raw.filter((v): v is string => typeof v === 'string' && v.length > 0)
126
+ return items.length ? items : undefined
127
+ }