dsh-vscode-mode 0.20.3 → 0.20.4
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 +5 -4
- package/lib/client.js +349 -232
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/reviewBar.ts +41 -0
- package/src/client/styles/editor.css +3 -0
- package/src/client/ui/DiffBox.ts +11 -108
- package/src/client/ui/EditorView.ts +11 -1
- package/src/client/ui/OfficialDiffBar.tsx +23 -18
- package/src/client/ui/PerfSettings.ts +1 -1
- package/src/client/ui/SidecarDiffBar.tsx +154 -0
- package/src/client/ui/reviewAllRow.tsx +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.4",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车,界面状态(页签/光标/侧栏/编辑 Tab/导航历史)镜像到 DSH home 跨重启恢复",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"keywords": [
|
package/src/client/reviewBar.ts
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* 官方差异栏派生视图:位置、导航提示、失效说明与 Keep 提示的唯一来源。
|
|
3
3
|
* 计数口径分离:待审是「记录数」,可跳转是「块数」,两者不同源,
|
|
4
4
|
* 全部块失效时必须给出可操作叙事,不能只留一句「无法定位」。
|
|
5
|
+
* 另含侧车(legacy)栏的标识文案与官方链路不可用说明:两套栏外观相近,
|
|
6
|
+
* 必须显式区分,否则用户会误判为插件未升级。
|
|
5
7
|
* 作者 ddj 2026年10月27号
|
|
6
8
|
*/
|
|
9
|
+
import { sessionErrorText } from './sessionError.js'
|
|
7
10
|
|
|
8
11
|
/** 官方差异栏输入:全部来自 EditorView dock 快照,不读取磁盘或 RPC。 */
|
|
9
12
|
export interface ReviewBarInput {
|
|
@@ -83,3 +86,41 @@ export function reviewBarView(input: ReviewBarInput): ReviewBarView {
|
|
|
83
86
|
|
|
84
87
|
return { position: '—', navPrevTitle: '当前文件没有可定位差异', navNextTitle: '当前文件没有可定位差异', notice: '', noticeTone: 'info', keepHint, pendingTitle }
|
|
85
88
|
}
|
|
89
|
+
|
|
90
|
+
/** 非官方栏标识:没有官方待审记录时回落的那套栏必须自述来源,避免被当成旧版插件。 */
|
|
91
|
+
export const SIDECAR_LABEL = '历史改动'
|
|
92
|
+
|
|
93
|
+
/** 非官方栏标识的悬停说明:讲清这些改动为何不在官方列表里。 */
|
|
94
|
+
export const SIDECAR_LABEL_TITLE = '这些改动没有进入官方审查列表,是插件早期版本自动保存的改动留痕。Keep 只标记为已看过,不改文件;Undo 会校验后写回文件。'
|
|
95
|
+
|
|
96
|
+
/** 非官方 Keep 的作用域说明:只标记记录,不写磁盘。 */
|
|
97
|
+
export const SIDECAR_KEEP_TITLE = '标记这些历史改动为已看过:只改记录,不改写磁盘文件,也不影响官方审查。'
|
|
98
|
+
|
|
99
|
+
/** 非官方 Undo 的作用域说明:走原有回滚校验写回磁盘,但不影响官方记录。 */
|
|
100
|
+
export const SIDECAR_UNDO_TITLE = '撤销这些历史改动(写回受回滚校验保护):只影响这份历史记录,不影响官方审查。'
|
|
101
|
+
|
|
102
|
+
/** 当前端没有该会话时,差异栏里给中性说明而不是红色报错。 */
|
|
103
|
+
export const SESSION_DOWN_NOTICE = '这个会话没在当前端激活,官方审查数据暂时读不到;下面的历史改动仍可查看、Keep、Undo。'
|
|
104
|
+
|
|
105
|
+
/** 官方链路不可用提示;null 表示官方元数据正常,不需要提示。 */
|
|
106
|
+
export interface OfficialDownView {
|
|
107
|
+
/** 提示正文,已含「官方差异不可用」前缀与可读原因 */
|
|
108
|
+
text: string
|
|
109
|
+
/** 重试按钮文案 */
|
|
110
|
+
retryLabel: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 把官方元数据通道的失败翻译成可操作提示。
|
|
115
|
+
* @public
|
|
116
|
+
* @author ddj 2026年10月28号
|
|
117
|
+
* @param error `edrv.review.list` 返回的错误原文(reviewFeed 的 error 字段)
|
|
118
|
+
* @returns 提示正文与重试文案;空错误表示官方通道正常
|
|
119
|
+
*/
|
|
120
|
+
export function officialDownView(error: unknown): OfficialDownView | null {
|
|
121
|
+
const reason = sessionErrorText(error).trim()
|
|
122
|
+
if (!reason) return null
|
|
123
|
+
|
|
124
|
+
return { text: `官方差异不可用:${reason}`, retryLabel: '重试' }
|
|
125
|
+
}
|
|
126
|
+
|
|
@@ -305,6 +305,9 @@
|
|
|
305
305
|
[data-edrv-diff-dock-shell] .edrv-review-notice { padding: 7px 12px; border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; overflow-wrap: anywhere; }
|
|
306
306
|
[data-edrv-diff-dock-shell] .edrv-review-error { color: var(--dsw-alias-state-error-primary, #b64043); }
|
|
307
307
|
[data-edrv-diff-dock-shell] .edrv-review-notice-warn { color: var(--dsw-alias-state-warning-primary, #a86a1f); }
|
|
308
|
+
[data-edrv-diff-dock-shell] .edrv-review-source { flex: none; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-secondary, #d4dae3); color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; line-height: 16px; white-space: nowrap; }
|
|
309
|
+
[data-edrv-diff-dock-shell] .edrv-sidecar-down { display: flex; align-items: center; gap: 8px; }
|
|
310
|
+
[data-edrv-diff-dock-shell] .edrv-sidecar-down-text { flex: 1; min-width: 0; }
|
|
308
311
|
[data-edrv-diff-dock-shell] .edrv-review-body { border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); max-height: min(240px, 30vh); overflow: auto; padding: 8px 12px; }
|
|
309
312
|
@container edrv-review (max-width: 420px) {
|
|
310
313
|
.edrv-reviewbar .edrv-review-heading { gap: 6px; padding-inline: 10px; }
|
package/src/client/ui/DiffBox.ts
CHANGED
|
@@ -1,58 +1,24 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
|
-
* dsh-vscode-mode client — DiffBox
|
|
4
|
-
*
|
|
3
|
+
* dsh-vscode-mode client — DiffBox:差异条入口,按数据源选栏。
|
|
4
|
+
* 官方记录 → OfficialDiffBar;其余(侧车历史捕获)→ SidecarDiffBar,
|
|
5
|
+
* 两套栏同版式,避免用户把侧车栏误判成旧版插件。
|
|
5
6
|
* 作者 ddj 2026-08-20
|
|
6
7
|
*/
|
|
7
8
|
import React from 'react'
|
|
8
|
-
import {
|
|
9
|
-
import { canDecideFile, diffDockText } from '../diffDock.js'
|
|
10
|
-
import { badgeOf } from './shared.js'
|
|
9
|
+
import { diffDockText } from '../diffDock.js'
|
|
11
10
|
import { OfficialDiffBar } from './OfficialDiffBar.js'
|
|
11
|
+
import { SidecarDiffBar } from './SidecarDiffBar.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* 差异条:当前文件内 diff 导航、Keep/Undo、详情展开与 ⋮ 二级菜单。
|
|
15
|
+
* @public
|
|
15
16
|
* @author ddj 2026年08月25号
|
|
16
|
-
* @param
|
|
17
|
-
* @returns
|
|
18
|
-
*/
|
|
19
|
-
function chevronOf(open) {
|
|
20
|
-
const path = open ? 'M3.25 5.25 7 9l3.75-3.75' : 'M3.25 8.75 7 5l3.75 3.75'
|
|
21
|
-
return React.createElement('svg', {
|
|
22
|
-
width: 14,
|
|
23
|
-
height: 14,
|
|
24
|
-
viewBox: '0 0 14 14',
|
|
25
|
-
fill: 'none',
|
|
26
|
-
'aria-hidden': true,
|
|
27
|
-
focusable: false,
|
|
28
|
-
}, React.createElement('path', {
|
|
29
|
-
d: path,
|
|
30
|
-
stroke: 'currentColor',
|
|
31
|
-
strokeWidth: 1.2,
|
|
32
|
-
strokeLinecap: 'round',
|
|
33
|
-
strokeLinejoin: 'round',
|
|
34
|
-
}))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
|
|
39
|
-
* (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
|
|
17
|
+
* @param props 差异上下文(官方或侧车),由 EditorView dock 快照提供
|
|
18
|
+
* @returns 会话态精简入口,或按数据源路由到的差异栏
|
|
40
19
|
*/
|
|
41
20
|
export function DiffBox(props) {
|
|
42
|
-
const {
|
|
43
|
-
pendingRegions = [], staleRegions = [], onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles,
|
|
44
|
-
allPendingCount = 0, onRollback, onJump, otherFiles = [], onOpenOther, onOpenLauncher, onRefresh, activePath,
|
|
45
|
-
diffIdx = 0, diffTotal = 0, fileIdx = 0, fileTotal = 0, onPrevDiff, onNextDiff, onPrevFile, onNextFile,
|
|
46
|
-
mode = 'editor', onOpenNextFile, dock = false,
|
|
47
|
-
} = props
|
|
48
|
-
const [expanded, setExpanded] = React.useState(false)
|
|
49
|
-
const [detailsOpen, setDetailsOpen] = React.useState(false)
|
|
50
|
-
const detailsId = React.useId()
|
|
51
|
-
const canAct = pendingRegions.length > 0
|
|
52
|
-
// Keep/Undo 覆盖无法定位的冲突差异:它们仍占待处理列表,但不在 pendingRegions 内
|
|
53
|
-
const canDecide = canDecideFile(pendingRegions.length, staleRegions.length)
|
|
54
|
-
|
|
55
|
-
const base = String(activePath || '').split(/[\\/]/).pop() || ''
|
|
21
|
+
const { fileTotal = 0, onOpenNextFile, mode = 'editor' } = props
|
|
56
22
|
|
|
57
23
|
if (mode === 'chat' || mode === 'editor-empty') {
|
|
58
24
|
return React.createElement('div', { className: 'edrv-diffbar edrv-diffbar-dock', 'data-edrv-diffbox': '1', 'data-edrv-diff-mode': mode },
|
|
@@ -67,68 +33,5 @@ export function DiffBox(props) {
|
|
|
67
33
|
}
|
|
68
34
|
|
|
69
35
|
if (props.official) return React.createElement(OfficialDiffBar, props)
|
|
70
|
-
|
|
71
|
-
let bodyEl = null
|
|
72
|
-
if (expanded) {
|
|
73
|
-
const rows = pendingRegions.map((r) => {
|
|
74
|
-
const key = callIdAttr(r.callId, r.idx)
|
|
75
|
-
return React.createElement('div', { key, className: 'edrv-diffrow', 'data-edrv-hunk': key, title: '跳转到 L' + (r.start ?? '?'), onClick: () => onJump(r) },
|
|
76
|
-
React.createElement('span', { className: 'edrv-diffrow-l' }, 'L' + (r.start ?? '?') + '-' + (r.end ?? '?')),
|
|
77
|
-
(r.oldLines.length ? React.createElement('span', { className: 'edrv-diffrow-o' }, '-' + r.oldLines.length) : null),
|
|
78
|
-
(r.newLines.length ? React.createElement('span', { className: 'edrv-diffrow-n' }, '+' + r.newLines.length) : null),
|
|
79
|
-
(r.create ? React.createElement('span', { className: 'edrv-diffrow-tag' }, '新建') : null),
|
|
80
|
-
React.createElement('span', { style: { flex: 1 } }))
|
|
81
|
-
})
|
|
82
|
-
const stale = staleRegions.map((r) => React.createElement('div', { key: 'stale' + callIdAttr(r.callId, r.idx), className: 'edrv-diffrow edrv-diffrow-stale', title: '差异无法定位(文件可能已被手动修改)' },
|
|
83
|
-
React.createElement('span', { className: 'edrv-diffrow-l' }, 'L' + (r.start ?? '?') + ' 无法定位'),
|
|
84
|
-
React.createElement('span', { className: 'edrv-diffrow-tag' }, '冲突/需刷新'),
|
|
85
|
-
React.createElement('span', { className: 'edrv-diffrow-tag' }, badgeOf(r.status))))
|
|
86
|
-
let othersEl = null
|
|
87
|
-
if (otherFiles.length) {
|
|
88
|
-
othersEl = React.createElement('div', { className: 'edrv-diffbox-others' },
|
|
89
|
-
React.createElement('span', { className: 'edrv-diffrow-tag' }, '其他差异文件 (' + otherFiles.length + '):'),
|
|
90
|
-
otherFiles.map((f) => React.createElement('button', { key: f.path, className: 'edrv-diffrow-file', title: f.label ?? f.path, onClick: () => onOpenOther(f.path) },
|
|
91
|
-
String(f.path).split(/[\\/]/).pop() + ' (' + f.pending + ')')))
|
|
92
|
-
}
|
|
93
|
-
bodyEl = React.createElement('div', { className: 'edrv-diffbar-body' }, ...rows, ...stale, othersEl)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const details = detailsOpen
|
|
97
|
-
? React.createElement('div', { className: 'edrv-diff-details', id: detailsId },
|
|
98
|
-
React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !allPendingCount, onClick: onAcceptAllFiles }, '✓ Keep All(全部文件采纳)'),
|
|
99
|
-
React.createElement('button', { className: 'edrv-diffmenu-item danger', disabled: !allPendingCount, onClick: onUndoAllFiles }, '↩ Undo All(全部文件不采纳)'),
|
|
100
|
-
React.createElement('div', { className: 'edrv-diffmenu-sep' }),
|
|
101
|
-
React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => onOpenLauncher('pending') }, '🗂 差异总览'),
|
|
102
|
-
React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => onOpenLauncher('archive') }, '📁 归档'),
|
|
103
|
-
React.createElement('div', { className: 'edrv-diffmenu-sep' }),
|
|
104
|
-
React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !canAct, onClick: onRollback }, '⟲ 回滚文件'),
|
|
105
|
-
React.createElement('button', { className: 'edrv-diffmenu-item', onClick: onRefresh }, '⟳ 刷新'))
|
|
106
|
-
: null
|
|
107
|
-
|
|
108
|
-
return React.createElement('div', { className: 'edrv-diffbar edrv-diffbar-dock', 'data-edrv-diffbox': '1', 'data-edrv-diff-mode': 'editor' },
|
|
109
|
-
React.createElement('div', { className: 'edrv-diffbar-row edrv-diffbar-editor-row' },
|
|
110
|
-
React.createElement('div', { className: 'edrv-diffbar-editor-content' },
|
|
111
|
-
React.createElement('div', { className: 'edrv-diffbar-nav' },
|
|
112
|
-
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异', disabled: !canAct, onClick: onPrevDiff }, '↑'),
|
|
113
|
-
React.createElement('span', { className: 'edrv-diffbar-count edrv-diffbar-progress', title: '当前文件内差异位置(点击展开/收起差异列表)', onClick: () => setExpanded((v) => !v) }, diffTotal ? (diffIdx + 1) + '/' + diffTotal : '0/0'),
|
|
114
|
-
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异', disabled: !canAct, onClick: onNextDiff }, '↓')),
|
|
115
|
-
React.createElement('div', { className: 'edrv-diffbar-files' },
|
|
116
|
-
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异文件', disabled: !fileTotal, onClick: onPrevFile }, '←'),
|
|
117
|
-
React.createElement('span', { className: 'edrv-diffbar-count edrv-count-file edrv-diffbar-progress', title: '差异文件位置' }, fileTotal ? (fileIdx + 1) + '/' + fileTotal + ' 文件' : '0/0 文件'),
|
|
118
|
-
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异文件', disabled: !fileTotal, onClick: onNextFile }, '→'),
|
|
119
|
-
React.createElement('span', { className: 'edrv-diffbar-file edrv-diffbar-summary', title: activePath || '' }, base)),
|
|
120
|
-
|
|
121
|
-
React.createElement('div', { className: 'edrv-diffbar-actions' },
|
|
122
|
-
React.createElement('button', { className: 'edrv-pill edrv-pill-keep', title: '采纳当前文件的全部差异', disabled: !canDecide, onClick: onAcceptFile }, '✓ Keep'),
|
|
123
|
-
React.createElement('button', { className: 'edrv-pill edrv-pill-undo', title: '不采纳当前文件的全部差异(回滚)', disabled: !canDecide, onClick: onUndoFile }, '↩ Undo'),
|
|
124
|
-
React.createElement('button', {
|
|
125
|
-
className: 'edrv-pill edrv-pill-ghost edrv-diff-details-toggle',
|
|
126
|
-
title: detailsOpen ? '收起差异操作' : '展开差异操作',
|
|
127
|
-
'aria-label': detailsOpen ? '收起差异操作' : '展开差异操作',
|
|
128
|
-
'aria-expanded': detailsOpen,
|
|
129
|
-
'aria-controls': detailsId,
|
|
130
|
-
onClick: () => setDetailsOpen((value) => !value),
|
|
131
|
-
}, chevronOf(detailsOpen))))),
|
|
132
|
-
bodyEl,
|
|
133
|
-
details)
|
|
36
|
+
return React.createElement(SidecarDiffBar, props)
|
|
134
37
|
}
|
|
@@ -128,6 +128,7 @@ import { clampToolbarPosition, loadToolbarPosition, saveToolbarPosition } from '
|
|
|
128
128
|
import { editorModelPathOf } from '../dap/modelPath.js'
|
|
129
129
|
import { dapTrace } from '../dap/trace.js'
|
|
130
130
|
import { isSessionMissingError, sessionErrorText, sessionMissingDetail, sessionMissingTitle } from '../sessionError.js'
|
|
131
|
+
import { officialDownView, SESSION_DOWN_NOTICE } from '../reviewBar.js'
|
|
131
132
|
|
|
132
133
|
/** 官方差异读取失败前缀:仅用于清理同源陈旧错误,不吞并保存类错误。@author ddj 2026年10月27号 */
|
|
133
134
|
const REVIEW_LOAD_ERROR = '官方差异读取失败:'
|
|
@@ -4487,13 +4488,22 @@ export function EditorView(props) {
|
|
|
4487
4488
|
* @returns 含布局及操作回调的差异快照
|
|
4488
4489
|
*/
|
|
4489
4490
|
const officialFile = review.page?.overview?.coveredPaths.find((path) => sameFile(path, active))
|
|
4491
|
+
// 官方通道自身的失败(list 失败 / 缺 overview)只影响官方栏,不能混进保存类错误。
|
|
4492
|
+
const officialDown = officialFile ? null : officialDownView(review.error)
|
|
4490
4493
|
const rawReviewError = review.error || error
|
|
4494
|
+
// 会话未在当前端激活属于环境提示,不是需要用户处置的错误:降级为中性说明,避免整栏红字。
|
|
4495
|
+
const sessionDown = isSessionMissingError(rawReviewError)
|
|
4496
|
+
const reviewErrorTone = sessionDown ? 'info' : 'error'
|
|
4497
|
+
const reviewErrorText = !rawReviewError ? undefined : sessionDown ? SESSION_DOWN_NOTICE : sessionErrorText(rawReviewError)
|
|
4491
4498
|
const buildDockSnapshot = () => ({
|
|
4492
4499
|
mode: editorDockMode(active),
|
|
4493
4500
|
official: officialFile ? { sessionId, page: review.page, path: officialFile,
|
|
4494
4501
|
pending: sum.files.find((file) => sameFile(file.path, active))?.pending ?? 0,
|
|
4495
4502
|
detail: officialDetail, records: officialRecords, onSelect: setSelectedReview } : null,
|
|
4496
|
-
|
|
4503
|
+
officialDown: officialDown ?? undefined,
|
|
4504
|
+
onRetryOfficial: () => refreshReview(sessionId),
|
|
4505
|
+
reviewError: reviewErrorText,
|
|
4506
|
+
reviewErrorTone,
|
|
4497
4507
|
layout,
|
|
4498
4508
|
pendingRegions,
|
|
4499
4509
|
staleRegions,
|
|
@@ -3,6 +3,7 @@ import { Button, Menu } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
|
3
3
|
import type { ReviewDetail, ReviewItem, ReviewPage } from '../../shared/review.js'
|
|
4
4
|
import { ReviewLedger } from './OfficialReview.js'
|
|
5
5
|
import { reviewBarView } from '../reviewBar.js'
|
|
6
|
+
import { ReviewAllRow } from './reviewAllRow.js'
|
|
6
7
|
|
|
7
8
|
export interface OfficialBarProps {
|
|
8
9
|
official: { sessionId: string; path: string; pending: number; detail?: ReviewDetail; records?: ReviewItem[]; page: ReviewPage; onSelect: (id: string) => void }
|
|
@@ -27,6 +28,7 @@ export interface OfficialBarProps {
|
|
|
27
28
|
onUndoFile: () => void
|
|
28
29
|
onOpenLauncher: (tab: string) => void
|
|
29
30
|
reviewError?: string
|
|
31
|
+
reviewErrorTone?: 'info' | 'error'
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
type Mark = 'left' | 'right' | 'up' | 'down' | 'check' | 'undo' | 'more' | 'file'
|
|
@@ -36,25 +38,17 @@ const marks: Record<Mark, string> = {
|
|
|
36
38
|
more: 'M3 8h.01M8 8h.01M13 8h.01', file: 'M9 2H4v12h8V5L9 2Zm0 0v4h3M6 9h4M6 11h3',
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
+
/**
|
|
42
|
+
* 差异栏图标:官方栏与侧车栏共用同一套 16×16 线性图标,避免两套栏外观不一致。
|
|
43
|
+
* @public
|
|
44
|
+
* @author ddj 2026年10月27号
|
|
45
|
+
* @param mark 工具栏动作
|
|
46
|
+
* @returns 装饰性一致描边图标(标签文字在按钮上)
|
|
47
|
+
*/
|
|
48
|
+
export function barIcon(mark: Mark): React.ReactElement {
|
|
41
49
|
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={marks[mark]} /></svg>
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
/** @private @author ddj 2026年10月27号 @param props Global workspace counts and bulk handlers. @returns Persistent global review controls, distinct from current-file actions. */
|
|
45
|
-
function ReviewAllRow(props: OfficialBarProps): React.ReactElement {
|
|
46
|
-
const total = props.fileTotal ?? 0
|
|
47
|
-
const current = total && (props.fileIdx ?? -1) >= 0 ? (props.fileIdx ?? 0) + 1 : 0
|
|
48
|
-
const disabled = props.reviewBatchBusy || !!props.batchUnavailable || !props.allPendingCount
|
|
49
|
-
return <div className="edrv-review-global" role="group" aria-label="全部待审文件">
|
|
50
|
-
<Button size="sm" variant="ghost" className="edrv-review-total" onClick={() => props.onOpenLauncher('pending')} title="打开当前工作区全部待审差异文件">差异文件 <strong>{current} / {total}</strong></Button>
|
|
51
|
-
<span className="edrv-review-all-actions">
|
|
52
|
-
<Button size="sm" variant="ghost" onClick={props.onAcceptAllFiles} disabled={disabled || !props.onAcceptAllFiles} title={props.batchUnavailable || 'Keep 当前工作区全部待审文件(含其他轮次),不改写磁盘'}>Keep All</Button>
|
|
53
|
-
<Button size="sm" variant="ghost" className="edrv-review-undo" onClick={props.onUndoAllFiles} disabled={disabled || !props.onUndoAllFiles} title={props.batchUnavailable || '检查全部待审记录后逐项 Undo;缺少证据或遇到冲突停止,不强制覆盖'}>Undo All</Button>
|
|
54
|
-
</span>
|
|
55
|
-
</div>
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
/** @public @author ddj 2026年10月27号 @param props Existing review callbacks and immutable record metadata. @returns Responsive review toolbar; no disk or decision side effects until an explicit action. */
|
|
59
53
|
export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
60
54
|
const { official, pendingRegions = [], staleRegions = [], diffIdx = 0 } = props
|
|
@@ -84,7 +78,18 @@ export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
|
84
78
|
const moreTrigger = <Button size="sm" variant="ghost" className="edrv-review-icon" aria-label="更多审查操作" title="更多审查操作" aria-haspopup="menu" aria-expanded={menu === 'more'} onClick={() => setMenu(menu === 'more' ? null : 'more')}>{barIcon('more')}</Button>
|
|
85
79
|
|
|
86
80
|
return <div className="edrv-diffbar edrv-diffbar-dock edrv-reviewbar" data-edrv-diffbox="1" data-edrv-diff-source="official" role="region" aria-label="文件差异审查">
|
|
87
|
-
<ReviewAllRow
|
|
81
|
+
<ReviewAllRow
|
|
82
|
+
total={props.fileTotal ?? 0}
|
|
83
|
+
current={(props.fileTotal ?? 0) && (props.fileIdx ?? -1) >= 0 ? (props.fileIdx ?? 0) + 1 : 0}
|
|
84
|
+
busy={props.reviewBatchBusy}
|
|
85
|
+
unavailable={props.batchUnavailable}
|
|
86
|
+
pending={props.allPendingCount}
|
|
87
|
+
onOpen={() => props.onOpenLauncher('pending')}
|
|
88
|
+
onKeepAll={props.onAcceptAllFiles}
|
|
89
|
+
onUndoAll={props.onUndoAllFiles}
|
|
90
|
+
keepTitle="Keep 当前工作区全部待审文件(含其他轮次),不改写磁盘"
|
|
91
|
+
undoTitle="检查全部待审记录后逐项 Undo;缺少证据或遇到冲突停止,不强制覆盖"
|
|
92
|
+
/>
|
|
88
93
|
{props.reviewBatchBusy ? <div className="edrv-review-notice" role="status">正在处理全部待审记录…</div> : null}
|
|
89
94
|
<div className="edrv-review-heading">
|
|
90
95
|
<span className="edrv-review-filemark">{barIcon('file')}</span>
|
|
@@ -110,7 +115,7 @@ export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
|
110
115
|
</div>
|
|
111
116
|
</div>
|
|
112
117
|
{detail && view.notice ? <div className={view.noticeTone === 'warn' ? 'edrv-review-notice edrv-review-notice-warn' : 'edrv-review-notice'} role="status">{view.notice}</div> : null}
|
|
113
|
-
{props.reviewError ? <div className=
|
|
118
|
+
{props.reviewError ? <div className={props.reviewErrorTone === 'info' ? 'edrv-review-notice' : 'edrv-review-notice edrv-review-error'} role={props.reviewErrorTone === 'info' ? 'status' : 'alert'}>{props.reviewError}</div> : null}
|
|
114
119
|
{expanded ? <div className="edrv-review-body"><ReviewLedger key={`${official.sessionId}:${official.path}`} sessionId={official.sessionId} first={official.page} path={official.path} /></div> : null}
|
|
115
120
|
</div>
|
|
116
121
|
}
|
|
@@ -320,7 +320,7 @@ export function PerfSettings() {
|
|
|
320
320
|
) : null
|
|
321
321
|
|
|
322
322
|
const guideBody = React.createElement('section', { className: 'vsm-panel' },
|
|
323
|
-
React.createElement('h3', { className: 'vsm-panel-title' }, '
|
|
323
|
+
React.createElement('h3', { className: 'vsm-panel-title' }, '历史改动摘要(agent / 脚本只取关键字段,不整份读)'),
|
|
324
324
|
React.createElement('div', { className: 'vsm-panel-body' },
|
|
325
325
|
React.createElement('p', null, '工作区根目录的 .dsh-edit-review.json(可能数 MB)只应通过摘要接口读取,禁止 agent 整份 read/write 进对话:'),
|
|
326
326
|
React.createElement('pre', { className: 'vsm-perf-block' },
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button, Menu } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
3
|
+
import { callIdAttr } from '../state/records.js'
|
|
4
|
+
import { SIDECAR_KEEP_TITLE, SIDECAR_LABEL, SIDECAR_LABEL_TITLE, SIDECAR_UNDO_TITLE, reviewBarView } from '../reviewBar.js'
|
|
5
|
+
import type { OfficialDownView } from '../reviewBar.js'
|
|
6
|
+
import { barIcon } from './OfficialDiffBar.js'
|
|
7
|
+
import { ReviewAllRow } from './reviewAllRow.js'
|
|
8
|
+
import { badgeOf } from './shared.js'
|
|
9
|
+
|
|
10
|
+
/** 侧车差异区域:由 records 派生的行范围,不含官方块元数据。 */
|
|
11
|
+
export interface SidecarRegion {
|
|
12
|
+
callId: string
|
|
13
|
+
idx: number
|
|
14
|
+
start?: number
|
|
15
|
+
end?: number
|
|
16
|
+
oldLines?: string[]
|
|
17
|
+
newLines?: string[]
|
|
18
|
+
create?: boolean
|
|
19
|
+
status?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 侧车栏输入:字段与官方栏对齐,缺的是官方记录元数据,多的是侧车区域的 keep/undo 能力。 */
|
|
23
|
+
export interface SidecarBarProps {
|
|
24
|
+
activePath?: string
|
|
25
|
+
pendingRegions?: SidecarRegion[]
|
|
26
|
+
staleRegions?: SidecarRegion[]
|
|
27
|
+
diffIdx?: number
|
|
28
|
+
/** 编辑器内容是否就绪;未就绪时不得断言差异失效 */
|
|
29
|
+
contentReady?: boolean
|
|
30
|
+
fileIdx?: number
|
|
31
|
+
fileTotal?: number
|
|
32
|
+
allPendingCount?: number
|
|
33
|
+
reviewBatchBusy?: boolean
|
|
34
|
+
/** 官方元数据通道失败时的可操作提示 */
|
|
35
|
+
officialDown?: OfficialDownView
|
|
36
|
+
onRetryOfficial?: () => void
|
|
37
|
+
onAcceptFile: () => void
|
|
38
|
+
onUndoFile: () => void
|
|
39
|
+
onAcceptAllFiles: () => void
|
|
40
|
+
onUndoAllFiles: () => void
|
|
41
|
+
onPrevFile: () => void
|
|
42
|
+
onNextFile: () => void
|
|
43
|
+
onPrevDiff: () => void
|
|
44
|
+
onNextDiff: () => void
|
|
45
|
+
onOpenLauncher: (tab: string) => void
|
|
46
|
+
onJump?: (region: SidecarRegion) => void
|
|
47
|
+
onRollback?: () => void
|
|
48
|
+
onRefresh?: () => void
|
|
49
|
+
reviewError?: string
|
|
50
|
+
reviewErrorTone?: 'info' | 'error'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 侧车(历史捕获)差异栏:与官方栏同一套版式、同一套按钮位置,只把数据源与标识换掉。
|
|
55
|
+
* 两套栏并存是数据现实,但外观不能分家,否则用户会以为插件版本不一致。
|
|
56
|
+
* @public
|
|
57
|
+
* @author ddj 2026年10月28号
|
|
58
|
+
* @param props 侧车区域计数、批量可用性与回调
|
|
59
|
+
* @returns 与官方栏同构的差异工具栏;展开后列出侧车区域
|
|
60
|
+
*/
|
|
61
|
+
export function SidecarDiffBar(props: SidecarBarProps): React.ReactElement {
|
|
62
|
+
const pendingRegions = props.pendingRegions ?? []
|
|
63
|
+
const staleRegions = props.staleRegions ?? []
|
|
64
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
65
|
+
const [menu, setMenu] = React.useState<'more' | null>(null)
|
|
66
|
+
const name = String(props.activePath || '').split(/[\\/]/).pop() || '当前文件'
|
|
67
|
+
const count = pendingRegions.length
|
|
68
|
+
const total = count + staleRegions.length
|
|
69
|
+
const busy = props.reviewBatchBusy === true
|
|
70
|
+
const canDecide = total > 0 && !busy
|
|
71
|
+
const view = reviewBarView({
|
|
72
|
+
jumps: count, stale: staleRegions.length, index: props.diffIdx ?? 0,
|
|
73
|
+
ready: props.contentReady === true, pendingRecords: total, canKeep: true,
|
|
74
|
+
})
|
|
75
|
+
const down = props.officialDown
|
|
76
|
+
|
|
77
|
+
/** @private @author ddj 2026年10月28号 @param tab 既有总览页签 @returns 打开既有总览并收起菜单 */
|
|
78
|
+
function openOverview(tab: string): void { props.onOpenLauncher(tab); setMenu(null) }
|
|
79
|
+
|
|
80
|
+
/** @private @author ddj 2026年10月28号 @param mark 工具栏图标 @param label 无障碍名称 @param action 既有回调 @param disabled 是否禁用 @param title 禁用原因 @returns 紧凑导航按钮 */
|
|
81
|
+
function navButton(mark: Parameters<typeof barIcon>[0], label: string, action: () => void, disabled: boolean, title?: string): React.ReactElement {
|
|
82
|
+
return <Button size="sm" variant="ghost" className="edrv-review-icon" aria-label={label} title={title || label} disabled={disabled} onClick={action}>{barIcon(mark)}</Button>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const moreTrigger = <Button size="sm" variant="ghost" className="edrv-review-icon" aria-label="更多审查操作" title="更多审查操作" aria-haspopup="menu" aria-expanded={menu === 'more'} onClick={() => setMenu(menu === 'more' ? null : 'more')}>{barIcon('more')}</Button>
|
|
86
|
+
|
|
87
|
+
const body = expanded
|
|
88
|
+
? <div className="edrv-review-body">
|
|
89
|
+
{pendingRegions.map((region) => {
|
|
90
|
+
const key = callIdAttr(region.callId, region.idx)
|
|
91
|
+
return <div key={key} className="edrv-diffrow" data-edrv-hunk={key} title={'跳转到 L' + (region.start ?? '?')} onClick={() => props.onJump?.(region)}>
|
|
92
|
+
<span className="edrv-diffrow-l">{'L' + (region.start ?? '?') + '-' + (region.end ?? '?')}</span>
|
|
93
|
+
{(region.oldLines?.length ?? 0) > 0 ? <span className="edrv-diffrow-o">{'-' + (region.oldLines?.length ?? 0)}</span> : null}
|
|
94
|
+
{(region.newLines?.length ?? 0) > 0 ? <span className="edrv-diffrow-n">{'+' + (region.newLines?.length ?? 0)}</span> : null}
|
|
95
|
+
{region.create ? <span className="edrv-diffrow-tag">新建</span> : null}
|
|
96
|
+
</div>
|
|
97
|
+
})}
|
|
98
|
+
{staleRegions.map((region) => <div key={'stale' + callIdAttr(region.callId, region.idx)} className="edrv-diffrow edrv-diffrow-stale" title="差异无法定位(文件可能已被手动修改)">
|
|
99
|
+
<span className="edrv-diffrow-l">{'L' + (region.start ?? '?') + ' 无法定位'}</span>
|
|
100
|
+
<span className="edrv-diffrow-tag">冲突/需刷新</span>
|
|
101
|
+
<span className="edrv-diffrow-tag">{badgeOf(region.status ?? '')}</span>
|
|
102
|
+
</div>)}
|
|
103
|
+
</div>
|
|
104
|
+
: null
|
|
105
|
+
|
|
106
|
+
return <div className="edrv-diffbar edrv-diffbar-dock edrv-reviewbar" data-edrv-diffbox="1" data-edrv-diff-source="sidecar" role="region" aria-label="文件差异审查">
|
|
107
|
+
<ReviewAllRow
|
|
108
|
+
total={props.fileTotal ?? 0}
|
|
109
|
+
current={(props.fileTotal ?? 0) && (props.fileIdx ?? -1) >= 0 ? (props.fileIdx ?? 0) + 1 : 0}
|
|
110
|
+
busy={busy}
|
|
111
|
+
pending={props.allPendingCount}
|
|
112
|
+
onOpen={() => props.onOpenLauncher('pending')}
|
|
113
|
+
onKeepAll={props.onAcceptAllFiles}
|
|
114
|
+
onUndoAll={props.onUndoAllFiles}
|
|
115
|
+
keepTitle="把这批历史改动全部标记为已看过(只改记录,不动磁盘文件)"
|
|
116
|
+
undoTitle="逐项撤销这些历史改动;缺少证据或遇到冲突即停止"
|
|
117
|
+
/>
|
|
118
|
+
{busy ? <div className="edrv-review-notice" role="status">正在处理历史改动…</div> : null}
|
|
119
|
+
{down ? <div className="edrv-review-notice edrv-review-notice-warn edrv-sidecar-down" role="status">
|
|
120
|
+
<span className="edrv-sidecar-down-text">{down.text}</span>
|
|
121
|
+
{props.onRetryOfficial ? <Button size="sm" variant="ghost" onClick={props.onRetryOfficial} title="重新读取官方待审记录">{down.retryLabel || '重试'}</Button> : null}
|
|
122
|
+
</div> : null}
|
|
123
|
+
<div className="edrv-review-heading">
|
|
124
|
+
<span className="edrv-review-filemark">{barIcon('file')}</span>
|
|
125
|
+
<span className="edrv-review-filename" title={props.activePath}>{name}</span>
|
|
126
|
+
<span className="edrv-review-source" title={SIDECAR_LABEL_TITLE}>{SIDECAR_LABEL}</span>
|
|
127
|
+
<span className="edrv-review-pending" title={view.pendingTitle}>待审 <strong>{total}</strong></span>
|
|
128
|
+
</div>
|
|
129
|
+
<div className="edrv-review-tools">
|
|
130
|
+
<div className="edrv-review-nav" role="group" aria-label="文件导航">
|
|
131
|
+
{navButton('left', '上一个差异文件', props.onPrevFile, !props.fileTotal)}
|
|
132
|
+
{navButton('right', '下一个差异文件', props.onNextFile, !props.fileTotal)}
|
|
133
|
+
</div>
|
|
134
|
+
<div className="edrv-review-nav edrv-review-blocknav" role="group" aria-label="差异导航">
|
|
135
|
+
{navButton('up', '上一个差异', props.onPrevDiff, !count, view.navPrevTitle)}
|
|
136
|
+
<span className="edrv-review-position" aria-live="polite">{view.position}</span>
|
|
137
|
+
{navButton('down', '下一个差异', props.onNextDiff, !count, view.navNextTitle)}
|
|
138
|
+
</div>
|
|
139
|
+
<div className="edrv-review-actions">
|
|
140
|
+
<Button size="sm" variant="ghost" className="edrv-review-undo" icon={barIcon('undo')} onClick={props.onUndoFile} disabled={!canDecide} title={SIDECAR_UNDO_TITLE}>Undo</Button>
|
|
141
|
+
<Button size="sm" variant="primary" className="edrv-review-keep" icon={barIcon('check')} onClick={props.onAcceptFile} disabled={!canDecide} title={SIDECAR_KEEP_TITLE}>Keep</Button>
|
|
142
|
+
<Button size="sm" variant="ghost" className="edrv-review-details" onClick={() => setExpanded(!expanded)} aria-expanded={expanded} aria-label={expanded ? '收起差异' : '展开差异'} title={expanded ? '收起差异' : '展开差异'}>详情{barIcon(expanded ? 'up' : 'down')}</Button>
|
|
143
|
+
<Menu open={menu === 'more'} anchor={moreTrigger} items={[{ id: 'pending', label: '差异总览' }, { id: 'archive', label: '归档 / 历史' }, { id: 'rollback', label: '回滚当前文件' }, { id: 'refresh', label: '刷新' }]} onSelect={(id: string) => {
|
|
144
|
+
if (id === 'rollback') { setMenu(null); props.onRollback?.(); return }
|
|
145
|
+
if (id === 'refresh') { setMenu(null); props.onRefresh?.(); return }
|
|
146
|
+
openOverview(id)
|
|
147
|
+
}} onClose={() => setMenu(null)} side="top" align="end" portal compact autoFocus />
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
{view.notice ? <div className={view.noticeTone === 'warn' ? 'edrv-review-notice edrv-review-notice-warn' : 'edrv-review-notice'} role="status">{view.notice}</div> : null}
|
|
151
|
+
{props.reviewError ? <div className={props.reviewErrorTone === 'info' ? 'edrv-review-notice' : 'edrv-review-notice edrv-review-error'} role={props.reviewErrorTone === 'info' ? 'status' : 'alert'}>{props.reviewError}</div> : null}
|
|
152
|
+
{body}
|
|
153
|
+
</div>
|
|
154
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
3
|
+
|
|
4
|
+
/** 全工作区批量行输入:官方栏与侧车栏共用同一套外观与禁用语义。 */
|
|
5
|
+
export interface ReviewAllRowProps {
|
|
6
|
+
/** 当前文件序号(1 基,0 表示未定位) */
|
|
7
|
+
current: number
|
|
8
|
+
/** 差异文件总数 */
|
|
9
|
+
total: number
|
|
10
|
+
/** 批量操作进行中 */
|
|
11
|
+
busy?: boolean
|
|
12
|
+
/** 批量不可用原因(悬浮提示) */
|
|
13
|
+
unavailable?: string
|
|
14
|
+
/** 工作区待处理项总数;为 0 时两个按钮禁用 */
|
|
15
|
+
pending?: number
|
|
16
|
+
/** 打开差异总览 */
|
|
17
|
+
onOpen: () => void
|
|
18
|
+
/** 全部 Keep */
|
|
19
|
+
onKeepAll?: () => void
|
|
20
|
+
/** 全部 Undo */
|
|
21
|
+
onUndoAll?: () => void
|
|
22
|
+
/** Keep All 悬浮说明 */
|
|
23
|
+
keepTitle?: string
|
|
24
|
+
/** Undo All 悬浮说明 */
|
|
25
|
+
undoTitle?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 全局批量行:差异文件计数 + Keep All / Undo All。
|
|
30
|
+
* 两套差异栏(官方 / 侧车)必须长得一样,否则用户会以为看到的是不同产品。
|
|
31
|
+
* @public
|
|
32
|
+
* @author ddj 2026年10月28号
|
|
33
|
+
* @param props 计数、批量可用性与回调
|
|
34
|
+
* @returns 持久化的全工作区控制条
|
|
35
|
+
*/
|
|
36
|
+
export function ReviewAllRow(props: ReviewAllRowProps): React.ReactElement {
|
|
37
|
+
const total = props.total ?? 0
|
|
38
|
+
const current = total && props.current > 0 ? props.current : 0
|
|
39
|
+
const disabled = props.busy || !!props.unavailable || !props.pending
|
|
40
|
+
|
|
41
|
+
return <div className="edrv-review-global" role="group" aria-label="全部待审文件">
|
|
42
|
+
<Button size="sm" variant="ghost" className="edrv-review-total" onClick={props.onOpen} title="打开当前工作区全部待审差异文件">差异文件 <strong>{current} / {total}</strong></Button>
|
|
43
|
+
<span className="edrv-review-all-actions">
|
|
44
|
+
<Button size="sm" variant="ghost" onClick={props.onKeepAll} disabled={disabled || !props.onKeepAll} title={props.unavailable || props.keepTitle || 'Keep 当前工作区全部待审文件'}>Keep All</Button>
|
|
45
|
+
<Button size="sm" variant="ghost" className="edrv-review-undo" onClick={props.onUndoAll} disabled={disabled || !props.onUndoAll} title={props.unavailable || props.undoTitle || 'Undo 当前工作区全部待审文件'}>Undo All</Button>
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
}
|