dsh-vscode-mode 0.4.6 → 0.5.1
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 +38 -2
- package/lib/client.js +9316 -4038
- package/lib/client.js.map +1 -1
- package/lib/index.js +1852 -68
- package/lib/index.js.map +1 -1
- package/package.json +8 -8
- package/src/client/ai/inlineProvider.ts +42 -1
- package/src/client/index.ts +50 -45
- package/src/client/monaco/lsp/index.ts +33 -3
- package/src/client/monaco/lsp/lspClient.ts +3 -1
- package/src/client/monaco/lsp/providers.ts +60 -6
- package/src/client/navHighlight.ts +96 -0
- package/src/client/officialSidebar.ts +67 -3
- package/src/client/paths.ts +2 -0
- package/src/client/rpc.ts +32 -12
- package/src/client/sessionScope.ts +183 -0
- package/src/client/sidebar/SidebarView.ts +6 -2
- package/src/client/sidebar/menuItems.ts +93 -2
- package/src/client/sidebar/panels/FileExplorer.ts +18 -1
- package/src/client/sidebar/panels/SvnPanel.ts +303 -0
- package/src/client/sidebar/panels/index.ts +44 -3
- package/src/client/sidebar/types.ts +20 -0
- package/src/client/snippets/provider.ts +26 -4
- package/src/client/styles/editor.css +177 -0
- package/src/client/svnActions.ts +153 -0
- package/src/client/svnLog.ts +356 -0
- package/src/client/svnStatus.ts +363 -0
- package/src/client/svnStore.ts +150 -0
- package/src/client/tabActions.ts +30 -5
- package/src/client/tabMenu.ts +51 -0
- package/src/client/ui/AiSettings.ts +8 -0
- package/src/client/ui/EditorView.ts +632 -35
- package/src/client/ui/LogDialog.ts +333 -0
- package/src/client/ui/McpSettings.ts +98 -0
- package/src/client/ui/ModalShell.ts +79 -0
- package/src/client/ui/SideBySideDiff.tsx +139 -0
- package/src/client/ui/SnippetsPicker.ts +70 -58
- package/src/client/ui/SvnDiffPanel.ts +50 -0
- package/src/client/ui/SvnLogDialog.ts +1141 -0
- package/src/client/ui/SvnLogStats.ts +243 -0
- package/src/client/ui/commandCatalog.ts +72 -0
- package/src/compat.ts +1 -1
- package/src/debugLog.ts +81 -12
- package/src/dshVersion.ts +8 -1
- package/src/fileOpenSettings.ts +40 -3
- package/src/index.ts +5 -1
- package/src/rpc.ts +41 -4
- package/src/shared/ai.ts +25 -0
- package/src/shared/rpc.ts +53 -1
- package/src/shared/svn.ts +377 -0
- package/src/shared/svnActions.ts +296 -0
- package/src/svn.ts +870 -0
- package/src/svnLog.ts +315 -0
- package/src/svnText.ts +175 -0
- package/src/svnXml.ts +110 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 侧边栏「SVN 变更」面板(工作副本状态视图)。
|
|
4
|
+
*
|
|
5
|
+
* 形态对齐 TortoiseSVN「Check for Modifications」的**列表 + 分组 + 行内动作**骨架:
|
|
6
|
+
* 头部(标题 / 工作副本根名 / ⟳)+ 显示开关(未版本控制 / 忽略项)+ 批量条 +
|
|
7
|
+
* 按 changelist 分组的行列表 + 空态/加载态/未受管理态。行 = 状态字母(按 SVN_STATUS_TONE
|
|
8
|
+
* 着色)+ 相对路径 + 动作按钮(比较 / 加入 / 还原);双击行走「与基线比较」。
|
|
9
|
+
*
|
|
10
|
+
* 数据来源:ctx.svnChanges(EditorView 经 svnStatus.ensureSvnChanges 拉取后注入),
|
|
11
|
+
* 本面板不自行发 RPC,避免与文件树重复拉取;⟳ 触发 refreshSvnChanges 后由事件回流重渲染。
|
|
12
|
+
* 显示开关持久化到 localStorage(CACHE_KEY.svn + scope),与既有面板一致。
|
|
13
|
+
* 作者 ddj 2026年09月16号
|
|
14
|
+
*/
|
|
15
|
+
import React from 'react'
|
|
16
|
+
import { IconRefreshOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
17
|
+
import {
|
|
18
|
+
SVN_ADD_LABEL,
|
|
19
|
+
SVN_DIFF_BASE_LABEL,
|
|
20
|
+
SVN_REVERT_LABEL,
|
|
21
|
+
SVN_STATUS_LABEL,
|
|
22
|
+
SVN_STATUS_LETTER,
|
|
23
|
+
SVN_STATUS_TONE,
|
|
24
|
+
isSvnDiffable,
|
|
25
|
+
svnVisibleChanges,
|
|
26
|
+
} from '../../../shared/svn.js'
|
|
27
|
+
import { CACHE_KEY } from '../../paths.js'
|
|
28
|
+
import { refreshSvnChanges, svnAdd, svnRevert } from '../../svnStatus.js'
|
|
29
|
+
import type { SidebarCtx } from '../types.js'
|
|
30
|
+
|
|
31
|
+
/** 未分组条目的分组键(changelist 为空)。 */
|
|
32
|
+
const NO_CHANGELIST = '(未分组)'
|
|
33
|
+
|
|
34
|
+
/** 还原确认文案(新增文件不会被删除是 svn revert 的实际语义,须如实告知)。 */
|
|
35
|
+
const REVERT_CONFIRM_HEAD = '确认还原以下 '
|
|
36
|
+
const REVERT_CONFIRM_TAIL = ' 个文件的本地改动?新增(A)文件在磁盘上会保留,仅取消登记。'
|
|
37
|
+
|
|
38
|
+
// --region 显示开关持久化
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 读取面板显示开关(损坏/不可用安全)。
|
|
42
|
+
* @author ddj 2026年09月16号
|
|
43
|
+
* @param scope 作用域键
|
|
44
|
+
* @returns 开关(默认:显示未版本控制、隐藏忽略项)
|
|
45
|
+
*/
|
|
46
|
+
function loadFilter(scope) {
|
|
47
|
+
const fallback = { unversioned: true, ignored: false }
|
|
48
|
+
if (!scope) return fallback
|
|
49
|
+
try {
|
|
50
|
+
const raw = window.localStorage.getItem(CACHE_KEY.svn + scope)
|
|
51
|
+
if (!raw) return fallback
|
|
52
|
+
const parsed = JSON.parse(raw)
|
|
53
|
+
return {
|
|
54
|
+
unversioned: parsed?.unversioned !== false,
|
|
55
|
+
ignored: parsed?.ignored === true,
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return fallback
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 写面板显示开关(配额/隐私模式失败静默)。
|
|
64
|
+
* @author ddj 2026年09月16号
|
|
65
|
+
* @param scope 作用域键
|
|
66
|
+
* @param filter 开关
|
|
67
|
+
*/
|
|
68
|
+
function saveFilter(scope, filter) {
|
|
69
|
+
if (!scope) return
|
|
70
|
+
try {
|
|
71
|
+
window.localStorage.setItem(CACHE_KEY.svn + scope, JSON.stringify(filter))
|
|
72
|
+
} catch (error) { /* 配额满/隐私模式忽略 */ }
|
|
73
|
+
}
|
|
74
|
+
// --endregion
|
|
75
|
+
|
|
76
|
+
// --region 展示元素
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 刷新按钮图标(官方 IconRefreshOutline16;原语缺失回落文本 ⟳)。
|
|
80
|
+
* @author ddj 2026年09月16号
|
|
81
|
+
* @returns 图标元素或回落文本
|
|
82
|
+
*/
|
|
83
|
+
function refreshIconEl() {
|
|
84
|
+
if (typeof IconRefreshOutline16 !== 'function') return '⟳'
|
|
85
|
+
return React.createElement(IconRefreshOutline16, { size: 14 })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 条目状态字母元素(按 tone 着色)。
|
|
90
|
+
* @author ddj 2026年09月16号
|
|
91
|
+
* @param entry 变更条目
|
|
92
|
+
* @returns 字母元素
|
|
93
|
+
*/
|
|
94
|
+
function statusLetterEl(entry) {
|
|
95
|
+
const tone = SVN_STATUS_TONE[entry.status] ?? 'plain'
|
|
96
|
+
return React.createElement('span', {
|
|
97
|
+
className: 'edrv-svn-ch edrv-svn-ch-' + tone,
|
|
98
|
+
title: SVN_STATUS_LABEL[entry.status] ?? entry.status,
|
|
99
|
+
}, SVN_STATUS_LETTER[entry.status] ?? '?')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 按 changelist 把条目分组(未分组恒排末位;组内保持工作副本原顺序)。
|
|
104
|
+
* @author ddj 2026年09月16号
|
|
105
|
+
* @param entries 已过滤的变更清单
|
|
106
|
+
* @returns 分组数组(name + entries)
|
|
107
|
+
*/
|
|
108
|
+
export function groupByChangelist(entries) {
|
|
109
|
+
const groups = new Map()
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
const name = entry.changelist || NO_CHANGELIST
|
|
112
|
+
const bucket = groups.get(name)
|
|
113
|
+
if (bucket) bucket.push(entry)
|
|
114
|
+
else groups.set(name, [entry])
|
|
115
|
+
}
|
|
116
|
+
const named = [...groups.entries()].filter(([name]) => name !== NO_CHANGELIST)
|
|
117
|
+
const rest = groups.get(NO_CHANGELIST)
|
|
118
|
+
if (rest) named.push([NO_CHANGELIST, rest])
|
|
119
|
+
return named.map(([name, items]) => ({ name, entries: items }))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 单条变更行(状态字母 + 路径 + 行内动作;双击 = 与基线比较)。
|
|
124
|
+
* @author ddj 2026年09月16号
|
|
125
|
+
* @param props.entry 变更条目
|
|
126
|
+
* @param props.busy 批量动作进行中(按钮置灰)
|
|
127
|
+
* @param props.onDiff 打开基线差异
|
|
128
|
+
* @param props.onAdd 加入版本控制
|
|
129
|
+
* @param props.onRevert 还原
|
|
130
|
+
* @returns 行元素
|
|
131
|
+
*/
|
|
132
|
+
function SvnRow(props) {
|
|
133
|
+
const { entry, busy, onDiff, onAdd, onRevert } = props
|
|
134
|
+
const diffable = isSvnDiffable(entry.path, entry.status)
|
|
135
|
+
const act = (label, title, danger, run) => React.createElement('button', {
|
|
136
|
+
className: 'edrv-svn-act' + (danger ? ' edrv-svn-act-danger' : ''),
|
|
137
|
+
title,
|
|
138
|
+
disabled: busy,
|
|
139
|
+
onClick: (event) => { event.stopPropagation(); run() },
|
|
140
|
+
}, label)
|
|
141
|
+
return React.createElement('div', {
|
|
142
|
+
className: 'edrv-svn-row',
|
|
143
|
+
title: entry.path,
|
|
144
|
+
onDoubleClick: () => { if (diffable) onDiff(entry.path) },
|
|
145
|
+
},
|
|
146
|
+
statusLetterEl(entry),
|
|
147
|
+
React.createElement('span', { className: 'edrv-svn-path' }, entry.path),
|
|
148
|
+
React.createElement('span', { className: 'edrv-svn-acts' },
|
|
149
|
+
(diffable ? act('比较', SVN_DIFF_BASE_LABEL + '(BASE 与工作区并排)', false, () => onDiff(entry.path)) : null),
|
|
150
|
+
(!entry.versioned ? act('加入', SVN_ADD_LABEL, false, () => onAdd([entry.path])) : null),
|
|
151
|
+
(entry.versioned ? act('还原', SVN_REVERT_LABEL + '(放弃本地改动)', true, () => onRevert([entry.path])) : null)))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 列表主体:分组行 + 条目行;未受管理/加载中/无变更各自给出稳定文案。
|
|
156
|
+
* @author ddj 2026年09月16号
|
|
157
|
+
* @param state 列表状态(managed/entries/visible/groups)
|
|
158
|
+
* @param handlers 行动作(onDiff/onAdd/onRevert/busy)
|
|
159
|
+
* @returns 主体元素或元素数组
|
|
160
|
+
*/
|
|
161
|
+
function svnListBody(state, handlers) {
|
|
162
|
+
if (!state.managed) return React.createElement('div', { className: 'edrv-tree-loading' }, '当前工作区不受 SVN 管理')
|
|
163
|
+
if (state.entries === null) return React.createElement('div', { className: 'edrv-tree-loading' }, '读取变更中…')
|
|
164
|
+
if (!state.visible.length) return React.createElement('div', { className: 'edrv-tree-loading' }, '无变更(工作副本干净)')
|
|
165
|
+
const rows = []
|
|
166
|
+
for (const group of state.groups) {
|
|
167
|
+
if (state.groups.length > 1 || group.name !== NO_CHANGELIST) {
|
|
168
|
+
rows.push(React.createElement('div', { key: 'g:' + group.name, className: 'edrv-svn-group' },
|
|
169
|
+
React.createElement('span', null, 'changelist: ' + group.name),
|
|
170
|
+
React.createElement('span', { className: 'edrv-svn-group-n' }, String(group.entries.length))))
|
|
171
|
+
}
|
|
172
|
+
for (const entry of group.entries) {
|
|
173
|
+
rows.push(React.createElement(SvnRow, Object.assign({ key: entry.path, entry }, handlers)))
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return rows
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 工具条(显示开关 + 批量动作)。
|
|
181
|
+
* @author ddj 2026年09月16号
|
|
182
|
+
* @param props.filter 当前开关
|
|
183
|
+
* @param props.count 可见条目数
|
|
184
|
+
* @param props.paths 批量动作目标(add/revert 各自清单)
|
|
185
|
+
* @param props.handlers 动作回调(toggle/add/revert/busy)
|
|
186
|
+
* @returns 工具条元素数组
|
|
187
|
+
*/
|
|
188
|
+
function svnToolbarEl(props) {
|
|
189
|
+
const { filter, count, paths, handlers } = props
|
|
190
|
+
const check = (label, title, checked, onChange) => React.createElement('label', {
|
|
191
|
+
className: 'edrv-svn-check', title,
|
|
192
|
+
},
|
|
193
|
+
React.createElement('input', { type: 'checkbox', checked, onChange }),
|
|
194
|
+
React.createElement('span', null, label))
|
|
195
|
+
return [
|
|
196
|
+
React.createElement('div', { key: 'tools', className: 'edrv-svn-toolbar' },
|
|
197
|
+
check('未版本控制', '显示未纳入版本控制的文件', filter.unversioned, handlers.toggleUnversioned),
|
|
198
|
+
check('忽略项', '显示被忽略的文件', filter.ignored, handlers.toggleIgnored)),
|
|
199
|
+
(count
|
|
200
|
+
? React.createElement('div', { key: 'bulk', className: 'edrv-svn-bulk' },
|
|
201
|
+
React.createElement('span', { className: 'edrv-svn-count' }, String(count) + ' 项'),
|
|
202
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
203
|
+
(paths.unversioned.length
|
|
204
|
+
? React.createElement('button', {
|
|
205
|
+
className: 'edrv-svn-act', disabled: handlers.busy,
|
|
206
|
+
title: '把全部未版本控制文件加入版本控制',
|
|
207
|
+
onClick: () => handlers.add(paths.unversioned),
|
|
208
|
+
}, '全部加入')
|
|
209
|
+
: null),
|
|
210
|
+
(paths.revert.length
|
|
211
|
+
? React.createElement('button', {
|
|
212
|
+
className: 'edrv-svn-act edrv-svn-act-danger', disabled: handlers.busy,
|
|
213
|
+
title: '还原全部已受版本控制文件的本地改动',
|
|
214
|
+
onClick: () => handlers.revert(paths.revert),
|
|
215
|
+
}, '全部还原')
|
|
216
|
+
: null))
|
|
217
|
+
: null),
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
// --endregion
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 「SVN 变更」面板主体。
|
|
224
|
+
* @author ddj 2026年09月16号
|
|
225
|
+
* @param props.ctx 面板共享上下文(sessionId/scope/svnChanges/svn/notify/openSvnDiff)
|
|
226
|
+
* @returns 面板元素
|
|
227
|
+
*/
|
|
228
|
+
export function SvnPanel(props) {
|
|
229
|
+
const ctx = props?.ctx
|
|
230
|
+
const sessionId = ctx?.sessionId
|
|
231
|
+
const scope = ctx?.scope
|
|
232
|
+
const entries = ctx?.svnChanges ?? null
|
|
233
|
+
const managed = Boolean(ctx?.svn?.managed && ctx?.svn?.svnCli)
|
|
234
|
+
const [filter, setFilter] = React.useState(() => loadFilter(scope))
|
|
235
|
+
const [busy, setBusy] = React.useState(false)
|
|
236
|
+
|
|
237
|
+
// 作用域切换:重读该作用域自己的开关(同一工作区共享)
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
setFilter(loadFilter(scope))
|
|
240
|
+
}, [scope])
|
|
241
|
+
|
|
242
|
+
const applyFilter = (next) => {
|
|
243
|
+
setFilter(next)
|
|
244
|
+
saveFilter(scope, next)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 批量动作统一流程:先 confirm(仅还原需要)→ 执行 → 反馈 → 强制重查。
|
|
249
|
+
* @author ddj 2026年09月16号
|
|
250
|
+
* @param kind 'add' | 'revert'
|
|
251
|
+
* @param paths 目标相对路径列表
|
|
252
|
+
*/
|
|
253
|
+
const runBatch = (kind, paths) => {
|
|
254
|
+
if (!paths.length) return
|
|
255
|
+
if (kind === 'revert') {
|
|
256
|
+
const head = REVERT_CONFIRM_HEAD + paths.length + REVERT_CONFIRM_TAIL
|
|
257
|
+
if (!window.confirm(head + '\n\n' + paths.join('\n'))) return
|
|
258
|
+
}
|
|
259
|
+
setBusy(true)
|
|
260
|
+
const task = kind === 'revert' ? svnRevert(sessionId, paths) : svnAdd(sessionId, paths)
|
|
261
|
+
void task.then((outcome) => {
|
|
262
|
+
ctx?.notify?.(outcome.message)
|
|
263
|
+
refreshSvnChanges(sessionId, scope)
|
|
264
|
+
}).finally(() => setBusy(false))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const visible = entries === null ? [] : svnVisibleChanges(entries, filter)
|
|
268
|
+
const openSvnDiff = (p) => ctx?.openSvnDiff?.(p)
|
|
269
|
+
const handlers = {
|
|
270
|
+
busy,
|
|
271
|
+
onDiff: openSvnDiff,
|
|
272
|
+
onAdd: (paths) => runBatch('add', paths),
|
|
273
|
+
onRevert: (paths) => runBatch('revert', paths),
|
|
274
|
+
}
|
|
275
|
+
const listState = { managed, entries, visible, groups: groupByChangelist(visible) }
|
|
276
|
+
const toolbar = svnToolbarEl({
|
|
277
|
+
filter,
|
|
278
|
+
count: visible.length,
|
|
279
|
+
paths: {
|
|
280
|
+
unversioned: visible.filter((entry) => !entry.versioned).map((entry) => entry.path),
|
|
281
|
+
revert: visible.filter((entry) => entry.versioned).map((entry) => entry.path),
|
|
282
|
+
},
|
|
283
|
+
handlers: Object.assign({}, handlers, {
|
|
284
|
+
toggleUnversioned: () => applyFilter({ ...filter, unversioned: !filter.unversioned }),
|
|
285
|
+
toggleIgnored: () => applyFilter({ ...filter, ignored: !filter.ignored }),
|
|
286
|
+
add: (paths) => runBatch('add', paths),
|
|
287
|
+
revert: (paths) => runBatch('revert', paths),
|
|
288
|
+
}),
|
|
289
|
+
})
|
|
290
|
+
const rootName = ctx?.svn?.wcRoot ? String(ctx.svn.wcRoot).split(/[\\/]/).pop() || ctx.svn.wcRoot : ''
|
|
291
|
+
|
|
292
|
+
return React.createElement('div', { className: 'edrv-side-panel' },
|
|
293
|
+
React.createElement('div', { className: 'edrv-side-head' },
|
|
294
|
+
React.createElement('span', { className: 'edrv-side-title' }, 'SVN 变更'),
|
|
295
|
+
React.createElement('span', { className: 'edrv-side-root', title: ctx?.svn?.wcRoot || '' }, rootName),
|
|
296
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
297
|
+
React.createElement('button', {
|
|
298
|
+
className: 'edrv-side-btn', title: '刷新变更',
|
|
299
|
+
onClick: () => { refreshSvnChanges(sessionId, scope); ctx?.refreshRecords?.() },
|
|
300
|
+
}, refreshIconEl())),
|
|
301
|
+
...toolbar,
|
|
302
|
+
React.createElement('div', { className: 'edrv-svn-list' }, svnListBody(listState, handlers)))
|
|
303
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
|
-
* dsh-vscode-mode client — 侧边栏面板定义(文件管理 + 搜索 +
|
|
3
|
+
* dsh-vscode-mode client — 侧边栏面板定义(文件管理 + 搜索 + 规则 + SVN 变更)。
|
|
4
4
|
* 活动栏图标用官方 UI 原语(跟随 DSH 主题与皮肤);原语缺失时由 SidebarView 回落文本渲染。
|
|
5
|
-
*
|
|
5
|
+
* 「SVN 变更」带 visible 守卫:仅受 SVN 管理的工作区出现(非 SVN 工作区零变化)。
|
|
6
|
+
* 作者 ddj 2026-08-26 / 2026-09-03 / 2026-09-10 / 2026-09-16
|
|
6
7
|
*/
|
|
7
8
|
import React from 'react'
|
|
8
|
-
import { IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
9
|
+
import { IconBranchOutline16, IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
9
10
|
import { FileExplorer } from './FileExplorer.js'
|
|
10
11
|
import { SearchPanel } from './SearchPanel.js'
|
|
11
12
|
import { RulesPanel } from './RulesPanel.js'
|
|
13
|
+
import { SvnPanel } from './SvnPanel.js'
|
|
12
14
|
import type { SidebarPanelDef, SidebarCtx } from '../types.js'
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -56,3 +58,42 @@ export function createRulesPanel(): SidebarPanelDef {
|
|
|
56
58
|
render: (ctx: SidebarCtx) => React.createElement(RulesPanel, { ctx }),
|
|
57
59
|
}
|
|
58
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 构造「SVN 变更」面板定义。
|
|
64
|
+
* 徽标 = 过滤后仍显示的变更数(0/未加载不显示);visible 守卫保证非 SVN 工作区
|
|
65
|
+
* 既无活动栏图标也无面板(避免出现永远空的入口)。
|
|
66
|
+
* @author ddj 2026年09月16号
|
|
67
|
+
* @returns 面板定义(图标 = 官方 IconBranchOutline16;无原语时回落文本)
|
|
68
|
+
*/
|
|
69
|
+
export function createSvnPanel(): SidebarPanelDef {
|
|
70
|
+
return {
|
|
71
|
+
id: 'svn',
|
|
72
|
+
title: 'SVN 变更',
|
|
73
|
+
icon: typeof IconBranchOutline16 === 'function' ? IconBranchOutline16 : '⎇',
|
|
74
|
+
order: 25,
|
|
75
|
+
visible: (ctx: SidebarCtx) => Boolean(ctx?.svn?.managed && ctx?.svn?.svnCli),
|
|
76
|
+
badge: (ctx: SidebarCtx) => {
|
|
77
|
+
const entries = ctx?.svnChanges
|
|
78
|
+
if (!Array.isArray(entries)) return null
|
|
79
|
+
const count = svnVisibleCount(entries)
|
|
80
|
+
return count > 0 ? count : null
|
|
81
|
+
},
|
|
82
|
+
render: (ctx: SidebarCtx) => React.createElement(SvnPanel, { ctx }),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 徽标计数:与面板默认过滤口径一致(显示未版本控制、隐藏忽略项)。
|
|
88
|
+
* @author ddj 2026年09月16号
|
|
89
|
+
* @param entries 变更清单
|
|
90
|
+
* @returns 计数
|
|
91
|
+
*/
|
|
92
|
+
function svnVisibleCount(entries) {
|
|
93
|
+
let count = 0
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
if (entry?.status === 'ignored' || entry?.status === 'normal' || entry?.status === 'external') continue
|
|
96
|
+
count += 1
|
|
97
|
+
}
|
|
98
|
+
return count
|
|
99
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { OutlineSourceRegistry } from '../outline/types.js'
|
|
7
7
|
import type { AddToConversation } from '../addToConversation.js'
|
|
8
|
+
import type { SvnChangeEntry, SvnStatusPayload } from '../../shared/svn.js'
|
|
8
9
|
import type { TreeMenuRegistry } from './contextMenu.js'
|
|
9
10
|
|
|
10
11
|
/** 面板可用的共享上下文(由 SidebarView 从 EditorView 注入,面板不直碰其内部)。 */
|
|
@@ -32,6 +33,20 @@ export interface SidebarCtx {
|
|
|
32
33
|
fileMenuItems?: TreeMenuRegistry
|
|
33
34
|
/** 「添加到对话」动作集(文件/文件夹引用注入对话输入框;缺省时菜单项降级提示)。 */
|
|
34
35
|
addToConversation?: AddToConversation
|
|
36
|
+
/** SVN 能力状态(未加载/未检出为 null;SVN 菜单项显隐判定)。 */
|
|
37
|
+
svn?: SvnStatusPayload | null
|
|
38
|
+
/** SVN 变更清单(未加载为 null;文件树徽标与变更类菜单项判定)。 */
|
|
39
|
+
svnChanges?: SvnChangeEntry[] | null
|
|
40
|
+
/** 路径 → 变更条目(文件树徽标查表;由 svnStatus.svnChangeMapOf 产出)。 */
|
|
41
|
+
svnChangeMap?: Record<string, SvnChangeEntry>
|
|
42
|
+
/** 打开 SVN 基线差异视图(变更面板行内「与基线比较」;缺省时面板降级提示)。 */
|
|
43
|
+
openSvnDiff?: (path: string) => void
|
|
44
|
+
/** 重查 SVN 变更清单(CLI 更新/还原/加入版本控制后由菜单动作调用)。 */
|
|
45
|
+
refreshSvnChanges?: () => void
|
|
46
|
+
/** 打开 SVN 日志弹窗(P3;菜单/命令栏「查看日志」)。 */
|
|
47
|
+
openSvnLog?: (path?: string) => void
|
|
48
|
+
/** 确认对话框(破坏性动作守卫;缺省走 window.confirm,测试可注入)。 */
|
|
49
|
+
confirm?: (message: string) => boolean
|
|
35
50
|
/** 面板动作反馈(如右键菜单操作结果 → 编辑区路径栏状态)。 */
|
|
36
51
|
notify?: (message: string) => void
|
|
37
52
|
}
|
|
@@ -45,6 +60,11 @@ export interface SidebarPanelDef {
|
|
|
45
60
|
order?: number
|
|
46
61
|
/** 活动栏徽标:返回数字/null;缺省不显示。 */
|
|
47
62
|
badge?: (ctx: SidebarCtx) => number | null
|
|
63
|
+
/**
|
|
64
|
+
* 面板可用性守卫:返回 false 时活动栏图标与面板区都不出现(每次渲染求值)。
|
|
65
|
+
* 用于能力相关面板(如「SVN 变更」仅在受 SVN 管理时出现);缺省恒可见。
|
|
66
|
+
*/
|
|
67
|
+
visible?: (ctx: SidebarCtx) => boolean
|
|
48
68
|
/** 面板区渲染(ctx 每次渲染注入最新快照)。 */
|
|
49
69
|
render: (ctx: SidebarCtx) => unknown
|
|
50
70
|
}
|
|
@@ -23,6 +23,16 @@ let pending = null
|
|
|
23
23
|
let registered = false
|
|
24
24
|
let disposer = null
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 片段 provider 注销器的全局挂点名。
|
|
28
|
+
*
|
|
29
|
+
* 为什么挂 window:`window.monaco` 由 loader 注入后**跨插件重载存活**,而模块级
|
|
30
|
+
* registered/disposer 会随 bundle 重新求值复位 —— 只判 registered 会在每次重载后
|
|
31
|
+
* 重复注册补全 provider(同一编辑器出现重复候补)。故注销器落 window 供跨代认领与清理。
|
|
32
|
+
* @author ddj 2026年09月18号
|
|
33
|
+
*/
|
|
34
|
+
const SNIPPET_GLOBAL = '__edrvSnippetsDisposer__'
|
|
35
|
+
|
|
26
36
|
/** 当前会话 id(补全按会话工作区叠加项目片段;EditorView 装配时注入)。 */
|
|
27
37
|
let sessionId = null
|
|
28
38
|
|
|
@@ -102,6 +112,12 @@ export function entriesForLanguage(entries, languageId) {
|
|
|
102
112
|
*/
|
|
103
113
|
export function registerSnippetProvider(monaco) {
|
|
104
114
|
if (registered || !monaco?.languages?.registerCompletionItemProvider) return
|
|
115
|
+
// 跨重载守卫:上一代 bundle 注册的 provider 仍在存活 window.monaco 上(注销器已落 window)
|
|
116
|
+
const host = /* @__PURE__ */ (typeof window === 'undefined' ? undefined : window)
|
|
117
|
+
if (host && host[SNIPPET_GLOBAL]) {
|
|
118
|
+
registered = true
|
|
119
|
+
return
|
|
120
|
+
}
|
|
105
121
|
const snippetKind = monaco.languages.CompletionItemKind?.Snippet
|
|
106
122
|
const asSnippet = monaco.languages.CompletionItemInsertTextRule?.InsertAsSnippet
|
|
107
123
|
// 缺少片段枚举(精简版 Monaco)时不注册:否则候选项会退化为纯文本插入,误导用户
|
|
@@ -135,6 +151,8 @@ export function registerSnippetProvider(monaco) {
|
|
|
135
151
|
}
|
|
136
152
|
},
|
|
137
153
|
})
|
|
154
|
+
// 注销器落 window:跨重载认领(见 SNIPPET_GLOBAL 说明)
|
|
155
|
+
if (host) host[SNIPPET_GLOBAL] = disposer
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
/**
|
|
@@ -147,13 +165,17 @@ export function setupSnippets(monaco) {
|
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
/**
|
|
150
|
-
* 卸载:注销 provider
|
|
151
|
-
*
|
|
168
|
+
* 卸载:注销 provider 并复位状态(插件热重载/卸载时调用)。
|
|
169
|
+
* 兼容上一代 bundle 遗留的 window 注销器(模块级 disposer 为空时仍能清干净)。
|
|
170
|
+
* @author ddj 2026年09月10号 / 2026年09月18号
|
|
152
171
|
*/
|
|
153
172
|
export function disposeSnippets() {
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
const host = /* @__PURE__ */ (typeof window === 'undefined' ? undefined : window)
|
|
174
|
+
const target = (typeof disposer === 'function' ? disposer : null) ?? (host ? host[SNIPPET_GLOBAL] : null)
|
|
175
|
+
if (target && typeof target.dispose === 'function') {
|
|
176
|
+
try { target.dispose() } catch { /* 已注销 */ }
|
|
156
177
|
}
|
|
178
|
+
if (host) delete host[SNIPPET_GLOBAL]
|
|
157
179
|
disposer = null
|
|
158
180
|
registered = false
|
|
159
181
|
invalidateSnippets()
|