dsh-vscode-mode 0.1.62 → 0.2.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 +8 -0
- package/lib/client.js +1554 -87
- package/lib/client.js.map +1 -1
- package/lib/index.js +9 -2
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/commandBridge.ts +83 -0
- package/src/client/commandGlobals.ts +13 -0
- package/src/client/commandPaletteStore.ts +165 -0
- package/src/client/commandRegistry.ts +144 -0
- package/src/client/commandSearch.ts +89 -0
- package/src/client/editorModelState.ts +30 -0
- package/src/client/index.ts +55 -4
- package/src/client/keybindings.ts +71 -15
- package/src/client/monaco/loader.ts +3 -3
- package/src/client/monaco/theme.ts +313 -7
- package/src/client/officialSidebar.ts +45 -0
- package/src/client/outline/index.ts +4 -3
- package/src/client/sidebar/SidebarView.ts +16 -1
- package/src/client/sidebar/panels/FileExplorer.ts +58 -4
- package/src/client/sidebar/panels/index.ts +12 -10
- package/src/client/sidebar/types.ts +2 -2
- package/src/client/styles/editor.css +47 -28
- package/src/client/ui/ClaimRouter.ts +96 -0
- package/src/client/ui/CommandPalette.ts +158 -0
- package/src/client/ui/ConversationDiffDock.ts +17 -9
- package/src/client/ui/EditorView.ts +153 -15
- package/src/client/ui/KeybindingsPanel.ts +1 -1
- package/src/client/ui/OfficialSideTab.ts +8 -9
- package/src/client/ui/QuickOpen.ts +11 -3
- package/src/client/ui/commandCatalog.ts +179 -0
- package/src/client-primitives.d.ts +37 -0
- package/src/shared/keybindings.ts +10 -1
|
@@ -11,6 +11,7 @@ import { summarize } from '../state/records.js'
|
|
|
11
11
|
import { nextDiffPath } from '../diffDock.js'
|
|
12
12
|
import { readDiffDock, subscribeDiffDock } from '../diffDockStore.js'
|
|
13
13
|
import { DiffBox } from './DiffBox.js'
|
|
14
|
+
import { CommandPalette } from './CommandPalette.js'
|
|
14
15
|
|
|
15
16
|
const nextIndexBySession = new Map()
|
|
16
17
|
|
|
@@ -60,12 +61,17 @@ export function ConversationDiffDock(props) {
|
|
|
60
61
|
}, content)
|
|
61
62
|
|
|
62
63
|
const editorSnapshot = readDiffDock(sessionId)
|
|
64
|
+
// 命令栏浮层的兜底宿主:本 dock 是每会话常驻 slot(编辑区未挂载时仍能唤起命令栏);
|
|
65
|
+
// 与 EditorView 同时挂载时由 commandPaletteStore 的单实例认领保证只渲染一份。
|
|
66
|
+
const palette = React.createElement(CommandPalette, { key: 'edrv-palette', sessionId })
|
|
63
67
|
if (editorSnapshot) {
|
|
64
|
-
if (editorSnapshot.mode === 'editor-empty' && !editorSnapshot.fileTotal) return
|
|
65
|
-
return
|
|
68
|
+
if (editorSnapshot.mode === 'editor-empty' && !editorSnapshot.fileTotal) return palette
|
|
69
|
+
return React.createElement(React.Fragment, null,
|
|
70
|
+
renderDock(React.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId),
|
|
71
|
+
palette)
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
if (!sessionId || !summary?.pendingFiles?.length) return
|
|
74
|
+
if (!sessionId || !summary?.pendingFiles?.length) return palette
|
|
69
75
|
|
|
70
76
|
const paths = summary.pendingFiles.map((file) => file.path)
|
|
71
77
|
const click = () => {
|
|
@@ -75,10 +81,12 @@ export function ConversationDiffDock(props) {
|
|
|
75
81
|
if (next.path) openDiffView(next.path)
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
return React.createElement(React.Fragment, null,
|
|
85
|
+
renderDock(React.createElement(DiffBox, {
|
|
86
|
+
mode: 'chat',
|
|
87
|
+
dock: true,
|
|
88
|
+
fileTotal: paths.length,
|
|
89
|
+
onOpenNextFile: click,
|
|
90
|
+
}), sessionId || 'chat'),
|
|
91
|
+
palette)
|
|
84
92
|
}
|
|
@@ -14,11 +14,12 @@ import { langOf, loadMonaco } from '../monaco/loader.js'
|
|
|
14
14
|
import { dataUrlOf, isImagePath, isSvgPath } from '../imagePreview.js'
|
|
15
15
|
import { base64ToBytes, isPdfPath } from '../pdfPreview.js'
|
|
16
16
|
import { createPdfPanel } from '../pdf/pdfPanel.js'
|
|
17
|
-
import {
|
|
17
|
+
import { applyOfficial, registerThemes, themeNameOf } from '../monaco/theme.js'
|
|
18
18
|
import { createDiffRenderer } from '../monaco/diffRender.js'
|
|
19
19
|
import { ST, callIdAttr, noopHunk, summarize } from '../state/records.js'
|
|
20
20
|
import { diffRegions } from '../state/regions.js'
|
|
21
21
|
import { QuickOpen } from './QuickOpen.js'
|
|
22
|
+
import { CommandPalette } from './CommandPalette.js'
|
|
22
23
|
import { DiffLauncher } from './DiffLauncher.js'
|
|
23
24
|
import { SidebarView } from '../sidebar/SidebarView.js'
|
|
24
25
|
import { clearDiffDock, publishDiffDock } from '../diffDockStore.js'
|
|
@@ -130,6 +131,12 @@ export function EditorView(props) {
|
|
|
130
131
|
const navCursorTimerRef = React.useRef(null) // 光标位置防抖记录计时器
|
|
131
132
|
const navBackRef = React.useRef(null) // 后退动作最新闭包(窗口级键盘监听读取)
|
|
132
133
|
const navForwardRef = React.useRef(null) // 前进动作最新闭包(窗口级键盘监听读取)
|
|
134
|
+
const cycleTabRef = React.useRef(null) // 页签循环动作最新闭包(Ctrl+Alt+←/→、Ctrl+PgUp/PgDn)
|
|
135
|
+
const rowNavColRef = React.useRef(null) // 整行上下移动的期望列(连续移动保持列位)
|
|
136
|
+
const rowNavMoveRef = React.useRef(false) // 本次光标变化是否由整行移动触发(否则清空期望列)
|
|
137
|
+
const activeRef = React.useRef(null) // 当前活动文件的最新值(空依赖闭包/指令回调读取)
|
|
138
|
+
activeRef.current = active
|
|
139
|
+
const tabsHostRef = React.useRef(null) // 页签栏容器(切换后把当前页签滚入可见区)
|
|
133
140
|
const [navTick, setNavTick] = React.useState(0) // 历史可用性版本(按钮 disabled 重渲染)
|
|
134
141
|
const hoverRegionsRef = React.useRef([]) // 当前 pending 区域镜像(稳定回调读取)
|
|
135
142
|
const lineRegionMapRef = React.useRef(new Map()) // 行 → 区域 映射(hover 命中)
|
|
@@ -324,6 +331,23 @@ export function EditorView(props) {
|
|
|
324
331
|
})
|
|
325
332
|
}
|
|
326
333
|
|
|
334
|
+
/**
|
|
335
|
+
* 在已打开页签间循环切换(文件分页归编辑器自带页签栏)。
|
|
336
|
+
* @author ddj 2026年09月10号
|
|
337
|
+
* @param step 步进(+1 下一个 / -1 上一个)
|
|
338
|
+
*/
|
|
339
|
+
const cycleTab = (step) => {
|
|
340
|
+
if (tabs.length < 2) return
|
|
341
|
+
const idx = tabs.findIndex((t) => t.path === active)
|
|
342
|
+
const next = tabs[(idx + step + tabs.length) % tabs.length]
|
|
343
|
+
if (!next || next.path === active) return
|
|
344
|
+
flushSave()
|
|
345
|
+
saveViewState(active)
|
|
346
|
+
recordNav()
|
|
347
|
+
setActive(next.path)
|
|
348
|
+
}
|
|
349
|
+
cycleTabRef.current = cycleTab
|
|
350
|
+
|
|
327
351
|
/**
|
|
328
352
|
* 轮询去重:前后记录内容一致时返回原引用,React 跳过重渲染,
|
|
329
353
|
* 阻断 5s 轮询 → memo 链 → diff 重渲染/日志 的空转。
|
|
@@ -766,6 +790,93 @@ export function EditorView(props) {
|
|
|
766
790
|
return () => window.removeEventListener('keydown', onKey, true)
|
|
767
791
|
}, [])
|
|
768
792
|
|
|
793
|
+
// 页签循环切换:Ctrl+Alt+←/→(主候选,浏览器不占用)或 Ctrl+PgUp/PgDn(部分宿主可用)
|
|
794
|
+
React.useEffect(() => {
|
|
795
|
+
const onKey = (e) => {
|
|
796
|
+
const step = matchEvent(e, bindingsOf('edrv.nextTab')) ? 1
|
|
797
|
+
: matchEvent(e, bindingsOf('edrv.prevTab')) ? -1
|
|
798
|
+
: 0
|
|
799
|
+
if (step === 0) return
|
|
800
|
+
e.preventDefault(); e.stopPropagation()
|
|
801
|
+
if (cycleTabRef.current) cycleTabRef.current(step)
|
|
802
|
+
}
|
|
803
|
+
window.addEventListener('keydown', onKey, true)
|
|
804
|
+
return () => window.removeEventListener('keydown', onKey, true)
|
|
805
|
+
}, [])
|
|
806
|
+
|
|
807
|
+
// 整行上下移动的实现见下方「指令系统接线」effect(moveRow 单点定义,命令栏与键位共用)。
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 指令系统接线:命令栏/键位/第三方派发的 `edrv.command.*` 事件落到编辑器动作。
|
|
811
|
+
* 全部动作经最新闭包执行(与窗口级键位监听同源);事件名逐条字面书写,
|
|
812
|
+
* 便于与指令目录(ui/commandCatalog)静态对照(tests/commands.test.ts 有断言)。
|
|
813
|
+
* @author ddj 2026年09月10号
|
|
814
|
+
*/
|
|
815
|
+
React.useEffect(() => {
|
|
816
|
+
/** 整行上下移动:Monaco 内置 cursorUp/Down 会丢列位,故按期望列自行定位(连续移动保持同列)。 */
|
|
817
|
+
const moveRow = (step) => {
|
|
818
|
+
const ed = editorRef.current
|
|
819
|
+
const model = ed?.getModel?.()
|
|
820
|
+
const pos = ed?.getPosition?.()
|
|
821
|
+
if (!ed || !model || !pos) return
|
|
822
|
+
const col = rowNavColRef.current ?? pos.column
|
|
823
|
+
const line = Math.max(1, Math.min(model.getLineCount(), pos.lineNumber + step))
|
|
824
|
+
if (line === pos.lineNumber) return
|
|
825
|
+
const maxCol = model.getLineMaxColumn(line)
|
|
826
|
+
rowNavColRef.current = col
|
|
827
|
+
rowNavMoveRef.current = true
|
|
828
|
+
ed.setPosition({ lineNumber: line, column: Math.max(1, Math.min(col, maxCol)) })
|
|
829
|
+
ed.revealLineInCenterIfOutsideViewport?.(line)
|
|
830
|
+
}
|
|
831
|
+
const handlers = [
|
|
832
|
+
['edrv.command.save', () => { if (editorRef.current?.getModel?.()) { flushSave(); doSaveRef.current?.(false) } }],
|
|
833
|
+
// 快速打开由 QuickOpen 自己接该事件(它持有搜索框 ref),此处不重复实现
|
|
834
|
+
['edrv.command.toggleSidebar', () => setSidebarOn((v) => !v)],
|
|
835
|
+
['edrv.command.searchInFiles', () => {
|
|
836
|
+
setSidebarOn(true)
|
|
837
|
+
setActivePanel('search')
|
|
838
|
+
setTimeout(() => window.dispatchEvent(new CustomEvent('edrv:search-focus')), 0)
|
|
839
|
+
}],
|
|
840
|
+
['edrv.command.navigateBack', () => navBackRef.current?.()],
|
|
841
|
+
['edrv.command.navigateForward', () => navForwardRef.current?.()],
|
|
842
|
+
['edrv.command.nextTab', () => cycleTabRef.current?.(1)],
|
|
843
|
+
['edrv.command.prevTab', () => cycleTabRef.current?.(-1)],
|
|
844
|
+
['edrv.command.nextEditorRow', () => moveRow(1)],
|
|
845
|
+
['edrv.command.prevEditorRow', () => moveRow(-1)],
|
|
846
|
+
['edrv.command.goToDefinition', () => { const ed = editorRef.current; if (ed) void runGoToDefinition(ed) }],
|
|
847
|
+
['edrv.command.findReferences', () => { const ed = editorRef.current; if (ed) void runFindReferences(ed) }],
|
|
848
|
+
['edrv.command.triggerAi', () => editorRef.current?.trigger?.('edrv-ai', 'editor.action.inlineSuggest.trigger', null)],
|
|
849
|
+
['edrv.command.openInExplorer', () => {
|
|
850
|
+
const path = activeRef.current
|
|
851
|
+
if (!path) { setStatus('无活动文件'); return }
|
|
852
|
+
menuHandlersRef.current?.openInExplorer?.(path)
|
|
853
|
+
}],
|
|
854
|
+
]
|
|
855
|
+
const byName = new Map(handlers)
|
|
856
|
+
const onCommand = (event) => {
|
|
857
|
+
const action = byName.get(event.type)
|
|
858
|
+
if (!action) return
|
|
859
|
+
try { action() } catch (error) { dbg(sessionId, '指令 ' + event.type + ' 失败:' + String(error)) }
|
|
860
|
+
}
|
|
861
|
+
for (const [eventName] of handlers) window.addEventListener(eventName, onCommand)
|
|
862
|
+
return () => {
|
|
863
|
+
for (const [eventName] of handlers) window.removeEventListener(eventName, onCommand)
|
|
864
|
+
}
|
|
865
|
+
}, [sessionId])
|
|
866
|
+
|
|
867
|
+
// 活动页签滚动可见(页签栏溢出时键盘切换/打开文件后把当前页签带回视野):
|
|
868
|
+
// 只调整页签栏自身 scrollLeft,不触发页面滚动。
|
|
869
|
+
React.useEffect(() => {
|
|
870
|
+
const host = tabsHostRef.current
|
|
871
|
+
if (!host) return
|
|
872
|
+
const el = host.querySelector?.('.edrv-tab-active')
|
|
873
|
+
if (!el) return
|
|
874
|
+
const left = el.offsetLeft
|
|
875
|
+
const right = left + el.offsetWidth
|
|
876
|
+
if (left < host.scrollLeft) host.scrollLeft = left
|
|
877
|
+
else if (right > host.scrollLeft + host.clientWidth) host.scrollLeft = right - host.clientWidth
|
|
878
|
+
}, [active, tabs.length])
|
|
879
|
+
|
|
769
880
|
// 鼠标侧键后退/前进:Logitech 官方默认「后退/前进」= XButton 鼠标事件(button 3/4),不产生键盘事件。
|
|
770
881
|
// pointerdown 触发导航(preventDefault 后兼容 mousedown 可能不再触发,避免双触发);
|
|
771
882
|
// mousedown 兜底仅取消浏览器历史导航默认动作(MDN:preventDefault mousedown/pointerdown 可抑制)。
|
|
@@ -840,20 +951,25 @@ export function EditorView(props) {
|
|
|
840
951
|
return () => { alive = false }
|
|
841
952
|
}, [monaco, monacoErr])
|
|
842
953
|
|
|
843
|
-
//
|
|
954
|
+
// 主题:Monaco 就绪后注册现役双套(幂等)+ 应用跟随官方的令牌主题;
|
|
955
|
+
// 官方主题事件(edrv:theme-change,经 ctx.theme/属性观察广播)与系统明暗切换时跟随重刷。
|
|
844
956
|
React.useEffect(() => {
|
|
845
957
|
if (!monaco) return
|
|
846
958
|
registerThemes(monaco)
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
if (
|
|
852
|
-
query.addEventListener('change',
|
|
853
|
-
|
|
959
|
+
const apply = () => applyOfficial(monaco)
|
|
960
|
+
apply()
|
|
961
|
+
window.addEventListener('edrv:theme-change', apply)
|
|
962
|
+
const query = typeof window.matchMedia === 'function' ? window.matchMedia('(prefers-color-scheme: dark)') : null
|
|
963
|
+
if (query) {
|
|
964
|
+
if (typeof query.addEventListener === 'function') query.addEventListener('change', apply)
|
|
965
|
+
else query.addListener(apply)
|
|
966
|
+
}
|
|
967
|
+
return () => {
|
|
968
|
+
window.removeEventListener('edrv:theme-change', apply)
|
|
969
|
+
if (!query) return
|
|
970
|
+
if (typeof query.removeEventListener === 'function') query.removeEventListener('change', apply)
|
|
971
|
+
else query.removeListener(apply)
|
|
854
972
|
}
|
|
855
|
-
query.addListener(onChange)
|
|
856
|
-
return () => query.removeListener(onChange)
|
|
857
973
|
}, [monaco])
|
|
858
974
|
|
|
859
975
|
monacoRef.current = monaco
|
|
@@ -1014,6 +1130,9 @@ export function EditorView(props) {
|
|
|
1014
1130
|
})
|
|
1015
1131
|
ed.onDidChangeCursorPosition((e) => {
|
|
1016
1132
|
setCursor('Ln ' + e.position.lineNumber + ', Col ' + e.position.column)
|
|
1133
|
+
// 整行移动(↓↑)刚定位时保留期望列;其余光标移动(点击/打字/方向键)清空期望列
|
|
1134
|
+
if (rowNavMoveRef.current) rowNavMoveRef.current = false
|
|
1135
|
+
else rowNavColRef.current = null
|
|
1017
1136
|
// 导航历史:同文件内光标移动防抖记录(程序化恢复触发的位置与栈顶去重,无副作用)
|
|
1018
1137
|
if (navCursorTimerRef.current) clearTimeout(navCursorTimerRef.current)
|
|
1019
1138
|
navCursorTimerRef.current = setTimeout(() => {
|
|
@@ -1075,6 +1194,20 @@ export function EditorView(props) {
|
|
|
1075
1194
|
run: (edx) => { void runFindReferences(edx) },
|
|
1076
1195
|
})
|
|
1077
1196
|
ed.addCommand(m.KeyCode.F12, () => { void runGoToDefinition(ed) })
|
|
1197
|
+
// 整行上下移动 + 命令栏:只登记 Monaco 右键菜单入口(不绑键位)。
|
|
1198
|
+
// 键位由指令桥统一 capture 派发 `edrv.command.*`,此处再绑一次会双执行。
|
|
1199
|
+
ed.addAction({
|
|
1200
|
+
id: 'edrv.prevEditorRow', label: '上一编辑行', contextMenuGroupId: '1_edrv',
|
|
1201
|
+
run: () => window.dispatchEvent(new CustomEvent('edrv.command.prevEditorRow')),
|
|
1202
|
+
})
|
|
1203
|
+
ed.addAction({
|
|
1204
|
+
id: 'edrv.nextEditorRow', label: '下一编辑行', contextMenuGroupId: '1_edrv',
|
|
1205
|
+
run: () => window.dispatchEvent(new CustomEvent('edrv.command.nextEditorRow')),
|
|
1206
|
+
})
|
|
1207
|
+
ed.addAction({
|
|
1208
|
+
id: 'edrv.showCommands', label: '显示所有命令', contextMenuGroupId: '1_edrv',
|
|
1209
|
+
run: () => window.dispatchEvent(new CustomEvent('edrv.command.showCommands')),
|
|
1210
|
+
})
|
|
1078
1211
|
bindLspEditor(ed)
|
|
1079
1212
|
bindLspUnderline(ed, m)
|
|
1080
1213
|
// AI 补全:编辑器实例登记(差异静默判定用)+ Alt+\ 手动触发 ghost text
|
|
@@ -1470,7 +1603,7 @@ export function EditorView(props) {
|
|
|
1470
1603
|
}).catch((e) => { setStatus('打开失败'); setError('打开异常:' + String(e)) })
|
|
1471
1604
|
}
|
|
1472
1605
|
|
|
1473
|
-
const tabsEl = React.createElement('div', { className: 'edrv-tabs', style: { flex: '1 1 auto', minWidth: 0 } },
|
|
1606
|
+
const tabsEl = React.createElement('div', { className: 'edrv-tabs', ref: tabsHostRef, style: { flex: '1 1 auto', minWidth: 0 } },
|
|
1474
1607
|
tabs.map((t) => React.createElement('div', {
|
|
1475
1608
|
key: t.path,
|
|
1476
1609
|
className: 'edrv-tab' + (t.path === active ? ' edrv-tab-active' : ''),
|
|
@@ -1601,7 +1734,7 @@ export function EditorView(props) {
|
|
|
1601
1734
|
} else if (!active) {
|
|
1602
1735
|
body = React.createElement('div', { className: 'edrv-empty' },
|
|
1603
1736
|
React.createElement('div', null, '暂无打开的文件'),
|
|
1604
|
-
React.createElement('div', { style: { fontSize: 12 } }, '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开工作区文件;agent 修改文件后顶部会出现差异角标'))
|
|
1737
|
+
React.createElement('div', { style: { fontSize: 12 } }, '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开工作区文件;' + (chordOf('edrv.showCommands') ?? 'Ctrl+Shift+P') + ' 打开命令栏;agent 修改文件后顶部会出现差异角标'))
|
|
1605
1738
|
} else if (content === null && loadError) {
|
|
1606
1739
|
body = loadingBody('文件加载失败:' + loadError, 0, () => {
|
|
1607
1740
|
setLoadError(null)
|
|
@@ -1780,14 +1913,19 @@ export function EditorView(props) {
|
|
|
1780
1913
|
mainCol)
|
|
1781
1914
|
|
|
1782
1915
|
const baseStyle = { minHeight: 0, display: 'flex', flexDirection: 'column', background: 'var(--dsw-alias-bg-base,transparent)', overflow: 'hidden' }
|
|
1916
|
+
// 命令栏浮层(Ctrl+Shift+P / F1):portal 到 body,但需挂在插件自己的 React 树里;
|
|
1917
|
+
// 三种布局形态都渲染 EditorView,故这里挂载即可,多宿主由 store 单实例认领兜底。
|
|
1918
|
+
const paletteEl = React.createElement(CommandPalette, { key: 'edrv-palette', sessionId })
|
|
1783
1919
|
const rootEl = layout === 'side'
|
|
1784
1920
|
? React.createElement('div', { ref: viewRootRef, 'data-edrv-view': '1', 'data-edrv-layout': 'side', className: 'edrv-view-side', style: Object.assign({}, baseStyle, { height: '100%' }) },
|
|
1785
1921
|
editorRow,
|
|
1786
1922
|
menuBackdrop,
|
|
1787
|
-
tabMenuEl
|
|
1923
|
+
tabMenuEl,
|
|
1924
|
+
paletteEl)
|
|
1788
1925
|
: React.createElement('div', { ref: viewRootRef, 'data-edrv-view': '1', style: Object.assign({}, baseStyle, { height: 'var(--edrv-editor-height, 100%)', maxHeight: 'var(--edrv-editor-height, 100%)' }) },
|
|
1789
1926
|
editorRow,
|
|
1790
1927
|
menuBackdrop,
|
|
1791
|
-
tabMenuEl
|
|
1928
|
+
tabMenuEl,
|
|
1929
|
+
paletteEl)
|
|
1792
1930
|
return rootEl
|
|
1793
1931
|
}
|
|
@@ -174,7 +174,7 @@ export function KeybindingsPanel() {
|
|
|
174
174
|
|
|
175
175
|
return React.createElement('section', { className: 'vsm-general-page' },
|
|
176
176
|
React.createElement('h2', null, '快捷键'),
|
|
177
|
-
React.createElement('p', null, '
|
|
177
|
+
React.createElement('p', null, '配置编辑器视图的快捷键(命令栏 / 保存 / 快速打开 / 侧边栏 / 搜索 / 后退前进 / 页签与编辑行导航)。修改即时写入配置,重新聚焦编辑器后生效;命令栏(默认 Ctrl+Shift+P 或 F1)可搜索并执行下列全部命令。'),
|
|
178
178
|
unavailable && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, '设置服务暂不可用,当前使用默认键位。'),
|
|
179
179
|
error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
|
|
180
180
|
message && React.createElement('div', { className: 'vsm-kb-message' }, message),
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
|
-
* dsh-vscode-mode client — OfficialSideTab:DSH 官方右侧 Sidebar
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* EditorView 打开流;按会话 key 保证切会话干净重建。
|
|
3
|
+
* dsh-vscode-mode client — OfficialSideTab:DSH 官方右侧 Sidebar「文件编辑」页签正文包装。
|
|
4
|
+
* 用途:页类型 Tab(edrvEditor)经 navigation.params(openPath/focusDiff/line)打开;
|
|
5
|
+
* 兼作 file 认领转发(ClaimRouter)失败时的兜底正文(就地挂编辑器,旧行为)。
|
|
6
|
+
* 派发既有 edrv:open-editor 事件复用 EditorView 打开流;按会话 key 保证切会话干净重建。
|
|
8
7
|
* Tab 正文切换后重挂载时按最新导航恢复打开(对齐 better-sidebar 形态的种子行为)。
|
|
9
|
-
* 作者 ddj 2026年09月09号
|
|
8
|
+
* 作者 ddj 2026年09月09号 / 2026年09月10号
|
|
10
9
|
*/
|
|
11
10
|
import React from 'react'
|
|
12
11
|
import { EditorView } from './EditorView.js'
|
|
13
12
|
import { setSideEditorMounted } from '../sidebarBridge.js'
|
|
14
|
-
import { editorMountEpoch, markEditorMounted, parseOfficialFileAddress, resolveNavOpen } from '../officialSidebar.js'
|
|
13
|
+
import { editorMountEpoch, markEditorMounted, parseOfficialFileAddress, resolveNavLine, resolveNavOpen } from '../officialSidebar.js'
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* 官方侧边栏 Tab 正文组件(keyed slot sidebar.right.pane.tab 装配)。
|
|
@@ -71,10 +70,10 @@ export function OfficialSideTab(props) {
|
|
|
71
70
|
}))
|
|
72
71
|
return
|
|
73
72
|
}
|
|
74
|
-
// 页类型 Tab:params
|
|
73
|
+
// 页类型 Tab:params 携带打开请求(line = 行引用/工具行跳转的行号定位)
|
|
75
74
|
const request = resolveNavOpen(navigation?.params)
|
|
76
75
|
window.dispatchEvent(new CustomEvent('edrv:open-editor', {
|
|
77
|
-
detail: { path: request.path, focusDiff: request.focusDiff },
|
|
76
|
+
detail: { path: request.path, focusDiff: request.focusDiff, line: resolveNavLine(navigation?.params) },
|
|
78
77
|
}))
|
|
79
78
|
})
|
|
80
79
|
|
|
@@ -49,17 +49,25 @@ export function QuickOpen(props) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
React.useEffect(() => {
|
|
52
|
+
const openBox = () => {
|
|
53
|
+
inputRef.current?.focus?.()
|
|
54
|
+
setOpen(true)
|
|
55
|
+
}
|
|
52
56
|
const onKey = (e) => {
|
|
53
57
|
if (matchEvent(e, bindingsOf('edrv.quickOpen'))) {
|
|
54
58
|
e.preventDefault(); e.stopPropagation()
|
|
55
|
-
|
|
56
|
-
setOpen(true)
|
|
59
|
+
openBox()
|
|
57
60
|
} else if (e.key === 'Escape') {
|
|
58
61
|
setOpen(false); inputRef.current?.blur?.()
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
window.addEventListener('keydown', onKey, true)
|
|
62
|
-
|
|
65
|
+
// 指令系统入口(命令栏「快速打开文件」):与键位复用同一动作
|
|
66
|
+
window.addEventListener('edrv.command.quickOpen', openBox)
|
|
67
|
+
return () => {
|
|
68
|
+
window.removeEventListener('keydown', onKey, true)
|
|
69
|
+
window.removeEventListener('edrv.command.quickOpen', openBox)
|
|
70
|
+
}
|
|
63
71
|
}, [])
|
|
64
72
|
|
|
65
73
|
const dirText = (p) => {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 编辑器指令目录(纯元数据)。
|
|
3
|
+
* 「一条命令 = 一条注册数据」:命令栏、快捷键设置页、快捷键监听与 Monaco 右键菜单
|
|
4
|
+
* 全部从本表读取;新增一条能力只需在 EDITOR_COMMANDS 追加一项(可选 keybinding)。
|
|
5
|
+
* run 只派发 `edrv.command.*` 窗口事件,不直接触碰 React/Monaco(`edrv.command.` 前缀
|
|
6
|
+
* 与既有 `edrv:` 刷新/主题事件分属不同命名空间,互不干扰)。
|
|
7
|
+
* 作者 ddj 2026年09月10号
|
|
8
|
+
*/
|
|
9
|
+
import { hasEditorModel } from '../editorModelState.js'
|
|
10
|
+
|
|
11
|
+
/** 一条编辑器指令(展示 + 执行 + 可用性)。 */
|
|
12
|
+
export interface CommandDef {
|
|
13
|
+
/** 稳定命令 id(`edrv.` 前缀;第三方注册请避让该前缀)。 */
|
|
14
|
+
id: string
|
|
15
|
+
/** 命令栏与设置页展示名。 */
|
|
16
|
+
label: string
|
|
17
|
+
/** 命令栏分组名。 */
|
|
18
|
+
category: string
|
|
19
|
+
/** 组内排序(小者优先)。 */
|
|
20
|
+
order?: number
|
|
21
|
+
/** 默认键位弦(可选;可含 `|` 多候选)。声明后由快捷键设置页展示与录制。 */
|
|
22
|
+
keybinding?: string
|
|
23
|
+
/** 运行体:派发窗口事件或直接执行。 */
|
|
24
|
+
run: () => void
|
|
25
|
+
/** 可用性判定(缺省视为始终可用;返回 false 时命令栏隐藏且 run 被拒)。 */
|
|
26
|
+
available?: () => boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 派发编辑器命令事件(`edrv.command.<action>`)。
|
|
31
|
+
* 无 window 的运行环境(纯 Node 单测)静默跳过,命令本身不因此失败。
|
|
32
|
+
* @author ddj 2026年09月10号
|
|
33
|
+
* @param action 动作名(不含前缀)
|
|
34
|
+
*/
|
|
35
|
+
function emit(action: string): void {
|
|
36
|
+
if (typeof window === 'undefined') return
|
|
37
|
+
window.dispatchEvent(new CustomEvent('edrv.command.' + action))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 编辑器命令事件名前缀(EditorView / QuickOpen / 第三方据此监听)。 */
|
|
41
|
+
export const COMMAND_EVENT_PREFIX = 'edrv.command.'
|
|
42
|
+
|
|
43
|
+
/** 需要活动编辑器模型才可用的命令(命令栏隐藏并拒绝执行)。 */
|
|
44
|
+
function needsModel(): boolean {
|
|
45
|
+
return hasEditorModel()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 面板区显隐切换始终可用(未挂载编辑器时 run 为空操作)。 */
|
|
49
|
+
function alwaysAvailable(): boolean {
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 光标整行下移(编辑行导航)。
|
|
55
|
+
* @author ddj 2026年09月10号
|
|
56
|
+
* @returns 命令定义
|
|
57
|
+
*/
|
|
58
|
+
function nextEditorRowDef(): CommandDef {
|
|
59
|
+
return {
|
|
60
|
+
id: 'edrv.nextEditorRow', label: '下一编辑行(光标整行下移)', category: '导航', order: 50,
|
|
61
|
+
keybinding: 'Ctrl+Alt+ArrowDown', available: needsModel,
|
|
62
|
+
run: () => emit('nextEditorRow'),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 光标整行上移(编辑行导航)。
|
|
68
|
+
* @author ddj 2026年09月10号
|
|
69
|
+
* @returns 命令定义
|
|
70
|
+
*/
|
|
71
|
+
function prevEditorRowDef(): CommandDef {
|
|
72
|
+
return {
|
|
73
|
+
id: 'edrv.prevEditorRow', label: '上一编辑行(光标整行上移)', category: '导航', order: 60,
|
|
74
|
+
keybinding: 'Ctrl+Alt+ArrowUp', available: needsModel,
|
|
75
|
+
run: () => emit('prevEditorRow'),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 编辑器内置指令目录(顺序 = 快捷键设置页展示顺序)。
|
|
81
|
+
* 前置 8 条的键位由 EditorView / QuickOpen 自行 capture 监听(历史实现),
|
|
82
|
+
* 故不进 BRIDGE_COMMANDS,避免同一按键双执行。
|
|
83
|
+
* @author ddj 2026年09月10号
|
|
84
|
+
*/
|
|
85
|
+
export const EDITOR_COMMANDS: readonly CommandDef[] = [
|
|
86
|
+
{
|
|
87
|
+
id: 'edrv.save', label: '保存文件', category: '文件', order: 10,
|
|
88
|
+
keybinding: 'Ctrl+S', available: needsModel,
|
|
89
|
+
run: () => emit('save'),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'edrv.quickOpen', label: '快速打开文件', category: '文件', order: 20,
|
|
93
|
+
keybinding: 'Ctrl+P', available: alwaysAvailable,
|
|
94
|
+
run: () => emit('quickOpen'),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'edrv.toggleSidebar', label: '切换侧边栏', category: '视图', order: 10,
|
|
98
|
+
keybinding: 'Ctrl+B', available: alwaysAvailable,
|
|
99
|
+
run: () => emit('toggleSidebar'),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'edrv.searchInFiles', label: '在工作区中搜索', category: '视图', order: 20,
|
|
103
|
+
keybinding: 'Ctrl+Shift+F', available: alwaysAvailable,
|
|
104
|
+
run: () => emit('searchInFiles'),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'edrv.navigateBack', label: '后退(导航历史)', category: '导航', order: 10,
|
|
108
|
+
keybinding: 'Alt+ArrowLeft|Ctrl+Alt+-', available: needsModel,
|
|
109
|
+
run: () => emit('navigateBack'),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'edrv.navigateForward', label: '前进(导航历史)', category: '导航', order: 20,
|
|
113
|
+
keybinding: 'Alt+ArrowRight|Ctrl+Shift+-', available: needsModel,
|
|
114
|
+
run: () => emit('navigateForward'),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'edrv.nextTab', label: '下一个页签', category: '导航', order: 30,
|
|
118
|
+
keybinding: 'Ctrl+Alt+ArrowRight|Ctrl+PageDown', available: needsModel,
|
|
119
|
+
run: () => emit('nextTab'),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'edrv.prevTab', label: '上一个页签', category: '导航', order: 40,
|
|
123
|
+
keybinding: 'Ctrl+Alt+ArrowLeft|Ctrl+PageUp', available: needsModel,
|
|
124
|
+
run: () => emit('prevTab'),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'edrv.goToDefinition', label: '转到定义', category: '语言智能', order: 10,
|
|
128
|
+
available: needsModel,
|
|
129
|
+
run: () => emit('goToDefinition'),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'edrv.findReferences', label: '查找所有引用', category: '语言智能', order: 20,
|
|
133
|
+
available: needsModel,
|
|
134
|
+
run: () => emit('findReferences'),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'edrv.triggerAi', label: '触发 AI 内联补全', category: '语言智能', order: 30,
|
|
138
|
+
available: needsModel,
|
|
139
|
+
run: () => emit('triggerAi'),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'edrv.openInExplorer', label: '在文件浏览器中打开', category: '文件', order: 30,
|
|
143
|
+
available: needsModel,
|
|
144
|
+
run: () => emit('openInExplorer'),
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 桥接派发指令(无原生监听,键位由 commandBridge 统一 capture 处理)。
|
|
150
|
+
* 新增「只填目录、不写监听」的编辑器指令一律放这里:键位、命令栏、设置页自动可用。
|
|
151
|
+
* @author ddj 2026年09月10号
|
|
152
|
+
*/
|
|
153
|
+
export const BRIDGE_COMMANDS: readonly CommandDef[] = [
|
|
154
|
+
nextEditorRowDef(),
|
|
155
|
+
prevEditorRowDef(),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 命令栏自身的命令(避免循环依赖,由 commandBridge 注入 run 后注册)。
|
|
160
|
+
* @author ddj 2026年09月10号
|
|
161
|
+
* @param run 打开命令栏
|
|
162
|
+
* @returns 命令定义
|
|
163
|
+
*/
|
|
164
|
+
export function showCommandsDef(run: () => void): CommandDef {
|
|
165
|
+
return {
|
|
166
|
+
id: 'edrv.showCommands', label: '显示所有命令', category: '视图', order: 1,
|
|
167
|
+
keybinding: 'Ctrl+Shift+P|F1', available: alwaysAvailable, run,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 需要全局键位派发的指令(桥接类 + 命令栏自身;不含 EditorView/QuickOpen 原生监听的那些)。
|
|
173
|
+
* @author ddj 2026年09月10号
|
|
174
|
+
* @param showCommands 命令栏命令定义
|
|
175
|
+
* @returns 派发集合
|
|
176
|
+
*/
|
|
177
|
+
export function dispatchedCommands(showCommands: CommandDef): readonly CommandDef[] {
|
|
178
|
+
return [...BRIDGE_COMMANDS, showCommands]
|
|
179
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 官方 UI 原语虚拟模块的最小类型垫片(@deepseek-ai/dsh-client-ui-primitives)。
|
|
3
|
+
*
|
|
4
|
+
* 该模块由 DSH web 前端的 loader 在运行时提供(磁盘上没有对应包目录,官方 40+ 个
|
|
5
|
+
* 客户端包同样以 require 方式消费),本垫片只声明本插件用到的成员,供 tsc 类型检查;
|
|
6
|
+
* 运行时取值为 loader 注入实例。完整成员清单见官方前端产物导出表。
|
|
7
|
+
* 作者 ddj 2026-09-10
|
|
8
|
+
*/
|
|
9
|
+
declare module '@deepseek-ai/dsh-client-ui-primitives' {
|
|
10
|
+
/** 图标组件通用 props(官方 Icon* 组件与 FileTypeIcon 共用形状)。 */
|
|
11
|
+
export interface EdrvIconProps {
|
|
12
|
+
/** 图标边长(px);省略时用图标自带尺寸。 */
|
|
13
|
+
size?: number
|
|
14
|
+
className?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 按文件名(或扩展名)分类的官方文件类型判别符(未知类型返回 'other')。 */
|
|
18
|
+
export function classifyFileType(name: string): string
|
|
19
|
+
|
|
20
|
+
/** 官方文件类型图标:按 classifyFileType 的 kind 渲染(跟随官方主题配色)。 */
|
|
21
|
+
export const FileTypeIcon: (props: EdrvIconProps & { kind: string }) => unknown
|
|
22
|
+
|
|
23
|
+
/** 目录闭合图标(官方文件树同款)。 */
|
|
24
|
+
export const IconFolderClose16: (props: EdrvIconProps) => unknown
|
|
25
|
+
/** 目录展开图标(官方文件树同款)。 */
|
|
26
|
+
export const IconFolderOpen16: (props: EdrvIconProps) => unknown
|
|
27
|
+
/** 目录轮廓图标(活动栏「文件管理」)。 */
|
|
28
|
+
export const IconFolderOpenOutline16: (props: EdrvIconProps) => unknown
|
|
29
|
+
/** 刷新图标(面板工具条)。 */
|
|
30
|
+
export const IconRefreshOutline16: (props: EdrvIconProps) => unknown
|
|
31
|
+
/** 搜索图标(活动栏「搜索」)。 */
|
|
32
|
+
export const IconSearchOutline16: (props: EdrvIconProps) => unknown
|
|
33
|
+
/** 列表+笔图标(活动栏「规则」)。 */
|
|
34
|
+
export const IconListPenOutline16: (props: EdrvIconProps) => unknown
|
|
35
|
+
/** 代码图标(活动栏「大纲」)。 */
|
|
36
|
+
export const IconCodeOutline16: (props: EdrvIconProps) => unknown
|
|
37
|
+
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* 纯数据模块:host(settings schema 默认值)与 client(执行匹配/设置页)共用。
|
|
4
4
|
* 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 = 未绑定;
|
|
5
5
|
* `|` 连接多个候选(任一命中即触发),如 `Alt+ArrowLeft|Ctrl+Alt+-`。
|
|
6
|
-
*
|
|
6
|
+
* 约束:本模块被 host 与 client 两半共用,**禁止 import client 侧模块**(React/浏览器 API)。
|
|
7
|
+
* 与 client/ui/commandCatalog 的一致性由 tests/commands.test.ts 断言兜底。
|
|
8
|
+
* 作者 ddj 2026年08月26号 / 2026年09月10号
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
/** 命令 id → 默认键位(可为多候选)。命令目录以此为准,新增命令只需加一项。 */
|
|
@@ -14,6 +16,13 @@ export const KEYBINDING_DEFAULTS: Record<string, string> = {
|
|
|
14
16
|
'edrv.searchInFiles': 'Ctrl+Shift+F',
|
|
15
17
|
'edrv.navigateBack': 'Alt+ArrowLeft|Ctrl+Alt+-',
|
|
16
18
|
'edrv.navigateForward': 'Alt+ArrowRight|Ctrl+Shift+-',
|
|
19
|
+
// 页签循环:主候选避开浏览器保留键(Ctrl+Tab / Ctrl+PgUp/PgDn 会被浏览器截获)
|
|
20
|
+
'edrv.nextTab': 'Ctrl+Alt+ArrowRight|Ctrl+PageDown',
|
|
21
|
+
'edrv.prevTab': 'Ctrl+Alt+ArrowLeft|Ctrl+PageUp',
|
|
22
|
+
// 命令栏(Ctrl+Shift+P 主候选;F1 为 VS Code 同款第二候选)与编辑行导航
|
|
23
|
+
'edrv.showCommands': 'Ctrl+Shift+P|F1',
|
|
24
|
+
'edrv.nextEditorRow': 'Ctrl+Alt+ArrowDown',
|
|
25
|
+
'edrv.prevEditorRow': 'Ctrl+Alt+ArrowUp',
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
/**
|