dsh-vscode-mode 0.1.1

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 (163) hide show
  1. package/README.md +95 -0
  2. package/assets/compare_idle.png +0 -0
  3. package/assets/compare_select.png +0 -0
  4. package/assets/vendor/monaco/vs/abap-D-t0cyap.js +1 -0
  5. package/assets/vendor/monaco/vs/apex-CcIm7xu6.js +1 -0
  6. package/assets/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +97 -0
  7. package/assets/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +30 -0
  8. package/assets/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +30 -0
  9. package/assets/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +474 -0
  10. package/assets/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +62 -0
  11. package/assets/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +67722 -0
  12. package/assets/vendor/monaco/vs/azcli-BA0tQDCg.js +1 -0
  13. package/assets/vendor/monaco/vs/basic-languages/monaco.contribution.js +1 -0
  14. package/assets/vendor/monaco/vs/bat-C397hTD6.js +1 -0
  15. package/assets/vendor/monaco/vs/bicep-DF5aW17k.js +2 -0
  16. package/assets/vendor/monaco/vs/cameligo-plsz8qhj.js +1 -0
  17. package/assets/vendor/monaco/vs/clojure-Y2auQMzK.js +1 -0
  18. package/assets/vendor/monaco/vs/coffee-Bu45yuWE.js +1 -0
  19. package/assets/vendor/monaco/vs/cpp-CkKPQIni.js +1 -0
  20. package/assets/vendor/monaco/vs/csharp-CX28MZyh.js +1 -0
  21. package/assets/vendor/monaco/vs/csp-D8uWnyxW.js +1 -0
  22. package/assets/vendor/monaco/vs/css-CaeNmE3S.js +3 -0
  23. package/assets/vendor/monaco/vs/css.worker-CyhWkhHo.js +1 -0
  24. package/assets/vendor/monaco/vs/cssMode-CV6Ay48H.js +1 -0
  25. package/assets/vendor/monaco/vs/cypher-DVThT8BS.js +1 -0
  26. package/assets/vendor/monaco/vs/dart-CmGfCvrO.js +1 -0
  27. package/assets/vendor/monaco/vs/dockerfile-CZqqYdch.js +1 -0
  28. package/assets/vendor/monaco/vs/ecl-30fUercY.js +1 -0
  29. package/assets/vendor/monaco/vs/editor/editor.main.css +1 -0
  30. package/assets/vendor/monaco/vs/editor/editor.main.js +1 -0
  31. package/assets/vendor/monaco/vs/editor/editor.worker.js +1 -0
  32. package/assets/vendor/monaco/vs/editor-KLE6jdfb.js +831 -0
  33. package/assets/vendor/monaco/vs/editor.js +1 -0
  34. package/assets/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +35 -0
  35. package/assets/vendor/monaco/vs/elixir-xjPaIfzF.js +1 -0
  36. package/assets/vendor/monaco/vs/flow9-DqtmStfK.js +1 -0
  37. package/assets/vendor/monaco/vs/freemarker2-FWreY7v7.js +3 -0
  38. package/assets/vendor/monaco/vs/fsharp-BOMdg4U1.js +1 -0
  39. package/assets/vendor/monaco/vs/go-D_hbi-Jt.js +1 -0
  40. package/assets/vendor/monaco/vs/graphql-CKUU4kLG.js +1 -0
  41. package/assets/vendor/monaco/vs/handlebars-CZi4pHCH.js +1 -0
  42. package/assets/vendor/monaco/vs/hcl-DTaboeZW.js +1 -0
  43. package/assets/vendor/monaco/vs/html-j_7ZTNRU.js +1 -0
  44. package/assets/vendor/monaco/vs/html.worker-CA3iAimZ.js +1 -0
  45. package/assets/vendor/monaco/vs/htmlMode-27by_KM4.js +1 -0
  46. package/assets/vendor/monaco/vs/index-049xrXrp.js +1 -0
  47. package/assets/vendor/monaco/vs/index-CBVt3dzv.js +5 -0
  48. package/assets/vendor/monaco/vs/index.js +1 -0
  49. package/assets/vendor/monaco/vs/ini-CsNwO04R.js +1 -0
  50. package/assets/vendor/monaco/vs/initialize-DL0l1TGY.js +1 -0
  51. package/assets/vendor/monaco/vs/java-CI4ZMsH9.js +1 -0
  52. package/assets/vendor/monaco/vs/javascript-DTJl4Jn1.js +1 -0
  53. package/assets/vendor/monaco/vs/json.worker-BizpAl9O.js +1 -0
  54. package/assets/vendor/monaco/vs/jsonMode-DLUiMUrI.js +1 -0
  55. package/assets/vendor/monaco/vs/julia-BwzEvaQw.js +1 -0
  56. package/assets/vendor/monaco/vs/kotlin-IUYPiTV8.js +1 -0
  57. package/assets/vendor/monaco/vs/language/css/css.worker.js +66 -0
  58. package/assets/vendor/monaco/vs/language/css/monaco.contribution.js +1 -0
  59. package/assets/vendor/monaco/vs/language/html/html.worker.js +443 -0
  60. package/assets/vendor/monaco/vs/language/html/monaco.contribution.js +1 -0
  61. package/assets/vendor/monaco/vs/language/json/json.worker.js +17 -0
  62. package/assets/vendor/monaco/vs/language/json/monaco.contribution.js +1 -0
  63. package/assets/vendor/monaco/vs/language/typescript/monaco.contribution.js +1 -0
  64. package/assets/vendor/monaco/vs/language/typescript/ts.worker.js +67693 -0
  65. package/assets/vendor/monaco/vs/less-C0eDYdqa.js +2 -0
  66. package/assets/vendor/monaco/vs/lexon-iON-Kj97.js +1 -0
  67. package/assets/vendor/monaco/vs/liquid-CLJYelW6.js +1 -0
  68. package/assets/vendor/monaco/vs/loader.js +1368 -0
  69. package/assets/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +2 -0
  70. package/assets/vendor/monaco/vs/lua-DtygF91M.js +1 -0
  71. package/assets/vendor/monaco/vs/m3-CsR4AuFi.js +1 -0
  72. package/assets/vendor/monaco/vs/main-BEx-Fmlo.js +15 -0
  73. package/assets/vendor/monaco/vs/main-DsK8pnKg.js +3 -0
  74. package/assets/vendor/monaco/vs/markdown-C_rD0bIw.js +1 -0
  75. package/assets/vendor/monaco/vs/mdx-CpzZIPHF.js +1 -0
  76. package/assets/vendor/monaco/vs/mips-CiYP61RB.js +1 -0
  77. package/assets/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +1 -0
  78. package/assets/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +1 -0
  79. package/assets/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +1 -0
  80. package/assets/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +1 -0
  81. package/assets/vendor/monaco/vs/msdax-C38-sJlp.js +1 -0
  82. package/assets/vendor/monaco/vs/mysql-CdtbpvbG.js +1 -0
  83. package/assets/vendor/monaco/vs/nls/lang/zh-cn.d.ts +1 -0
  84. package/assets/vendor/monaco/vs/nls/lang/zh-cn.js +9 -0
  85. package/assets/vendor/monaco/vs/nls.messages-loader.js +1 -0
  86. package/assets/vendor/monaco/vs/objective-c-CntZFaHX.js +1 -0
  87. package/assets/vendor/monaco/vs/pascal-r6kuqfl_.js +1 -0
  88. package/assets/vendor/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
  89. package/assets/vendor/monaco/vs/perl-DABw_TcH.js +1 -0
  90. package/assets/vendor/monaco/vs/pgsql-me_jFXeX.js +1 -0
  91. package/assets/vendor/monaco/vs/php-D_kh-9LK.js +1 -0
  92. package/assets/vendor/monaco/vs/pla-VfZjczW0.js +1 -0
  93. package/assets/vendor/monaco/vs/postiats-BBSzz8Pk.js +1 -0
  94. package/assets/vendor/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
  95. package/assets/vendor/monaco/vs/powershell-B-7ap1zc.js +1 -0
  96. package/assets/vendor/monaco/vs/protobuf-BmtuEB1A.js +2 -0
  97. package/assets/vendor/monaco/vs/pug-BRpRNeEb.js +1 -0
  98. package/assets/vendor/monaco/vs/python-CqWUUgfu.js +1 -0
  99. package/assets/vendor/monaco/vs/qsharp-BzsFaUU9.js +1 -0
  100. package/assets/vendor/monaco/vs/r-f8dDdrp4.js +1 -0
  101. package/assets/vendor/monaco/vs/razor-hok5y9Km.js +1 -0
  102. package/assets/vendor/monaco/vs/redis-fvZQY4PI.js +1 -0
  103. package/assets/vendor/monaco/vs/redshift-45Et0LQi.js +1 -0
  104. package/assets/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
  105. package/assets/vendor/monaco/vs/ruby-CZO8zYTz.js +1 -0
  106. package/assets/vendor/monaco/vs/rust-Bfetafyc.js +1 -0
  107. package/assets/vendor/monaco/vs/sb-3GYllVck.js +1 -0
  108. package/assets/vendor/monaco/vs/scala-foMgrKo1.js +1 -0
  109. package/assets/vendor/monaco/vs/scheme-CHdMtr7p.js +1 -0
  110. package/assets/vendor/monaco/vs/scss-C1cmLt9V.js +3 -0
  111. package/assets/vendor/monaco/vs/shell-ClXCKCEW.js +1 -0
  112. package/assets/vendor/monaco/vs/solidity-MZ6ExpPy.js +1 -0
  113. package/assets/vendor/monaco/vs/sophia-DWkuSsPQ.js +1 -0
  114. package/assets/vendor/monaco/vs/sparql-AUGFYSyk.js +1 -0
  115. package/assets/vendor/monaco/vs/sql-32GpJSV2.js +1 -0
  116. package/assets/vendor/monaco/vs/st-CuDFIVZ_.js +1 -0
  117. package/assets/vendor/monaco/vs/swift-t-PfMj7W.js +1 -0
  118. package/assets/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
  119. package/assets/vendor/monaco/vs/tcl-D74tq1nH.js +1 -0
  120. package/assets/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +61 -0
  121. package/assets/vendor/monaco/vs/ts.worker-2QLmBukE.js +1 -0
  122. package/assets/vendor/monaco/vs/tsMode-B0S_kp2q.js +11 -0
  123. package/assets/vendor/monaco/vs/twig-C6taOxMV.js +1 -0
  124. package/assets/vendor/monaco/vs/typescript-_2t_511t.js +1 -0
  125. package/assets/vendor/monaco/vs/typespec-8OfoLt6R.js +1 -0
  126. package/assets/vendor/monaco/vs/vb-Dyb2648j.js +1 -0
  127. package/assets/vendor/monaco/vs/wgsl-BhLXMOR0.js +298 -0
  128. package/assets/vendor/monaco/vs/workers-BBttULjf.js +1 -0
  129. package/assets/vendor/monaco/vs/xml-cZjNDqDT.js +1 -0
  130. package/assets/vendor/monaco/vs/yaml-A1fOIdH6.js +1 -0
  131. package/cordis.patch.yml +5 -0
  132. package/lib/client.js +1914 -0
  133. package/lib/client.js.map +1 -0
  134. package/lib/index.js +1298 -0
  135. package/lib/index.js.map +1 -0
  136. package/package.json +60 -0
  137. package/src/capture.ts +73 -0
  138. package/src/client/events.ts +36 -0
  139. package/src/client/index.ts +37 -0
  140. package/src/client/monaco/diffRender.ts +162 -0
  141. package/src/client/monaco/loader.ts +71 -0
  142. package/src/client/rpc.ts +28 -0
  143. package/src/client/state/records.ts +90 -0
  144. package/src/client/state/regions.ts +81 -0
  145. package/src/client/styles/css.d.ts +2 -0
  146. package/src/client/styles/editor.css +87 -0
  147. package/src/client/ui/DiffBadge.ts +51 -0
  148. package/src/client/ui/DiffBarEmpty.ts +37 -0
  149. package/src/client/ui/DiffBox.ts +79 -0
  150. package/src/client/ui/DiffLauncher.ts +124 -0
  151. package/src/client/ui/EditorView.ts +577 -0
  152. package/src/client/ui/QuickOpen.ts +91 -0
  153. package/src/client/ui/shared.ts +28 -0
  154. package/src/index.ts +43 -0
  155. package/src/model.ts +197 -0
  156. package/src/registry.ts +35 -0
  157. package/src/revert.ts +107 -0
  158. package/src/routes.ts +138 -0
  159. package/src/rpc.ts +295 -0
  160. package/src/shared/rpc.ts +67 -0
  161. package/src/shared/types.ts +121 -0
  162. package/src/store.ts +244 -0
  163. package/src/workspace.ts +74 -0
@@ -0,0 +1,71 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — Monaco 加载与语言映射(AMD 构建,离线随包分发)。
4
+ * 迁移自原 src/client/index.ts 的 MONACO_BASE/LANG_BY_EXT/langOf/loadMonaco,语义不改。
5
+ * 作者 ddj 2026-08-20
6
+ */
7
+
8
+ export const MONACO_BASE = '/edrv/vendor/monaco/vs'
9
+ let monacoPromise = null
10
+
11
+ /** 扩展名 → Monaco language id(常见语言;未知回退 plaintext)。 */
12
+ export const LANG_BY_EXT = {
13
+ js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
14
+ ts: 'typescript', tsx: 'typescript', mts: 'typescript', cts: 'typescript',
15
+ json: 'json', jsonc: 'json', md: 'markdown', markdown: 'markdown', mdx: 'mdx',
16
+ css: 'css', scss: 'scss', less: 'less', html: 'html', htm: 'html', vue: 'html', xml: 'xml',
17
+ yaml: 'yaml', yml: 'yaml', toml: 'ini', ini: 'ini', cfg: 'ini',
18
+ py: 'python', sh: 'shell', bash: 'shell', zsh: 'shell', ps1: 'powershell',
19
+ lua: 'lua', java: 'java', c: 'c', h: 'c', cpp: 'cpp', cc: 'cpp', cxx: 'cpp', hpp: 'cpp',
20
+ cs: 'csharp', go: 'go', rs: 'rust', rb: 'ruby', php: 'php', sql: 'sql',
21
+ swift: 'swift', kt: 'kotlin', kts: 'kotlin', dart: 'dart', dockerfile: 'dockerfile'
22
+ }
23
+
24
+ /**
25
+ * 路径 → Monaco language id:取 basename 扩展名(dockerfile/Dockerfile 特判)。
26
+ * @author ddj 2026年08月20号
27
+ * @param path 文件路径
28
+ * @returns language id
29
+ */
30
+ export function langOf(path) {
31
+ const base = String(path || '').split(/[\\/]/).pop() || ''
32
+ if (/^dockerfile$/i.test(base)) return 'dockerfile'
33
+ const dot = base.lastIndexOf('.')
34
+ if (dot <= 0) return 'plaintext'
35
+ return LANG_BY_EXT[base.slice(dot + 1).toLowerCase()] ?? 'plaintext'
36
+ }
37
+
38
+ /**
39
+ * 加载 Monaco Editor(AMD 构建,随插件包离线分发):注入 loader.js → require.config → editor.main。
40
+ * @author ddj 2026年08月20号
41
+ * @returns Promise<object> window.monaco
42
+ */
43
+ export function loadMonaco() {
44
+ if (!monacoPromise) {
45
+ monacoPromise = new Promise((resolve, reject) => {
46
+ const boot = () => {
47
+ try {
48
+ window.require.config({ paths: { vs: MONACO_BASE } })
49
+ window.require(['vs/editor/editor.main'], () => resolve(window.monaco), (err) => {
50
+ monacoPromise = null
51
+ reject(new Error('Monaco 模块加载失败:' + String(err)))
52
+ })
53
+ } catch (error) {
54
+ monacoPromise = null
55
+ reject(error)
56
+ }
57
+ }
58
+ const existing = document.querySelector('script[data-edrv-monaco-loader]')
59
+ if (existing) boot()
60
+ else {
61
+ const s = document.createElement('script')
62
+ s.src = MONACO_BASE + '/loader.js'
63
+ s.dataset.edrvMonacoLoader = '1'
64
+ s.onload = boot
65
+ s.onerror = () => { monacoPromise = null; reject(new Error('Monaco loader 加载失败')) }
66
+ document.head.appendChild(s)
67
+ }
68
+ })
69
+ }
70
+ return monacoPromise
71
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * dsh-vscode-mode client — 类型化 RPC 包装 + 诊断日志。
3
+ * 迁移自原 src/client/index.ts 的 rpc/dbg,语义不改。
4
+ * 作者 ddj 2026-08-20
5
+ */
6
+ import { RPC_PATH } from '../shared/rpc.js'
7
+ import type { RpcMethod, RpcRequestMap, RpcResult } from '../shared/rpc.js'
8
+
9
+ /** 同源 RPC 调用(host /edrv/rpc 精确路由)。 */
10
+ export function rpc<M extends RpcMethod>(method: M, args: RpcRequestMap[M]): Promise<RpcResult<M>> {
11
+ return fetch(RPC_PATH, {
12
+ method: 'POST',
13
+ headers: { 'content-type': 'application/json' },
14
+ body: JSON.stringify({ method, args }),
15
+ }).then((res) => res.json()) as Promise<RpcResult<M>>
16
+ }
17
+
18
+ /**
19
+ * 诊断日志:统一走 host edrv.debug 落盘,避免 console 刷屏。
20
+ * @author ddj 2026年08月20号
21
+ * @param sessionId 会话 id
22
+ * @param text 日志文本
23
+ */
24
+ export function dbg(sessionId: string | undefined, text: string): void {
25
+ try {
26
+ rpc('edrv.debug', { sessionId, text }).catch(() => {})
27
+ } catch (e) { /* 日志失败忽略 */ }
28
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * dsh-vscode-mode client — 记录/差异状态纯函数(可单测)。
3
+ * 迁移自原 src/client/index.ts 前段,语义一字不改。
4
+ * 作者 ddj 2026-08-20
5
+ */
6
+ import type { CallHunk, Hunk, RecordView } from '../../shared/types.js'
7
+
8
+ /** 差异决策状态。 */
9
+ export const ST = { PENDING: 'pending', ACCEPTED: 'accepted', REJECTED: 'rejected' } as const
10
+ export type Status = 'pending' | 'accepted' | 'rejected'
11
+
12
+ /** 记录视图摘要(按文件分组)。 */
13
+ export interface FileSummary {
14
+ path: string
15
+ recs: RecordView[]
16
+ pending: number
17
+ }
18
+ export interface Summary {
19
+ files: FileSummary[]
20
+ pendingFiles: FileSummary[]
21
+ totalFiles: number
22
+ }
23
+
24
+ /** hunk 的 DOM 定位 key(callId:idx)。 */
25
+ export function callIdAttr(callId: string, idx: number): string {
26
+ return String(callId) + ':' + String(idx)
27
+ }
28
+
29
+ /** 某 hunk 的当前决策状态(缺省 pending)。 */
30
+ export function statusAt(rec: RecordView | undefined, idx: number): Status {
31
+ if (!rec) return ST.PENDING
32
+ if (Array.isArray(rec.decisions?.perHunk) && rec.decisions.perHunk[idx] !== undefined) return rec.decisions.perHunk[idx] as Status
33
+ return (rec.decisions?.call as Status) ?? ST.PENDING
34
+ }
35
+
36
+ /** hunk 是否为空差异(old===new,无实际内容变化,不可操作)。create 记录不视为空差异。 */
37
+ export function noopHunk(rec: RecordView | undefined, h: Hunk | CallHunk | null | undefined): boolean {
38
+ if (!rec || rec.create === true) return false
39
+ const precise = rec.toolName === 'edit' && rec.callHunk ? rec.callHunk : h
40
+ const oldText = precise?.oldText ?? null
41
+ const newText = precise?.newText ?? null
42
+ return oldText !== null && oldText === newText
43
+ }
44
+
45
+ /** 记录是否仍有待处理差异(superseded / 全部已决策 = false)。 */
46
+ export function isRecPending(rec: RecordView): boolean {
47
+ if (!rec || rec.superseded === true) return false
48
+ const perHunk = Array.isArray(rec.decisions?.perHunk) ? rec.decisions.perHunk : []
49
+ if (perHunk.length) {
50
+ // 只认存在至少一个"非空差异且未决策"的 hunk
51
+ for (let i = 0; i < perHunk.length; i++) {
52
+ if (perHunk[i] !== 'accepted' && perHunk[i] !== 'rejected' && !noopHunk(rec, (rec.hunks || [])[i])) return true
53
+ }
54
+ return false
55
+ }
56
+ return rec.decisions.call === 'pending' || rec.decisions.call === undefined
57
+ }
58
+
59
+ /** 记录待处理差异"处"数(按 hunk,跳过空差异)。 */
60
+ export function pendingCount(recs: RecordView[]): number {
61
+ let n = 0
62
+ for (const rec of recs) {
63
+ if (!isRecPending(rec)) continue
64
+ const perHunk = Array.isArray(rec.decisions?.perHunk) ? rec.decisions.perHunk : []
65
+ if (perHunk.length) {
66
+ for (let i = 0; i < perHunk.length; i++) {
67
+ if (perHunk[i] === 'pending' && !noopHunk(rec, (rec.hunks || [])[i])) n++
68
+ }
69
+ } else {
70
+ const hunks = Array.isArray(rec.hunks) ? rec.hunks : []
71
+ for (const h of hunks) if (!noopHunk(rec, h)) n++
72
+ if (!hunks.length && !noopHunk(rec, rec.callHunk)) n = Math.max(n, 1)
73
+ }
74
+ }
75
+ return n
76
+ }
77
+
78
+ /** 按文件分组的差异摘要(角标/Launcher 用)。 */
79
+ export function summarize(records: RecordView[]): Summary {
80
+ const byPath = new Map<string, RecordView[]>()
81
+ for (const r of records) {
82
+ if (!byPath.has(r.path)) byPath.set(r.path, [])
83
+ byPath.get(r.path)!.push(r)
84
+ }
85
+ const files: FileSummary[] = []
86
+ for (const [path, recs] of byPath) files.push({ path, recs, pending: pendingCount(recs) })
87
+ files.sort((a, b) => (a.path < b.path ? -1 : 1))
88
+ const pendingFiles = files.filter((f) => f.pending > 0)
89
+ return { files, pendingFiles, totalFiles: pendingFiles.length }
90
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * dsh-vscode-mode client — 差异区域计算纯函数(可单测)。
3
+ * 迁移自原 src/client/index.ts 的 diffRegions/trimCommonLines/countLinesBefore,语义不改。
4
+ * 作者 ddj 2026-08-20
5
+ */
6
+ import type { Hunk, RecordView } from '../../shared/types.js'
7
+ import { ST, noopHunk, statusAt } from './records.js'
8
+ import type { Status } from './records.js'
9
+
10
+ /** 差异区域(文件内一段待处理/已处理的变更)。 */
11
+ export interface Region {
12
+ callId: string
13
+ idx: number
14
+ start?: number
15
+ end?: number
16
+ oldLines: string[]
17
+ newLines: string[]
18
+ whole?: boolean
19
+ status: Status
20
+ create: boolean
21
+ rec: RecordView
22
+ superseded: boolean
23
+ stale?: boolean
24
+ }
25
+
26
+ /** 统计 index 之前(不含)的换行数 → 0 基行号。 */
27
+ export function countLinesBefore(text: string, index: number): number {
28
+ let n = 0
29
+ for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) n++
30
+ return n
31
+ }
32
+
33
+ /**
34
+ * 行级公共前缀/后缀裁剪:old/new 首尾相同的行视为未变化(上下文),只保留真正变更的中间段。
35
+ * @author ddj 2026年08月20号
36
+ * @param oldLines 替换前内容按行拆分
37
+ * @param newLines 替换后内容按行拆分
38
+ * @returns 裁剪后的变更段与公共前缀行数
39
+ */
40
+ export function trimCommonLines(oldLines: string[], newLines: string[]): { oldLines: string[]; newLines: string[]; shift: number } {
41
+ let prefix = 0
42
+ const maxPrefix = Math.min(oldLines.length, newLines.length)
43
+ while (prefix < maxPrefix && oldLines[prefix] === newLines[prefix]) prefix++
44
+ let suffix = 0
45
+ const maxSuffix = Math.min(oldLines.length - prefix, newLines.length - prefix)
46
+ while (suffix < maxSuffix && oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix]) suffix++
47
+ return { oldLines: oldLines.slice(prefix, oldLines.length - suffix), newLines: newLines.slice(prefix, newLines.length - suffix), shift: prefix }
48
+ }
49
+
50
+ /** 计算文件内各差异区域(行范围 + old/new + 状态),用于行内绿标注与 DiffBox。 */
51
+ export function diffRegions(records: RecordView[], content: string | null): Region[] {
52
+ const regions: Region[] = []
53
+ if (content === null) return regions
54
+ const lines = content.split('\n')
55
+ for (const rec of records) {
56
+ for (let i = 0; i < rec.hunks.length; i++) {
57
+ const h: Hunk = rec.hunks[i]
58
+ if (noopHunk(rec, h)) continue // 空差异(old===new)无实际变化,跳过
59
+ const status = statusAt(rec, i)
60
+ if (rec.create) {
61
+ regions.push({ callId: rec.callId, idx: i, start: 1, end: lines.length, oldLines: [], newLines: lines.slice(), whole: true, status, create: true, rec, superseded: rec.superseded === true })
62
+ continue
63
+ }
64
+ const precise = rec.toolName === 'edit' && rec.callHunk ? rec.callHunk : h
65
+ const newText = precise.newText ?? ''
66
+ const at = content.indexOf(newText)
67
+ if (at < 0) {
68
+ regions.push({ callId: rec.callId, idx: i, stale: true, status, create: false, oldLines: [], newLines: [], rec, superseded: rec.superseded === true })
69
+ continue
70
+ }
71
+ const start = countLinesBefore(content, at) + 1
72
+ const oldLines = precise.oldText === null ? [] : precise.oldText.split('\n')
73
+ const newLines = newText.split('\n')
74
+ const trimmed = trimCommonLines(oldLines, newLines)
75
+ const regionStart = start + trimmed.shift
76
+ regions.push({ callId: rec.callId, idx: i, start: regionStart, end: regionStart + trimmed.newLines.length, oldLines: trimmed.oldLines, newLines: trimmed.newLines, status, create: false, rec, superseded: rec.superseded === true })
77
+ }
78
+ }
79
+ regions.sort((a, b) => (a.start ?? Infinity) - (b.start ?? Infinity))
80
+ return regions
81
+ }
@@ -0,0 +1,2 @@
1
+ /** tsdown CSS-inline 插件识别的纯 CSS 导入声明。 */
2
+ declare module '*.css'
@@ -0,0 +1,87 @@
1
+ /* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */
2
+ /* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):
3
+ 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */
4
+ [data-edrv-view] { --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }
5
+
6
+ [data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }
7
+ [data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
8
+ [data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
9
+ [data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }
10
+ [data-edrv-view] .edrv-tab .edrv-tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }
11
+ [data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }
12
+ [data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: rgba(255,255,255,.12); color: var(--dsw-alias-label-primary, #eee); }
13
+ [data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }
14
+ [data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
15
+ [data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }
16
+ [data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
17
+ [data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }
18
+ [data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }
19
+ [data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }
20
+ [data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }
21
+ [data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }
22
+ [data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }
23
+ [data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }
24
+ [data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }
25
+ [data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }
26
+ [data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
27
+ [data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }
28
+ [data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: #fff; font-weight: 600; white-space: nowrap; }
29
+ [data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }
30
+ [data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }
31
+ [data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }
32
+ [data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }
33
+ [data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }
34
+ [data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }
35
+ [data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }
36
+ [data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
37
+ [data-edrv-view] .edrv-mn-add-line { background: rgba(15,157,88,.10); }
38
+ [data-edrv-view] .edrv-mn-gutter-add { color: #0f9d58 !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }
39
+ [data-edrv-view] .edrv-mn-gutter-add::before { content: "+"; }
40
+ [data-edrv-view] .edrv-del-zone { background: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px rgba(217,83,79,.12); }
41
+ [data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }
42
+ [data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }
43
+ [data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; pointer-events: none; z-index: 6; }
44
+ [data-edrv-view] .edrv-minus-item { position: absolute; width: 16px; text-align: center; color: #d9534f; font-weight: 700; font-size: 12.5px; line-height: 20px; }
45
+ [data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }
46
+ [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }
47
+ [data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }
48
+ [data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 16px rgba(31,41,51,.12); overflow: visible; display: flex; flex-direction: column; }
49
+ [data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; flex-wrap: nowrap; }
50
+ [data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }
51
+ [data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }
52
+ [data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
53
+ [data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }
54
+ [data-edrv-view] .edrv-pill-keep { background: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }
55
+ [data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }
56
+ [data-edrv-view] .edrv-pill-ghost { background: #ffffff; color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
57
+ [data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }
58
+ [data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }
59
+ [data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }
60
+ [data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
61
+ [data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }
62
+ [data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }
63
+ [data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
64
+ [data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }
65
+ [data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
66
+ [data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }
67
+ [data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }
68
+ [data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }
69
+ [data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }
70
+ [data-edrv-view] .edrv-diffrow-stale { border-color: rgba(217,119,6,.5); cursor: default; }
71
+ [data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }
72
+ [data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
73
+ [data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }
74
+ /* 编辑区 hover 差异块的 Keep/Undo 浮层 */
75
+ [data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }
76
+ [data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }
77
+ [data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }
78
+ [data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }
79
+ [data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }
80
+ [data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }
81
+ [data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }
82
+ [data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
83
+ [data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }
84
+ [data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }
85
+ [data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }
86
+ [data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }
87
+ [data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }
@@ -0,0 +1,51 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — DiffBadge:header 差异角标(仅工作区有差异时显示)。
4
+ * 迁移自原 src/client/index.ts 的 DiffBadge,语义不改。
5
+ * 作者 ddj 2026-08-20
6
+ */
7
+ import React from 'react'
8
+ import { rpc } from '../rpc.js'
9
+ import { openEditorView, emitShowLauncher } from '../events.js'
10
+ import { summarize } from '../state/records.js'
11
+
12
+ /**
13
+ * 会话 header 差异角标:5s 轮询 edrv.list,有差异时显示数字徽章,点击打开编辑区并拉起 DiffLauncher。
14
+ */
15
+ export function DiffBadge(props) {
16
+ const sessionId = props?.sessionId
17
+ const [n, setN] = React.useState(0)
18
+ const seq = React.useRef(0)
19
+
20
+ const load = React.useCallback(() => {
21
+ if (!sessionId) return
22
+ const s = ++seq.current
23
+ rpc('edrv.list', { sessionId }).then((res) => {
24
+ if (s !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return
25
+ setN(summarize(res.records).totalFiles)
26
+ }).catch(() => {})
27
+ }, [sessionId])
28
+
29
+ React.useEffect(() => {
30
+ load()
31
+ const t = setInterval(load, 5000)
32
+ const onRefresh = () => load()
33
+ window.addEventListener('edrv:refresh', onRefresh)
34
+ return () => { clearInterval(t); window.removeEventListener('edrv:refresh', onRefresh) }
35
+ }, [load])
36
+
37
+ if (!n) return null
38
+
39
+ const click = () => {
40
+ openEditorView(null)
41
+ setTimeout(() => { emitShowLauncher() }, 120)
42
+ }
43
+
44
+ return React.createElement('button', {
45
+ onClick: click,
46
+ title: n + ' 个文件有差异,点击在编辑区查看',
47
+ style: { position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 30, height: 30, padding: 0, background: 'transparent', border: 'none', borderRadius: 6, cursor: 'pointer' },
48
+ },
49
+ React.createElement('img', { src: '/edrv/assets/compare-select.png', alt: '差异', style: { width: 22, height: 22, display: 'block' } }),
50
+ React.createElement('span', { style: { position: 'absolute', top: 1, right: 0, minWidth: 15, height: 15, padding: '0 3px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-primary,#d97706)', color: '#fff', fontSize: 10, lineHeight: '15px', textAlign: 'center', fontWeight: 700, boxSizing: 'border-box' } }, String(n)))
51
+ }
@@ -0,0 +1,37 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — DiffBarEmpty:无文件打开 / 当前文件无差异但有差异文件时的空态浮窗。
4
+ * 迁移自原 src/client/index.ts 的 DiffBarEmpty,语义不改。
5
+ * 作者 ddj 2026-08-20
6
+ */
7
+ import React from 'react'
8
+
9
+ /**
10
+ * 空态差异条:提示当前状态 + 查看下一个差异文件 + ⋮(总览/归档/刷新)。
11
+ */
12
+ export function DiffBarEmpty(props) {
13
+ const { sum, active, staleCount, onNextFile, onOpenLauncher, onRefresh } = props
14
+ const [menuOpen, setMenuOpen] = React.useState(false)
15
+ const n = sum?.totalFiles ?? 0
16
+ const tip = active
17
+ ? (staleCount > 0 ? '当前文件有 ' + staleCount + ' 处差异无法定位(可能已被后续修改影响)' : '当前文件无待处理差异')
18
+ : '未打开文件'
19
+
20
+ const menu = menuOpen
21
+ ? React.createElement(React.Fragment, null,
22
+ React.createElement('div', { style: { position: 'fixed', inset: 0, zIndex: 44 }, onClick: () => setMenuOpen(false) }),
23
+ React.createElement('div', { className: 'edrv-diffbar-menu' },
24
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onOpenLauncher('pending'); setMenuOpen(false) } }, '🗂 差异总览'),
25
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onOpenLauncher('archive'); setMenuOpen(false) } }, '📁 归档'),
26
+ React.createElement('div', { className: 'edrv-diffmenu-sep' }),
27
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onRefresh(); setMenuOpen(false) } }, '⟳ 刷新')))
28
+ : null
29
+
30
+ return React.createElement('div', { className: 'edrv-diffbar', 'data-edrv-diffbox': '1' },
31
+ React.createElement('div', { className: 'edrv-diffbar-row' },
32
+ React.createElement('span', { className: 'edrv-diffbar-count' }, '⚠ 差异 ' + n + ' 文件'),
33
+ React.createElement('span', { className: 'edrv-diffbar-file' }, tip),
34
+ React.createElement('button', { className: 'edrv-pill edrv-pill-keep', title: '打开并跳转到下一个差异文件', onClick: onNextFile }, '查看下一个差异文件'),
35
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '更多操作', onClick: () => setMenuOpen((v) => !v) }, '⋮')),
36
+ menu)
37
+ }
@@ -0,0 +1,79 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
4
+ * 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
5
+ * 作者 ddj 2026-08-20
6
+ */
7
+ import React from 'react'
8
+ import { callIdAttr } from '../state/records.js'
9
+ import { badgeOf } from './shared.js'
10
+
11
+ /**
12
+ * 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
13
+ * (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
14
+ */
15
+ export function DiffBox(props) {
16
+ const {
17
+ pendingRegions, staleRegions, onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles,
18
+ allPendingCount, onRollback, onJump, otherFiles, onOpenOther, onOpenLauncher, onRefresh, activePath,
19
+ diffIdx, diffTotal, fileIdx, fileTotal, onPrevDiff, onNextDiff, onPrevFile, onNextFile,
20
+ } = props
21
+ const [expanded, setExpanded] = React.useState(false)
22
+ const [menuOpen, setMenuOpen] = React.useState(false)
23
+ const canAct = pendingRegions.length > 0
24
+
25
+ const base = String(activePath || '').split(/[\\/]/).pop() || ''
26
+
27
+ let bodyEl = null
28
+ if (expanded) {
29
+ const rows = pendingRegions.map((r) => {
30
+ const key = callIdAttr(r.callId, r.idx)
31
+ return React.createElement('div', { key, className: 'edrv-diffrow', 'data-edrv-hunk': key, title: '跳转到 L' + (r.start ?? '?'), onClick: () => onJump(r) },
32
+ React.createElement('span', { className: 'edrv-diffrow-l' }, 'L' + (r.start ?? '?') + '-' + (r.end ?? '?')),
33
+ (r.oldLines.length ? React.createElement('span', { className: 'edrv-diffrow-o' }, '-' + r.oldLines.length) : null),
34
+ (r.newLines.length ? React.createElement('span', { className: 'edrv-diffrow-n' }, '+' + r.newLines.length) : null),
35
+ (r.create ? React.createElement('span', { className: 'edrv-diffrow-tag' }, '新建') : null),
36
+ React.createElement('span', { style: { flex: 1 } }))
37
+ })
38
+ const stale = staleRegions.map((r) => React.createElement('div', { key: 'stale' + callIdAttr(r.callId, r.idx), className: 'edrv-diffrow edrv-diffrow-stale', title: '差异无法定位(文件可能已被手动修改)' },
39
+ React.createElement('span', { className: 'edrv-diffrow-l' }, 'L' + (r.start ?? '?') + ' 无法定位'),
40
+ React.createElement('span', { className: 'edrv-diffrow-tag' }, badgeOf(r.status))))
41
+ let othersEl = null
42
+ if (otherFiles.length) {
43
+ othersEl = React.createElement('div', { className: 'edrv-diffbox-others' },
44
+ React.createElement('span', { className: 'edrv-diffrow-tag' }, '其他差异文件 (' + otherFiles.length + '):'),
45
+ otherFiles.map((f) => React.createElement('button', { key: f.path, className: 'edrv-diffrow-file', title: f.path, onClick: () => onOpenOther(f.path) },
46
+ String(f.path).split(/[\\/]/).pop() + ' (' + f.pending + ')')))
47
+ }
48
+ bodyEl = React.createElement('div', { className: 'edrv-diffbar-body' }, ...rows, ...stale, othersEl)
49
+ }
50
+
51
+ const menu = menuOpen
52
+ ? React.createElement(React.Fragment, null,
53
+ React.createElement('div', { style: { position: 'fixed', inset: 0, zIndex: 44 }, onClick: () => setMenuOpen(false) }),
54
+ React.createElement('div', { className: 'edrv-diffbar-menu' },
55
+ React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !allPendingCount, onClick: () => { onAcceptAllFiles(); setMenuOpen(false) } }, '✓ Keep All(全部文件采纳)'),
56
+ React.createElement('button', { className: 'edrv-diffmenu-item danger', disabled: !allPendingCount, onClick: () => { onUndoAllFiles(); setMenuOpen(false) } }, '↩ Undo All(全部文件不采纳)'),
57
+ React.createElement('div', { className: 'edrv-diffmenu-sep' }),
58
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onOpenLauncher('pending'); setMenuOpen(false) } }, '🗂 差异总览'),
59
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onOpenLauncher('archive'); setMenuOpen(false) } }, '📁 归档'),
60
+ React.createElement('div', { className: 'edrv-diffmenu-sep' }),
61
+ React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !canAct, onClick: () => { onRollback(); setMenuOpen(false) } }, '⟲ 回滚文件'),
62
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onRefresh(); setMenuOpen(false) } }, '⟳ 刷新')))
63
+ : null
64
+
65
+ return React.createElement('div', { className: 'edrv-diffbar', 'data-edrv-diffbox': '1' },
66
+ React.createElement('div', { className: 'edrv-diffbar-row' },
67
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异', disabled: !canAct, onClick: onPrevDiff }, '↑'),
68
+ React.createElement('span', { className: 'edrv-diffbar-count', title: '当前文件内差异位置(点击展开/收起差异列表)', onClick: () => setExpanded((v) => !v) }, diffTotal ? (diffIdx + 1) + '/' + diffTotal : '0/0'),
69
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异', disabled: !canAct, onClick: onNextDiff }, '↓'),
70
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异文件', disabled: !fileTotal, onClick: onPrevFile }, '←'),
71
+ React.createElement('span', { className: 'edrv-diffbar-count edrv-count-file', title: '差异文件位置' }, fileTotal ? (fileIdx + 1) + '/' + fileTotal + ' 文件' : '0/0 文件'),
72
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异文件', disabled: !fileTotal, onClick: onNextFile }, '→'),
73
+ React.createElement('span', { className: 'edrv-diffbar-file', title: activePath || '' }, base),
74
+ React.createElement('button', { className: 'edrv-pill edrv-pill-keep', title: '采纳当前文件的全部差异', disabled: !canAct, onClick: onAcceptFile }, '✓ Keep'),
75
+ React.createElement('button', { className: 'edrv-pill edrv-pill-undo', title: '不采纳当前文件的全部差异(回滚)', disabled: !canAct, onClick: onUndoFile }, '↩ Undo'),
76
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '更多操作', onClick: () => setMenuOpen((v) => !v) }, '⋮')),
77
+ bodyEl,
78
+ menu)
79
+ }