dsh-vscode-mode 0.5.2 → 0.6.0
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 +78 -5
- package/lib/client.js +5579 -236
- package/lib/client.js.map +1 -1
- package/lib/index.js +2358 -126
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/compat.ts +1 -1
- package/src/client/dap/BpWidget.ts +262 -0
- package/src/client/dap/bpMenu.ts +91 -0
- package/src/client/dap/bpRowMenu.ts +53 -0
- package/src/client/dap/breakpoints.ts +222 -0
- package/src/client/dap/decorate.ts +128 -0
- package/src/client/dap/hintLine.ts +29 -0
- package/src/client/dap/hover.ts +166 -0
- package/src/client/dap/hoverMode.ts +159 -0
- package/src/client/dap/hoverTree.ts +722 -0
- package/src/client/dap/launchInsert.ts +229 -0
- package/src/client/dap/launchSnippetProvider.ts +206 -0
- package/src/client/dap/modelPath.ts +23 -0
- package/src/client/dap/panelSplit.ts +105 -0
- package/src/client/dap/pidPick.ts +24 -0
- package/src/client/dap/store.ts +571 -0
- package/src/client/dap/toolbarDrag.ts +51 -0
- package/src/client/dap/trace.ts +22 -0
- package/src/client/dap/variableTree.ts +62 -0
- package/src/client/editorLimit.ts +31 -0
- package/src/client/index.ts +13 -0
- package/src/client/markdownPreview.ts +24 -0
- package/src/client/md/componentType.ts +28 -0
- package/src/client/md/mdPanel.ts +98 -0
- package/src/client/monaco/lsp/index.ts +15 -0
- package/src/client/monaco/lsp/providers.ts +2 -0
- package/src/client/monaco/theme.ts +15 -0
- package/src/client/searchSeed.ts +64 -0
- package/src/client/sidebar/panels/DebugPanel.ts +613 -0
- package/src/client/sidebar/panels/SearchPanel.ts +70 -15
- package/src/client/sidebar/panels/SvnPanel.ts +167 -23
- package/src/client/sidebar/panels/index.ts +19 -0
- package/src/client/sidebar/types.ts +2 -0
- package/src/client/styles/editor.css +186 -1
- package/src/client/svnActions.ts +18 -1
- package/src/client/svnLog.ts +3 -3
- package/src/client/svnStatus.ts +140 -2
- package/src/client/tabActions.ts +54 -0
- package/src/client/ui/EditorView.ts +711 -29
- package/src/client/ui/McpSettings.ts +29 -0
- package/src/client/ui/SideBySideDiff.tsx +214 -29
- package/src/client/ui/SvnDiffPanel.ts +21 -8
- package/src/client/ui/SvnLogDialog.ts +44 -2
- package/src/client/ui/SvnPatchDialog.ts +63 -0
- package/src/client/ui/SvnSumDialog.ts +77 -0
- package/src/client/ui/commandCatalog.ts +54 -0
- package/src/client/ui/horizontalWheel.ts +85 -0
- package/src/client/ui/svnExport.ts +71 -0
- package/src/client-primitives.d.ts +34 -0
- package/src/dap/configSnippets.ts +269 -0
- package/src/dap/discovery.ts +198 -0
- package/src/dap/launchConfig.ts +127 -0
- package/src/dap/manager.ts +588 -0
- package/src/dap/processList.ts +76 -0
- package/src/dap/protocol.ts +80 -0
- package/src/dap/provider.ts +49 -0
- package/src/dap/rpc.ts +192 -0
- package/src/dap/sourcePath.ts +82 -0
- package/src/fileOpenSettings.ts +3 -0
- package/src/index.ts +7 -3
- package/src/lsp/providers.ts +3 -2
- package/src/reveal.ts +31 -20
- package/src/rpc.ts +11 -3
- package/src/search/ripgrep.ts +127 -11
- package/src/shared/dap.ts +262 -0
- package/src/shared/editorLimit.ts +46 -0
- package/src/shared/keybindings.ts +10 -0
- package/src/shared/rpc.ts +40 -2
- package/src/shared/svn.ts +147 -0
- package/src/shared/svnActions.ts +20 -0
- package/src/svn.ts +320 -11
- package/src/workspace.ts +0 -75
|
@@ -12,6 +12,7 @@ import { rpc } from '../../rpc.js'
|
|
|
12
12
|
import type { SidebarCtx } from '../types.js'
|
|
13
13
|
import { CACHE_KEY } from '../../paths.js'
|
|
14
14
|
import { workspaceScopeOf } from '../../state/scopeStore.js'
|
|
15
|
+
import { takeSearchSeed } from '../../searchSeed.js'
|
|
15
16
|
|
|
16
17
|
const DEBOUNCE_MS = 250
|
|
17
18
|
const INCLUDE_PLACEHOLDER = '例如 *.ts, src/**/include'
|
|
@@ -25,6 +26,23 @@ export function splitGlobs(text) {
|
|
|
25
26
|
return String(text ?? '').split(',').map((item) => item.trim()).filter(Boolean)
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 读取按作用域持久化的搜索条件(损坏/缺失返回 null)。
|
|
31
|
+
* 抽成函数而非内联:恢复 effect 内有两处消费者(填状态、同步 requestRef),
|
|
32
|
+
* 且解析必须只做一次(JSON.parse 重复调用既慢又可能两次结果不一致)。
|
|
33
|
+
* @author ddj 2026年09月18号
|
|
34
|
+
* @param key 作用域缓存键
|
|
35
|
+
* @returns 已解析的搜索条件对象;无/损坏返回 null
|
|
36
|
+
*/
|
|
37
|
+
export function savedSearchOf(key: string) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(localStorage.getItem(key) || 'null')
|
|
40
|
+
return parsed && typeof parsed === 'object' ? parsed : null
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
/**
|
|
29
47
|
* 命中行文本三段切分(before/命中/after,列为 1-based UTF-16)。
|
|
30
48
|
* @param text 行文本
|
|
@@ -82,20 +100,31 @@ export function SearchPanel(props) {
|
|
|
82
100
|
// 按作用域恢复上次查询、选项与过滤模式(VSCode 记忆搜索词)
|
|
83
101
|
React.useEffect(() => {
|
|
84
102
|
if (!scope) return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
const saved = savedSearchOf(CACHE_KEY.search + String(scope))
|
|
104
|
+
if (saved) {
|
|
105
|
+
if (typeof saved.query === 'string') setQuery(saved.query)
|
|
106
|
+
if (typeof saved.matchCase === 'boolean') setMatchCase(saved.matchCase)
|
|
107
|
+
if (typeof saved.wholeWord === 'boolean') setWholeWord(saved.wholeWord)
|
|
108
|
+
if (typeof saved.regex === 'boolean') setRegex(saved.regex)
|
|
109
|
+
if (typeof saved.includeText === 'string') setIncludeText(saved.includeText)
|
|
110
|
+
if (typeof saved.excludeText === 'string') setExcludeText(saved.excludeText)
|
|
111
|
+
if (typeof saved.onlyActive === 'boolean') setOnlyActive(saved.onlyActive)
|
|
112
|
+
if (typeof saved.excludeOn === 'boolean') setExcludeOn(saved.excludeOn)
|
|
113
|
+
if (typeof saved.sectionOpen === 'boolean') setSectionOpen(saved.sectionOpen)
|
|
114
|
+
// 显式把恢复后的选项同步进 requestRef:render 期的同步要等下一次渲染才生效,
|
|
115
|
+
// 而紧随其后的「选区种子」effect 会在本次 commit 内立即搜索,读到的会是陈旧选项。
|
|
116
|
+
requestRef.current = {
|
|
117
|
+
matchCase: saved.matchCase === true,
|
|
118
|
+
wholeWord: saved.wholeWord === true,
|
|
119
|
+
regex: saved.regex === true,
|
|
120
|
+
include: saved.onlyActive === true
|
|
121
|
+
? (activePathRef.current ? [activePathRef.current] : [])
|
|
122
|
+
: splitGlobs(typeof saved.includeText === 'string' ? saved.includeText : ''),
|
|
123
|
+
exclude: saved.excludeOn === true
|
|
124
|
+
? splitGlobs(typeof saved.excludeText === 'string' ? saved.excludeText : '')
|
|
125
|
+
: [],
|
|
97
126
|
}
|
|
98
|
-
}
|
|
127
|
+
}
|
|
99
128
|
}, [scope])
|
|
100
129
|
|
|
101
130
|
// 查询/选项/过滤变化 → 持久化
|
|
@@ -191,9 +220,35 @@ export function SearchPanel(props) {
|
|
|
191
220
|
})
|
|
192
221
|
}
|
|
193
222
|
|
|
194
|
-
|
|
223
|
+
/**
|
|
224
|
+
* 取用一次性选区种子(需求 1:Ctrl+Shift+F 时把编辑器选中文本填入搜索框)。
|
|
225
|
+
* 有种子即填入并立即搜索(不再等 250ms 防抖),同时全选便于直接改写;无种子为 no-op。
|
|
226
|
+
* @author ddj 2026年09月18号
|
|
227
|
+
* @returns 是否消费到种子
|
|
228
|
+
*/
|
|
229
|
+
const applySeed = () => {
|
|
230
|
+
const seed = takeSearchSeed()
|
|
231
|
+
if (!seed) return false
|
|
232
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
233
|
+
queryRef.current = seed
|
|
234
|
+
setQuery(seed)
|
|
235
|
+
runSearch(seed)
|
|
236
|
+
inputRef.current?.select?.()
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 挂载时消费种子:侧栏原本收起 → 派发 edrv:search-focus 时本面板尚未挂载,
|
|
241
|
+
// 事件无人接收,故必须由挂载路径兜底。
|
|
242
|
+
// ⚠️ 声明在「按作用域恢复」effect 之后:React 按声明序执行 effect,恢复值先落地,
|
|
243
|
+
// 种子再覆盖,避免被记忆的旧查询词盖掉用户刚选中的内容。
|
|
244
|
+
React.useEffect(() => { applySeed() }, [])
|
|
245
|
+
|
|
246
|
+
// Ctrl+Shift+F 重复触发:消费种子(有则填入)并聚焦输入框(EditorView 派发 edrv:search-focus)
|
|
195
247
|
React.useEffect(() => {
|
|
196
|
-
const onFocus = () =>
|
|
248
|
+
const onFocus = () => {
|
|
249
|
+
applySeed()
|
|
250
|
+
inputRef.current?.focus?.()
|
|
251
|
+
}
|
|
197
252
|
window.addEventListener('edrv:search-focus', onFocus)
|
|
198
253
|
return () => window.removeEventListener('edrv:search-focus', onFocus)
|
|
199
254
|
}, [])
|
|
@@ -22,10 +22,12 @@ import {
|
|
|
22
22
|
SVN_STATUS_LETTER,
|
|
23
23
|
SVN_STATUS_TONE,
|
|
24
24
|
isSvnDiffable,
|
|
25
|
+
pairMissingWithUnversioned,
|
|
25
26
|
svnVisibleChanges,
|
|
26
27
|
} from '../../../shared/svn.js'
|
|
27
28
|
import { CACHE_KEY } from '../../paths.js'
|
|
28
|
-
import { refreshSvnChanges, svnAdd, svnRevert } from '../../svnStatus.js'
|
|
29
|
+
import { refreshSvnChanges, svnAdd, svnConflictArtifacts, svnFileSizes, svnRemoteStatus, svnRevert } from '../../svnStatus.js'
|
|
30
|
+
import { csvOf, downloadText, htmlTableOf } from '../../ui/svnExport.js'
|
|
29
31
|
import type { SidebarCtx } from '../types.js'
|
|
30
32
|
|
|
31
33
|
/** 未分组条目的分组键(changelist 为空)。 */
|
|
@@ -44,7 +46,7 @@ const REVERT_CONFIRM_TAIL = ' 个文件的本地改动?新增(A)文件在
|
|
|
44
46
|
* @returns 开关(默认:显示未版本控制、隐藏忽略项)
|
|
45
47
|
*/
|
|
46
48
|
function loadFilter(scope) {
|
|
47
|
-
const fallback = { unversioned: true, ignored: false }
|
|
49
|
+
const fallback = { unversioned: true, ignored: false, name: '' }
|
|
48
50
|
if (!scope) return fallback
|
|
49
51
|
try {
|
|
50
52
|
const raw = window.localStorage.getItem(CACHE_KEY.svn + scope)
|
|
@@ -53,6 +55,8 @@ function loadFilter(scope) {
|
|
|
53
55
|
return {
|
|
54
56
|
unversioned: parsed?.unversioned !== false,
|
|
55
57
|
ignored: parsed?.ignored === true,
|
|
58
|
+
// W1-2 名称过滤随开关一并持久化(旧数据缺字段 = 不过滤,向后兼容)
|
|
59
|
+
name: typeof parsed?.name === 'string' ? parsed.name : '',
|
|
56
60
|
}
|
|
57
61
|
} catch (error) {
|
|
58
62
|
return fallback
|
|
@@ -121,16 +125,18 @@ export function groupByChangelist(entries) {
|
|
|
121
125
|
|
|
122
126
|
/**
|
|
123
127
|
* 单条变更行(状态字母 + 路径 + 行内动作;双击 = 与基线比较)。
|
|
124
|
-
* @author ddj 2026年09月16号
|
|
128
|
+
* @author ddj 2026年09月16号 / 2026年09月20号
|
|
125
129
|
* @param props.entry 变更条目
|
|
126
130
|
* @param props.busy 批量动作进行中(按钮置灰)
|
|
127
131
|
* @param props.onDiff 打开基线差异
|
|
128
132
|
* @param props.onAdd 加入版本控制
|
|
129
133
|
* @param props.onRevert 还原
|
|
134
|
+
* @param props.onConflict 冲突副本对比(W2-4;仅冲突行出现按钮)
|
|
135
|
+
* @param props.pairNote 疑似改名标记文案(W2-5;空 = 无配对)
|
|
130
136
|
* @returns 行元素
|
|
131
137
|
*/
|
|
132
138
|
function SvnRow(props) {
|
|
133
|
-
const { entry, busy, onDiff, onAdd, onRevert } = props
|
|
139
|
+
const { entry, busy, onDiff, onAdd, onRevert, onConflict, pairNote } = props
|
|
134
140
|
const diffable = isSvnDiffable(entry.path, entry.status)
|
|
135
141
|
const act = (label, title, danger, run) => React.createElement('button', {
|
|
136
142
|
className: 'edrv-svn-act' + (danger ? ' edrv-svn-act-danger' : ''),
|
|
@@ -145,7 +151,9 @@ function SvnRow(props) {
|
|
|
145
151
|
},
|
|
146
152
|
statusLetterEl(entry),
|
|
147
153
|
React.createElement('span', { className: 'edrv-svn-path' }, entry.path),
|
|
154
|
+
(pairNote ? React.createElement('span', { className: 'edrv-svn-pair-mark', title: pairNote }, '⇄') : null),
|
|
148
155
|
React.createElement('span', { className: 'edrv-svn-acts' },
|
|
156
|
+
(entry.status === 'conflicted' ? act('冲突对比', '与 .mine/.rN 冲突副本并排对比(解决指引见差异视图顶部)', false, () => onConflict(entry.path)) : null),
|
|
149
157
|
(diffable ? act('比较', SVN_DIFF_BASE_LABEL + '(BASE 与工作区并排)', false, () => onDiff(entry.path)) : null),
|
|
150
158
|
(!entry.versioned ? act('加入', SVN_ADD_LABEL, false, () => onAdd([entry.path])) : null),
|
|
151
159
|
(entry.versioned ? act('还原', SVN_REVERT_LABEL + '(放弃本地改动)', true, () => onRevert([entry.path])) : null)))
|
|
@@ -161,7 +169,11 @@ function SvnRow(props) {
|
|
|
161
169
|
function svnListBody(state, handlers) {
|
|
162
170
|
if (!state.managed) return React.createElement('div', { className: 'edrv-tree-loading' }, '当前工作区不受 SVN 管理')
|
|
163
171
|
if (state.entries === null) return React.createElement('div', { className: 'edrv-tree-loading' }, '读取变更中…')
|
|
164
|
-
if (!state.visible.length)
|
|
172
|
+
if (!state.visible.length) {
|
|
173
|
+
// W1-2:名称过滤生效时区分「无匹配」与「无变更」,提示清空过滤框
|
|
174
|
+
return React.createElement('div', { className: 'edrv-tree-loading' },
|
|
175
|
+
state.nameFiltered ? '无匹配条目(名称过滤生效中,清空过滤框可看全部)' : '无变更(工作副本干净)')
|
|
176
|
+
}
|
|
165
177
|
const rows = []
|
|
166
178
|
for (const group of state.groups) {
|
|
167
179
|
if (state.groups.length > 1 || group.name !== NO_CHANGELIST) {
|
|
@@ -170,47 +182,64 @@ function svnListBody(state, handlers) {
|
|
|
170
182
|
React.createElement('span', { className: 'edrv-svn-group-n' }, String(group.entries.length))))
|
|
171
183
|
}
|
|
172
184
|
for (const entry of group.entries) {
|
|
173
|
-
rows.push(React.createElement(SvnRow, Object.assign({ key: entry.path, entry }, handlers)))
|
|
185
|
+
rows.push(React.createElement(SvnRow, Object.assign({ key: entry.path, entry, pairNote: handlers.pairNotes?.[entry.path] }, handlers)))
|
|
174
186
|
}
|
|
175
187
|
}
|
|
176
188
|
return rows
|
|
177
189
|
}
|
|
178
190
|
|
|
179
191
|
/**
|
|
180
|
-
* 工具条(显示开关 +
|
|
181
|
-
* @author ddj 2026年09月16号
|
|
182
|
-
* @param props.filter
|
|
183
|
-
* @param props.count
|
|
184
|
-
* @param props.
|
|
185
|
-
* @param props.
|
|
192
|
+
* 工具条(显示开关 + 名称过滤 + 批量动作 + 导出)。
|
|
193
|
+
* @author ddj 2026年09月16号 / 2026年09月20号
|
|
194
|
+
* @param props.filter 当前开关(含 W1-2 name)
|
|
195
|
+
* @param props.count 可见条目数(名称过滤后)
|
|
196
|
+
* @param props.countAll 全量条数(未按名称过滤,供「x/y 项」展示)
|
|
197
|
+
* @param props.paths 批量动作目标(add/revert 各自清单;按未过滤集合计算)
|
|
198
|
+
* @param props.handlers 动作回调(toggle/add/revert/onNameFilter/onExportCsv/onExportHtml/busy)
|
|
186
199
|
* @returns 工具条元素数组
|
|
187
200
|
*/
|
|
188
201
|
function svnToolbarEl(props) {
|
|
189
|
-
const { filter, count, paths, handlers } = props
|
|
202
|
+
const { filter, count, countAll, paths, handlers } = props
|
|
190
203
|
const check = (label, title, checked, onChange) => React.createElement('label', {
|
|
191
204
|
className: 'edrv-svn-check', title,
|
|
192
205
|
},
|
|
193
206
|
React.createElement('input', { type: 'checkbox', checked, onChange }),
|
|
194
207
|
React.createElement('span', null, label))
|
|
208
|
+
const nameFiltered = String(filter.name || '').trim() !== ''
|
|
195
209
|
return [
|
|
196
210
|
React.createElement('div', { key: 'tools', className: 'edrv-svn-toolbar' },
|
|
197
211
|
check('未版本控制', '显示未纳入版本控制的文件', filter.unversioned, handlers.toggleUnversioned),
|
|
198
|
-
check('忽略项', '显示被忽略的文件', filter.ignored, handlers.toggleIgnored)
|
|
212
|
+
check('忽略项', '显示被忽略的文件', filter.ignored, handlers.toggleIgnored),
|
|
213
|
+
React.createElement('input', {
|
|
214
|
+
className: 'edrv-svn-name-filter',
|
|
215
|
+
placeholder: '名称过滤:* ? 通配或子串',
|
|
216
|
+
title: '按路径过滤条目(* 任意串、? 单字符;无通配符按子串包含,忽略大小写;只影响列表展示,不缩小批量动作范围)',
|
|
217
|
+
value: filter.name || '',
|
|
218
|
+
onChange: (event) => handlers.onNameFilter(event.target.value),
|
|
219
|
+
})),
|
|
199
220
|
(count
|
|
200
221
|
? React.createElement('div', { key: 'bulk', className: 'edrv-svn-bulk' },
|
|
201
|
-
React.createElement('span', { className: 'edrv-svn-count' }, String(count) + ' 项'),
|
|
222
|
+
React.createElement('span', { className: 'edrv-svn-count' }, String(count) + (nameFiltered ? '/' + countAll : '') + ' 项'),
|
|
202
223
|
React.createElement('span', { style: { flex: 1 } }),
|
|
224
|
+
React.createElement('button', {
|
|
225
|
+
className: 'edrv-svn-act', title: '导出当前列表为 CSV(带 BOM,Excel 直开;状态/路径/changelist/修订)',
|
|
226
|
+
onClick: handlers.onExportCsv,
|
|
227
|
+
}, '导出CSV'),
|
|
228
|
+
React.createElement('button', {
|
|
229
|
+
className: 'edrv-svn-act', title: '导出当前列表为 HTML 报表(仅本地查看,不外发)',
|
|
230
|
+
onClick: handlers.onExportHtml,
|
|
231
|
+
}, '导出HTML'),
|
|
203
232
|
(paths.unversioned.length
|
|
204
233
|
? React.createElement('button', {
|
|
205
234
|
className: 'edrv-svn-act', disabled: handlers.busy,
|
|
206
|
-
title: '
|
|
235
|
+
title: '把全部未版本控制文件加入版本控制(不受名称过滤影响)',
|
|
207
236
|
onClick: () => handlers.add(paths.unversioned),
|
|
208
237
|
}, '全部加入')
|
|
209
238
|
: null),
|
|
210
239
|
(paths.revert.length
|
|
211
240
|
? React.createElement('button', {
|
|
212
241
|
className: 'edrv-svn-act edrv-svn-act-danger', disabled: handlers.busy,
|
|
213
|
-
title: '
|
|
242
|
+
title: '还原全部已受版本控制文件的本地改动(不受名称过滤影响)',
|
|
214
243
|
onClick: () => handlers.revert(paths.revert),
|
|
215
244
|
}, '全部还原')
|
|
216
245
|
: null))
|
|
@@ -233,10 +262,17 @@ export function SvnPanel(props) {
|
|
|
233
262
|
const managed = Boolean(ctx?.svn?.managed && ctx?.svn?.svnCli)
|
|
234
263
|
const [filter, setFilter] = React.useState(() => loadFilter(scope))
|
|
235
264
|
const [busy, setBusy] = React.useState(false)
|
|
265
|
+
// W2-3:远端检查结果(null = 未检查;{ failed: true } = 失败降级;否则 { outdated, againstRev })
|
|
266
|
+
const [remote, setRemote] = React.useState(null)
|
|
267
|
+
const [remoteBusy, setRemoteBusy] = React.useState(false)
|
|
268
|
+
// W2-5:配对候选的文件大小(path → size|null)与成对结果
|
|
269
|
+
const [sizes, setSizes] = React.useState({})
|
|
236
270
|
|
|
237
|
-
//
|
|
271
|
+
// 作用域切换:重读该作用域自己的开关(同一工作区共享)并清掉远端/配对的会话内状态
|
|
238
272
|
React.useEffect(() => {
|
|
239
273
|
setFilter(loadFilter(scope))
|
|
274
|
+
setRemote(null)
|
|
275
|
+
setSizes({})
|
|
240
276
|
}, [scope])
|
|
241
277
|
|
|
242
278
|
const applyFilter = (next) => {
|
|
@@ -264,40 +300,148 @@ export function SvnPanel(props) {
|
|
|
264
300
|
}).finally(() => setBusy(false))
|
|
265
301
|
}
|
|
266
302
|
|
|
267
|
-
|
|
303
|
+
/**
|
|
304
|
+
* 远端更新检查(W2-3;host 侧 15s 短超时,离线/超时降级为提示条文案,不阻塞面板)。
|
|
305
|
+
* @author ddj 2026年09月20号
|
|
306
|
+
*/
|
|
307
|
+
const checkRemote = () => {
|
|
308
|
+
if (remoteBusy) return
|
|
309
|
+
setRemoteBusy(true)
|
|
310
|
+
void svnRemoteStatus(sessionId, '').then((outcome) => {
|
|
311
|
+
if (!outcome.ok) {
|
|
312
|
+
setRemote({ failed: true })
|
|
313
|
+
ctx?.notify?.(outcome.message)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
setRemote({ outdated: outcome.outdated ?? [], againstRev: outcome.againstRev ?? null })
|
|
317
|
+
}).finally(() => setRemoteBusy(false))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 冲突副本对比入口(W2-4,只读):扫描 .mine/.working/.rN → 默认首个 .rN ↔ .mine 并排;
|
|
322
|
+
* resolve 指引在差异视图顶部展示,本入口绝不自动执行 svn resolve(归 P2 破坏性批次)。
|
|
323
|
+
* @author ddj 2026年09月20号
|
|
324
|
+
* @param path 冲突文件的工作区相对路径
|
|
325
|
+
*/
|
|
326
|
+
const openConflictDiff = (path) => {
|
|
327
|
+
void svnConflictArtifacts(sessionId, path).then((outcome) => {
|
|
328
|
+
if (!outcome.ok) { ctx?.notify?.(outcome.message); return }
|
|
329
|
+
const artifacts = outcome.artifacts ?? []
|
|
330
|
+
const revSide = artifacts.find((item) => item.kind === 'rev')
|
|
331
|
+
const mineSide = artifacts.find((item) => item.kind === 'mine')
|
|
332
|
+
if (!revSide || !mineSide) {
|
|
333
|
+
ctx?.notify?.('未发现冲突副本(.mine/.rN):' + artifacts.map((item) => item.name).join('、'))
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
if (!ctx?.openSvnLocalPair) { ctx?.notify?.('当前视图不支持冲突对比'); return }
|
|
337
|
+
ctx.openSvnLocalPair(revSide.path, mineSide.path, '.r' + (revSide.rev ?? '?'), '.mine', path)
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// W1-2:展示集合(含名称过滤)与批量动作集合(不含名称过滤)分离,「全部加入/还原」不缩小范围
|
|
342
|
+
const shown = entries === null ? [] : svnVisibleChanges(entries, filter)
|
|
343
|
+
const actionable = entries === null ? [] : svnVisibleChanges(entries, { unversioned: filter.unversioned, ignored: filter.ignored })
|
|
344
|
+
const nameFiltered = String(filter.name || '').trim() !== ''
|
|
345
|
+
|
|
346
|
+
// W2-5:`!`(missing)与 `?`(unversioned)共存时才做大小查询(候选少,一次批量查完成对)
|
|
347
|
+
const missingEntries = entries === null ? [] : entries.filter((entry) => entry.status === 'missing')
|
|
348
|
+
const unversionedEntries = entries === null ? [] : entries.filter((entry) => entry.status === 'unversioned')
|
|
349
|
+
const candidates = [...missingEntries, ...unversionedEntries].map((entry) => entry.path)
|
|
350
|
+
const pairsKnown = missingEntries.length > 0 && unversionedEntries.length > 0
|
|
351
|
+
&& candidates.every((path) => path in sizes)
|
|
352
|
+
React.useEffect(() => {
|
|
353
|
+
if (!pairsKnown && candidates.length) {
|
|
354
|
+
void svnFileSizes(sessionId, candidates).then((table) => setSizes(table))
|
|
355
|
+
}
|
|
356
|
+
}, [pairsKnown, candidates.join('\n'), sessionId]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
357
|
+
const pairNoteOf = pairsKnown
|
|
358
|
+
? (() => {
|
|
359
|
+
const notes = {}
|
|
360
|
+
for (const pair of pairMissingWithUnversioned(entries ?? [], sizes)) {
|
|
361
|
+
notes[pair.missingPath] = '疑似改名:原文件 → ' + pair.unversionedPath
|
|
362
|
+
notes[pair.unversionedPath] = '疑似改名:← 原文件 ' + pair.missingPath
|
|
363
|
+
}
|
|
364
|
+
return notes
|
|
365
|
+
})()
|
|
366
|
+
: {}
|
|
367
|
+
|
|
268
368
|
const openSvnDiff = (p) => ctx?.openSvnDiff?.(p)
|
|
269
369
|
const handlers = {
|
|
270
370
|
busy,
|
|
271
371
|
onDiff: openSvnDiff,
|
|
372
|
+
onConflict: openConflictDiff,
|
|
373
|
+
pairNotes: pairNoteOf,
|
|
272
374
|
onAdd: (paths) => runBatch('add', paths),
|
|
273
375
|
onRevert: (paths) => runBatch('revert', paths),
|
|
274
376
|
}
|
|
275
|
-
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 导出当前展示列表(W1-4):CSV 带 BOM 供 Excel 直开;HTML 本地报表;失败经 notify 提示。
|
|
380
|
+
* @author ddj 2026年09月20号
|
|
381
|
+
* @param kind 导出格式('csv' | 'html')
|
|
382
|
+
*/
|
|
383
|
+
const onExport = (kind) => {
|
|
384
|
+
const rows = shown.map((entry) => [
|
|
385
|
+
SVN_STATUS_LETTER[entry.status] || '',
|
|
386
|
+
entry.path,
|
|
387
|
+
entry.changelist || '',
|
|
388
|
+
entry.revision && entry.revision > 0 ? String(entry.revision) : '',
|
|
389
|
+
])
|
|
390
|
+
const headers = ['状态', '路径', 'changelist', '修订']
|
|
391
|
+
const stamp = new Date().toISOString().slice(0, 10)
|
|
392
|
+
const done = kind === 'html'
|
|
393
|
+
? downloadText('svn-changes-' + stamp + '.html', htmlTableOf(headers, rows, 'SVN 变更列表 ' + stamp), 'text/html')
|
|
394
|
+
: downloadText('svn-changes-' + stamp + '.csv', csvOf([headers, ...rows]), 'text/csv')
|
|
395
|
+
if (!done) ctx?.notify?.('导出失败:浏览器下载能力不可用')
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const listState = { managed, entries, visible: shown, groups: groupByChangelist(shown), nameFiltered }
|
|
276
399
|
const toolbar = svnToolbarEl({
|
|
277
400
|
filter,
|
|
278
|
-
count:
|
|
401
|
+
count: shown.length,
|
|
402
|
+
countAll: actionable.length,
|
|
279
403
|
paths: {
|
|
280
|
-
unversioned:
|
|
281
|
-
revert:
|
|
404
|
+
unversioned: actionable.filter((entry) => !entry.versioned).map((entry) => entry.path),
|
|
405
|
+
revert: actionable.filter((entry) => entry.versioned).map((entry) => entry.path),
|
|
282
406
|
},
|
|
283
407
|
handlers: Object.assign({}, handlers, {
|
|
284
408
|
toggleUnversioned: () => applyFilter({ ...filter, unversioned: !filter.unversioned }),
|
|
285
409
|
toggleIgnored: () => applyFilter({ ...filter, ignored: !filter.ignored }),
|
|
410
|
+
onNameFilter: (name) => applyFilter({ ...filter, name }),
|
|
411
|
+
onExportCsv: () => onExport('csv'),
|
|
412
|
+
onExportHtml: () => onExport('html'),
|
|
286
413
|
add: (paths) => runBatch('add', paths),
|
|
287
414
|
revert: (paths) => runBatch('revert', paths),
|
|
288
415
|
}),
|
|
289
416
|
})
|
|
290
417
|
const rootName = ctx?.svn?.wcRoot ? String(ctx.svn.wcRoot).split(/[\\/]/).pop() || ctx.svn.wcRoot : ''
|
|
291
418
|
|
|
419
|
+
// W2-3:远端更新提示条(null = 未检查;failed = 降级文案;否则展示落后数与 against 修订)
|
|
420
|
+
const remoteEl = remote === null
|
|
421
|
+
? null
|
|
422
|
+
: React.createElement('div', { className: 'edrv-svn-hintbar' + (remote.failed ? ' edrv-svn-hintbar-warn' : '') },
|
|
423
|
+
React.createElement('span', { style: { flex: 1 } },
|
|
424
|
+
remote.failed
|
|
425
|
+
? '远端检查失败(网络不可达或超时;可用 ⟳ 或「检查远端」重试)'
|
|
426
|
+
: (remote.outdated.length
|
|
427
|
+
? '远端有 ' + remote.outdated.length + ' 个更新(against r' + (remote.againstRev ?? '?') + '),其中本地已改 ' + remote.outdated.filter((item) => item.path && entries?.some((entry) => entry.path === item.path && entry.versioned)).length + ' 个;更新前请先提交或还原'
|
|
428
|
+
: '远端无更新(against r' + (remote.againstRev ?? '?') + ')')),
|
|
429
|
+
React.createElement('button', { className: 'edrv-svn-act', title: '关闭提示条', onClick: () => setRemote(null) }, '✕'))
|
|
430
|
+
|
|
292
431
|
return React.createElement('div', { className: 'edrv-side-panel' },
|
|
293
432
|
React.createElement('div', { className: 'edrv-side-head' },
|
|
294
433
|
React.createElement('span', { className: 'edrv-side-title' }, 'SVN 变更'),
|
|
295
434
|
React.createElement('span', { className: 'edrv-side-root', title: ctx?.svn?.wcRoot || '' }, rootName),
|
|
296
435
|
React.createElement('span', { style: { flex: 1 } }),
|
|
436
|
+
React.createElement('button', {
|
|
437
|
+
className: 'edrv-side-btn', title: '检查远端更新(svn status -u;约数秒,离线自动降级)',
|
|
438
|
+
onClick: checkRemote,
|
|
439
|
+
}, remoteBusy ? '…' : '⇅'),
|
|
297
440
|
React.createElement('button', {
|
|
298
441
|
className: 'edrv-side-btn', title: '刷新变更',
|
|
299
442
|
onClick: () => { refreshSvnChanges(sessionId, scope); ctx?.refreshRecords?.() },
|
|
300
443
|
}, refreshIconEl())),
|
|
444
|
+
remoteEl,
|
|
301
445
|
...toolbar,
|
|
302
446
|
React.createElement('div', { className: 'edrv-svn-list' }, svnListBody(listState, handlers)))
|
|
303
447
|
}
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react'
|
|
9
9
|
import { IconBranchOutline16, IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
10
|
+
import { dapStore } from '../../dap/store.js'
|
|
10
11
|
import { FileExplorer } from './FileExplorer.js'
|
|
11
12
|
import { SearchPanel } from './SearchPanel.js'
|
|
12
13
|
import { RulesPanel } from './RulesPanel.js'
|
|
13
14
|
import { SvnPanel } from './SvnPanel.js'
|
|
15
|
+
import { DebugPanel } from './DebugPanel.js'
|
|
14
16
|
import type { SidebarPanelDef, SidebarCtx } from '../types.js'
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -97,3 +99,20 @@ function svnVisibleCount(entries) {
|
|
|
97
99
|
}
|
|
98
100
|
return count
|
|
99
101
|
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 构造「调试」面板定义(VS Code 调试视图四段布局 + REPL)。
|
|
105
|
+
* 图标用文本回落(官方原语图标集未含调试类图标时由 SidebarView 文本渲染)。
|
|
106
|
+
* @author ddj 2026年09月29号
|
|
107
|
+
* @returns 面板定义(徽标 = 已暂停时显示 1,吸引注意)
|
|
108
|
+
*/
|
|
109
|
+
export function createDebugPanel(): SidebarPanelDef {
|
|
110
|
+
return {
|
|
111
|
+
id: 'debug',
|
|
112
|
+
title: '调试',
|
|
113
|
+
icon: '🐞',
|
|
114
|
+
order: 22,
|
|
115
|
+
badge: () => (dapStore.getSnapshot().phase === 'paused' ? 1 : null),
|
|
116
|
+
render: (ctx: SidebarCtx) => React.createElement(DebugPanel, { ctx }),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -41,6 +41,8 @@ export interface SidebarCtx {
|
|
|
41
41
|
svnChangeMap?: Record<string, SvnChangeEntry>
|
|
42
42
|
/** 打开 SVN 基线差异视图(变更面板行内「与基线比较」;缺省时面板降级提示)。 */
|
|
43
43
|
openSvnDiff?: (path: string) => void
|
|
44
|
+
/** 打开双侧本地文件差异(W2-4 冲突副本 `.rN` ↔ `.mine`;缺省时面板降级提示)。 */
|
|
45
|
+
openSvnLocalPair?: (left: string, right: string, leftLabel: string, rightLabel: string, title: string) => void
|
|
44
46
|
/** 重查 SVN 变更清单(CLI 更新/还原/加入版本控制后由菜单动作调用)。 */
|
|
45
47
|
refreshSvnChanges?: () => void
|
|
46
48
|
/** 打开 SVN 日志弹窗(P3;菜单/命令栏「查看日志」)。 */
|