dsh-vscode-mode 0.1.7 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vscode-mode",
3
- "version": "0.1.7",
3
+ "version": "0.1.10",
4
4
  "description": "DSH 上的类 VSCode 编码体验:Monaco 中央编辑器(文件页签/QuickOpen/状态栏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚),状态持久化到工作区旁车",
5
5
  "keywords": [
6
6
  "dsh",
@@ -86,8 +86,15 @@ export function createDiffRenderer(log) {
86
86
  const lineH = editor.getOption(monaco.editor.EditorOption.lineHeight) || 20
87
87
  let li = null
88
88
  try { li = editor.getLayoutInfo() } catch (e) { /* ignore */ }
89
- const left = li ? li.decorationsLeft : 0
89
+ const rawLeft = li ? li.decorationsLeft : 0
90
90
  const width = li && li.decorationsWidth > 0 ? li.decorationsWidth : 16
91
+ // decorationsLeft 是 Monaco 内部坐标,需限制在宿主根节点内,避免窄窗口/边框
92
+ // 或布局尚未完成时把减号绘制到编辑器左、右边界之外。
93
+ const maxLeft = Math.max(0, root.clientWidth - width)
94
+ // 字符本身有少量字体侧向留白;给标记列留出 4px 内缩,避免减号半个
95
+ // 字符落到 Monaco 根节点左边框外,同时仍限制右侧不越界。
96
+ const markerInset = 12
97
+ const left = Math.max(0, Math.min(maxLeft, rawLeft + markerInset))
91
98
  const lineCount = editor.getModel().getLineCount()
92
99
  let itemIdx = 0
93
100
  let zi = 0
@@ -100,7 +107,8 @@ export function createDiffRenderer(log) {
100
107
  for (let i = 0; i < z.n; i++) {
101
108
  const item = document.createElement('div')
102
109
  item.className = 'edrv-minus-item'
103
- item.textContent = '-'
110
+ // 使用 CSS 线段代替文本字符,避免不同字体的侧向留白把 “-” 绘制到边界外。
111
+ item.textContent = ''
104
112
  item.style.width = width + 'px'
105
113
  item.style.height = lineH + 'px'
106
114
  item.dataset.zone = String(zi)
@@ -130,7 +138,14 @@ export function createDiffRenderer(log) {
130
138
  const edRect = root.getBoundingClientRect()
131
139
  const st = editor.getScrollTop() || 0
132
140
  const items = overlay.querySelectorAll('.edrv-minus-item')
141
+ let correctedLeft = left
142
+ try {
143
+ const layout = editor.getLayoutInfo()
144
+ const markerWidth = layout.decorationsWidth > 0 ? layout.decorationsWidth : width
145
+ correctedLeft = Math.max(0, Math.min(Math.max(0, root.clientWidth - markerWidth), (layout.decorationsLeft || 0) + 12))
146
+ } catch (e) { /* 保留首次布局计算结果 */ }
133
147
  for (const item of items) {
148
+ item.style.left = correctedLeft + 'px'
134
149
  const zIndex = Number(item.dataset.zone)
135
150
  const rIndex = Number(item.dataset.row)
136
151
  const z = createdZones[zIndex]
@@ -7,6 +7,46 @@
7
7
 
8
8
  export const MONACO_BASE = '/edrv/vendor/monaco/vs'
9
9
  let monacoPromise = null
10
+ let monacoStage = { phase: 'idle', progress: 0, message: '准备加载 Monaco…' }
11
+ const stageListeners = new Set()
12
+
13
+ /**
14
+ * Monaco 加载阶段:这是阶段进度而非网络字节进度,避免误导用户。
15
+ * @author ddj 2026年08月22号
16
+ */
17
+ export const MONACO_STAGES = {
18
+ loader: { progress: 18, message: '加载 Monaco 引导模块…' },
19
+ core: { progress: 72, message: '加载编辑器核心模块…' },
20
+ ready: { progress: 100, message: 'Monaco 编辑器已就绪' },
21
+ error: { progress: 0, message: 'Monaco 编辑器加载失败' },
22
+ }
23
+
24
+ /**
25
+ * 发布 Monaco 加载阶段,监听器异常不得影响编辑器加载。
26
+ * @author ddj 2026年08月22号
27
+ * @param phase 阶段名
28
+ * @param progress 阶段百分比
29
+ * @param message 用户可读状态
30
+ */
31
+ function publishStage(phase, progress, message) {
32
+ monacoStage = { phase, progress, message }
33
+ for (const listener of stageListeners) {
34
+ try { listener(monacoStage) } catch (error) { /* UI 回调异常忽略 */ }
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 订阅 Monaco 加载阶段。
40
+ * @author ddj 2026年08月22号
41
+ * @param listener 阶段回调
42
+ * @returns 取消订阅函数
43
+ */
44
+ function subscribeStage(listener) {
45
+ if (typeof listener !== 'function') return () => {}
46
+ stageListeners.add(listener)
47
+ listener(monacoStage)
48
+ return () => stageListeners.delete(listener)
49
+ }
10
50
 
11
51
  /** 扩展名 → Monaco language id(常见语言;未知回退 plaintext)。 */
12
52
  export const LANG_BY_EXT = {
@@ -40,19 +80,26 @@ export function langOf(path) {
40
80
  * @author ddj 2026年08月20号
41
81
  * @returns Promise<object> window.monaco
42
82
  */
43
- export function loadMonaco() {
83
+ export function loadMonaco(onProgress) {
84
+ const unsubscribe = subscribeStage(onProgress)
44
85
  if (!monacoPromise) {
86
+ publishStage('loader', MONACO_STAGES.loader.progress, MONACO_STAGES.loader.message)
45
87
  monacoPromise = new Promise((resolve, reject) => {
88
+ const fail = (error) => {
89
+ monacoPromise = null
90
+ publishStage('error', MONACO_STAGES.error.progress, MONACO_STAGES.error.message)
91
+ reject(error)
92
+ }
46
93
  const boot = () => {
47
94
  try {
48
95
  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
- })
96
+ publishStage('core', MONACO_STAGES.core.progress, MONACO_STAGES.core.message)
97
+ window.require(['vs/editor/editor.main'], () => {
98
+ publishStage('ready', MONACO_STAGES.ready.progress, MONACO_STAGES.ready.message)
99
+ resolve(window.monaco)
100
+ }, (err) => fail(new Error('Monaco 模块加载失败:' + String(err))))
53
101
  } catch (error) {
54
- monacoPromise = null
55
- reject(error)
102
+ fail(error)
56
103
  }
57
104
  }
58
105
  const existing = document.querySelector('script[data-edrv-monaco-loader]')
@@ -62,10 +109,10 @@ export function loadMonaco() {
62
109
  s.src = MONACO_BASE + '/loader.js'
63
110
  s.dataset.edrvMonacoLoader = '1'
64
111
  s.onload = boot
65
- s.onerror = () => { monacoPromise = null; reject(new Error('Monaco loader 加载失败')) }
112
+ s.onerror = () => fail(new Error('Monaco loader 加载失败'))
66
113
  document.head.appendChild(s)
67
114
  }
68
115
  })
69
116
  }
70
- return monacoPromise
117
+ return monacoPromise.finally(unsubscribe)
71
118
  }
@@ -40,8 +40,9 @@
40
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
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
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; }
43
+ [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
+ [data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }
45
+ [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; }
45
46
  [data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }
46
47
  [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }
47
48
  [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; }
@@ -83,5 +84,18 @@
83
84
  [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
85
  [data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }
85
86
  [data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }
87
+ [data-edrv-view] .monaco-editor { overflow: hidden !important; }
88
+ [data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }
89
+ [data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,
90
+ [data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }
86
91
  [data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }
87
92
  [data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }
93
+ [data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }
94
+ [data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }
95
+ [data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }
96
+ [data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }
97
+ [data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }
98
+ @media (prefers-reduced-motion: reduce) {
99
+ [data-edrv-view] .edrv-progress-value { transition: none; }
100
+ }
101
+
@@ -35,6 +35,8 @@ export function EditorView(props) {
35
35
  const [content, setContent] = React.useState(null)
36
36
  const [status, setStatus] = React.useState('')
37
37
  const [error, setError] = React.useState(null)
38
+ const [loadError, setLoadError] = React.useState(null)
39
+ const [loadStage, setLoadStage] = React.useState({ progress: 0, message: '准备加载编辑器…' })
38
40
  const [openInput, setOpenInput] = React.useState(false)
39
41
  const [pathDraft, setPathDraft] = React.useState('')
40
42
  const [cursor, setCursor] = React.useState('')
@@ -118,11 +120,27 @@ export function EditorView(props) {
118
120
 
119
121
  const loadContent = (path, sid) => {
120
122
  const seq = ++loadSeqRef.current
123
+ setLoadError(null)
124
+ setLoadStage({ progress: monaco ? 72 : 12, message: '读取文件内容…' })
121
125
  rpc('edrv.read', { sessionId: sid, path }).then((res) => {
122
126
  if (seq !== loadSeqRef.current || path !== active) return
123
- if (res && res.ok) { setContent(res.content); setStatus('已加载') }
124
- else { setError(res?.error ? String(res.error) : '读取失败'); setStatus('读取失败') }
125
- }).catch((e) => { if (seq === loadSeqRef.current) { setError('read异常:' + String(e)); setStatus('读取失败') } })
127
+ if (res && res.ok) {
128
+ setContent(res.content)
129
+ setLoadStage((prev) => ({ progress: Math.max(84, prev.progress), message: '文件已读取,准备创建编辑器…' }))
130
+ setStatus('已加载')
131
+ } else {
132
+ const message = res?.error ? String(res.error) : '读取失败'
133
+ setLoadError(message)
134
+ setError(message)
135
+ setStatus('读取失败')
136
+ }
137
+ }).catch((e) => {
138
+ if (seq !== loadSeqRef.current) return
139
+ const message = 'read异常:' + String(e)
140
+ setLoadError(message)
141
+ setError(message)
142
+ setStatus('读取失败')
143
+ })
126
144
  }
127
145
 
128
146
  // 挂载轮询 + 事件订阅
@@ -173,14 +191,27 @@ export function EditorView(props) {
173
191
 
174
192
  React.useEffect(() => {
175
193
  if (!active) return
176
- setContent(null); setStatus('加载中…')
194
+ setContent(null)
195
+ setLoadError(null)
196
+ setLoadStage({ progress: 10, message: '准备读取文件…' })
197
+ setStatus('加载中…')
177
198
  loadContent(active, sessionId)
178
199
  }, [active, sessionId])
179
200
 
180
201
  React.useEffect(() => {
181
202
  if (monaco || monacoErr) return
182
203
  let alive = true
183
- loadMonaco().then((m) => { if (alive) setMonaco(m) }).catch((e) => { if (alive) { setMonacoErr(String(e?.message ?? e)); setStatus('Monaco 不可用') } })
204
+ const onProgress = (stage) => {
205
+ if (!alive) return
206
+ const progress = stage.phase === 'ready' ? 70 : Math.round(10 + stage.progress * 0.6)
207
+ setLoadStage((prev) => ({ progress: Math.max(prev.progress, progress), message: stage.message }))
208
+ }
209
+ loadMonaco(onProgress).then((m) => { if (alive) setMonaco(m) }).catch((e) => {
210
+ if (alive) {
211
+ setMonacoErr(String(e?.message ?? e))
212
+ setStatus('Monaco 不可用')
213
+ }
214
+ })
184
215
  return () => { alive = false }
185
216
  }, [monaco, monacoErr])
186
217
 
@@ -235,12 +266,14 @@ export function EditorView(props) {
235
266
  const ed = editorRef.current
236
267
  const model = getModel(active, content)
237
268
  if (ed.getModel() !== model) ed.setModel(model)
269
+ setLoadStage((prev) => ({ progress: Math.max(96, prev.progress), message: '创建编辑器视图…' }))
238
270
  }, [monaco, active, content])
239
271
 
240
272
  // 行内差异自绘(decorations / view zones / minus overlay)→ diffRenderer
241
273
  React.useEffect(() => {
242
274
  if (!monaco || !editorRef.current || !active || content === null) return
243
275
  diffRendererRef.current.render(monaco, editorRef.current, pendingRegions, sessionId)
276
+ setLoadStage({ progress: 100, message: '编辑器已就绪' })
244
277
  }, [monaco, active, content, pendingRegions])
245
278
 
246
279
  React.useEffect(() => () => {
@@ -525,19 +558,48 @@ export function EditorView(props) {
525
558
 
526
559
  const otherFiles = sum.pendingFiles.filter((f) => f.path !== active)
527
560
 
561
+ /**
562
+ * 渲染编辑器/文件加载进度面板。
563
+ * @author ddj 2026年08月22号
564
+ * @param message 当前加载阶段
565
+ * @param progress 阶段进度
566
+ * @param retry 可选重试回调
567
+ * @returns 加载面板 React 元素
568
+ */
569
+ const loadingBody = (message, progress, retry) => React.createElement('div', { className: 'edrv-empty edrv-loading', 'aria-live': 'polite' },
570
+ React.createElement('div', { className: 'edrv-loading-title' }, message),
571
+ React.createElement('div', {
572
+ className: 'edrv-progress',
573
+ role: 'progressbar',
574
+ 'aria-label': '编辑器加载进度',
575
+ 'aria-valuemin': 0,
576
+ 'aria-valuemax': 100,
577
+ 'aria-valuenow': progress,
578
+ }, React.createElement('div', { className: 'edrv-progress-value', style: { width: Math.max(0, Math.min(100, progress)) + '%' } })),
579
+ React.createElement('div', { className: 'edrv-loading-percent' }, Math.round(progress) + '%'),
580
+ retry ? React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: retry }, '重试') : null)
581
+
528
582
  let body
529
583
  if (!monaco && !monacoErr) {
530
- body = React.createElement('div', { className: 'edrv-empty' }, '正在加载 Monaco 编辑器…')
584
+ body = loadingBody(loadStage.message, loadStage.progress)
531
585
  } else if (monacoErr) {
532
586
  body = React.createElement('div', { className: 'edrv-empty' },
533
587
  React.createElement('div', null, 'Monaco 编辑器加载失败:' + String(monacoErr)),
534
- React.createElement('div', { style: { fontSize: 11 } }, '请确认插件包 assets/vendor/monaco 完整(/edrv/vendor 路由可达)'))
588
+ React.createElement('div', { style: { fontSize: 11 } }, '请确认插件包 assets/vendor/monaco 完整(/edrv/vendor 路由可达)'),
589
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: () => { setMonacoErr(null); setLoadStage({ progress: 0, message: '准备重试 Monaco…' }) } }, '重试'))
535
590
  } else if (!active) {
536
591
  body = React.createElement('div', { className: 'edrv-empty' },
537
592
  React.createElement('div', null, '暂无打开的文件'),
538
593
  React.createElement('div', { style: { fontSize: 12 } }, '使用右上搜索框 (Ctrl+P) 打开工作区文件;agent 修改文件后顶部会出现差异角标'))
594
+ } else if (content === null && loadError) {
595
+ body = loadingBody('文件加载失败:' + loadError, 0, () => {
596
+ setLoadError(null)
597
+ setContent(null)
598
+ setLoadStage({ progress: 10, message: '重新读取文件…' })
599
+ loadContent(active, sessionId)
600
+ })
539
601
  } else if (content === null) {
540
- body = React.createElement('div', { className: 'edrv-empty' }, '加载中…')
602
+ body = loadingBody(loadStage.message || '读取文件内容…', loadStage.progress)
541
603
  } else {
542
604
  body = React.createElement('div', { className: 'edrv-monaco-host' },
543
605
  React.createElement('div', { ref: ensureEditor }))