dsh-vscode-mode 0.20.2 → 0.20.3
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 +105 -14
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/reviewBar.ts +85 -0
- package/src/client/sessionError.ts +12 -0
- package/src/client/styles/editor.css +1 -0
- package/src/client/ui/EditorView.ts +10 -3
- package/src/client/ui/OfficialDiffBar.tsx +14 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
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": [
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 官方差异栏派生视图:位置、导航提示、失效说明与 Keep 提示的唯一来源。
|
|
3
|
+
* 计数口径分离:待审是「记录数」,可跳转是「块数」,两者不同源,
|
|
4
|
+
* 全部块失效时必须给出可操作叙事,不能只留一句「无法定位」。
|
|
5
|
+
* 作者 ddj 2026年10月27号
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 官方差异栏输入:全部来自 EditorView dock 快照,不读取磁盘或 RPC。 */
|
|
9
|
+
export interface ReviewBarInput {
|
|
10
|
+
/** 可定位(非 stale)的差异块数 */
|
|
11
|
+
jumps: number
|
|
12
|
+
/** 无法定位的历史差异块数 */
|
|
13
|
+
stale: number
|
|
14
|
+
/** 当前差异块索引(0 基) */
|
|
15
|
+
index: number
|
|
16
|
+
/** 编辑器内容是否已就绪;未就绪时不得断言差异失效 */
|
|
17
|
+
ready: boolean
|
|
18
|
+
/** 当前文件的官方待审记录数 */
|
|
19
|
+
pendingRecords: number
|
|
20
|
+
/** 当前记录是否可 Keep(detail.capability.keep) */
|
|
21
|
+
canKeep: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 官方差异栏派生文案;调用方只负责渲染,不再自行拼装计数与提示。 */
|
|
25
|
+
export interface ReviewBarView {
|
|
26
|
+
/** 差异位置文案,如 `2 / 6`、`无可跳转`、`载入中` */
|
|
27
|
+
position: string
|
|
28
|
+
/** 上一个差异按钮提示(兼作禁用原因) */
|
|
29
|
+
navPrevTitle: string
|
|
30
|
+
/** 下一个差异按钮提示(兼作禁用原因) */
|
|
31
|
+
navNextTitle: string
|
|
32
|
+
/** 失效说明,空串表示不显示 */
|
|
33
|
+
notice: string
|
|
34
|
+
/** 说明级别:warn 表示需要用户决策 */
|
|
35
|
+
noticeTone: 'info' | 'warn'
|
|
36
|
+
/** Keep 按钮提示 */
|
|
37
|
+
keepHint: string
|
|
38
|
+
/** 待审计数提示 */
|
|
39
|
+
pendingTitle: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @private @author ddj 2026年10月27号 @param value 任意计数输入 @returns 非负整数,NaN/负数/非数字归零 */
|
|
43
|
+
function safeCount(value: unknown): number {
|
|
44
|
+
const parsed = Number(value)
|
|
45
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @private @author ddj 2026年10月27号 @returns 无差异可跳转时的 Keep 提示 */
|
|
49
|
+
function fullStaleKeepHint(): string {
|
|
50
|
+
return '保留该记录的全部改动并结案:这些改动已不在文件中,记录将被标记为已采纳(不改写磁盘)'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 派生差异栏文案。
|
|
55
|
+
* @public
|
|
56
|
+
* @author ddj 2026年10月27号
|
|
57
|
+
* @param input 差异计数、就绪状态与 Keep 能力
|
|
58
|
+
* @returns 栏内位置、导航提示与失效说明
|
|
59
|
+
*/
|
|
60
|
+
export function reviewBarView(input: ReviewBarInput): ReviewBarView {
|
|
61
|
+
const jumps = safeCount(input.jumps)
|
|
62
|
+
const stale = safeCount(input.stale)
|
|
63
|
+
const index = safeCount(input.index)
|
|
64
|
+
const pendingTitle = `${safeCount(input.pendingRecords)} 条官方待审记录` + (input.ready ? `,本文件可跳转 ${jumps} 处` : '')
|
|
65
|
+
const keepHint = jumps === 0 && stale > 0 && input.canKeep ? fullStaleKeepHint() : '保留当前记录的未处理修改'
|
|
66
|
+
|
|
67
|
+
// 载入期所有块天然 stale,此时只能报告进度,不能断言改动已失效。
|
|
68
|
+
if (input.ready !== true) {
|
|
69
|
+
return { position: '载入中', navPrevTitle: '文件载入中', navNextTitle: '文件载入中', notice: '', noticeTone: 'info', keepHint, pendingTitle }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (jumps > 0) {
|
|
73
|
+
const at = Math.min(index + 1, jumps)
|
|
74
|
+
const notice = stale > 0 ? `${stale} 处历史差异无法定位(其余 ${jumps} 处可跳转),展开详情查看原始增删。` : ''
|
|
75
|
+
return { position: `${at} / ${jumps}`, navPrevTitle: '上一个差异', navNextTitle: '下一个差异', notice, noticeTone: 'info', keepHint, pendingTitle }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (stale > 0) {
|
|
79
|
+
const reason = '该记录的改动已不在当前文件中,无可跳转差异'
|
|
80
|
+
const notice = `本记录 ${stale} 处改动均已不在当前文件中(被后续编辑覆盖或已撤销);可点 Keep 结案(只标记已采纳,不改写磁盘),或展开详情核对原始增删。`
|
|
81
|
+
return { position: '无可跳转', navPrevTitle: reason, navNextTitle: reason, notice, noticeTone: 'warn', keepHint, pendingTitle }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { position: '—', navPrevTitle: '当前文件没有可定位差异', navNextTitle: '当前文件没有可定位差异', notice: '', noticeTone: 'info', keepHint, pendingTitle }
|
|
85
|
+
}
|
|
@@ -36,3 +36,15 @@ export function sessionMissingDetail(): string {
|
|
|
36
36
|
return '该会话可能正被其他 DSH 实例(桌面端 / Web 端)占用,或尚未在当前实例激活。' +
|
|
37
37
|
'请在持有该会话的一端查看,或在本端打开该会话后重试。'
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 把任意错误文本归一为可展示文案:会话缺失类替换为终态提示,其余原样返回。
|
|
42
|
+
* 差异栏等狭窄区域共用,避免裸「会话不存在」与业务提示混排。
|
|
43
|
+
* @author ddj 2026年10月27号
|
|
44
|
+
* @param text 任意错误文本(host 业务 error、异常 message 等原始形态)
|
|
45
|
+
* @returns 会话缺失为「标题:详情」,其他错误为原文本,空值返回空串
|
|
46
|
+
*/
|
|
47
|
+
export function sessionErrorText(text: unknown): string {
|
|
48
|
+
if (isSessionMissingError(text)) return sessionMissingTitle() + ':' + sessionMissingDetail()
|
|
49
|
+
return String(text ?? '')
|
|
50
|
+
}
|
|
@@ -304,6 +304,7 @@
|
|
|
304
304
|
[data-edrv-diff-dock-shell] .edrv-reviewbar button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #3b63d9); outline-offset: 2px; }
|
|
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
|
+
[data-edrv-diff-dock-shell] .edrv-review-notice-warn { color: var(--dsw-alias-state-warning-primary, #a86a1f); }
|
|
307
308
|
[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; }
|
|
308
309
|
@container edrv-review (max-width: 420px) {
|
|
309
310
|
.edrv-reviewbar .edrv-review-heading { gap: 6px; padding-inline: 10px; }
|
|
@@ -127,7 +127,10 @@ import { buildBpMenu } from '../dap/bpMenu.js'
|
|
|
127
127
|
import { clampToolbarPosition, loadToolbarPosition, saveToolbarPosition } from '../dap/toolbarDrag.js'
|
|
128
128
|
import { editorModelPathOf } from '../dap/modelPath.js'
|
|
129
129
|
import { dapTrace } from '../dap/trace.js'
|
|
130
|
-
import { isSessionMissingError, sessionMissingDetail, sessionMissingTitle } from '../sessionError.js'
|
|
130
|
+
import { isSessionMissingError, sessionErrorText, sessionMissingDetail, sessionMissingTitle } from '../sessionError.js'
|
|
131
|
+
|
|
132
|
+
/** 官方差异读取失败前缀:仅用于清理同源陈旧错误,不吞并保存类错误。@author ddj 2026年10月27号 */
|
|
133
|
+
const REVIEW_LOAD_ERROR = '官方差异读取失败:'
|
|
131
134
|
|
|
132
135
|
/**
|
|
133
136
|
* 只在 Lua 注解类型槽开启注释自动建议;跨文件/离开类型槽立即恢复默认。
|
|
@@ -588,7 +591,9 @@ export function EditorView(props) {
|
|
|
588
591
|
if (!result.ok) throw new Error(result.error)
|
|
589
592
|
setOfficialRecords((previous) => JSON.stringify(previous) === JSON.stringify(page.page.items) ? previous : page.page.items)
|
|
590
593
|
setOfficialDetail((previous) => JSON.stringify(previous) === JSON.stringify(result.detail) ? previous : result.detail)
|
|
591
|
-
|
|
594
|
+
// 读取成功后清除同源陈旧错误,避免会话恢复后仍挂着上一次失败提示。
|
|
595
|
+
setError((previous) => (typeof previous === 'string' && previous.startsWith(REVIEW_LOAD_ERROR) ? null : previous))
|
|
596
|
+
} catch (cause) { if (!controller.signal.aborted) setError(REVIEW_LOAD_ERROR + String(cause)) }
|
|
592
597
|
}
|
|
593
598
|
void loadReview()
|
|
594
599
|
return () => controller.abort()
|
|
@@ -4482,12 +4487,13 @@ export function EditorView(props) {
|
|
|
4482
4487
|
* @returns 含布局及操作回调的差异快照
|
|
4483
4488
|
*/
|
|
4484
4489
|
const officialFile = review.page?.overview?.coveredPaths.find((path) => sameFile(path, active))
|
|
4490
|
+
const rawReviewError = review.error || error
|
|
4485
4491
|
const buildDockSnapshot = () => ({
|
|
4486
4492
|
mode: editorDockMode(active),
|
|
4487
4493
|
official: officialFile ? { sessionId, page: review.page, path: officialFile,
|
|
4488
4494
|
pending: sum.files.find((file) => sameFile(file.path, active))?.pending ?? 0,
|
|
4489
4495
|
detail: officialDetail, records: officialRecords, onSelect: setSelectedReview } : null,
|
|
4490
|
-
reviewError:
|
|
4496
|
+
reviewError: rawReviewError ? sessionErrorText(rawReviewError) : undefined,
|
|
4491
4497
|
layout,
|
|
4492
4498
|
pendingRegions,
|
|
4493
4499
|
staleRegions,
|
|
@@ -4507,6 +4513,7 @@ export function EditorView(props) {
|
|
|
4507
4513
|
onRefresh: reloadFile,
|
|
4508
4514
|
activePath: active,
|
|
4509
4515
|
diffIdx: contentReady ? diffIdx : 0,
|
|
4516
|
+
contentReady,
|
|
4510
4517
|
diffTotal: displayDiffTotal(contentReady, pendingRegions.length, sum.files.find((file) => sameFile(file.path, active))?.pending ?? 0),
|
|
4511
4518
|
fileIdx,
|
|
4512
4519
|
fileTotal: sum.pendingFiles.length,
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
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
|
+
import { reviewBarView } from '../reviewBar.js'
|
|
5
6
|
|
|
6
7
|
export interface OfficialBarProps {
|
|
7
8
|
official: { sessionId: string; path: string; pending: number; detail?: ReviewDetail; records?: ReviewItem[]; page: ReviewPage; onSelect: (id: string) => void }
|
|
@@ -9,6 +10,8 @@ export interface OfficialBarProps {
|
|
|
9
10
|
pendingRegions?: unknown[]
|
|
10
11
|
staleRegions?: unknown[]
|
|
11
12
|
diffIdx?: number
|
|
13
|
+
/** 编辑器内容是否就绪;未就绪时不得断言差异失效 */
|
|
14
|
+
contentReady?: boolean
|
|
12
15
|
fileTotal?: number
|
|
13
16
|
fileIdx?: number
|
|
14
17
|
allPendingCount?: number
|
|
@@ -61,7 +64,8 @@ export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
|
61
64
|
const detail = official.detail
|
|
62
65
|
const records = official.records ?? []
|
|
63
66
|
const count = pendingRegions.length
|
|
64
|
-
const
|
|
67
|
+
const view = reviewBarView({ jumps: count, stale: staleRegions.length, index: diffIdx, ready: props.contentReady === true,
|
|
68
|
+
pendingRecords: official.pending, canKeep: detail?.capability.keep === true })
|
|
65
69
|
|
|
66
70
|
/** @private @author ddj 2026年10月27号 @returns Close current popup without changing selection. */
|
|
67
71
|
function closeMenu(): void { setMenu(null) }
|
|
@@ -69,9 +73,9 @@ export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
|
69
73
|
function selectRecord(id: string): void { official.onSelect(id); closeMenu() }
|
|
70
74
|
/** @private @author ddj 2026年10月27号 @param tab Existing launcher tab. @returns Open existing overview, never create another review entry. */
|
|
71
75
|
function openOverview(tab: string): void { props.onOpenLauncher(tab); closeMenu() }
|
|
72
|
-
/** @private @author ddj 2026年10月27号 @param mark Navigation icon. @param label Accessible action. @param action Existing callback. @param disabled Whether navigation is unavailable. @returns Compact keyboard-focusable navigation control. */
|
|
73
|
-
function navButton(mark: Mark, label: string, action: () => void, disabled: boolean): React.ReactElement {
|
|
74
|
-
return <Button size="sm" variant="ghost" className="edrv-review-icon" aria-label={label} title={label} disabled={disabled} onClick={action}>{barIcon(mark)}</Button>
|
|
76
|
+
/** @private @author ddj 2026年10月27号 @param mark Navigation icon. @param label Accessible action. @param action Existing callback. @param disabled Whether navigation is unavailable. @param title Hover reason, defaults to the label. @returns Compact keyboard-focusable navigation control. */
|
|
77
|
+
function navButton(mark: Mark, label: string, action: () => void, disabled: boolean, title?: string): React.ReactElement {
|
|
78
|
+
return <Button size="sm" variant="ghost" className="edrv-review-icon" aria-label={label} title={title || label} disabled={disabled} onClick={action}>{barIcon(mark)}</Button>
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
const recordTrigger = <Button size="sm" variant="ghost" className="edrv-review-record" aria-label="选择官方差异记录" aria-haspopup="menu" aria-expanded={menu === 'records'} disabled={!records.length} onClick={() => setMenu(menu === 'records' ? null : 'records')}>
|
|
@@ -85,7 +89,7 @@ export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
|
85
89
|
<div className="edrv-review-heading">
|
|
86
90
|
<span className="edrv-review-filemark">{barIcon('file')}</span>
|
|
87
91
|
<span className="edrv-review-filename" title={props.activePath}>{name}</span>
|
|
88
|
-
<span className="edrv-review-pending" title={
|
|
92
|
+
<span className="edrv-review-pending" title={view.pendingTitle}>待审 <strong>{official.pending}</strong></span>
|
|
89
93
|
<Menu open={menu === 'records'} anchor={recordTrigger} items={records.map((item) => ({ id: item.id, label: `轮次 ${item.turn} / 事件 ${item.seq}` }))} selectedId={detail?.item.id} onSelect={selectRecord} onClose={closeMenu} side="top" align="end" portal compact autoFocus />
|
|
90
94
|
</div>
|
|
91
95
|
<div className="edrv-review-tools">
|
|
@@ -94,18 +98,18 @@ export function OfficialDiffBar(props: OfficialBarProps): React.ReactElement {
|
|
|
94
98
|
{navButton('right', '下一个差异文件', props.onNextFile, !props.fileTotal)}
|
|
95
99
|
</div>
|
|
96
100
|
<div className="edrv-review-nav edrv-review-blocknav" role="group" aria-label="差异导航">
|
|
97
|
-
{navButton('up', '上一个差异', props.onPrevDiff, !count)}
|
|
98
|
-
<span className="edrv-review-position" aria-live="polite">{position}</span>
|
|
99
|
-
{navButton('down', '下一个差异', props.onNextDiff, !count)}
|
|
101
|
+
{navButton('up', '上一个差异', props.onPrevDiff, !count, view.navPrevTitle)}
|
|
102
|
+
<span className="edrv-review-position" aria-live="polite">{view.position}</span>
|
|
103
|
+
{navButton('down', '下一个差异', props.onNextDiff, !count, view.navNextTitle)}
|
|
100
104
|
</div>
|
|
101
105
|
<div className="edrv-review-actions">
|
|
102
106
|
<Button size="sm" variant="ghost" className="edrv-review-undo" icon={barIcon('undo')} onClick={props.onUndoFile} disabled={props.reviewBatchBusy || !detail?.capability.undo} title={detail?.capability.reason}>Undo</Button>
|
|
103
|
-
<Button size="sm" variant="primary" className="edrv-review-keep" icon={barIcon('check')} onClick={props.onAcceptFile} disabled={props.reviewBatchBusy || !detail?.capability.keep} title=
|
|
107
|
+
<Button size="sm" variant="primary" className="edrv-review-keep" icon={barIcon('check')} onClick={props.onAcceptFile} disabled={props.reviewBatchBusy || !detail?.capability.keep} title={detail?.capability.keep ? view.keepHint : detail?.capability.reason}>Keep</Button>
|
|
104
108
|
<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>
|
|
105
109
|
<Menu open={menu === 'more'} anchor={moreTrigger} items={[{ id: 'pending', label: '差异总览' }, { id: 'archive', label: '归档 / 历史' }]} onSelect={openOverview} onClose={closeMenu} side="top" align="end" portal compact autoFocus />
|
|
106
110
|
</div>
|
|
107
111
|
</div>
|
|
108
|
-
{detail &&
|
|
112
|
+
{detail && view.notice ? <div className={view.noticeTone === 'warn' ? 'edrv-review-notice edrv-review-notice-warn' : 'edrv-review-notice'} role="status">{view.notice}</div> : null}
|
|
109
113
|
{props.reviewError ? <div className="edrv-review-notice edrv-review-error" role="alert">{props.reviewError}</div> : null}
|
|
110
114
|
{expanded ? <div className="edrv-review-body"><ReviewLedger key={`${official.sessionId}:${official.path}`} sessionId={official.sessionId} first={official.page} path={official.path} /></div> : null}
|
|
111
115
|
</div>
|