dsh-vscode-mode 0.1.17 → 0.1.20

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.
@@ -0,0 +1,161 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
4
+ * 点目录展开/收起(首次经 edrv.listDir 拉取并缓存),点文件经 ctx.openFile 打开;
5
+ * 差异角标取 pendingByPath,活动文件高亮;edrv:refresh 事件与手动刷新重载树。
6
+ * 作者 ddj 2026-08-26
7
+ */
8
+ import React from 'react'
9
+ import { rpc } from '../../rpc.js'
10
+ import type { SidebarCtx } from '../types.js'
11
+
12
+ const DIR_CAP = 4000
13
+
14
+ /**
15
+ * 目录树面板主体。
16
+ * @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath)
17
+ */
18
+ export function FileExplorer(props) {
19
+ const ctx = props?.ctx
20
+ const sessionId = ctx?.sessionId
21
+ const openFile = ctx?.openFile
22
+ const activePath = ctx?.activePath ?? null
23
+ const pendingByPath = ctx?.pendingByPath ?? {}
24
+ const [root, setRoot] = React.useState(null)
25
+ const [dirs, setDirs] = React.useState({})
26
+ const [expanded, setExpanded] = React.useState({})
27
+ const [loading, setLoading] = React.useState({})
28
+ const [error, setError] = React.useState(null)
29
+ const tokensRef = React.useRef({})
30
+ // expanded 的 ref 镜像:edrv:refresh 监听用首次渲染闭包,但需读到最新展开态
31
+ const expandedRef = React.useRef({})
32
+ expandedRef.current = expanded
33
+
34
+ const loadDir = (rel) => {
35
+ const token = (tokensRef.current[rel] || 0) + 1
36
+ tokensRef.current[rel] = token
37
+ setLoading((prev) => Object.assign({}, prev, { [rel]: true }))
38
+ setError(null)
39
+ rpc('edrv.listDir', { sessionId, path: rel }).then((res) => {
40
+ if (tokensRef.current[rel] !== token) return
41
+ if (res && res.ok && Array.isArray(res.entries)) {
42
+ setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }))
43
+ if (res.root && !root) setRoot(res.root)
44
+ } else {
45
+ setError(res?.error ? String(res.error) : '读取目录失败')
46
+ }
47
+ }).catch((e) => {
48
+ if (tokensRef.current[rel] !== token) return
49
+ setError('读取目录异常:' + String(e))
50
+ }).finally(() => {
51
+ if (tokensRef.current[rel] !== token) return
52
+ setLoading((prev) => {
53
+ const next = Object.assign({}, prev)
54
+ delete next[rel]
55
+ return next
56
+ })
57
+ })
58
+ }
59
+
60
+ const toggle = (rel) => {
61
+ if (expanded[rel] === true) {
62
+ setExpanded((prev) => Object.assign({}, prev, { [rel]: false }))
63
+ return
64
+ }
65
+ if (!dirs[rel]) void loadDir(rel)
66
+ setExpanded((prev) => Object.assign({}, prev, { [rel]: true }))
67
+ }
68
+
69
+ const refresh = () => {
70
+ const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true)
71
+ tokensRef.current = {}
72
+ setDirs({})
73
+ setLoading({})
74
+ setError(null)
75
+ if (keep.length) {
76
+ const next = {}
77
+ for (const rel of keep) next[rel] = true
78
+ setExpanded(next)
79
+ for (const rel of keep) void loadDir(rel)
80
+ } else {
81
+ setExpanded({})
82
+ }
83
+ void loadDir('')
84
+ }
85
+
86
+ React.useEffect(() => {
87
+ tokensRef.current = {}
88
+ setDirs({})
89
+ setExpanded({})
90
+ setLoading({})
91
+ setError(null)
92
+ setRoot(null)
93
+ void loadDir('')
94
+ // eslint-disable-next-line react-hooks/exhaustive-deps
95
+ }, [sessionId])
96
+
97
+ React.useEffect(() => {
98
+ const onRefresh = () => refresh()
99
+ window.addEventListener('edrv:refresh', onRefresh)
100
+ return () => window.removeEventListener('edrv:refresh', onRefresh)
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [])
103
+
104
+ const rootName = root ? String(root).split(/[\\/]/).pop() || root : ''
105
+
106
+ const rowEl = (e, depth, isDir, isOpen) => {
107
+ const pending = isDir ? 0 : (pendingByPath[e.path] ?? 0)
108
+ const active = !isDir && e.path === activePath
109
+ const dim = e.type === 'other'
110
+ return React.createElement('div', {
111
+ key: e.path,
112
+ className: 'edrv-tree-row' + (active ? ' edrv-tree-active' : ''),
113
+ title: e.path,
114
+ style: { paddingLeft: 6 + depth * 14 },
115
+ onClick: () => { if (isDir) toggle(e.path); else openFile(e.path) },
116
+ },
117
+ React.createElement('span', { className: 'edrv-tree-chev' },
118
+ isDir ? (isOpen ? '▾' : '▸') : ''),
119
+ React.createElement('span', { className: 'edrv-tree-name' + (dim ? ' edrv-tree-dim' : '') },
120
+ isDir ? (isOpen ? '📂' : '📁') : '📄', ' ', e.name),
121
+ (pending > 0
122
+ ? React.createElement('span', { className: 'edrv-tree-badge' }, String(pending))
123
+ : null))
124
+ }
125
+
126
+ const rowsOf = (rel, depth) => {
127
+ const entries = dirs[rel]
128
+ if (!entries) {
129
+ return [React.createElement('div', { key: rel + ':loading', className: 'edrv-tree-loading', style: { paddingLeft: 6 + depth * 14 } },
130
+ loading[rel] ? '加载中…' : '无法加载')]
131
+ }
132
+ const rows = []
133
+ const cap = entries.length
134
+ for (let i = 0; i < cap; i++) {
135
+ const e = entries[i]
136
+ const isDir = e.type === 'directory'
137
+ const isOpen = expanded[e.path] === true
138
+ rows.push(rowEl(e, depth, isDir, isOpen))
139
+ if (isDir && isOpen) rows.push(...rowsOf(e.path, depth + 1))
140
+ }
141
+ if (cap >= DIR_CAP) {
142
+ rows.push(React.createElement('div', { key: rel + ':cap', className: 'edrv-tree-loading', style: { paddingLeft: 6 + depth * 14 } },
143
+ '目录条目过多,仅显示前 ' + DIR_CAP + ' 项'))
144
+ }
145
+ return rows
146
+ }
147
+
148
+ return React.createElement('div', { className: 'edrv-side-panel' },
149
+ React.createElement('div', { className: 'edrv-side-head' },
150
+ React.createElement('span', { className: 'edrv-side-title' }, '资源管理器'),
151
+ React.createElement('span', { className: 'edrv-side-root', title: root || '' }, rootName),
152
+ React.createElement('span', { style: { flex: 1 } }),
153
+ React.createElement('button', { className: 'edrv-side-btn', title: '刷新目录树', onClick: refresh }, '⟳')),
154
+ React.createElement('div', { className: 'edrv-tree' },
155
+ (error
156
+ ? React.createElement('div', { className: 'edrv-tree-error' },
157
+ React.createElement('span', null, String(error)),
158
+ React.createElement('button', { className: 'edrv-side-btn', onClick: () => refresh() }, '重试'))
159
+ : null),
160
+ rowsOf('', 0)))
161
+ }
@@ -0,0 +1,24 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
4
+ * 作者 ddj 2026-08-26
5
+ */
6
+ import React from 'react'
7
+ import { FileExplorer } from './FileExplorer.js'
8
+ import type { SidebarPanelDef, SidebarCtx } from '../types.js'
9
+
10
+ /**
11
+ * 构造文件管理面板定义。
12
+ * @author ddj 2026年08月26号
13
+ * @returns 面板定义(含活动栏徽标 = 差异文件总数)
14
+ */
15
+ export function createFilePanel(): SidebarPanelDef {
16
+ return {
17
+ id: 'explorer',
18
+ title: '文件管理',
19
+ icon: '📁',
20
+ order: 10,
21
+ badge: (ctx: SidebarCtx) => (ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null),
22
+ render: (ctx: SidebarCtx) => React.createElement(FileExplorer, { ctx }),
23
+ }
24
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * dsh-vscode-mode client — 侧边栏面板注册表。
3
+ * 镜像 fileOpeners.ts 的注册表模式:create + register(返回注销) + list + subscribe,
4
+ * 由 client/index.ts `ctx.provide('edrvSidebarPanels', registry)` 对外暴露(第三方可注册)。
5
+ * 作者 ddj 2026-08-26
6
+ */
7
+ import type { SidebarPanelDef } from './types.js'
8
+
9
+ export interface SidebarPanelRegistry {
10
+ register(panel: SidebarPanelDef): () => void
11
+ list(): readonly SidebarPanelDef[]
12
+ subscribe(listener: () => void): () => void
13
+ get(id: string): SidebarPanelDef | undefined
14
+ }
15
+
16
+ /** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
17
+ function panelRegister(entries: Map<string, SidebarPanelDef>, notify: () => void, panel: SidebarPanelDef): void {
18
+ if (!panel.id || typeof panel.render !== 'function') throw new TypeError('侧边栏面板必须提供 id 和 render')
19
+ entries.set(panel.id, panel)
20
+ notify()
21
+ }
22
+
23
+ /**
24
+ * 创建生命周期独立的面板注册表。
25
+ * @author ddj 2026年08月26号
26
+ * @returns 面板注册表
27
+ */
28
+ export function createSidebarPanelRegistry(): SidebarPanelRegistry {
29
+ const entries = new Map<string, SidebarPanelDef>()
30
+ const listeners = new Set<() => void>()
31
+ const notify = (): void => {
32
+ for (const listener of listeners) listener()
33
+ }
34
+ const list = (): readonly SidebarPanelDef[] =>
35
+ [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
36
+ return {
37
+ register(panel: SidebarPanelDef): () => void {
38
+ panelRegister(entries, notify, panel)
39
+ return () => {
40
+ if (entries.get(panel.id) !== panel) return
41
+ entries.delete(panel.id)
42
+ notify()
43
+ }
44
+ },
45
+ list,
46
+ subscribe(listener: () => void): () => void {
47
+ listeners.add(listener)
48
+ return () => listeners.delete(listener)
49
+ },
50
+ get: (id: string) => entries.get(id),
51
+ }
52
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * dsh-vscode-mode client — 侧边栏面板契约(类型 + 上下文)。
3
+ * 面板 = 活动栏一项 + 面板区一处渲染;新增面板只需注册一条 SidebarPanelDef。
4
+ * 作者 ddj 2026-08-26
5
+ */
6
+
7
+ /** 面板可用的共享上下文(由 SidebarView 从 EditorView 注入,面板不直碰其内部)。 */
8
+ export interface SidebarCtx {
9
+ sessionId?: string
10
+ openFile: (path: string) => void
11
+ activePath: string | null
12
+ /** 文件路径 → 待处理差异数(>0 才收录)。 */
13
+ pendingByPath: Record<string, number>
14
+ /** 按文件分组的差异摘要(records 状态派生)。 */
15
+ sum: { totalFiles: number; files: Array<{ path: string; pending: number }> }
16
+ /** 触发差异记录刷新(保存/决策后由 EditorView 统一 emitRefresh)。 */
17
+ refreshRecords: () => void
18
+ }
19
+
20
+ /** 单个侧边栏面板定义。 */
21
+ export interface SidebarPanelDef {
22
+ id: string
23
+ title: string
24
+ /** 活动栏图标(文本/SVG 均可,横向居中显示)。 */
25
+ icon: string
26
+ order?: number
27
+ /** 活动栏徽标:返回数字/null;缺省不显示。 */
28
+ badge?: (ctx: SidebarCtx) => number | null
29
+ /** 面板区渲染(ctx 每次渲染注入最新快照)。 */
30
+ render: (ctx: SidebarCtx) => unknown
31
+ }
@@ -1,7 +1,7 @@
1
1
  /* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */
2
2
  /* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):
3
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); }
4
+ [data-edrv-view] { box-sizing: border-box; --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
5
 
6
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
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; }
@@ -15,6 +15,8 @@
15
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
16
  [data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
17
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-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }
19
+ [data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }
18
20
  [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
21
  [data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }
20
22
  [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; }
@@ -43,11 +45,12 @@
43
45
  [data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }
44
46
  [data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }
45
47
  [data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
46
- [data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }
47
- [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }
48
+ [data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
49
+ [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }
48
50
  [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; }
49
- [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; }
50
- [data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; flex-wrap: nowrap; }
51
+ [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: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }
52
+ [data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }
53
+ [data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }
51
54
  [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; }
52
55
  [data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }
53
56
  [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; }
@@ -61,6 +64,12 @@
61
64
  [data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
62
65
  [data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }
63
66
  [data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }
67
+ /* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */
68
+ [data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }
69
+ [data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
70
+ [data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
71
+ [data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }
72
+ [data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }
64
73
  [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); }
65
74
  [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; }
66
75
  [data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
@@ -99,3 +108,116 @@
99
108
  [data-edrv-view] .edrv-progress-value { transition: none; }
100
109
  }
101
110
 
111
+ /* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */
112
+ [data-edrv-diff-dock-shell] {
113
+ box-sizing: border-box;
114
+ width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));
115
+ max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));
116
+ margin: 0 auto;
117
+ padding: 0;
118
+ flex: none;
119
+ border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);
120
+ border-radius: 12px;
121
+ background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));
122
+ box-shadow: none;
123
+ overflow: hidden;
124
+ }
125
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock {
126
+ box-sizing: border-box;
127
+ position: relative;
128
+ left: auto;
129
+ right: auto;
130
+ bottom: auto;
131
+ z-index: auto;
132
+ width: 100%;
133
+ max-width: none;
134
+ margin: 0;
135
+ display: flex;
136
+ flex-direction: column;
137
+ border: none;
138
+ border-radius: 0;
139
+ background: transparent;
140
+ box-shadow: none;
141
+ overflow: visible;
142
+ }
143
+ [data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }
144
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }
145
+ [data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }
146
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
147
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }
148
+ [data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }
149
+ [data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }
150
+ [data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }
151
+ [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }
152
+ [data-edrv-diff-dock-shell] .edrv-diffbar-nav,
153
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files,
154
+ [data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }
155
+ [data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }
156
+ [data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }
157
+ [data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }
158
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }
159
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }
160
+ [data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }
161
+ [data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }
162
+ [data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }
163
+ [data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }
164
+ [data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }
165
+ [data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }
166
+ [data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }
167
+ [data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }
168
+ [data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }
169
+ [data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }
170
+ [data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }
171
+ [data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }
172
+ [data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }
173
+ [data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }
174
+ [data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }
175
+ [data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }
176
+ [data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }
177
+ [data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }
178
+ [data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
179
+ [data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }
180
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }
181
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
182
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }
183
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }
184
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }
185
+ @media (max-width: 620px) {
186
+ [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }
187
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }
188
+ }
189
+
190
+ /* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */
191
+ [data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }
192
+ [data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }
193
+ [data-edrv-view] .edrv-main-col > .edrv-pathbar,
194
+ [data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }
195
+ [data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
196
+ [data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 0; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }
197
+ [data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }
198
+ [data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }
199
+ [data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
200
+ [data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }
201
+ [data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }
202
+ [data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }
203
+ [data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }
204
+ [data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
205
+ [data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }
206
+ [data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
207
+ [data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
208
+ [data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }
209
+ [data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
210
+ [data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }
211
+ [data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }
212
+ [data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
213
+ [data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }
214
+ [data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }
215
+ [data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
216
+ [data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
217
+ [data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }
218
+ [data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }
219
+ [data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }
220
+ [data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }
221
+ [data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }
222
+ [data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }
223
+
@@ -0,0 +1,84 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 会话级差异 dock。
4
+ * conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
5
+ * 作者 ddj 2026-08-26
6
+ */
7
+ import React from 'react'
8
+ import { rpc } from '../rpc.js'
9
+ import { openDiffView } from '../events.js'
10
+ import { summarize } from '../state/records.js'
11
+ import { nextDiffPath } from '../diffDock.js'
12
+ import { readDiffDock, subscribeDiffDock } from '../diffDockStore.js'
13
+ import { DiffBox } from './DiffBox.js'
14
+
15
+ const nextIndexBySession = new Map()
16
+
17
+ /**
18
+ * 对话输入框上方的唯一差异 dock。
19
+ * @author ddj 2026年08月26号
20
+ * @param props DSH 注入的会话属性
21
+ * @returns 差异 dock React 元素或 null
22
+ */
23
+ export function ConversationDiffDock(props) {
24
+ const sessionId = props?.sessionId
25
+ const [summary, setSummary] = React.useState(null)
26
+ const [, setStoreRevision] = React.useState(0)
27
+ const seq = React.useRef(0)
28
+
29
+ const load = React.useCallback(() => {
30
+ if (!sessionId) return
31
+ const current = ++seq.current
32
+ rpc('edrv.list', { sessionId }).then((res) => {
33
+ if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return
34
+ setSummary(summarize(res.records))
35
+ }).catch(() => {})
36
+ }, [sessionId])
37
+
38
+ React.useEffect(() => {
39
+ load()
40
+ const timer = setInterval(load, 5000)
41
+ const onRefresh = () => load()
42
+ window.addEventListener('edrv:refresh', onRefresh)
43
+ return () => {
44
+ clearInterval(timer)
45
+ window.removeEventListener('edrv:refresh', onRefresh)
46
+ }
47
+ }, [load])
48
+
49
+ React.useEffect(() => {
50
+ if (!sessionId) return undefined
51
+ const sync = () => setStoreRevision((value) => value + 1)
52
+ sync()
53
+ return subscribeDiffDock(sessionId, sync)
54
+ }, [sessionId])
55
+
56
+ const renderDock = (content, key) => React.createElement('div', {
57
+ key,
58
+ className: 'edrv-diff-dock-shell',
59
+ 'data-edrv-diff-dock-shell': '1',
60
+ }, content)
61
+
62
+ const editorSnapshot = readDiffDock(sessionId)
63
+ if (editorSnapshot) {
64
+ if (editorSnapshot.mode === 'editor-empty' && !editorSnapshot.fileTotal) return null
65
+ return renderDock(React.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId)
66
+ }
67
+
68
+ if (!sessionId || !summary?.pendingFiles?.length) return null
69
+
70
+ const paths = summary.pendingFiles.map((file) => file.path)
71
+ const click = () => {
72
+ const current = nextIndexBySession.get(sessionId) ?? 0
73
+ const next = nextDiffPath(paths, current)
74
+ nextIndexBySession.set(sessionId, next.index)
75
+ if (next.path) openDiffView(next.path)
76
+ }
77
+
78
+ return renderDock(React.createElement(DiffBox, {
79
+ mode: 'chat',
80
+ dock: true,
81
+ fileTotal: paths.length,
82
+ onOpenNextFile: click,
83
+ }), sessionId || 'chat')
84
+ }