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.
- package/README.md +95 -0
- package/assets/compare_idle.png +0 -0
- package/assets/compare_select.png +0 -0
- package/assets/vendor/monaco/vs/abap-D-t0cyap.js +1 -0
- package/assets/vendor/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/assets/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +97 -0
- package/assets/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +30 -0
- package/assets/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +30 -0
- package/assets/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +474 -0
- package/assets/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +62 -0
- package/assets/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +67722 -0
- package/assets/vendor/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/assets/vendor/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/bat-C397hTD6.js +1 -0
- package/assets/vendor/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/assets/vendor/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/assets/vendor/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/assets/vendor/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/assets/vendor/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/assets/vendor/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/assets/vendor/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/assets/vendor/monaco/vs/css-CaeNmE3S.js +3 -0
- package/assets/vendor/monaco/vs/css.worker-CyhWkhHo.js +1 -0
- package/assets/vendor/monaco/vs/cssMode-CV6Ay48H.js +1 -0
- package/assets/vendor/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/assets/vendor/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/assets/vendor/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/assets/vendor/monaco/vs/ecl-30fUercY.js +1 -0
- package/assets/vendor/monaco/vs/editor/editor.main.css +1 -0
- package/assets/vendor/monaco/vs/editor/editor.main.js +1 -0
- package/assets/vendor/monaco/vs/editor/editor.worker.js +1 -0
- package/assets/vendor/monaco/vs/editor-KLE6jdfb.js +831 -0
- package/assets/vendor/monaco/vs/editor.js +1 -0
- package/assets/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +35 -0
- package/assets/vendor/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/assets/vendor/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/assets/vendor/monaco/vs/freemarker2-FWreY7v7.js +3 -0
- package/assets/vendor/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/assets/vendor/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/assets/vendor/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/assets/vendor/monaco/vs/handlebars-CZi4pHCH.js +1 -0
- package/assets/vendor/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/assets/vendor/monaco/vs/html-j_7ZTNRU.js +1 -0
- package/assets/vendor/monaco/vs/html.worker-CA3iAimZ.js +1 -0
- package/assets/vendor/monaco/vs/htmlMode-27by_KM4.js +1 -0
- package/assets/vendor/monaco/vs/index-049xrXrp.js +1 -0
- package/assets/vendor/monaco/vs/index-CBVt3dzv.js +5 -0
- package/assets/vendor/monaco/vs/index.js +1 -0
- package/assets/vendor/monaco/vs/ini-CsNwO04R.js +1 -0
- package/assets/vendor/monaco/vs/initialize-DL0l1TGY.js +1 -0
- package/assets/vendor/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/assets/vendor/monaco/vs/javascript-DTJl4Jn1.js +1 -0
- package/assets/vendor/monaco/vs/json.worker-BizpAl9O.js +1 -0
- package/assets/vendor/monaco/vs/jsonMode-DLUiMUrI.js +1 -0
- package/assets/vendor/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/assets/vendor/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/assets/vendor/monaco/vs/language/css/css.worker.js +66 -0
- package/assets/vendor/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/html/html.worker.js +443 -0
- package/assets/vendor/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/json/json.worker.js +17 -0
- package/assets/vendor/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/assets/vendor/monaco/vs/language/typescript/ts.worker.js +67693 -0
- package/assets/vendor/monaco/vs/less-C0eDYdqa.js +2 -0
- package/assets/vendor/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/assets/vendor/monaco/vs/liquid-CLJYelW6.js +1 -0
- package/assets/vendor/monaco/vs/loader.js +1368 -0
- package/assets/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +2 -0
- package/assets/vendor/monaco/vs/lua-DtygF91M.js +1 -0
- package/assets/vendor/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/assets/vendor/monaco/vs/main-BEx-Fmlo.js +15 -0
- package/assets/vendor/monaco/vs/main-DsK8pnKg.js +3 -0
- package/assets/vendor/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/assets/vendor/monaco/vs/mdx-CpzZIPHF.js +1 -0
- package/assets/vendor/monaco/vs/mips-CiYP61RB.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +1 -0
- package/assets/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +1 -0
- package/assets/vendor/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/assets/vendor/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/assets/vendor/monaco/vs/nls/lang/zh-cn.d.ts +1 -0
- package/assets/vendor/monaco/vs/nls/lang/zh-cn.js +9 -0
- package/assets/vendor/monaco/vs/nls.messages-loader.js +1 -0
- package/assets/vendor/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/assets/vendor/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/assets/vendor/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/assets/vendor/monaco/vs/perl-DABw_TcH.js +1 -0
- package/assets/vendor/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/assets/vendor/monaco/vs/php-D_kh-9LK.js +1 -0
- package/assets/vendor/monaco/vs/pla-VfZjczW0.js +1 -0
- package/assets/vendor/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/assets/vendor/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/assets/vendor/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/assets/vendor/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/assets/vendor/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/assets/vendor/monaco/vs/python-CqWUUgfu.js +1 -0
- package/assets/vendor/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/assets/vendor/monaco/vs/r-f8dDdrp4.js +1 -0
- package/assets/vendor/monaco/vs/razor-hok5y9Km.js +1 -0
- package/assets/vendor/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/assets/vendor/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/assets/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/assets/vendor/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/assets/vendor/monaco/vs/rust-Bfetafyc.js +1 -0
- package/assets/vendor/monaco/vs/sb-3GYllVck.js +1 -0
- package/assets/vendor/monaco/vs/scala-foMgrKo1.js +1 -0
- package/assets/vendor/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/assets/vendor/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/assets/vendor/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/assets/vendor/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/assets/vendor/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/assets/vendor/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/assets/vendor/monaco/vs/sql-32GpJSV2.js +1 -0
- package/assets/vendor/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/assets/vendor/monaco/vs/swift-t-PfMj7W.js +1 -0
- package/assets/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/assets/vendor/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/assets/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +61 -0
- package/assets/vendor/monaco/vs/ts.worker-2QLmBukE.js +1 -0
- package/assets/vendor/monaco/vs/tsMode-B0S_kp2q.js +11 -0
- package/assets/vendor/monaco/vs/twig-C6taOxMV.js +1 -0
- package/assets/vendor/monaco/vs/typescript-_2t_511t.js +1 -0
- package/assets/vendor/monaco/vs/typespec-8OfoLt6R.js +1 -0
- package/assets/vendor/monaco/vs/vb-Dyb2648j.js +1 -0
- package/assets/vendor/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/assets/vendor/monaco/vs/workers-BBttULjf.js +1 -0
- package/assets/vendor/monaco/vs/xml-cZjNDqDT.js +1 -0
- package/assets/vendor/monaco/vs/yaml-A1fOIdH6.js +1 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +1914 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1298 -0
- package/lib/index.js.map +1 -0
- package/package.json +60 -0
- package/src/capture.ts +73 -0
- package/src/client/events.ts +36 -0
- package/src/client/index.ts +37 -0
- package/src/client/monaco/diffRender.ts +162 -0
- package/src/client/monaco/loader.ts +71 -0
- package/src/client/rpc.ts +28 -0
- package/src/client/state/records.ts +90 -0
- package/src/client/state/regions.ts +81 -0
- package/src/client/styles/css.d.ts +2 -0
- package/src/client/styles/editor.css +87 -0
- package/src/client/ui/DiffBadge.ts +51 -0
- package/src/client/ui/DiffBarEmpty.ts +37 -0
- package/src/client/ui/DiffBox.ts +79 -0
- package/src/client/ui/DiffLauncher.ts +124 -0
- package/src/client/ui/EditorView.ts +577 -0
- package/src/client/ui/QuickOpen.ts +91 -0
- package/src/client/ui/shared.ts +28 -0
- package/src/index.ts +43 -0
- package/src/model.ts +197 -0
- package/src/registry.ts +35 -0
- package/src/revert.ts +107 -0
- package/src/routes.ts +138 -0
- package/src/rpc.ts +295 -0
- package/src/shared/rpc.ts +67 -0
- package/src/shared/types.ts +121 -0
- package/src/store.ts +244 -0
- 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,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
|
+
}
|