dsh-vscode-mode 0.6.0 → 0.8.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 +40 -6
- package/assets/icon.svg +7 -0
- package/cordis.patch.yml +12 -0
- package/lib/client.js +9317 -7494
- package/lib/client.js.map +1 -1
- package/lib/index.js +1295 -173
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +3 -1
- package/src/client/compat.ts +134 -4
- package/src/client/copyText.ts +28 -0
- package/src/client/fileClipboard.ts +46 -0
- package/src/client/imagePreview.ts +76 -2
- package/src/client/index.ts +68 -39
- package/src/client/monaco/lsp/lspClient.ts +46 -0
- package/src/client/monaco/lsp/providers.ts +245 -1
- package/src/client/nativeOpenStore.ts +91 -0
- package/src/client/officialSidebar.ts +11 -45
- package/src/client/openWithDialog.ts +66 -0
- package/src/client/outline/index.ts +1 -1
- package/src/client/pdf/pdfPanel.ts +18 -0
- package/src/client/searchSeed.ts +36 -0
- package/src/client/settingsContext.ts +6 -4
- package/src/client/sidebar/contextMenu.ts +51 -10
- package/src/client/sidebar/menuItems.ts +473 -50
- package/src/client/sidebar/panels/FileExplorer.ts +1 -1
- package/src/client/sidebar/panels/SearchPanel.ts +24 -3
- package/src/client/sidebar/panels/SvnPanel.ts +1 -1
- package/src/client/sidebar/panels/index.ts +1 -1
- package/src/client/sidebar/types.ts +6 -0
- package/src/client/styles/editor.css +5 -3
- package/src/client/svnStore.ts +6 -1
- package/src/client/tabActions.ts +22 -10
- package/src/client/ui/EditorView.ts +198 -25
- package/src/client/ui/McpSettings.ts +30 -0
- package/src/client/ui/OfficialSideTab.ts +1 -0
- package/src/client/ui/PerfSettings.ts +4 -1
- package/src/client/ui/PromptDialog.ts +63 -0
- package/src/client/ui/SideEditorTab.ts +1 -0
- package/src/client/ui/commandCatalog.ts +14 -0
- package/src/client/ui/icons.ts +72 -0
- package/src/compat.ts +2 -2
- package/src/dshVersion.ts +2 -1
- package/src/fileOpenSettings.ts +163 -40
- package/src/index.ts +15 -1
- package/src/lsp/client.ts +24 -0
- package/src/lsp/rpc.ts +57 -0
- package/src/lsp/server.ts +272 -2
- package/src/perf.ts +90 -4
- package/src/routes.ts +13 -1
- package/src/rpc.ts +294 -36
- package/src/shared/fsNames.ts +115 -0
- package/src/shared/keybindings.ts +2 -0
- package/src/shared/lsp.ts +79 -0
- package/src/shared/nativeOpen.ts +91 -0
- package/src/shared/rpc.ts +158 -1
|
@@ -12,7 +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
|
+
import { takeSearchSeed, takeSearchScope } from '../../searchSeed.js'
|
|
16
16
|
|
|
17
17
|
const DEBOUNCE_MS = 250
|
|
18
18
|
const INCLUDE_PLACEHOLDER = '例如 *.ts, src/**/include'
|
|
@@ -237,15 +237,36 @@ export function SearchPanel(props) {
|
|
|
237
237
|
return true
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
/**
|
|
241
|
+
* 取用一次性目录过滤种子(资源管理器右键「在文件夹中查找…」)。
|
|
242
|
+
* 有种子即覆盖「包含」过滤为该目录、关闭「仅当前文件」并展开过滤区(让用户看到限定范围);
|
|
243
|
+
* 已有可用搜索词时按新范围立即重搜。无种子为 no-op。
|
|
244
|
+
* @author ddj 2026年09月22号
|
|
245
|
+
* @returns 是否消费到种子
|
|
246
|
+
*/
|
|
247
|
+
const applyScopeSeed = () => {
|
|
248
|
+
const dir = takeSearchScope()
|
|
249
|
+
if (!dir) return false
|
|
250
|
+
const glob = dir + '/**'
|
|
251
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
252
|
+
setIncludeText(glob)
|
|
253
|
+
setOnlyActive(false)
|
|
254
|
+
setSectionOpen(true)
|
|
255
|
+
requestRef.current = Object.assign({}, requestRef.current, { include: splitGlobs(glob) })
|
|
256
|
+
if (String(queryRef.current).trim().length >= 2) runSearch(queryRef.current)
|
|
257
|
+
return true
|
|
258
|
+
}
|
|
259
|
+
|
|
240
260
|
// 挂载时消费种子:侧栏原本收起 → 派发 edrv:search-focus 时本面板尚未挂载,
|
|
241
261
|
// 事件无人接收,故必须由挂载路径兜底。
|
|
242
262
|
// ⚠️ 声明在「按作用域恢复」effect 之后:React 按声明序执行 effect,恢复值先落地,
|
|
243
263
|
// 种子再覆盖,避免被记忆的旧查询词盖掉用户刚选中的内容。
|
|
244
|
-
React.useEffect(() => { applySeed() }, [])
|
|
264
|
+
React.useEffect(() => { applyScopeSeed(); applySeed() }, [])
|
|
245
265
|
|
|
246
|
-
// Ctrl+Shift+F
|
|
266
|
+
// Ctrl+Shift+F / 「在文件夹中查找…」重复触发:消费种子(有则填入)并聚焦输入框(EditorView 派发 edrv:search-focus)
|
|
247
267
|
React.useEffect(() => {
|
|
248
268
|
const onFocus = () => {
|
|
269
|
+
applyScopeSeed()
|
|
249
270
|
applySeed()
|
|
250
271
|
inputRef.current?.focus?.()
|
|
251
272
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 作者 ddj 2026-08-26 / 2026-09-03 / 2026-09-10 / 2026-09-16
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react'
|
|
9
|
-
import { IconBranchOutline16, IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '
|
|
9
|
+
import { IconBranchOutline16, IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '../../ui/icons.js'
|
|
10
10
|
import { dapStore } from '../../dap/store.js'
|
|
11
11
|
import { FileExplorer } from './FileExplorer.js'
|
|
12
12
|
import { SearchPanel } from './SearchPanel.js'
|
|
@@ -51,6 +51,12 @@ export interface SidebarCtx {
|
|
|
51
51
|
confirm?: (message: string) => boolean
|
|
52
52
|
/** 面板动作反馈(如右键菜单操作结果 → 编辑区路径栏状态)。 */
|
|
53
53
|
notify?: (message: string) => void
|
|
54
|
+
/** 名称输入弹窗(资源管理器右键的新建/重命名;取消/空输入返回 null,缺省时相关项降级提示)。 */
|
|
55
|
+
prompt?: (title: string, initial: string) => Promise<string | null>
|
|
56
|
+
/** 「打开方式…」:在已注册打开器间选择并打开(缺省时该项隐藏)。 */
|
|
57
|
+
openWith?: (path: string) => void
|
|
58
|
+
/** 「在文件夹中查找…」:限定目录并跳到搜索面板(缺省时该项隐藏)。 */
|
|
59
|
+
searchInFolder?: (dir: string) => void
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/** 单个侧边栏面板定义。 */
|
|
@@ -71,9 +71,11 @@
|
|
|
71
71
|
[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
72
72
|
[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }
|
|
73
73
|
[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
[data-edrv-view] .edrv-imgview-
|
|
74
|
+
/* 图片预览:居中交给子项 margin:auto(auto 外边距优先吃正向空闲空间;溢出时空闲为负 → 外边距归 0、
|
|
75
|
+
flex-start 起点对齐 → 放大后四边均可滚动到达,规避 justify-content:center 双侧溢出不可达问题) */
|
|
76
|
+
[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }
|
|
77
|
+
[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; flex-shrink: 0; margin: auto; align-self: flex-start; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }
|
|
78
|
+
[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); margin: auto; }
|
|
77
79
|
[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
|
|
78
80
|
[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }
|
|
79
81
|
[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
package/src/client/svnStore.ts
CHANGED
|
@@ -90,7 +90,12 @@ export function createSvnStore<M extends RpcMethod, T>(config: SvnStoreConfig<M,
|
|
|
90
90
|
|
|
91
91
|
const ensure = (sessionId: string | undefined, key: string | null | undefined, force = false, extra?: Record<string, unknown>): void => {
|
|
92
92
|
if (!key || inFlight.has(key)) return
|
|
93
|
-
if (!force && cache.has(key))
|
|
93
|
+
if (!force && cache.has(key)) {
|
|
94
|
+
// 缓存命中也要把跨 scope 镜像切回该 key:否则从非 SVN 工作区切回后,
|
|
95
|
+
// latest()(命令栏 svnCurrentStatus 等只读判定的数据源)仍残留上一个工作区的载荷
|
|
96
|
+
latestValue = cache.get(key) as T
|
|
97
|
+
return
|
|
98
|
+
}
|
|
94
99
|
const fixed = config.args ? config.args(key) : {}
|
|
95
100
|
const task = rpc(config.method, { ...fixed, ...extra, sessionId, force: force || undefined } as RpcRequestMap[M])
|
|
96
101
|
.then((res) => {
|
package/src/client/tabActions.ts
CHANGED
|
@@ -374,6 +374,25 @@ function normalizeSlashes(path: string | null | undefined): string {
|
|
|
374
374
|
return String(path ?? '').replace(/\\/g, '/')
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
/**
|
|
378
|
+
* 路径改写(资源管理器右键重命名后页签/脏标同步用):
|
|
379
|
+
* 目标本身替换为新路径,其子树按前缀跟随改写,无关路径原样返回。
|
|
380
|
+
* @author ddj 2026年09月22号
|
|
381
|
+
* @param path 待改写路径
|
|
382
|
+
* @param from 原路径
|
|
383
|
+
* @param to 新路径
|
|
384
|
+
* @returns 改写后的路径
|
|
385
|
+
*/
|
|
386
|
+
export function remapPathOf(path: string, from: string, to: string): string {
|
|
387
|
+
const text = normalizeSlashes(path)
|
|
388
|
+
const src = normalizeSlashes(from).replace(/\/+$/, '')
|
|
389
|
+
const dst = normalizeSlashes(to).replace(/\/+$/, '')
|
|
390
|
+
if (!src || !dst || src === dst) return text
|
|
391
|
+
if (text === src) return dst
|
|
392
|
+
if (text.startsWith(src + '/')) return dst + text.slice(src.length)
|
|
393
|
+
return text
|
|
394
|
+
}
|
|
395
|
+
|
|
377
396
|
/**
|
|
378
397
|
* 文件路径的祖先目录(由浅到深):`a/b/c.ts` → `['a', 'a/b']`。
|
|
379
398
|
* 根级文件、绝对路径与含 `..` 的路径返回空数组。
|
|
@@ -392,14 +411,7 @@ export function ancestorDirsOf(path: string): string[] {
|
|
|
392
411
|
return out
|
|
393
412
|
}
|
|
394
413
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
* @param path 路径
|
|
399
|
-
* @returns 末段文件名;空路径返回空串
|
|
400
|
-
*/
|
|
401
|
-
export function baseNameOf(path: string): string {
|
|
402
|
-
const text = normalizeSlashes(path)
|
|
403
|
-
return text.split('/').filter(Boolean).pop() ?? ''
|
|
404
|
-
}
|
|
414
|
+
// baseNameOf(取末段文件名)已收敛到 shared/fsNames.ts(host 与 client 共用同一份语义),
|
|
415
|
+
// 此处转出口保持既有引用不动。
|
|
416
|
+
export { baseNameOf } from '../shared/fsNames.js'
|
|
405
417
|
// --endregion
|
|
@@ -11,10 +11,12 @@ import React from 'react'
|
|
|
11
11
|
import { dbg, rpc } from '../rpc.js'
|
|
12
12
|
import { emitFileChanged, emitRefresh } from '../events.js'
|
|
13
13
|
import { langOf, loadMonaco, snippetLanguageOf } from '../monaco/loader.js'
|
|
14
|
-
import { dataUrlOf, isImagePath, isSvgPath } from '../imagePreview.js'
|
|
14
|
+
import { clampZoom, clearZoomMem, dataUrlOf, isImagePath, isSvgPath, recallZoom, rememberZoom, zoomKeyOf, zoomStepOf } from '../imagePreview.js'
|
|
15
15
|
import { isMarkdownPath } from '../markdownPreview.js'
|
|
16
16
|
import { MarkdownPanel } from '../md/mdPanel.js'
|
|
17
|
-
import { base64ToBytes, isPdfPath } from '../pdfPreview.js'
|
|
17
|
+
import { base64ToBytes, bytesToBase64, isPdfPath } from '../pdfPreview.js'
|
|
18
|
+
import { inNativePath, tryNativeOpen } from '../nativeOpenStore.js'
|
|
19
|
+
import { readBinaryPreview } from '../../shared/rpc.js'
|
|
18
20
|
import {
|
|
19
21
|
clearBaseline,
|
|
20
22
|
clearReadVersion,
|
|
@@ -50,7 +52,7 @@ import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybi
|
|
|
50
52
|
import { getSidebarMinWidth } from '../sidebarMin.js'
|
|
51
53
|
import { navHistoryFor } from '../navHistory.js'
|
|
52
54
|
import { statusOfAdd } from '../addToConversation.js'
|
|
53
|
-
import { setSearchSeed } from '../searchSeed.js'
|
|
55
|
+
import { setSearchScope, setSearchSeed } from '../searchSeed.js'
|
|
54
56
|
import { CACHE_KEY } from '../paths.js'
|
|
55
57
|
import { runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
|
|
56
58
|
import { bindLspUnderline } from '../monaco/lsp/underline.js'
|
|
@@ -62,7 +64,7 @@ import { setupLaunchJson, prefetchSnippets } from '../dap/launchSnippetProvider.
|
|
|
62
64
|
import { findArrayPos, LAUNCH_JSON_RE } from '../dap/launchInsert.js'
|
|
63
65
|
import {
|
|
64
66
|
absoluteOf, ancestorDirsOf, applyClose, baseNameOf, closeAll, closeOthers, closeRight, closeSaved,
|
|
65
|
-
evictPlan, insertTab, isTreeRevealable, normalizeTabs, pickActive, relativeOf, tabPathOf, togglePin,
|
|
67
|
+
evictPlan, insertTab, isTreeRevealable, normalizeTabs, pickActive, relativeOf, remapPathOf, tabPathOf, togglePin,
|
|
66
68
|
} from '../tabActions.js'
|
|
67
69
|
import { getMaxOpenEditors } from '../editorLimit.js'
|
|
68
70
|
import { buildTabMenu } from '../tabMenu.js'
|
|
@@ -75,6 +77,9 @@ import { isSvnDiffable } from '../../shared/svn.js'
|
|
|
75
77
|
import type { SvnAction } from '../../shared/svn.js'
|
|
76
78
|
import { createSaveTimer } from '../saveDebounce.js'
|
|
77
79
|
import { ContextMenu } from './ContextMenu.js'
|
|
80
|
+
import { promptName } from './PromptDialog.js'
|
|
81
|
+
import { openWithDialog } from '../openWithDialog.js'
|
|
82
|
+
import { copyText as copyClipText } from '../copyText.js'
|
|
78
83
|
import { SvnLogDialog } from './SvnLogDialog.js'
|
|
79
84
|
import { SvnPatchDialog } from './SvnPatchDialog.js'
|
|
80
85
|
import { SvnSumDialog } from './SvnSumDialog.js'
|
|
@@ -118,6 +123,22 @@ function dirOfRel(relPath) {
|
|
|
118
123
|
/** 跳转目标高亮的保留时长(LSP/搜索跳转落地后给用户的位置提示,到期自动清除)。 */
|
|
119
124
|
const NAV_FLASH_MS = 1200
|
|
120
125
|
|
|
126
|
+
/** 二进制通道回退日志的一次性闸(整页仅报一次,避免每次读图/读 PDF 刷屏)。 */
|
|
127
|
+
let binFallbackLogged = false
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 二进制通道回退的一次性诊断日志:readBinaryPreview 本次尝试过新通道并失败时上报。
|
|
131
|
+
* 有效期:随页面生命周期仅一条;dbg 本身受诊断开关控制(关=零输出零落盘)。
|
|
132
|
+
* @author ddj 2026年09月22号
|
|
133
|
+
* @param sid 会话 id
|
|
134
|
+
* @param out readBinaryPreview 统一结果(带 probed 才说明本次尝试过新通道)
|
|
135
|
+
*/
|
|
136
|
+
const noteBinFallback = (sid, out) => {
|
|
137
|
+
if (binFallbackLogged || !out?.probed) return
|
|
138
|
+
binFallbackLogged = true
|
|
139
|
+
dbg(sid, 'edrv.readBinary 不可用(host 未接线/旧版或网络失败),已回退 base64', 'debug')
|
|
140
|
+
}
|
|
141
|
+
|
|
121
142
|
/**
|
|
122
143
|
* 从 Monaco 语言目录取可选语言 id 列表(代码片段新建时的语言下拉来源)。
|
|
123
144
|
* 旧版 Monaco / 目录不可读时返回 undefined,由调用方回落 shared 的内置常量。
|
|
@@ -194,6 +215,8 @@ export function EditorView(props) {
|
|
|
194
215
|
const [imageSrc, setImageSrc] = React.useState(null) // 图片预览 data URL(图片 tab 专用,文本 tab 恒为 null)
|
|
195
216
|
const [imgSize, setImgSize] = React.useState(null) // 图片自然尺寸 { w, h }(路径栏 meta)
|
|
196
217
|
const [imgBroken, setImgBroken] = React.useState(false) // 图片解码失败(onError),显示占位与重试
|
|
218
|
+
const [imgZoom, setImgZoom] = React.useState(null) // 图片缩放比例(null=适应宽度;会话+路径级记忆,见 imagePreview.zoomKeyOf)
|
|
219
|
+
const imgElRef = React.useRef(null) // 预览 <img> 元素(适应宽度态下步进取实测缩放基准)
|
|
197
220
|
const svgTextRef = React.useRef(new Set()) // 强制以文本打开的 SVG 路径集合(toggleSvgText 维护)
|
|
198
221
|
const mdPreviewRef = React.useRef(new Set()) // 处于预览态的 Markdown 路径集合(toggleMdPreview 维护;不持久化)
|
|
199
222
|
const [pdfBytes, setPdfBytes] = React.useState(null) // 当前 PDF tab 的原始字节(null=加载中/非 PDF)
|
|
@@ -630,6 +653,59 @@ export function EditorView(props) {
|
|
|
630
653
|
setStatus(label + '(' + closing.length + ' 个)')
|
|
631
654
|
}
|
|
632
655
|
|
|
656
|
+
/**
|
|
657
|
+
* 删除落盘后的页签收尾:关闭目标及其子树页签。
|
|
658
|
+
* 不走 closeTab 的「先落盘」:文件已删除,落盘会把删除的文件写回磁盘。
|
|
659
|
+
* @author ddj 2026年09月22号
|
|
660
|
+
* @param path 已删除的工作区相对路径
|
|
661
|
+
*/
|
|
662
|
+
const dropDeletedTabs = (path) => {
|
|
663
|
+
const doomed = tabsRef.current.filter((t) => t.path === path || t.path.startsWith(path + '/'))
|
|
664
|
+
if (!doomed.length) return
|
|
665
|
+
for (const tab of doomed) releaseTab(tab.path)
|
|
666
|
+
commitClose(applyClose(tabsRef.current, new Set(doomed.map((t) => t.path)), activeRef.current))
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* 重命名落盘后的页签改写:目标及其子树页签路径整体改写(含脏标与活动页签)。
|
|
671
|
+
* @author ddj 2026年09月22号
|
|
672
|
+
* @param from 原路径
|
|
673
|
+
* @param to 新路径
|
|
674
|
+
*/
|
|
675
|
+
const remapRenamedTabs = (from, to) => {
|
|
676
|
+
setTabs((prev) => prev.map((tab) => ({ ...tab, path: remapPathOf(tab.path, from, to) })))
|
|
677
|
+
setDirtyMap((prev) => {
|
|
678
|
+
const next = {}
|
|
679
|
+
for (const key of Object.keys(prev)) next[remapPathOf(key, from, to)] = prev[key]
|
|
680
|
+
return next
|
|
681
|
+
})
|
|
682
|
+
setActive((prev) => (prev ? remapPathOf(prev, from, to) : prev))
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// 文件树右键的重命名/删除 → 页签同步(动作在 sidebar/menuItems.ts,经窗口事件桥接;
|
|
686
|
+
// 经 ref 读最新闭包,避免空依赖 effect 捕获陈旧状态)
|
|
687
|
+
const tabFsSyncRef = React.useRef({ rename: () => {}, drop: () => {} })
|
|
688
|
+
tabFsSyncRef.current = { rename: remapRenamedTabs, drop: dropDeletedTabs }
|
|
689
|
+
React.useEffect(() => {
|
|
690
|
+
const onRenamed = (event) => {
|
|
691
|
+
const from = event?.detail?.from
|
|
692
|
+
const to = event?.detail?.to
|
|
693
|
+
if (typeof from !== 'string' || !from || typeof to !== 'string' || !to || from === to) return
|
|
694
|
+
tabFsSyncRef.current.rename(from, to)
|
|
695
|
+
}
|
|
696
|
+
const onDeleted = (event) => {
|
|
697
|
+
const path = event?.detail?.path
|
|
698
|
+
if (typeof path !== 'string' || !path) return
|
|
699
|
+
tabFsSyncRef.current.drop(path)
|
|
700
|
+
}
|
|
701
|
+
window.addEventListener('edrv:path-renamed', onRenamed)
|
|
702
|
+
window.addEventListener('edrv:path-deleted', onDeleted)
|
|
703
|
+
return () => {
|
|
704
|
+
window.removeEventListener('edrv:path-renamed', onRenamed)
|
|
705
|
+
window.removeEventListener('edrv:path-deleted', onDeleted)
|
|
706
|
+
}
|
|
707
|
+
}, [])
|
|
708
|
+
|
|
633
709
|
/**
|
|
634
710
|
* 在已打开页签间循环切换(文件分页归编辑器自带页签栏)。
|
|
635
711
|
* @author ddj 2026年09月10号
|
|
@@ -772,26 +848,36 @@ export function EditorView(props) {
|
|
|
772
848
|
setImgSize(null)
|
|
773
849
|
setImgBroken(false)
|
|
774
850
|
setLoadStage({ progress: monaco ? 72 : 12, message: '读取图片…' })
|
|
775
|
-
rpc
|
|
851
|
+
readBinaryPreview(rpc, { sessionId: sid, path }).then((out) => {
|
|
776
852
|
if (seq !== loadSeqRef.current || path !== active) return
|
|
777
|
-
if (
|
|
778
|
-
|
|
853
|
+
if (out.ok && out.via === 'binary') {
|
|
854
|
+
// 新通道:原始字节本地组 data URL(线上省 base64,本地编码为 img src 所需不可省)
|
|
855
|
+
recordBaseline(scope, path, out.version || version)
|
|
856
|
+
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
857
|
+
setImageSrc(dataUrlOf(bytesToBase64(out.bytes), out.mime))
|
|
858
|
+
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
859
|
+
setStatus('已加载')
|
|
860
|
+
return
|
|
861
|
+
}
|
|
862
|
+
noteBinFallback(sid, out)
|
|
863
|
+
if (out.ok && out.via === 'base64') {
|
|
864
|
+
if (!out.mime) {
|
|
779
865
|
setLoadError('host 版本过旧:图片响应缺少 mime')
|
|
780
866
|
setError('host 版本过旧:图片响应缺少 mime')
|
|
781
867
|
setStatus('读取失败')
|
|
782
868
|
return
|
|
783
869
|
}
|
|
784
|
-
const imgVersion =
|
|
870
|
+
const imgVersion = out.version ?? version
|
|
785
871
|
recordBaseline(scope, path, imgVersion)
|
|
786
872
|
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
787
|
-
setImageSrc(dataUrlOf(
|
|
873
|
+
setImageSrc(dataUrlOf(out.content, out.mime))
|
|
788
874
|
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
789
875
|
setStatus('已加载')
|
|
790
876
|
return
|
|
791
877
|
}
|
|
792
|
-
const base =
|
|
878
|
+
const base = out.error ? String(out.error) : '读取失败'
|
|
793
879
|
// 带出 host 解析后的真实路径:跳转失败时一眼看出是路径解析错还是目标不存在
|
|
794
|
-
const message =
|
|
880
|
+
const message = out.resolvedPath ? base + ':' + String(out.resolvedPath) : base
|
|
795
881
|
setLoadError(message)
|
|
796
882
|
setError(message)
|
|
797
883
|
setStatus('读取失败')
|
|
@@ -828,21 +914,34 @@ export function EditorView(props) {
|
|
|
828
914
|
setStatus('已加载')
|
|
829
915
|
return
|
|
830
916
|
}
|
|
831
|
-
rpc
|
|
917
|
+
readBinaryPreview(rpc, { sessionId: sid, path }).then((out) => {
|
|
832
918
|
if (seq !== loadSeqRef.current || path !== active) return
|
|
833
|
-
if (
|
|
834
|
-
const pdfVersion =
|
|
919
|
+
if (out.ok && out.via === 'binary') {
|
|
920
|
+
const pdfVersion = out.version || version
|
|
835
921
|
recordBaseline(scope, path, pdfVersion)
|
|
836
922
|
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
837
|
-
|
|
923
|
+
// 缓存仍存 base64(pdfB64CacheRef 形状不变,命中路径零改动);字节直通面板省一次解码
|
|
924
|
+
cache.set(path, bytesToBase64(out.bytes))
|
|
838
925
|
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
839
|
-
setPdfBytes(
|
|
926
|
+
setPdfBytes(out.bytes)
|
|
840
927
|
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
841
928
|
setStatus('已加载')
|
|
842
929
|
return
|
|
843
930
|
}
|
|
844
|
-
|
|
845
|
-
|
|
931
|
+
noteBinFallback(sid, out)
|
|
932
|
+
if (out.ok && out.via === 'base64') {
|
|
933
|
+
const pdfVersion = out.version ?? version
|
|
934
|
+
recordBaseline(scope, path, pdfVersion)
|
|
935
|
+
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
936
|
+
cache.set(path, out.content)
|
|
937
|
+
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
938
|
+
setPdfBytes(base64ToBytes(out.content))
|
|
939
|
+
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
940
|
+
setStatus('已加载')
|
|
941
|
+
return
|
|
942
|
+
}
|
|
943
|
+
const base = out.error ? String(out.error) : '读取失败'
|
|
944
|
+
const message = out.resolvedPath ? base + ':' + String(out.resolvedPath) : base
|
|
846
945
|
setLoadError(message)
|
|
847
946
|
setError(message)
|
|
848
947
|
setStatus('读取失败')
|
|
@@ -1313,6 +1412,10 @@ export function EditorView(props) {
|
|
|
1313
1412
|
if (!event?.detail?.scope || event.detail.scope === scope) setSvnStatus(getSvnStatus(scope))
|
|
1314
1413
|
}
|
|
1315
1414
|
window.addEventListener('edrv:svn-status', onSvnStatus)
|
|
1415
|
+
// 缓存命中时 ensure 直接 return、不广播事件,须同步读回当前 scope 的载荷:
|
|
1416
|
+
// 否则从非 SVN 工作区切回 SVN 工作区后,状态残留上一个工作区的 managed:false,
|
|
1417
|
+
// 右键菜单/页签菜单的 SVN 组会整组消失(与下方变更清单 effect 的同步语句同理)。
|
|
1418
|
+
setSvnStatus(getSvnStatus(scope))
|
|
1316
1419
|
return () => window.removeEventListener('edrv:svn-status', onSvnStatus)
|
|
1317
1420
|
}, [sessionId, scope])
|
|
1318
1421
|
|
|
@@ -1453,6 +1556,11 @@ export function EditorView(props) {
|
|
|
1453
1556
|
['edrv.command.svnTortoiseDiff', () => runSvnTortoise('diff', activeRef.current)],
|
|
1454
1557
|
['edrv.command.svnTortoiseBlame', () => runSvnTortoise('blame', activeRef.current)],
|
|
1455
1558
|
['edrv.command.svnTortoiseRevert', () => runSvnTortoise('revert', activeRef.current)],
|
|
1559
|
+
// 转到行:转发 Monaco 原生 action(widget 本体/占位/跳转全原生,插件只补键位与命令栏入口)
|
|
1560
|
+
['edrv.command.goToLine', () => {
|
|
1561
|
+
const ed = editorRef.current
|
|
1562
|
+
if (ed?.getModel?.()) ed.trigger('edrv-goto', 'editor.action.gotoLine', null)
|
|
1563
|
+
}],
|
|
1456
1564
|
// 诊断日志弹窗(同片段选择器:执行一条命令即关命令栏,避免残浮层遮挡)
|
|
1457
1565
|
['edrv.command.showLogs', () => { closeCommandPalette(); setDlogOpen(true) }],
|
|
1458
1566
|
]
|
|
@@ -1847,6 +1955,7 @@ export function EditorView(props) {
|
|
|
1847
1955
|
// model 不在此销毁:跨挂载缓存按作用域存活(modelCache 切作用域时统一释放)
|
|
1848
1956
|
for (const ctl of pdfCtlRef.current.values()) ctl.destroy()
|
|
1849
1957
|
pdfCtlRef.current.clear()
|
|
1958
|
+
clearZoomMem() // 图片缩放记忆随 EditorView 卸载清空(会话级生命周期,不持久化)
|
|
1850
1959
|
const root = editorRef.current && editorRef.current.getDomNode ? editorRef.current.getDomNode() : null
|
|
1851
1960
|
if (root) {
|
|
1852
1961
|
const ov = root.querySelector('.edrv-minus-overlay')
|
|
@@ -2340,15 +2449,58 @@ export function EditorView(props) {
|
|
|
2340
2449
|
}
|
|
2341
2450
|
toggleMdPreviewRef.current = toggleMdPreview
|
|
2342
2451
|
|
|
2452
|
+
/** 当前图片的会话+路径级缩放记忆键(页签切换回来保留同一缩放)。 */
|
|
2453
|
+
const imgZoomKey = zoomKeyOf(sessionId, active)
|
|
2454
|
+
|
|
2455
|
+
// 页签/会话切换时恢复该图片的记忆缩放(无记忆 = 初始适应宽度,等价 PDF page-width)
|
|
2456
|
+
React.useEffect(() => { setImgZoom(recallZoom(imgZoomKey)) }, [imgZoomKey])
|
|
2457
|
+
|
|
2343
2458
|
/**
|
|
2344
|
-
*
|
|
2345
|
-
* @author ddj 2026年09月
|
|
2459
|
+
* 应用图片缩放并写入页签记忆(null = 适应宽度;数值经 clampZoom 收敛 25%–400%)。
|
|
2460
|
+
* @author ddj 2026年09月22号
|
|
2461
|
+
* @param next 目标缩放(null = 适应宽度)
|
|
2462
|
+
*/
|
|
2463
|
+
const applyImgZoom = (next) => {
|
|
2464
|
+
const value = next === null ? null : clampZoom(next)
|
|
2465
|
+
setImgZoom(value)
|
|
2466
|
+
rememberZoom(imgZoomKey, value)
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
/**
|
|
2470
|
+
* 图片缩放步进(10%/档,clamp 25%–400%);适应宽度态先按渲染尺寸实测当前缩放作基准。
|
|
2471
|
+
* @author ddj 2026年09月22号
|
|
2472
|
+
* @param dir 方向:1 放大 / -1 缩小
|
|
2473
|
+
*/
|
|
2474
|
+
const stepImgZoom = (dir) => {
|
|
2475
|
+
const el = imgElRef.current
|
|
2476
|
+
const measured = el && el.naturalWidth ? el.clientWidth / el.naturalWidth : 1
|
|
2477
|
+
applyImgZoom(zoomStepOf(imgZoom ?? measured, dir))
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
/**
|
|
2481
|
+
* 图片预览面板:工具条(尺寸 meta / 缩放 −+适应宽度 / SVG 文本切换 / 刷新)+ 棋盘底图片。
|
|
2482
|
+
* 初始适应宽度(等价 PDF page-width,0.1.7 官方统一缩放);按钮样式与 PDF 工具条同款。
|
|
2483
|
+
* @author ddj 2026年09月08号 / 2026年09月22号
|
|
2346
2484
|
*/
|
|
2347
2485
|
const imagePanel = () => React.createElement('div', { className: 'edrv-imgview' },
|
|
2348
2486
|
React.createElement('div', { className: 'edrv-imgview-bar' },
|
|
2349
2487
|
React.createElement('span', { className: 'edrv-imgview-meta' },
|
|
2350
2488
|
imgBroken ? '图片无法显示' : (imgSize ? imgSize.w + '×' + imgSize.h : '…')),
|
|
2351
2489
|
React.createElement('span', { style: { flex: 1 } }),
|
|
2490
|
+
React.createElement('span', { className: 'edrv-pdf-page' },
|
|
2491
|
+
imgZoom === null ? '适应' : Math.round(imgZoom * 100) + '%'),
|
|
2492
|
+
React.createElement('button', {
|
|
2493
|
+
className: 'edrv-pill edrv-pill-ghost edrv-pdf-zoom', title: '缩小(10%/档)',
|
|
2494
|
+
onClick: () => stepImgZoom(-1),
|
|
2495
|
+
}, '−'),
|
|
2496
|
+
React.createElement('button', {
|
|
2497
|
+
className: 'edrv-pill edrv-pill-ghost edrv-pdf-zoom', title: '放大(10%/档)',
|
|
2498
|
+
onClick: () => stepImgZoom(1),
|
|
2499
|
+
}, '+'),
|
|
2500
|
+
React.createElement('button', {
|
|
2501
|
+
className: 'edrv-pill edrv-pill-ghost', title: '恢复适应宽度',
|
|
2502
|
+
disabled: imgZoom === null, onClick: () => applyImgZoom(null),
|
|
2503
|
+
}, '适应宽度'),
|
|
2352
2504
|
(isSvgPath(active) ? React.createElement('button', {
|
|
2353
2505
|
className: 'edrv-pill edrv-pill-ghost', title: '以文本方式查看/编辑(SVG 为文本格式)',
|
|
2354
2506
|
onClick: () => toggleSvgText(active),
|
|
@@ -2361,8 +2513,12 @@ export function EditorView(props) {
|
|
|
2361
2513
|
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: () => reloadFile() }, '重试'))
|
|
2362
2514
|
: React.createElement('img', {
|
|
2363
2515
|
className: 'edrv-imgview-img',
|
|
2516
|
+
ref: imgElRef,
|
|
2364
2517
|
src: imageSrc,
|
|
2365
2518
|
alt: active || '',
|
|
2519
|
+
style: (imgZoom !== null && imgSize)
|
|
2520
|
+
? { width: Math.round(imgSize.w * imgZoom) + 'px', maxWidth: 'none', maxHeight: 'none' }
|
|
2521
|
+
: undefined,
|
|
2366
2522
|
onLoad: (e) => { setImgBroken(false); setImgSize({ w: e.target.naturalWidth, h: e.target.naturalHeight }) },
|
|
2367
2523
|
onError: () => setImgBroken(true),
|
|
2368
2524
|
}))))
|
|
@@ -2798,17 +2954,15 @@ export function EditorView(props) {
|
|
|
2798
2954
|
|
|
2799
2955
|
/**
|
|
2800
2956
|
* 复制文本到剪贴板(状态栏反馈;浏览器拒绝时提示,不抛异常)。
|
|
2801
|
-
*
|
|
2957
|
+
* 剪贴板写入与降级文案收敛到 client/copyText.ts(文件树菜单同源共用)。
|
|
2958
|
+
* @author ddj 2026年09月11号 / 2026年09月22号
|
|
2802
2959
|
* @param text 待复制文本
|
|
2803
2960
|
* @param okText 成功文案
|
|
2804
2961
|
*/
|
|
2805
2962
|
const copyText = (text, okText) => {
|
|
2806
2963
|
const value = String(text ?? '')
|
|
2807
2964
|
if (!value) { setStatus('无可复制内容'); return }
|
|
2808
|
-
|
|
2809
|
-
navigator.clipboard.writeText(value)
|
|
2810
|
-
.then(() => setStatus(okText + ':' + value))
|
|
2811
|
-
.catch(() => setStatus('复制失败(浏览器拒绝剪贴板写入)'))
|
|
2965
|
+
void copyClipText(value, okText + ':' + value, setStatus)
|
|
2812
2966
|
}
|
|
2813
2967
|
|
|
2814
2968
|
/**
|
|
@@ -2937,6 +3091,10 @@ export function EditorView(props) {
|
|
|
2937
3091
|
|
|
2938
3092
|
const openFile = (path, focusDiff) => {
|
|
2939
3093
|
if (!path) return
|
|
3094
|
+
// 原生打开范围(nativeOpenExts 设置):命中且非差异跳转 → 走官方渲染器
|
|
3095
|
+
//(focusDiff=true 跳过:差异审查对象必为本插件编辑的文本文件,审查优先);
|
|
3096
|
+
// handler 缺失/失败(旧版无 openResource / seat 未挂)→ 回退现状页签打开,不阻断
|
|
3097
|
+
if (!focusDiff && inNativePath(path) && tryNativeOpen(path)) return
|
|
2940
3098
|
// 打开文件即离开基线差异审阅态(差异视图是「当前文件」的临时视图)
|
|
2941
3099
|
setSvnDiff(null)
|
|
2942
3100
|
recordNav()
|
|
@@ -3393,6 +3551,21 @@ export function EditorView(props) {
|
|
|
3393
3551
|
openSvnLog: (p) => openSvnLog(p),
|
|
3394
3552
|
confirm: (message) => (typeof window === 'undefined' ? false : window.confirm(message)),
|
|
3395
3553
|
notify: (message) => setStatus(message),
|
|
3554
|
+
// 名称输入弹窗(资源管理器右键的新建文件/新建文件夹/重命名)
|
|
3555
|
+
prompt: promptName,
|
|
3556
|
+
// 「打开方式…」(资源管理器右键、文件目标):打开器选择弹窗(注册表由 index 装配传入)
|
|
3557
|
+
openWith: (p) => {
|
|
3558
|
+
const openers = props.fileOpeners
|
|
3559
|
+
if (!openers) return
|
|
3560
|
+
openWithDialog(openers, { sessionId, cwd: cwd ?? undefined }, p, (opener) => {
|
|
3561
|
+
if (opener) setStatus('已用「' + opener.label + '」打开:' + baseNameOf(p))
|
|
3562
|
+
})
|
|
3563
|
+
},
|
|
3564
|
+
// 「在文件夹中查找…」(资源管理器右键、目录目标):目录过滤种子 + 跳搜索面板
|
|
3565
|
+
searchInFolder: (dir) => {
|
|
3566
|
+
setSearchScope(dir)
|
|
3567
|
+
openSearchPanel()
|
|
3568
|
+
},
|
|
3396
3569
|
}
|
|
3397
3570
|
|
|
3398
3571
|
// 调试工具条(VS Code 浮动条紧凑版,图2 形态):配置下拉 + 启动/继续 + 单步组 + 停止 + 相位。
|
|
@@ -13,6 +13,7 @@ import { SettingsContext } from '../settingsContext.js'
|
|
|
13
13
|
import { normalizeSidebarMinWidth, SIDEBAR_MIN_DEFAULT } from '../sidebarMin.js'
|
|
14
14
|
import { EDITOR_LIMIT_CEIL, EDITOR_LIMIT_DEFAULT, normalizeMaxOpenEditors } from '../../shared/editorLimit.js'
|
|
15
15
|
import { TORTOISE_DIR_DEFAULT } from '../../shared/svn.js'
|
|
16
|
+
import { DEFAULT_NATIVE_CSV } from '../../shared/nativeOpen.js'
|
|
16
17
|
import { KeybindingsPanel } from './KeybindingsPanel.js'
|
|
17
18
|
import { LspSettings } from './LspSettings.js'
|
|
18
19
|
import { PerfSettings } from './PerfSettings.js'
|
|
@@ -139,6 +140,8 @@ function GeneralSettings({ registry }) {
|
|
|
139
140
|
const [minDraft, setMinDraft] = React.useState(null) // 输入草稿(null=跟随已保存值;blur/Enter 提交)
|
|
140
141
|
const [limit, setLimit] = React.useState(EDITOR_LIMIT_DEFAULT)
|
|
141
142
|
const [limitDraft, setLimitDraft] = React.useState(null) // 页签上限草稿(同上提交语义)
|
|
143
|
+
const [nativeSaved, setNativeSaved] = React.useState(DEFAULT_NATIVE_CSV) // 原生打开范围已保存值
|
|
144
|
+
const [nativeDraft, setNativeDraft] = React.useState(null) // 原生打开范围草稿(同上提交语义)
|
|
142
145
|
const settings = React.useContext(SettingsContext)
|
|
143
146
|
const snapshot = settings?.getSnapshot?.()
|
|
144
147
|
const loading = !snapshot || snapshot.status === 'loading'
|
|
@@ -151,6 +154,8 @@ function GeneralSettings({ registry }) {
|
|
|
151
154
|
if (typeof next === 'string') setTool(next)
|
|
152
155
|
setMinW(normalizeSidebarMinWidth(snap?.value?.sidebarMinWidth))
|
|
153
156
|
setLimit(normalizeMaxOpenEditors(snap?.value?.maxOpenEditors))
|
|
157
|
+
const native = snap?.value?.nativeOpenExts
|
|
158
|
+
setNativeSaved(typeof native === 'string' && native.trim() ? native : DEFAULT_NATIVE_CSV)
|
|
154
159
|
}
|
|
155
160
|
onChange()
|
|
156
161
|
return settings?.subscribe?.(onChange)
|
|
@@ -191,6 +196,18 @@ function GeneralSettings({ registry }) {
|
|
|
191
196
|
setLimitDraft(null)
|
|
192
197
|
saveLimit(limitDraft)
|
|
193
198
|
}
|
|
199
|
+
/** 提交原生打开范围(逗号分隔后缀;空串回落默认让位清单后持久化)。 */
|
|
200
|
+
const saveNative = (raw) => {
|
|
201
|
+
const next = String(raw ?? '').trim() || DEFAULT_NATIVE_CSV
|
|
202
|
+
setNativeSaved(next); setNativeDraft(null); setBusy(true); setError('')
|
|
203
|
+
if (!settings?.set) { setError('设置服务不可用'); setBusy(false); return }
|
|
204
|
+
settings.set('nativeOpenExts', next).catch((e) => setError(String(e))).finally(() => setBusy(false))
|
|
205
|
+
}
|
|
206
|
+
/** 结束输入(blur/Enter)时提交原生打开范围草稿。 */
|
|
207
|
+
const commitNative = () => {
|
|
208
|
+
if (nativeDraft === null) return
|
|
209
|
+
saveNative(nativeDraft)
|
|
210
|
+
}
|
|
194
211
|
const closeDevForm = () => {
|
|
195
212
|
if (!window.confirm('关闭开发形态:插件将切换为正式版安装(版本依赖 + 删除工作区链接),pnpm 装配后需重启 DSH 生效。确认关闭?')) return
|
|
196
213
|
setDevBusy(true)
|
|
@@ -249,6 +266,19 @@ function GeneralSettings({ registry }) {
|
|
|
249
266
|
onKeyDown: (event) => { if (event.key === 'Enter') commitLimit() },
|
|
250
267
|
}),
|
|
251
268
|
React.createElement('small', null, '0 = 不限制;超限时关闭最久未使用的页签(固定页签除外)')),
|
|
269
|
+
React.createElement('label', { className: 'vsm-general-row' },
|
|
270
|
+
React.createElement('span', null, '原生打开范围'),
|
|
271
|
+
React.createElement('input', {
|
|
272
|
+
type: 'text',
|
|
273
|
+
value: nativeDraft ?? nativeSaved,
|
|
274
|
+
disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
|
|
275
|
+
title: '逗号分隔后缀(如 doc,xlsx):文件树点击命中时用 DSH 官方预览原生打开;留空 = 默认让位清单(Office + 不可预览二进制)',
|
|
276
|
+
placeholder: 'doc,xlsx,…(留空恢复默认)',
|
|
277
|
+
onChange: (event) => setNativeDraft(event.target.value),
|
|
278
|
+
onBlur: commitNative,
|
|
279
|
+
onKeyDown: (event) => { if (event.key === 'Enter') commitNative() },
|
|
280
|
+
}),
|
|
281
|
+
React.createElement('small', null, '逗号分隔;命中的文件用 DSH 原生预览打开,留空恢复默认(Office + 二进制,csv/tsv 需显式加入)')),
|
|
252
282
|
),
|
|
253
283
|
),
|
|
254
284
|
React.createElement(SvnSettingsSection, null),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
3
|
* dsh-vscode-mode client — PerfSettings:「性能优化」设置子页(会话卫生 + 压缩调优)。
|
|
4
|
-
* -
|
|
4
|
+
* - 会话盘点:全工作区/每会话体积、活跃标记、官方归档/置顶徽标,按体积排序;
|
|
5
5
|
* - 移出到归档(先规划→确认→执行→可恢复)与归档清除;
|
|
6
6
|
* - DSH 内置压缩调优:写 profile patch(compaction-basic / tool-result-pruner 更低阈值,
|
|
7
7
|
* 带备份与撤销,需重启生效);
|
|
@@ -67,6 +67,9 @@ function SessionRow({ row, checked, disabled, onToggle }) {
|
|
|
67
67
|
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-size ' + (row.bytes >= 1024 * 1024 ? 'big' : '') }, formatBytes(row.bytes)),
|
|
68
68
|
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-mtime', title: new Date(row.mtime).toLocaleString() }, when),
|
|
69
69
|
row.active ? React.createElement('span', { className: 'vsm-perf-cell vsm-perf-active' }, '活跃') : null,
|
|
70
|
+
// 官方侧栏归档/置顶状态徽标(旧 host 不回传字段 → undefined 按 false,不渲染;样式复用面板现有 vsm-perf-active 徽标)
|
|
71
|
+
row.archived ? React.createElement('span', { className: 'vsm-perf-cell vsm-perf-active', title: '官方侧栏已归档(持久标志位,未搬目录)' }, '已归档') : null,
|
|
72
|
+
row.pinned ? React.createElement('span', { className: 'vsm-perf-cell vsm-perf-active', title: '官方侧栏已置顶' }, '已置顶') : null,
|
|
70
73
|
)
|
|
71
74
|
}
|
|
72
75
|
|