dsh-vscode-mode 0.7.0 → 0.9.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 +37 -6
- package/assets/icon.svg +7 -0
- package/cordis.patch.yml +16 -0
- package/lib/client.js +2804 -300
- package/lib/client.js.map +1 -1
- package/lib/index.js +1975 -191
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +4 -2
- package/src/ai/inline.ts +19 -2
- package/src/ai/svnTriage.ts +150 -0
- package/src/capture.ts +225 -51
- package/src/client/addToConversation.ts +203 -1
- package/src/client/compat.ts +134 -4
- package/src/client/imagePreview.ts +76 -2
- package/src/client/index.ts +65 -36
- 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/outline/index.ts +1 -1
- package/src/client/pdf/pdfPanel.ts +18 -0
- package/src/client/pendingNav.ts +141 -0
- package/src/client/settingsContext.ts +6 -4
- package/src/client/sidebar/panels/FileExplorer.ts +2 -15
- package/src/client/sidebar/panels/SvnPanel.ts +682 -72
- package/src/client/sidebar/panels/index.ts +1 -1
- package/src/client/sidebar/panels/svnListBudget.ts +58 -0
- package/src/client/state/records.ts +7 -1
- package/src/client/styles/editor.css +47 -5
- package/src/client/svnStatus.ts +206 -1
- package/src/client/svnStore.ts +6 -1
- package/src/client/ui/AiSettings.ts +47 -14
- package/src/client/ui/DiffBox.ts +1 -1
- package/src/client/ui/DiffLauncher.ts +6 -4
- package/src/client/ui/EditorView.ts +149 -22
- package/src/client/ui/McpSettings.ts +30 -0
- package/src/client/ui/PerfSettings.ts +4 -1
- package/src/client/ui/SvnAiPlanDialog.ts +293 -0
- package/src/client/ui/icons.ts +91 -0
- package/src/compat.ts +26 -5
- package/src/dshVersion.ts +2 -1
- package/src/fileOpenSettings.ts +274 -49
- package/src/index.ts +18 -1
- package/src/lsp/client.ts +24 -0
- package/src/lsp/rpc.ts +57 -0
- package/src/lsp/server.ts +272 -2
- package/src/model.ts +2 -1
- package/src/perf.ts +90 -4
- package/src/remoteWorkspace.ts +177 -0
- package/src/routes.ts +13 -1
- package/src/rpc.ts +115 -39
- package/src/shared/ai.ts +12 -0
- package/src/shared/lsp.ts +79 -0
- package/src/shared/nativeOpen.ts +91 -0
- package/src/shared/rpc.ts +162 -2
- package/src/shared/svn.ts +296 -10
- package/src/shared/types.ts +5 -1
- package/src/svn.ts +235 -13
|
@@ -11,10 +11,13 @@ 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 { clearPendingNav, putPendingNav, readPendingNav } from '../pendingNav.js'
|
|
20
|
+
import { readBinaryPreview } from '../../shared/rpc.js'
|
|
18
21
|
import {
|
|
19
22
|
clearBaseline,
|
|
20
23
|
clearReadVersion,
|
|
@@ -121,6 +124,22 @@ function dirOfRel(relPath) {
|
|
|
121
124
|
/** 跳转目标高亮的保留时长(LSP/搜索跳转落地后给用户的位置提示,到期自动清除)。 */
|
|
122
125
|
const NAV_FLASH_MS = 1200
|
|
123
126
|
|
|
127
|
+
/** 二进制通道回退日志的一次性闸(整页仅报一次,避免每次读图/读 PDF 刷屏)。 */
|
|
128
|
+
let binFallbackLogged = false
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 二进制通道回退的一次性诊断日志:readBinaryPreview 本次尝试过新通道并失败时上报。
|
|
132
|
+
* 有效期:随页面生命周期仅一条;dbg 本身受诊断开关控制(关=零输出零落盘)。
|
|
133
|
+
* @author ddj 2026年09月22号
|
|
134
|
+
* @param sid 会话 id
|
|
135
|
+
* @param out readBinaryPreview 统一结果(带 probed 才说明本次尝试过新通道)
|
|
136
|
+
*/
|
|
137
|
+
const noteBinFallback = (sid, out) => {
|
|
138
|
+
if (binFallbackLogged || !out?.probed) return
|
|
139
|
+
binFallbackLogged = true
|
|
140
|
+
dbg(sid, 'edrv.readBinary 不可用(host 未接线/旧版或网络失败),已回退 base64', 'debug')
|
|
141
|
+
}
|
|
142
|
+
|
|
124
143
|
/**
|
|
125
144
|
* 从 Monaco 语言目录取可选语言 id 列表(代码片段新建时的语言下拉来源)。
|
|
126
145
|
* 旧版 Monaco / 目录不可读时返回 undefined,由调用方回落 shared 的内置常量。
|
|
@@ -197,6 +216,8 @@ export function EditorView(props) {
|
|
|
197
216
|
const [imageSrc, setImageSrc] = React.useState(null) // 图片预览 data URL(图片 tab 专用,文本 tab 恒为 null)
|
|
198
217
|
const [imgSize, setImgSize] = React.useState(null) // 图片自然尺寸 { w, h }(路径栏 meta)
|
|
199
218
|
const [imgBroken, setImgBroken] = React.useState(false) // 图片解码失败(onError),显示占位与重试
|
|
219
|
+
const [imgZoom, setImgZoom] = React.useState(null) // 图片缩放比例(null=适应宽度;会话+路径级记忆,见 imagePreview.zoomKeyOf)
|
|
220
|
+
const imgElRef = React.useRef(null) // 预览 <img> 元素(适应宽度态下步进取实测缩放基准)
|
|
200
221
|
const svgTextRef = React.useRef(new Set()) // 强制以文本打开的 SVG 路径集合(toggleSvgText 维护)
|
|
201
222
|
const mdPreviewRef = React.useRef(new Set()) // 处于预览态的 Markdown 路径集合(toggleMdPreview 维护;不持久化)
|
|
202
223
|
const [pdfBytes, setPdfBytes] = React.useState(null) // 当前 PDF tab 的原始字节(null=加载中/非 PDF)
|
|
@@ -828,26 +849,36 @@ export function EditorView(props) {
|
|
|
828
849
|
setImgSize(null)
|
|
829
850
|
setImgBroken(false)
|
|
830
851
|
setLoadStage({ progress: monaco ? 72 : 12, message: '读取图片…' })
|
|
831
|
-
rpc
|
|
852
|
+
readBinaryPreview(rpc, { sessionId: sid, path }).then((out) => {
|
|
832
853
|
if (seq !== loadSeqRef.current || path !== active) return
|
|
833
|
-
if (
|
|
834
|
-
|
|
854
|
+
if (out.ok && out.via === 'binary') {
|
|
855
|
+
// 新通道:原始字节本地组 data URL(线上省 base64,本地编码为 img src 所需不可省)
|
|
856
|
+
recordBaseline(scope, path, out.version || version)
|
|
857
|
+
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
858
|
+
setImageSrc(dataUrlOf(bytesToBase64(out.bytes), out.mime))
|
|
859
|
+
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
860
|
+
setStatus('已加载')
|
|
861
|
+
return
|
|
862
|
+
}
|
|
863
|
+
noteBinFallback(sid, out)
|
|
864
|
+
if (out.ok && out.via === 'base64') {
|
|
865
|
+
if (!out.mime) {
|
|
835
866
|
setLoadError('host 版本过旧:图片响应缺少 mime')
|
|
836
867
|
setError('host 版本过旧:图片响应缺少 mime')
|
|
837
868
|
setStatus('读取失败')
|
|
838
869
|
return
|
|
839
870
|
}
|
|
840
|
-
const imgVersion =
|
|
871
|
+
const imgVersion = out.version ?? version
|
|
841
872
|
recordBaseline(scope, path, imgVersion)
|
|
842
873
|
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
843
|
-
setImageSrc(dataUrlOf(
|
|
874
|
+
setImageSrc(dataUrlOf(out.content, out.mime))
|
|
844
875
|
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
845
876
|
setStatus('已加载')
|
|
846
877
|
return
|
|
847
878
|
}
|
|
848
|
-
const base =
|
|
879
|
+
const base = out.error ? String(out.error) : '读取失败'
|
|
849
880
|
// 带出 host 解析后的真实路径:跳转失败时一眼看出是路径解析错还是目标不存在
|
|
850
|
-
const message =
|
|
881
|
+
const message = out.resolvedPath ? base + ':' + String(out.resolvedPath) : base
|
|
851
882
|
setLoadError(message)
|
|
852
883
|
setError(message)
|
|
853
884
|
setStatus('读取失败')
|
|
@@ -884,21 +915,34 @@ export function EditorView(props) {
|
|
|
884
915
|
setStatus('已加载')
|
|
885
916
|
return
|
|
886
917
|
}
|
|
887
|
-
rpc
|
|
918
|
+
readBinaryPreview(rpc, { sessionId: sid, path }).then((out) => {
|
|
888
919
|
if (seq !== loadSeqRef.current || path !== active) return
|
|
889
|
-
if (
|
|
890
|
-
const pdfVersion =
|
|
920
|
+
if (out.ok && out.via === 'binary') {
|
|
921
|
+
const pdfVersion = out.version || version
|
|
891
922
|
recordBaseline(scope, path, pdfVersion)
|
|
892
923
|
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
893
|
-
|
|
924
|
+
// 缓存仍存 base64(pdfB64CacheRef 形状不变,命中路径零改动);字节直通面板省一次解码
|
|
925
|
+
cache.set(path, bytesToBase64(out.bytes))
|
|
894
926
|
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
895
|
-
setPdfBytes(
|
|
927
|
+
setPdfBytes(out.bytes)
|
|
896
928
|
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
897
929
|
setStatus('已加载')
|
|
898
930
|
return
|
|
899
931
|
}
|
|
900
|
-
|
|
901
|
-
|
|
932
|
+
noteBinFallback(sid, out)
|
|
933
|
+
if (out.ok && out.via === 'base64') {
|
|
934
|
+
const pdfVersion = out.version ?? version
|
|
935
|
+
recordBaseline(scope, path, pdfVersion)
|
|
936
|
+
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
937
|
+
cache.set(path, out.content)
|
|
938
|
+
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
939
|
+
setPdfBytes(base64ToBytes(out.content))
|
|
940
|
+
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
941
|
+
setStatus('已加载')
|
|
942
|
+
return
|
|
943
|
+
}
|
|
944
|
+
const base = out.error ? String(out.error) : '读取失败'
|
|
945
|
+
const message = out.resolvedPath ? base + ':' + String(out.resolvedPath) : base
|
|
902
946
|
setLoadError(message)
|
|
903
947
|
setError(message)
|
|
904
948
|
setStatus('读取失败')
|
|
@@ -1369,6 +1413,10 @@ export function EditorView(props) {
|
|
|
1369
1413
|
if (!event?.detail?.scope || event.detail.scope === scope) setSvnStatus(getSvnStatus(scope))
|
|
1370
1414
|
}
|
|
1371
1415
|
window.addEventListener('edrv:svn-status', onSvnStatus)
|
|
1416
|
+
// 缓存命中时 ensure 直接 return、不广播事件,须同步读回当前 scope 的载荷:
|
|
1417
|
+
// 否则从非 SVN 工作区切回 SVN 工作区后,状态残留上一个工作区的 managed:false,
|
|
1418
|
+
// 右键菜单/页签菜单的 SVN 组会整组消失(与下方变更清单 effect 的同步语句同理)。
|
|
1419
|
+
setSvnStatus(getSvnStatus(scope))
|
|
1372
1420
|
return () => window.removeEventListener('edrv:svn-status', onSvnStatus)
|
|
1373
1421
|
}, [sessionId, scope])
|
|
1374
1422
|
|
|
@@ -1908,6 +1956,7 @@ export function EditorView(props) {
|
|
|
1908
1956
|
// model 不在此销毁:跨挂载缓存按作用域存活(modelCache 切作用域时统一释放)
|
|
1909
1957
|
for (const ctl of pdfCtlRef.current.values()) ctl.destroy()
|
|
1910
1958
|
pdfCtlRef.current.clear()
|
|
1959
|
+
clearZoomMem() // 图片缩放记忆随 EditorView 卸载清空(会话级生命周期,不持久化)
|
|
1911
1960
|
const root = editorRef.current && editorRef.current.getDomNode ? editorRef.current.getDomNode() : null
|
|
1912
1961
|
if (root) {
|
|
1913
1962
|
const ov = root.querySelector('.edrv-minus-overlay')
|
|
@@ -2209,6 +2258,12 @@ export function EditorView(props) {
|
|
|
2209
2258
|
// 「已就绪」会提前定位;随后真实内容到达触发 model 重设/内容写入,Monaco 会重置光标,
|
|
2210
2259
|
// 落点丢失且 pendingFocus 已消费,最终停在 {1,1}。实测(v0.4.6,Ctrl+点击枚举成员):
|
|
2211
2260
|
// opener 正确派发 line=349,落地后编辑器实际停在 1:1 且无高亮;同文件跳转不换内容故不复现。
|
|
2261
|
+
//
|
|
2262
|
+
// ⚠️ 第三次事故(2026-09-23 实测取证):并行改码触发插件 client HMR 热重载
|
|
2263
|
+
// (保留 console 装配日志重复 6-7 轮为证)→ 本组件被重挂载 → 挂载级 pendingFocusRef
|
|
2264
|
+
// 随旧实例销毁 → 「首开只打开文件不落行、热重载平息后第二次才正常」。
|
|
2265
|
+
// 修复:openFileAt 同步写 window.__edrvPendingNav__ 槽(跨热重载存活),本 effect 取
|
|
2266
|
+
// ref || 槽、成功落点后一并清;并加模型身份守卫(模型不是目标文件时不清、等重跑)。
|
|
2212
2267
|
React.useEffect(() => {
|
|
2213
2268
|
const ed = editorRef.current
|
|
2214
2269
|
if (!ed || !contentReady) return
|
|
@@ -2226,11 +2281,24 @@ export function EditorView(props) {
|
|
|
2226
2281
|
ed.focus()
|
|
2227
2282
|
return
|
|
2228
2283
|
}
|
|
2229
|
-
|
|
2284
|
+
// 快路径 = 挂载级 ref;交接路径 = window 槽(热重载重挂载后 ref 已丢、槽仍在)
|
|
2285
|
+
const refPf = pendingFocusRef.current
|
|
2286
|
+
const slotNav = refPf ? null : readPendingNav()
|
|
2287
|
+
const pf = refPf || (slotNav
|
|
2288
|
+
? { path: slotNav.path, region: null, line: slotNav.line ?? null, column: slotNav.column ?? 1, endLine: slotNav.endLine ?? null, endColumn: slotNav.endColumn ?? null }
|
|
2289
|
+
: null)
|
|
2230
2290
|
if (!pf || !sameFile(pf.path, active)) return
|
|
2291
|
+
// 单一清除点:ref 与 window 槽一并处理,防残留二次跳
|
|
2292
|
+
const dropPending = () => {
|
|
2293
|
+
pendingFocusRef.current = null
|
|
2294
|
+
if (pf.line != null) clearPendingNav()
|
|
2295
|
+
}
|
|
2296
|
+
// 模型身份守卫:编辑器模型必须已是目标文件才落点;不匹配则保留 pending(不清),
|
|
2297
|
+
// 依赖变化后的下一次运行再落(model 同步 effect 声明在本 effect 之前、同帧先执行)。
|
|
2231
2298
|
if (pf.line != null) {
|
|
2299
|
+
if (!sameFile(modelPathOf(ed), pf.path)) return
|
|
2232
2300
|
// 搜索命中 / LSP 跳转:直接定位到行/列(不依赖差异区域)并高亮目标区域
|
|
2233
|
-
|
|
2301
|
+
dropPending()
|
|
2234
2302
|
const line = Math.max(1, pf.line)
|
|
2235
2303
|
const column = Math.max(1, pf.column ?? 1)
|
|
2236
2304
|
ed.revealLineInCenter(line)
|
|
@@ -2241,7 +2309,8 @@ export function EditorView(props) {
|
|
|
2241
2309
|
}
|
|
2242
2310
|
const target = pf.region || pendingRegions[0]
|
|
2243
2311
|
if (!target) return
|
|
2244
|
-
|
|
2312
|
+
if (!sameFile(modelPathOf(ed), pf.path)) return
|
|
2313
|
+
dropPending()
|
|
2245
2314
|
ed.revealLineInCenter(Math.max(1, target.start ?? 1))
|
|
2246
2315
|
ed.setPosition({ lineNumber: Math.max(1, target.start ?? 1), column: 1 })
|
|
2247
2316
|
ed.focus()
|
|
@@ -2401,15 +2470,58 @@ export function EditorView(props) {
|
|
|
2401
2470
|
}
|
|
2402
2471
|
toggleMdPreviewRef.current = toggleMdPreview
|
|
2403
2472
|
|
|
2473
|
+
/** 当前图片的会话+路径级缩放记忆键(页签切换回来保留同一缩放)。 */
|
|
2474
|
+
const imgZoomKey = zoomKeyOf(sessionId, active)
|
|
2475
|
+
|
|
2476
|
+
// 页签/会话切换时恢复该图片的记忆缩放(无记忆 = 初始适应宽度,等价 PDF page-width)
|
|
2477
|
+
React.useEffect(() => { setImgZoom(recallZoom(imgZoomKey)) }, [imgZoomKey])
|
|
2478
|
+
|
|
2404
2479
|
/**
|
|
2405
|
-
*
|
|
2406
|
-
* @author ddj 2026年09月
|
|
2480
|
+
* 应用图片缩放并写入页签记忆(null = 适应宽度;数值经 clampZoom 收敛 25%–400%)。
|
|
2481
|
+
* @author ddj 2026年09月22号
|
|
2482
|
+
* @param next 目标缩放(null = 适应宽度)
|
|
2483
|
+
*/
|
|
2484
|
+
const applyImgZoom = (next) => {
|
|
2485
|
+
const value = next === null ? null : clampZoom(next)
|
|
2486
|
+
setImgZoom(value)
|
|
2487
|
+
rememberZoom(imgZoomKey, value)
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
/**
|
|
2491
|
+
* 图片缩放步进(10%/档,clamp 25%–400%);适应宽度态先按渲染尺寸实测当前缩放作基准。
|
|
2492
|
+
* @author ddj 2026年09月22号
|
|
2493
|
+
* @param dir 方向:1 放大 / -1 缩小
|
|
2494
|
+
*/
|
|
2495
|
+
const stepImgZoom = (dir) => {
|
|
2496
|
+
const el = imgElRef.current
|
|
2497
|
+
const measured = el && el.naturalWidth ? el.clientWidth / el.naturalWidth : 1
|
|
2498
|
+
applyImgZoom(zoomStepOf(imgZoom ?? measured, dir))
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
/**
|
|
2502
|
+
* 图片预览面板:工具条(尺寸 meta / 缩放 −+适应宽度 / SVG 文本切换 / 刷新)+ 棋盘底图片。
|
|
2503
|
+
* 初始适应宽度(等价 PDF page-width,0.1.7 官方统一缩放);按钮样式与 PDF 工具条同款。
|
|
2504
|
+
* @author ddj 2026年09月08号 / 2026年09月22号
|
|
2407
2505
|
*/
|
|
2408
2506
|
const imagePanel = () => React.createElement('div', { className: 'edrv-imgview' },
|
|
2409
2507
|
React.createElement('div', { className: 'edrv-imgview-bar' },
|
|
2410
2508
|
React.createElement('span', { className: 'edrv-imgview-meta' },
|
|
2411
2509
|
imgBroken ? '图片无法显示' : (imgSize ? imgSize.w + '×' + imgSize.h : '…')),
|
|
2412
2510
|
React.createElement('span', { style: { flex: 1 } }),
|
|
2511
|
+
React.createElement('span', { className: 'edrv-pdf-page' },
|
|
2512
|
+
imgZoom === null ? '适应' : Math.round(imgZoom * 100) + '%'),
|
|
2513
|
+
React.createElement('button', {
|
|
2514
|
+
className: 'edrv-pill edrv-pill-ghost edrv-pdf-zoom', title: '缩小(10%/档)',
|
|
2515
|
+
onClick: () => stepImgZoom(-1),
|
|
2516
|
+
}, '−'),
|
|
2517
|
+
React.createElement('button', {
|
|
2518
|
+
className: 'edrv-pill edrv-pill-ghost edrv-pdf-zoom', title: '放大(10%/档)',
|
|
2519
|
+
onClick: () => stepImgZoom(1),
|
|
2520
|
+
}, '+'),
|
|
2521
|
+
React.createElement('button', {
|
|
2522
|
+
className: 'edrv-pill edrv-pill-ghost', title: '恢复适应宽度',
|
|
2523
|
+
disabled: imgZoom === null, onClick: () => applyImgZoom(null),
|
|
2524
|
+
}, '适应宽度'),
|
|
2413
2525
|
(isSvgPath(active) ? React.createElement('button', {
|
|
2414
2526
|
className: 'edrv-pill edrv-pill-ghost', title: '以文本方式查看/编辑(SVG 为文本格式)',
|
|
2415
2527
|
onClick: () => toggleSvgText(active),
|
|
@@ -2422,8 +2534,12 @@ export function EditorView(props) {
|
|
|
2422
2534
|
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: () => reloadFile() }, '重试'))
|
|
2423
2535
|
: React.createElement('img', {
|
|
2424
2536
|
className: 'edrv-imgview-img',
|
|
2537
|
+
ref: imgElRef,
|
|
2425
2538
|
src: imageSrc,
|
|
2426
2539
|
alt: active || '',
|
|
2540
|
+
style: (imgZoom !== null && imgSize)
|
|
2541
|
+
? { width: Math.round(imgSize.w * imgZoom) + 'px', maxWidth: 'none', maxHeight: 'none' }
|
|
2542
|
+
: undefined,
|
|
2427
2543
|
onLoad: (e) => { setImgBroken(false); setImgSize({ w: e.target.naturalWidth, h: e.target.naturalHeight }) },
|
|
2428
2544
|
onError: () => setImgBroken(true),
|
|
2429
2545
|
}))))
|
|
@@ -2996,6 +3112,10 @@ export function EditorView(props) {
|
|
|
2996
3112
|
|
|
2997
3113
|
const openFile = (path, focusDiff) => {
|
|
2998
3114
|
if (!path) return
|
|
3115
|
+
// 原生打开范围(nativeOpenExts 设置):命中且非差异跳转 → 走官方渲染器
|
|
3116
|
+
//(focusDiff=true 跳过:差异审查对象必为本插件编辑的文本文件,审查优先);
|
|
3117
|
+
// handler 缺失/失败(旧版无 openResource / seat 未挂)→ 回退现状页签打开,不阻断
|
|
3118
|
+
if (!focusDiff && inNativePath(path) && tryNativeOpen(path)) return
|
|
2999
3119
|
// 打开文件即离开基线差异审阅态(差异视图是「当前文件」的临时视图)
|
|
3000
3120
|
setSvnDiff(null)
|
|
3001
3121
|
recordNav()
|
|
@@ -3023,6 +3143,9 @@ export function EditorView(props) {
|
|
|
3023
3143
|
const normalized = addTabNorm(path, true)
|
|
3024
3144
|
if (!normalized) return
|
|
3025
3145
|
pendingFocusRef.current = { path: normalized, region: null, line: line ?? null, column: column ?? 1, endLine: endLine ?? null, endColumn: endColumn ?? null }
|
|
3146
|
+
// 第三次事故(2026-09-23):插件 HMR 热重载会重挂载本组件、挂载级 ref 随旧实例销毁 →
|
|
3147
|
+
// 落点意图丢失。同步落 window 槽(跨热重载存活)做交接,落点成功后与 ref 一并清。
|
|
3148
|
+
putPendingNav({ path: normalized, line: line ?? null, column: column ?? 1, endLine: endLine ?? null, endColumn: endColumn ?? null })
|
|
3026
3149
|
setFocusRequest((value) => value + 1)
|
|
3027
3150
|
}
|
|
3028
3151
|
|
|
@@ -3426,6 +3549,10 @@ export function EditorView(props) {
|
|
|
3426
3549
|
: null
|
|
3427
3550
|
|
|
3428
3551
|
const sidebarPanels = props.sidebarPanels
|
|
3552
|
+
// SVN 变更查表(文件树徽标 O(1) 查表用):O(n) 建表只随数据/作用域变化重算,
|
|
3553
|
+
// 不再随编辑区每轮渲染重建(条目多时每帧新建几千键的 Record 徒增 GC 压力)。
|
|
3554
|
+
// @author ddj 2026年09月23号
|
|
3555
|
+
const svnChangeMapMemo = React.useMemo(() => svnChangeMapOf(scope), [scope, svnChanges])
|
|
3429
3556
|
const sidebarCtx = {
|
|
3430
3557
|
sessionId,
|
|
3431
3558
|
// 当前会话工作区(反应式 cwd;规则面板项目 Tab 自动匹配)
|
|
@@ -3445,7 +3572,7 @@ export function EditorView(props) {
|
|
|
3445
3572
|
svn: svnStatus,
|
|
3446
3573
|
// SVN 变更:面板列表 + 文件树徽标查表(客户端合成,不改 listDir 契约)
|
|
3447
3574
|
svnChanges,
|
|
3448
|
-
svnChangeMap:
|
|
3575
|
+
svnChangeMap: svnChangeMapMemo,
|
|
3449
3576
|
openSvnDiff: (p) => runSvnDiffBase(p),
|
|
3450
3577
|
openSvnLocalPair: runSvnDiffLocalPair,
|
|
3451
3578
|
refreshSvnChanges: () => refreshSvnChanges(sessionId, scope),
|
|
@@ -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
|
|