dsh-vscode-mode 0.12.0 → 0.13.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 +148 -112
- package/assets/shell/dsh-open.cs +65 -149
- package/assets/shell/dsh-open.mjs +241 -0
- package/assets/shell/dsh-open.ps1 +28 -47
- package/assets/shell/dsh-open.sh +25 -66
- package/lib/client.js +30185 -29711
- package/lib/client.js.map +1 -1
- package/lib/index.js +3889 -2336
- package/lib/index.js.map +1 -1
- package/package.json +9 -8
- package/skills/dsh-vscodemode-mcp/SKILL.md +94 -132
- package/src/childEnv.ts +19 -0
- package/src/client/addToConversation.ts +4 -2
- package/src/client/ai/inlineProvider.ts +1 -8
- package/src/client/commandBridge.ts +6 -2
- package/src/client/externalOpen.ts +40 -19
- package/src/client/index.ts +17 -15
- package/src/client/keybindings.ts +18 -1
- package/src/client/monaco/loader.ts +3 -1
- package/src/client/openFlow.ts +36 -28
- package/src/client/openReceipt.ts +64 -0
- package/src/client/panelCommands.ts +101 -0
- package/src/client/pdf/pdfLoader.ts +4 -2
- package/src/client/receiptState.ts +120 -0
- package/src/client/rpc.ts +30 -4
- package/src/client/shortcutsOfficial.ts +39 -4
- package/src/client/sidebarBridge.ts +7 -0
- package/src/client/styles/mcp.css +1 -19
- package/src/client/ui/DiffBadge.ts +2 -1
- package/src/client/ui/EditorView.ts +237 -55
- package/src/client/ui/McpSettings.ts +32 -7
- package/src/client/ui/OfficialSideTab.ts +5 -2
- package/src/client/ui/QuickOpen.ts +10 -9
- package/src/client/ui/commandCatalog.ts +5 -4
- package/src/client/useOpenReceipt.ts +128 -0
- package/src/compat.ts +5 -3
- package/src/dap/manager.ts +46 -13
- package/src/devForm.ts +252 -92
- package/src/externalHandoff.ts +93 -52
- package/src/fileOpenSettings.ts +1 -24
- package/src/hostImport.ts +19 -0
- package/src/httpGuard.ts +101 -0
- package/src/index.ts +106 -97
- package/src/integrate.ts +56 -50
- package/src/lsp/transport.ts +84 -127
- package/src/mcp.ts +58 -9
- package/src/mcpIsolation.ts +211 -102
- package/src/mcpProject.ts +220 -133
- package/src/mcpRuntime.ts +343 -0
- package/src/openBridge.ts +69 -0
- package/src/openInbox.ts +207 -0
- package/src/paths.ts +8 -5
- package/src/processTree.ts +63 -0
- package/src/reveal.ts +5 -3
- package/src/routes.ts +156 -154
- package/src/rpc.ts +14 -13
- package/src/shared/appUrl.ts +19 -0
- package/src/shared/externalOpen.ts +71 -0
- package/src/shared/integration.ts +2 -0
- package/src/shared/keybindings.ts +8 -5
- package/src/shared/mcp.ts +4 -1
- package/src/shared/rpc.ts +7 -4
- package/src/shared/svn.ts +6 -8
- package/src/svnPlanTool.ts +51 -0
- package/src/unityBridge.ts +26 -3
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +99 -203
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs +166 -0
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs.meta +2 -0
- package/unity/com.dsh.editor/README.md +16 -25
- package/unity/com.dsh.editor/package.json +2 -2
- package/src/client/ui/ShortcutSettings.ts +0 -294
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* 作者 ddj 2026-08-20
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react'
|
|
8
|
+
import { appUrl } from '../../shared/appUrl.js'
|
|
8
9
|
import { rpc } from '../rpc.js'
|
|
9
10
|
import { openEditorView, emitShowLauncher } from '../events.js'
|
|
10
11
|
import { summarize } from '../state/records.js'
|
|
@@ -48,6 +49,6 @@ export function DiffBadge(props) {
|
|
|
48
49
|
title: n + ' 个文件有差异,点击在编辑区查看',
|
|
49
50
|
style: { position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 30, height: 30, padding: 0, background: 'transparent', border: 'none', borderRadius: 6, cursor: 'pointer' },
|
|
50
51
|
},
|
|
51
|
-
React.createElement('img', { src: '/edrv/assets/compare-select.png', alt: '差异', style: { width: 22, height: 22, display: 'block' } }),
|
|
52
|
+
React.createElement('img', { src: appUrl('/edrv/assets/compare-select.png'), alt: '差异', style: { width: 22, height: 22, display: 'block' } }),
|
|
52
53
|
React.createElement('span', { style: { position: 'absolute', top: 1, right: 0, minWidth: 15, height: 15, padding: '0 3px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-primary,#d97706)', color: '#fff', fontSize: 10, lineHeight: '15px', textAlign: 'center', fontWeight: 700, boxSizing: 'border-box' } }, String(n)))
|
|
53
54
|
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* 作者 ddj 2026-08-20
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react'
|
|
11
|
+
import { useOpenReceipt } from '../useOpenReceipt.js'
|
|
12
|
+
import { acceptsLoad, receiptErrorFor, receiptPath, receiptReadyFor, receiptTabPath } from '../receiptState.js'
|
|
13
|
+
import { bindPanelCommand } from '../panelCommands.js'
|
|
11
14
|
import { dbg, rpc } from '../rpc.js'
|
|
12
15
|
import { emitFileChanged, emitRefresh } from '../events.js'
|
|
13
16
|
import { langOf, loadMonaco, snippetLanguageOf } from '../monaco/loader.js'
|
|
@@ -235,6 +238,7 @@ export function EditorView(props) {
|
|
|
235
238
|
const [dirtyMap, setDirtyMap] = React.useState({})
|
|
236
239
|
const [content, setContent] = React.useState(null)
|
|
237
240
|
const [contentPath, setContentPath] = React.useState(null)
|
|
241
|
+
const [receiptLoad, setReceiptLoad] = React.useState(null) // { sessionId, path, sequence, status, error }
|
|
238
242
|
const [imageSrc, setImageSrc] = React.useState(null) // 图片预览 data URL(图片 tab 专用,文本 tab 恒为 null)
|
|
239
243
|
const [imgSize, setImgSize] = React.useState(null) // 图片自然尺寸 { w, h }(路径栏 meta)
|
|
240
244
|
const [imgBroken, setImgBroken] = React.useState(false) // 图片解码失败(onError),显示占位与重试
|
|
@@ -804,6 +808,87 @@ export function EditorView(props) {
|
|
|
804
808
|
}).catch((e) => setError('list异常:' + String(e)))
|
|
805
809
|
}
|
|
806
810
|
|
|
811
|
+
/**
|
|
812
|
+
* Reject callbacks for a replaced file or loader generation.
|
|
813
|
+
* @private @author ddj 2026年09月28号
|
|
814
|
+
* @param path Normalized tab path.
|
|
815
|
+
* @param sequence Loader generation.
|
|
816
|
+
* @returns Whether this load still owns the active view.
|
|
817
|
+
*/
|
|
818
|
+
function loadCurrent(path, sequence) {
|
|
819
|
+
return sequence === loadSeqRef.current && path === activeRef.current
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Commit receipt metadata in the same React batch as loaded content.
|
|
824
|
+
* @private @author ddj 2026年09月28号
|
|
825
|
+
* @param path Loaded tab path.
|
|
826
|
+
* @param sid Session owning the operation.
|
|
827
|
+
* @param sequence Loader generation.
|
|
828
|
+
* @param failure Optional target-specific read/render error.
|
|
829
|
+
*/
|
|
830
|
+
function stampLoad(path, sid, sequence, failure) {
|
|
831
|
+
if (!loadCurrent(path, sequence)) return
|
|
832
|
+
setReceiptLoad({ path, cwd, sessionId: sid, sequence, status: failure ? 'error' : 'ready', error: failure })
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Report a read failure without attributing another operation's error to the file.
|
|
837
|
+
* @private @author ddj 2026年09月28号
|
|
838
|
+
* @param path Failed tab path.
|
|
839
|
+
* @param sid Session owning the load.
|
|
840
|
+
* @param sequence Loader generation.
|
|
841
|
+
* @param message Read failure description.
|
|
842
|
+
*/
|
|
843
|
+
function failLoad(path, sid, sequence, message) {
|
|
844
|
+
if (!loadCurrent(path, sequence)) return
|
|
845
|
+
stampLoad(path, sid, sequence, message)
|
|
846
|
+
setLoadError(message)
|
|
847
|
+
setError(message)
|
|
848
|
+
setStatus('读取失败')
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Attribute a preview decoder failure only to its still-current byte generation.
|
|
853
|
+
* @private @author ddj 2026年09月28号
|
|
854
|
+
* @param path Rendered file path.
|
|
855
|
+
* @param sid Rendering session.
|
|
856
|
+
* @param sequence Byte-load generation captured by the renderer.
|
|
857
|
+
* @param message Decoder failure.
|
|
858
|
+
*/
|
|
859
|
+
function renderLoadFail(path, sid, sequence, message) {
|
|
860
|
+
if (!loadCurrent(path, sequence)) return
|
|
861
|
+
/** @author ddj 2026年09月28号 @param current Committed load state. @returns Correlated failure or unchanged state. */
|
|
862
|
+
const reject = (current) => acceptsLoad(current, sid, path, sequence, loadSeqRef.current)
|
|
863
|
+
? { ...current, status: 'error', error: String(message) } : current
|
|
864
|
+
setReceiptLoad(reject)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Revalidate clean cached text without replacing edits made during the request.
|
|
869
|
+
* @private @author ddj 2026年09月28号
|
|
870
|
+
* @param model Cached text model.
|
|
871
|
+
* @param path Normalized target path.
|
|
872
|
+
* @param sid Session owning the read.
|
|
873
|
+
* @param sequence Loader generation.
|
|
874
|
+
* @param mark Existing disk-version bookkeeping callback.
|
|
875
|
+
*/
|
|
876
|
+
function checkCached(model, path, sid, sequence, mark) {
|
|
877
|
+
if (dirtyRef.current[path]) return
|
|
878
|
+
const before = model.getValue()
|
|
879
|
+
/** @author ddj 2026年09月28号 @param res Disk content for a still-clean cached model. */
|
|
880
|
+
const accept = (res) => {
|
|
881
|
+
if (!loadCurrent(path, sequence) || dirtyRef.current[path] || model.getValue() !== before) return
|
|
882
|
+
if (!res?.ok) return
|
|
883
|
+
mark(res.version)
|
|
884
|
+
if (res.content === before) return
|
|
885
|
+
saveViewState(path)
|
|
886
|
+
setContent(res.content)
|
|
887
|
+
setStatus('已加载')
|
|
888
|
+
}
|
|
889
|
+
rpc('edrv.read', { sessionId: sid, path }).then(accept).catch(() => {})
|
|
890
|
+
}
|
|
891
|
+
|
|
807
892
|
/**
|
|
808
893
|
* 加载文件内容(对齐 VSCode model 复用:会话内已打开的 model 直接秒显,
|
|
809
894
|
* 后台静默 RPC 校验防陈旧;首次打开走原读取流程)。
|
|
@@ -817,6 +902,8 @@ export function EditorView(props) {
|
|
|
817
902
|
*/
|
|
818
903
|
const loadContent = (path, sid, force, version) => {
|
|
819
904
|
const seq = ++loadSeqRef.current
|
|
905
|
+
setReceiptLoad({ path, cwd, sessionId: sid, sequence: seq, status: 'loading' })
|
|
906
|
+
setLoadError(null)
|
|
820
907
|
/**
|
|
821
908
|
* 记下磁盘版本基线;markRead=true 表示本次真的从磁盘读了内容
|
|
822
909
|
* (已读版本台账随之失效,下一轮轮询按新版本重新比对)。
|
|
@@ -845,25 +932,18 @@ export function EditorView(props) {
|
|
|
845
932
|
setContentPath(path)
|
|
846
933
|
setLoadStage((prev) => ({ progress: Math.max(84, prev.progress), message: '文件已读取,准备创建编辑器…' }))
|
|
847
934
|
setStatus('已加载')
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (res && res.ok) mark(res.version)
|
|
851
|
-
if (res && res.ok && res.content !== cachedModel.getValue()) {
|
|
852
|
-
saveViewState(path) // 内容更新前保留当前视图位置
|
|
853
|
-
setContent(res.content)
|
|
854
|
-
setStatus('已加载')
|
|
855
|
-
}
|
|
856
|
-
}).catch(() => { /* 校验失败保留缓存内容 */ })
|
|
935
|
+
stampLoad(path, sid, seq)
|
|
936
|
+
checkCached(cachedModel, path, sid, seq, mark)
|
|
857
937
|
return
|
|
858
938
|
}
|
|
859
|
-
setLoadError(null)
|
|
860
939
|
setLoadStage({ progress: monaco ? 72 : 12, message: '读取文件内容…' })
|
|
861
940
|
rpc('edrv.read', { sessionId: sid, path }).then((res) => {
|
|
862
|
-
if (
|
|
941
|
+
if (!loadCurrent(path, seq)) return
|
|
863
942
|
if (res && res.ok) {
|
|
864
943
|
mark(res.version, true)
|
|
865
944
|
setContent(res.content)
|
|
866
945
|
setContentPath(path)
|
|
946
|
+
stampLoad(path, sid, seq)
|
|
867
947
|
setLoadStage((prev) => ({ progress: Math.max(84, prev.progress), message: '文件已读取,准备创建编辑器…' }))
|
|
868
948
|
setStatus('已加载')
|
|
869
949
|
} else {
|
|
@@ -871,16 +951,12 @@ export function EditorView(props) {
|
|
|
871
951
|
dapTrace('read-failed', { path, error: base, resolvedPath: res?.resolvedPath, cwd })
|
|
872
952
|
// 带出 host 解析后的真实路径:跳转失败时一眼看出是路径解析错还是目标不存在
|
|
873
953
|
const message = res?.resolvedPath ? base + ':' + relativeOf(String(res.resolvedPath), cwd) : base
|
|
874
|
-
|
|
875
|
-
setError(message)
|
|
876
|
-
setStatus('读取失败')
|
|
954
|
+
failLoad(path, sid, seq, message)
|
|
877
955
|
}
|
|
878
956
|
}).catch((e) => {
|
|
879
|
-
if (seq
|
|
957
|
+
if (!loadCurrent(path, seq)) return
|
|
880
958
|
const message = 'read异常:' + String(e)
|
|
881
|
-
|
|
882
|
-
setError(message)
|
|
883
|
-
setStatus('读取失败')
|
|
959
|
+
failLoad(path, sid, seq, message)
|
|
884
960
|
})
|
|
885
961
|
}
|
|
886
962
|
|
|
@@ -898,12 +974,13 @@ export function EditorView(props) {
|
|
|
898
974
|
setImgBroken(false)
|
|
899
975
|
setLoadStage({ progress: monaco ? 72 : 12, message: '读取图片…' })
|
|
900
976
|
readBinaryPreview(rpc, { sessionId: sid, path }).then((out) => {
|
|
901
|
-
if (
|
|
977
|
+
if (!loadCurrent(path, seq)) return
|
|
902
978
|
if (out.ok && out.via === 'binary') {
|
|
903
979
|
// 新通道:原始字节本地组 data URL(线上省 base64,本地编码为 img src 所需不可省)
|
|
904
980
|
recordBaseline(scope, path, out.version || version)
|
|
905
981
|
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
906
982
|
setImageSrc(dataUrlOf(bytesToBase64(out.bytes), out.mime))
|
|
983
|
+
stampLoad(path, sid, seq)
|
|
907
984
|
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
908
985
|
setStatus('已加载')
|
|
909
986
|
return
|
|
@@ -911,15 +988,14 @@ export function EditorView(props) {
|
|
|
911
988
|
noteBinFallback(sid, out)
|
|
912
989
|
if (out.ok && out.via === 'base64') {
|
|
913
990
|
if (!out.mime) {
|
|
914
|
-
|
|
915
|
-
setError('host 版本过旧:图片响应缺少 mime')
|
|
916
|
-
setStatus('读取失败')
|
|
991
|
+
failLoad(path, sid, seq, 'host 版本过旧:图片响应缺少 mime')
|
|
917
992
|
return
|
|
918
993
|
}
|
|
919
994
|
const imgVersion = out.version ?? version
|
|
920
995
|
recordBaseline(scope, path, imgVersion)
|
|
921
996
|
clearReadVersion(scope, path) // 刚读过内容:已读版本台账失效,下一轮按新版本比对
|
|
922
997
|
setImageSrc(dataUrlOf(out.content, out.mime))
|
|
998
|
+
stampLoad(path, sid, seq)
|
|
923
999
|
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
924
1000
|
setStatus('已加载')
|
|
925
1001
|
return
|
|
@@ -927,15 +1003,11 @@ export function EditorView(props) {
|
|
|
927
1003
|
const base = out.error ? String(out.error) : '读取失败'
|
|
928
1004
|
// 带出 host 解析后的真实路径:跳转失败时一眼看出是路径解析错还是目标不存在
|
|
929
1005
|
const message = out.resolvedPath ? base + ':' + String(out.resolvedPath) : base
|
|
930
|
-
|
|
931
|
-
setError(message)
|
|
932
|
-
setStatus('读取失败')
|
|
1006
|
+
failLoad(path, sid, seq, message)
|
|
933
1007
|
}).catch((e) => {
|
|
934
|
-
if (seq
|
|
1008
|
+
if (!loadCurrent(path, seq)) return
|
|
935
1009
|
const message = 'read异常:' + String(e)
|
|
936
|
-
|
|
937
|
-
setError(message)
|
|
938
|
-
setStatus('读取失败')
|
|
1010
|
+
failLoad(path, sid, seq, message)
|
|
939
1011
|
})
|
|
940
1012
|
}
|
|
941
1013
|
|
|
@@ -959,12 +1031,13 @@ export function EditorView(props) {
|
|
|
959
1031
|
cache.delete(path)
|
|
960
1032
|
cache.set(path, hit)
|
|
961
1033
|
setPdfBytes(base64ToBytes(hit))
|
|
1034
|
+
stampLoad(path, sid, seq)
|
|
962
1035
|
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
963
1036
|
setStatus('已加载')
|
|
964
1037
|
return
|
|
965
1038
|
}
|
|
966
1039
|
readBinaryPreview(rpc, { sessionId: sid, path }).then((out) => {
|
|
967
|
-
if (
|
|
1040
|
+
if (!loadCurrent(path, seq)) return
|
|
968
1041
|
if (out.ok && out.via === 'binary') {
|
|
969
1042
|
const pdfVersion = out.version || version
|
|
970
1043
|
recordBaseline(scope, path, pdfVersion)
|
|
@@ -973,6 +1046,7 @@ export function EditorView(props) {
|
|
|
973
1046
|
cache.set(path, bytesToBase64(out.bytes))
|
|
974
1047
|
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
975
1048
|
setPdfBytes(out.bytes)
|
|
1049
|
+
stampLoad(path, sid, seq)
|
|
976
1050
|
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
977
1051
|
setStatus('已加载')
|
|
978
1052
|
return
|
|
@@ -985,21 +1059,18 @@ export function EditorView(props) {
|
|
|
985
1059
|
cache.set(path, out.content)
|
|
986
1060
|
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
987
1061
|
setPdfBytes(base64ToBytes(out.content))
|
|
1062
|
+
stampLoad(path, sid, seq)
|
|
988
1063
|
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
989
1064
|
setStatus('已加载')
|
|
990
1065
|
return
|
|
991
1066
|
}
|
|
992
1067
|
const base = out.error ? String(out.error) : '读取失败'
|
|
993
1068
|
const message = out.resolvedPath ? base + ':' + String(out.resolvedPath) : base
|
|
994
|
-
|
|
995
|
-
setError(message)
|
|
996
|
-
setStatus('读取失败')
|
|
1069
|
+
failLoad(path, sid, seq, message)
|
|
997
1070
|
}).catch((e) => {
|
|
998
|
-
if (seq
|
|
1071
|
+
if (!loadCurrent(path, seq)) return
|
|
999
1072
|
const message = 'read异常:' + String(e)
|
|
1000
|
-
|
|
1001
|
-
setError(message)
|
|
1002
|
-
setStatus('读取失败')
|
|
1073
|
+
failLoad(path, sid, seq, message)
|
|
1003
1074
|
})
|
|
1004
1075
|
}
|
|
1005
1076
|
|
|
@@ -1373,6 +1444,8 @@ export function EditorView(props) {
|
|
|
1373
1444
|
setTimeout(() => window.dispatchEvent(new CustomEvent('edrv:search-focus')), 0)
|
|
1374
1445
|
}
|
|
1375
1446
|
|
|
1447
|
+
React.useEffect(() => bindPanelCommand('searchInFiles', { sessionId, cwd: cwd ?? undefined }, openSearchPanel), [sessionId, cwd])
|
|
1448
|
+
|
|
1376
1449
|
// Ctrl+Shift+F / Ctrl+Shift+V / Alt+←→ / Ctrl+Alt+←→ 的 capture 监听已随官方派发改造移除(见上)。
|
|
1377
1450
|
|
|
1378
1451
|
// 整行上下移动的实现见下方「指令系统接线」effect(moveRow 单点定义,命令栏与键位共用)。
|
|
@@ -1457,7 +1530,6 @@ export function EditorView(props) {
|
|
|
1457
1530
|
['edrv.command.save', () => { if (editorRef.current?.getModel?.()) { flushSave(); doSaveRef.current?.(false) } }],
|
|
1458
1531
|
// 快速打开由 QuickOpen 自己接该事件(它持有搜索框 ref),此处不重复实现
|
|
1459
1532
|
['edrv.command.toggleSidebar', () => setSidebarOn((v) => !v)],
|
|
1460
|
-
['edrv.command.searchInFiles', () => openSearchPanel()],
|
|
1461
1533
|
['edrv.command.toggleMarkdownPreview', () => {
|
|
1462
1534
|
const path = activeRef.current
|
|
1463
1535
|
if (!isMarkdownPath(path)) { setStatus('当前文件不是 Markdown'); return }
|
|
@@ -1753,6 +1825,23 @@ export function EditorView(props) {
|
|
|
1753
1825
|
return true
|
|
1754
1826
|
}
|
|
1755
1827
|
|
|
1828
|
+
/**
|
|
1829
|
+
* Apply a save completion only to the unchanged model that supplied its text.
|
|
1830
|
+
* @private @author ddj 2026年09月28号
|
|
1831
|
+
* @param path Saved tab path.
|
|
1832
|
+
* @param text Text actually sent to disk.
|
|
1833
|
+
* @param model Model captured before the save request.
|
|
1834
|
+
*/
|
|
1835
|
+
function acceptSaved(path, text, model) {
|
|
1836
|
+
if (!model || modelsRef.current.get(path) !== model || model.isDisposed?.() || model.getValue() !== text) return
|
|
1837
|
+
setDirtyMap((current) => ({ ...current, [path]: false }))
|
|
1838
|
+
if (activeRef.current !== path) return
|
|
1839
|
+
setContent(text)
|
|
1840
|
+
setContentPath(path)
|
|
1841
|
+
setStatus('已保存 ' + new Date().toTimeString().slice(0, 8))
|
|
1842
|
+
setDiskFlag((prev) => (prev && prev.path === path ? null : prev))
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1756
1845
|
const doSave = (silent) => {
|
|
1757
1846
|
if (!active) return
|
|
1758
1847
|
// 外部改动待处理:自动保存(silent)直接放弃,避免陈旧缓冲覆盖磁盘;
|
|
@@ -1770,6 +1859,7 @@ export function EditorView(props) {
|
|
|
1770
1859
|
if (!ed) return
|
|
1771
1860
|
if (isImageActive) { setStatus('图片只读预览'); return }
|
|
1772
1861
|
const text = ed.getValue()
|
|
1862
|
+
const savedModel = ed.getModel?.()
|
|
1773
1863
|
const path = active
|
|
1774
1864
|
if (!silent) setStatus('保存中…')
|
|
1775
1865
|
// 在途登记:关闭路径的 persistDirty 据此跳过重复提交(内容在同一 tick 内取,必然相同)
|
|
@@ -1782,13 +1872,7 @@ export function EditorView(props) {
|
|
|
1782
1872
|
recordBaseline(scope, path, res.rev)
|
|
1783
1873
|
markReadVersion(scope, path, res.rev, true) // 缓冲即磁盘内容:同版本无需再读盘
|
|
1784
1874
|
clearSync(scope, path)
|
|
1785
|
-
|
|
1786
|
-
setContentPath(path)
|
|
1787
|
-
setDirtyMap((d) => Object.assign({}, d, { [path]: false }))
|
|
1788
|
-
if (path === active) {
|
|
1789
|
-
setStatus('已保存 ' + new Date().toTimeString().slice(0, 8))
|
|
1790
|
-
setDiskFlag((prev) => (prev && prev.path === path ? null : prev))
|
|
1791
|
-
}
|
|
1875
|
+
acceptSaved(path, text, savedModel)
|
|
1792
1876
|
refreshRecords()
|
|
1793
1877
|
emitRefresh()
|
|
1794
1878
|
// 片段配置文件保存后失效补全缓存(下次补全即读到新片段)
|
|
@@ -1879,7 +1963,7 @@ export function EditorView(props) {
|
|
|
1879
1963
|
syncTypeSuggest(ed, monaco)
|
|
1880
1964
|
restoreViewState(active)
|
|
1881
1965
|
setLoadStage((prev) => ({ progress: Math.max(96, prev.progress), message: '创建编辑器视图…' }))
|
|
1882
|
-
}, [monaco, active, content, contentReady, mdPreviewing])
|
|
1966
|
+
}, [monaco, active, content, contentReady, mdPreviewing, svnDiff])
|
|
1883
1967
|
|
|
1884
1968
|
// PDF 面板外壳 ref 回调(div 仅在 PDF 分支渲染,refs 先于 effect 就绪)
|
|
1885
1969
|
const ensurePdfHost = (node) => { pdfHostRef.current = node }
|
|
@@ -1889,21 +1973,32 @@ export function EditorView(props) {
|
|
|
1889
1973
|
const host = pdfHostRef.current
|
|
1890
1974
|
if (!isPdfActive || !pdfBytes || !host) return
|
|
1891
1975
|
const path = active
|
|
1976
|
+
const sequence = receiptLoad?.sequence
|
|
1977
|
+
if (!acceptsLoad(receiptLoad, sessionId, path, sequence, loadSeqRef.current)) return
|
|
1978
|
+
let mounting = true
|
|
1979
|
+
/** @author ddj 2026年09月28号 @param message PDF error; only startup failures affect an open receipt. */
|
|
1980
|
+
const pdfError = (message) => {
|
|
1981
|
+
setError(message)
|
|
1982
|
+
if (mounting && message) renderLoadFail(path, sessionId, sequence, message)
|
|
1983
|
+
}
|
|
1984
|
+
/** @author ddj 2026年09月28号 End startup error attribution before later save operations. */
|
|
1985
|
+
const mounted = () => { mounting = false }
|
|
1892
1986
|
const ctl = createPdfPanel({
|
|
1893
1987
|
sessionId,
|
|
1894
1988
|
path,
|
|
1895
1989
|
setStatus,
|
|
1896
|
-
setError,
|
|
1990
|
+
setError: pdfError,
|
|
1897
1991
|
onDirtyChange: (d) => setDirtyMap((prev) => Object.assign({}, prev, { [path]: d })),
|
|
1898
1992
|
onReload: () => loadContent(path, sessionId, true),
|
|
1899
1993
|
})
|
|
1900
1994
|
pdfCtlRef.current.set(path, ctl)
|
|
1901
|
-
void ctl.mount(host, pdfBytes)
|
|
1995
|
+
void ctl.mount(host, pdfBytes).then(mounted, pdfError)
|
|
1902
1996
|
return () => {
|
|
1997
|
+
mounting = false
|
|
1903
1998
|
ctl.destroy()
|
|
1904
1999
|
if (pdfCtlRef.current.get(path) === ctl) pdfCtlRef.current.delete(path)
|
|
1905
2000
|
}
|
|
1906
|
-
}, [isPdfActive, pdfBytes, active])
|
|
2001
|
+
}, [isPdfActive, pdfBytes, active, receiptLoad?.sequence])
|
|
1907
2002
|
|
|
1908
2003
|
// 行内差异自绘(decorations / view zones / minus overlay)→ diffRenderer
|
|
1909
2004
|
// ⚠️ 依赖含 mdPreviewing:切回源码时编辑器实例被重建(见上方 model 同步 effect 的说明),
|
|
@@ -1912,7 +2007,7 @@ export function EditorView(props) {
|
|
|
1912
2007
|
if (!monaco || !editorRef.current || !active || content === null) return
|
|
1913
2008
|
diffRendererRef.current.render(monaco, editorRef.current, pendingRegions, sessionId)
|
|
1914
2009
|
setLoadStage({ progress: 100, message: '编辑器已就绪' })
|
|
1915
|
-
}, [monaco, active, content, pendingRegions, mdPreviewing])
|
|
2010
|
+
}, [monaco, active, content, pendingRegions, mdPreviewing, svnDiff])
|
|
1916
2011
|
|
|
1917
2012
|
React.useEffect(() => () => {
|
|
1918
2013
|
flushSave()
|
|
@@ -2289,7 +2384,7 @@ export function EditorView(props) {
|
|
|
2289
2384
|
ed.revealLineInCenter(Math.max(1, target.start ?? 1))
|
|
2290
2385
|
ed.setPosition({ lineNumber: Math.max(1, target.start ?? 1), column: 1 })
|
|
2291
2386
|
ed.focus()
|
|
2292
|
-
}, [active, content, contentReady, pendingRegions, focusRequest, monaco, mdPreviewing])
|
|
2387
|
+
}, [active, content, contentReady, pendingRegions, focusRequest, monaco, mdPreviewing, svnDiff])
|
|
2293
2388
|
|
|
2294
2389
|
const jumpTo = (region) => {
|
|
2295
2390
|
if (editorRef.current) {
|
|
@@ -2473,6 +2568,16 @@ export function EditorView(props) {
|
|
|
2473
2568
|
applyImgZoom(zoomStepOf(imgZoom ?? measured, dir))
|
|
2474
2569
|
}
|
|
2475
2570
|
|
|
2571
|
+
/**
|
|
2572
|
+
* Preserve image UI behavior while attributing decoder failure to its rendered load.
|
|
2573
|
+
* @private @author ddj 2026年09月28号
|
|
2574
|
+
*/
|
|
2575
|
+
function imageFailed() {
|
|
2576
|
+
if (!receiptLoad || !loadCurrent(receiptLoad.path, receiptLoad.sequence)) return
|
|
2577
|
+
setImgBroken(true)
|
|
2578
|
+
renderLoadFail(receiptLoad.path, receiptLoad.sessionId, receiptLoad.sequence, '图片解码失败')
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2476
2581
|
/**
|
|
2477
2582
|
* 图片预览面板:工具条(尺寸 meta / 缩放 −+适应宽度 / SVG 文本切换 / 刷新)+ 棋盘底图片。
|
|
2478
2583
|
* 初始适应宽度(等价 PDF page-width,0.1.7 官方统一缩放);按钮样式与 PDF 工具条同款。
|
|
@@ -2510,13 +2615,14 @@ export function EditorView(props) {
|
|
|
2510
2615
|
: React.createElement('img', {
|
|
2511
2616
|
className: 'edrv-imgview-img',
|
|
2512
2617
|
ref: imgElRef,
|
|
2618
|
+
key: active + ':' + (receiptLoad?.sequence ?? 0),
|
|
2513
2619
|
src: imageSrc,
|
|
2514
2620
|
alt: active || '',
|
|
2515
2621
|
style: (imgZoom !== null && imgSize)
|
|
2516
2622
|
? { width: Math.round(imgSize.w * imgZoom) + 'px', maxWidth: 'none', maxHeight: 'none' }
|
|
2517
2623
|
: undefined,
|
|
2518
2624
|
onLoad: (e) => { setImgBroken(false); setImgSize({ w: e.target.naturalWidth, h: e.target.naturalHeight }) },
|
|
2519
|
-
onError:
|
|
2625
|
+
onError: imageFailed,
|
|
2520
2626
|
}))))
|
|
2521
2627
|
|
|
2522
2628
|
/**
|
|
@@ -3118,13 +3224,89 @@ export function EditorView(props) {
|
|
|
3118
3224
|
// G9:同 openFile,先归一再进页签,保证待跳转路径与 active 同形态
|
|
3119
3225
|
const normalized = addTabNorm(path, true)
|
|
3120
3226
|
if (!normalized) return
|
|
3227
|
+
stageFileAt(normalized, line, column, endLine, endColumn)
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
/**
|
|
3231
|
+
* Stage a normalized navigation target using the existing reload-safe handoff.
|
|
3232
|
+
* @private @author ddj 2026年09月28号
|
|
3233
|
+
* @param normalized Exact tab path.
|
|
3234
|
+
* @param line Optional line.
|
|
3235
|
+
* @param column Optional column.
|
|
3236
|
+
* @param endLine Optional range end line.
|
|
3237
|
+
* @param endColumn Optional range end column.
|
|
3238
|
+
*/
|
|
3239
|
+
function stageFileAt(normalized, line, column, endLine, endColumn) {
|
|
3121
3240
|
pendingFocusRef.current = { path: normalized, region: null, line: line ?? null, column: column ?? 1, endLine: endLine ?? null, endColumn: endColumn ?? null }
|
|
3122
|
-
// 第三次事故(2026-09-23):插件 HMR 热重载会重挂载本组件、挂载级 ref 随旧实例销毁 →
|
|
3123
|
-
// 落点意图丢失。同步落 window 槽(跨热重载存活)做交接,落点成功后与 ref 一并清。
|
|
3124
3241
|
putPendingNav({ path: normalized, line: line ?? null, column: column ?? 1, endLine: endLine ?? null, endColumn: endColumn ?? null })
|
|
3125
3242
|
setFocusRequest((value) => value + 1)
|
|
3126
3243
|
}
|
|
3127
3244
|
|
|
3245
|
+
/**
|
|
3246
|
+
* Open a receipt target without losing edits or leaving the requested source hidden.
|
|
3247
|
+
* @private @author ddj 2026年09月28号
|
|
3248
|
+
* @param request Claimed external open request.
|
|
3249
|
+
*/
|
|
3250
|
+
function openReceiptFile(request) {
|
|
3251
|
+
if (!request.path) return
|
|
3252
|
+
let path = receiptTabPath(request.path, cwd)
|
|
3253
|
+
const existing = tabsRef.current.find((tab) => receiptPath(tab.path, cwd) === receiptPath(path, cwd))
|
|
3254
|
+
if (existing) path = existing.path
|
|
3255
|
+
flushSave()
|
|
3256
|
+
saveViewState(active)
|
|
3257
|
+
recordNav()
|
|
3258
|
+
setSvnDiff(null)
|
|
3259
|
+
const positioned = request.line != null || request.column != null
|
|
3260
|
+
if (positioned && mdPreviewRef.current.has(path)) toggleMdPreview(path)
|
|
3261
|
+
setError(null)
|
|
3262
|
+
if (monacoErr) setMonacoErr(null)
|
|
3263
|
+
addTab(path, true)
|
|
3264
|
+
if (positioned) stageFileAt(path, request.line ?? 1, request.column ?? 1)
|
|
3265
|
+
const moved = receiptLoad && receiptPath(receiptLoad.path, receiptLoad.cwd) !== receiptPath(path, cwd)
|
|
3266
|
+
if (path !== active || !(loadError || receiptLoad?.status === 'error' || moved)) return
|
|
3267
|
+
if (pdfCtlRef.current.get(path)?.isDirty?.()) return
|
|
3268
|
+
pdfB64CacheRef.current.delete(path)
|
|
3269
|
+
loadContent(path, sessionId, false)
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* Build facts from the current render; model identity is the exact cached instance.
|
|
3274
|
+
* @private @author ddj 2026年09月28号
|
|
3275
|
+
* @param request Claimed target and optional position.
|
|
3276
|
+
* @returns State consumed by the tested receipt predicates.
|
|
3277
|
+
*/
|
|
3278
|
+
function receiptView(request) {
|
|
3279
|
+
const ed = editorRef.current
|
|
3280
|
+
const model = ed?.getModel?.()
|
|
3281
|
+
const modelMatches = Boolean(model && modelsRef.current.get(active) === model)
|
|
3282
|
+
let positionMatches = false
|
|
3283
|
+
if (modelMatches && (request.line != null || request.column != null)) {
|
|
3284
|
+
const expected = model.validatePosition({ lineNumber: request.line ?? 1, column: request.column ?? 1 })
|
|
3285
|
+
const actual = ed.getPosition()
|
|
3286
|
+
positionMatches = actual?.lineNumber === expected.lineNumber && actual?.column === expected.column
|
|
3287
|
+
}
|
|
3288
|
+
const image = imgElRef.current
|
|
3289
|
+
return {
|
|
3290
|
+
sessionId, cwd, active, sequence: loadSeqRef.current, load: receiptLoad,
|
|
3291
|
+
mounted: Boolean(viewRootRef.current), blocked: Boolean(svnDiff),
|
|
3292
|
+
kind: isImageActive ? 'image' : isPdfActive ? 'pdf' : mdPreviewing ? 'markdown' : 'text',
|
|
3293
|
+
contentPath, contentReady, modelMatches, positionMatches,
|
|
3294
|
+
imageLoaded: Boolean(imageSrc && !imgBroken && image?.getAttribute('src') === imageSrc && image.complete && image.naturalWidth > 0),
|
|
3295
|
+
pdfLoaded: Boolean(pdfBytes && pdfHostRef.current?.querySelector('.page[data-loaded="true"]')),
|
|
3296
|
+
engineError: monacoErr ? String(monacoErr) : null,
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
/** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Correlated actual readiness. */
|
|
3301
|
+
function receiptReady(request) {
|
|
3302
|
+
return receiptReadyFor(request, receiptView(request))
|
|
3303
|
+
}
|
|
3304
|
+
/** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Only its current load/render error. */
|
|
3305
|
+
function receiptError(request) {
|
|
3306
|
+
return receiptErrorFor(request, receiptView(request))
|
|
3307
|
+
}
|
|
3308
|
+
useOpenReceipt({ sessionId, open: openReceiptFile, ready: receiptReady, error: receiptError })
|
|
3309
|
+
|
|
3128
3310
|
// 停帧跳转/调试命令监听(openFileAt 每渲染重建,经 ref 让空依赖监听读最新闭包)
|
|
3129
3311
|
const dapOpenRef = React.useRef(null)
|
|
3130
3312
|
dapOpenRef.current = openFileAt
|
|
@@ -3420,7 +3602,7 @@ export function EditorView(props) {
|
|
|
3420
3602
|
} else if (isImageActive) {
|
|
3421
3603
|
body = loadingBody(loadStage.message || '读取图片…', loadStage.progress)
|
|
3422
3604
|
} else if (isPdfActive && pdfBytes) {
|
|
3423
|
-
body = React.createElement('div', { className: 'edrv-pdf-host', ref: ensurePdfHost, key: active })
|
|
3605
|
+
body = React.createElement('div', { className: 'edrv-pdf-host', ref: ensurePdfHost, key: active + ':' + (receiptLoad?.sequence ?? 0) })
|
|
3424
3606
|
} else if (isPdfActive) {
|
|
3425
3607
|
body = loadingBody(loadStage.message || '读取 PDF…', loadStage.progress)
|
|
3426
3608
|
} else if (mdPreviewing && contentReady) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
3
|
* dsh-vscode-mode client — VSCodeMode 设置区:通用 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
|
|
4
|
-
*
|
|
4
|
+
* (快捷键统一由 DSH 官方快捷键机制管理:通用页只提供「打开官方快捷键配置」跳转入口,
|
|
5
|
+
* 插件不再自建录键编辑器,也不再注册独立「快捷键」设置区。)
|
|
5
6
|
* MCP 管理 Tab 下含三个子页签:我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)。
|
|
6
7
|
* 作者 ddj 2026年08月22号 / 2026年08月26号 / 2026年08月27号 / 2026年10月
|
|
7
8
|
*/
|
|
@@ -11,6 +12,7 @@ import type { MpcProject, MpcServer } from '../../shared/mcp.js'
|
|
|
11
12
|
import '../styles/mcp.css'
|
|
12
13
|
import { availableOpeners, AUTO_OPEN_TOOL } from '../fileOpeners.js'
|
|
13
14
|
import { SettingsContext } from '../settingsContext.js'
|
|
15
|
+
import { openOfficialShortcuts } from '../shortcutsOfficial.js'
|
|
14
16
|
import { normalizeSidebarMinWidth, SIDEBAR_MIN_DEFAULT } from '../sidebarMin.js'
|
|
15
17
|
import { EDITOR_LIMIT_CEIL, EDITOR_LIMIT_DEFAULT, normalizeMaxOpenEditors } from '../../shared/editorLimit.js'
|
|
16
18
|
import { TORTOISE_DIR_DEFAULT } from '../../shared/svn.js'
|
|
@@ -44,9 +46,11 @@ function parsePairs(text) {
|
|
|
44
46
|
}))
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
/**
|
|
49
|
+
/** @author ddj 2026年09月28号 @param server Server lifecycle snapshot. @returns User-facing status. */
|
|
48
50
|
function statusOf(server) {
|
|
49
|
-
|
|
51
|
+
if (!server.enabled || server.status === 'disabled') return '已禁用'
|
|
52
|
+
const labels = { connected: '在线', connecting: '装配中', configured: '已配置,等待会话', unverified: '已加载,连接未确认', error: '错误' }
|
|
53
|
+
return labels[server.status] ?? '状态未知'
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/** MCP 服务卡片(全局与项目共用)。 */
|
|
@@ -60,7 +64,8 @@ function ServerCard({ server, onRefresh, onToggle, onRemove }) {
|
|
|
60
64
|
React.createElement('button', { className: 'vsm-switch ' + (server.enabled ? 'on' : ''), onClick: () => onToggle(server), 'aria-label': server.enabled ? '禁用' : '启用' }, server.enabled ? '●' : '○'),
|
|
61
65
|
),
|
|
62
66
|
),
|
|
63
|
-
React.createElement('div', { className: 'vsm-mcp-meta' }, statusOf(server), ' · ', server.toolCount, ' 个工具 · ', server.transport
|
|
67
|
+
React.createElement('div', { className: 'vsm-mcp-meta' }, statusOf(server), ' · ', server.toolCount, ' 个工具 · ', server.transport,
|
|
68
|
+
server.instanceCount !== undefined ? ' · ' + server.instanceCount + ' 个会话实例(各自连接)' : ''),
|
|
64
69
|
server.error && React.createElement('div', { className: 'vsm-mcp-error' }, server.error),
|
|
65
70
|
React.createElement('div', { className: 'vsm-mcp-tools' }, server.tools.map((tool) => React.createElement('span', { key: tool.name, className: 'vsm-mcp-chip', title: tool.description || tool.name }, tool.name))),
|
|
66
71
|
)
|
|
@@ -128,8 +133,8 @@ function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove }) {
|
|
|
128
133
|
return React.createElement('section', { className: 'vsm-project' }, head, body)
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
/** 通用设置:选择当前对话文件链接的打开器 + 开发形态开关 +
|
|
132
|
-
function GeneralSettings({ registry }) {
|
|
136
|
+
/** 通用设置:选择当前对话文件链接的打开器 + 开发形态开关 + 编辑器布局(侧边栏最小宽度)+ 官方快捷键入口。 */
|
|
137
|
+
function GeneralSettings({ registry, getShortcuts }) {
|
|
133
138
|
const [tool, setTool] = React.useState(AUTO_OPEN_TOOL)
|
|
134
139
|
const [busy, setBusy] = React.useState(false)
|
|
135
140
|
const [error, setError] = React.useState('')
|
|
@@ -142,6 +147,7 @@ function GeneralSettings({ registry }) {
|
|
|
142
147
|
const [limitDraft, setLimitDraft] = React.useState(null) // 页签上限草稿(同上提交语义)
|
|
143
148
|
const [nativeSaved, setNativeSaved] = React.useState(DEFAULT_NATIVE_CSV) // 原生打开范围已保存值
|
|
144
149
|
const [nativeDraft, setNativeDraft] = React.useState(null) // 原生打开范围草稿(同上提交语义)
|
|
150
|
+
const [kbMessage, setKbMessage] = React.useState('') // 打开官方快捷键弹窗的失败说明(空 = 未失败)
|
|
145
151
|
const settings = React.useContext(SettingsContext)
|
|
146
152
|
const snapshot = settings?.getSnapshot?.()
|
|
147
153
|
const loading = !snapshot || snapshot.status === 'loading'
|
|
@@ -218,6 +224,14 @@ function GeneralSettings({ registry }) {
|
|
|
218
224
|
setDevMessage(result.restart ? '已切换为正式版,重启 DSH 后生效。' : '已关闭开发形态。')
|
|
219
225
|
}).catch((e) => setDevMessage('关闭失败:' + String(e))).finally(() => setDevBusy(false))
|
|
220
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* 唤起官方快捷键弹窗(键位统一归官方机制;入口不可用时展示手动打开方式)。
|
|
229
|
+
* @author ddj 2026年09月28号
|
|
230
|
+
*/
|
|
231
|
+
const openShortcuts = () => {
|
|
232
|
+
const probe = typeof getShortcuts === 'function' ? getShortcuts : () => null
|
|
233
|
+
setKbMessage(openOfficialShortcuts(probe()))
|
|
234
|
+
}
|
|
221
235
|
const options = [{ id: AUTO_OPEN_TOOL, label: '自动选择', description: '按当前已注册工具自动选择' }, ...availableOpeners(registry).map((item) => ({ id: item.id, label: item.label, description: item.description }))]
|
|
222
236
|
const currentOption = options.some((item) => item.id === tool) ? tool : AUTO_OPEN_TOOL
|
|
223
237
|
const devFormRow = devForm?.enabled ? React.createElement('div', { className: 'vsm-general-row' },
|
|
@@ -281,6 +295,16 @@ function GeneralSettings({ registry }) {
|
|
|
281
295
|
React.createElement('small', null, '逗号分隔;命中的文件用 DSH 原生预览打开,留空恢复默认(Office + 二进制,csv/tsv 需显式加入)')),
|
|
282
296
|
),
|
|
283
297
|
),
|
|
298
|
+
React.createElement('section', { className: 'vsm-panel' },
|
|
299
|
+
React.createElement('h3', { className: 'vsm-panel-title' }, '快捷键'),
|
|
300
|
+
React.createElement('div', { className: 'vsm-panel-body' },
|
|
301
|
+
React.createElement('label', { className: 'vsm-general-row' },
|
|
302
|
+
React.createElement('span', null, '键位配置',
|
|
303
|
+
React.createElement('small', { className: 'vsm-devform-note' },
|
|
304
|
+
'插件命令(edrv.*)与 DSH 官方命令的键位统一由官方快捷键机制管理:录键、冲突校验、按平台默认值、恢复默认都在官方弹窗内完成;本插件不再单独提供录键编辑器。')),
|
|
305
|
+
React.createElement('button', { onClick: openShortcuts }, '打开官方快捷键配置')),
|
|
306
|
+
kbMessage && React.createElement('small', { className: 'vsm-devform-msg' }, kbMessage)),
|
|
307
|
+
),
|
|
284
308
|
React.createElement(SvnSettingsSection, null),
|
|
285
309
|
React.createElement(IntegrationSettings, null),
|
|
286
310
|
)
|
|
@@ -493,6 +517,7 @@ function McpManagePanel({ servers, projects, busy, draft, edit, resetDraft, refr
|
|
|
493
517
|
export function McpSettings(props) {
|
|
494
518
|
const openerRegistry = props?.openerRegistry ?? { list: () => [] }
|
|
495
519
|
const compatSummary = props?.compatSummary ?? (() => [])
|
|
520
|
+
const getShortcuts = props?.getShortcuts ?? (() => null)
|
|
496
521
|
const [servers, setServers] = React.useState<MpcServer[]>([])
|
|
497
522
|
const [projects, setProjects] = React.useState<MpcProject[]>([])
|
|
498
523
|
const [loading, setLoading] = React.useState(true)
|
|
@@ -580,7 +605,7 @@ export function McpSettings(props) {
|
|
|
580
605
|
const resetDraft = () => setDraft(EMPTY)
|
|
581
606
|
let body
|
|
582
607
|
if (loading) body = React.createElement('div', { className: 'vsm-mcp-empty' }, '正在读取 MCP 服务…')
|
|
583
|
-
else if (tab === 'general') body = React.createElement(GeneralSettings, { registry: openerRegistry })
|
|
608
|
+
else if (tab === 'general') body = React.createElement(GeneralSettings, { registry: openerRegistry, getShortcuts })
|
|
584
609
|
else if (tab === 'mcp') body = React.createElement(McpManagePanel, { servers, projects, busy, draft, edit, resetDraft,
|
|
585
610
|
saveGlobal: (close) => saveGlobal(close), saveProject: (workspacePath, close) => saveProject(workspacePath, close),
|
|
586
611
|
refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject })
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* 作者 ddj 2026年09月09号 / 2026年09月10号
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react'
|
|
11
|
+
import { pickSessionScope } from '../sessionScope.js'
|
|
11
12
|
import { EditorView } from './EditorView.js'
|
|
12
13
|
import { setSideEditorMounted } from '../sidebarBridge.js'
|
|
13
14
|
import { editorMountEpoch, markEditorActive, markEditorMounted, parseOfficialFileAddress, resolveNavLine, resolveNavOpen } from '../officialSidebar.js'
|
|
@@ -48,8 +49,10 @@ export function OfficialSideTab(props) {
|
|
|
48
49
|
const schedule = props?.schedule
|
|
49
50
|
const decide = () => {
|
|
50
51
|
if (editorMountEpoch() !== epoch) return
|
|
51
|
-
const current = props?.
|
|
52
|
-
|
|
52
|
+
const current = typeof props?.readSessionScope === 'function'
|
|
53
|
+
? props.readSessionScope().sessionId
|
|
54
|
+
: pickSessionScope({ list: props?.sessions?.list?.getSnapshot?.() }).sessionId
|
|
55
|
+
markEditorActive(Boolean(current && current !== self))
|
|
53
56
|
}
|
|
54
57
|
if (typeof schedule === 'function') schedule(decide, 0)
|
|
55
58
|
else decide()
|