dsh-vscode-mode 0.1.43 → 0.1.45
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 +3 -1
- package/lib/client.js +418 -92
- package/lib/client.js.map +1 -1
- package/lib/index.js +6 -3
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/keybindings.ts +41 -15
- package/src/client/monaco/lsp/providers.ts +129 -62
- package/src/client/navHistory.ts +104 -0
- package/src/client/styles/editor.css +2 -6
- package/src/client/ui/EditorView.ts +200 -10
- package/src/client/ui/KeybindingsPanel.ts +3 -3
- package/src/client/ui/QuickOpen.ts +2 -2
- package/src/shared/keybindings.ts +5 -2
|
@@ -22,7 +22,8 @@ import { editorHeight } from '../editorLayout.js'
|
|
|
22
22
|
import { revealInExplorer as revealPathInExplorer } from '../fileReveal.js'
|
|
23
23
|
import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
|
|
24
24
|
import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
|
|
25
|
-
import {
|
|
25
|
+
import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
|
|
26
|
+
import { createNavHistory } from '../navHistory.js'
|
|
26
27
|
import { CACHE_KEY } from '../paths.js'
|
|
27
28
|
import { bindLspEditor, runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
|
|
28
29
|
import { onLspProgress, refreshStatus } from '../monaco/lsp/index.js'
|
|
@@ -82,6 +83,14 @@ export function EditorView(props) {
|
|
|
82
83
|
const programmaticRef = React.useRef(false)
|
|
83
84
|
const bootRef = React.useRef(false)
|
|
84
85
|
const pendingFocusRef = React.useRef(null) // { path, region } 内容加载后跳转
|
|
86
|
+
// 导航历史(后退/前进):跨文件焦点位置;按会话隔离,会话切换重置
|
|
87
|
+
const navRef = React.useRef(null)
|
|
88
|
+
if (!navRef.current) navRef.current = createNavHistory()
|
|
89
|
+
const navPendingRef = React.useRef(null) // { path,line,column,viewState } 待恢复条目(内容就绪后消费)
|
|
90
|
+
const navCursorTimerRef = React.useRef(null) // 光标位置防抖记录计时器
|
|
91
|
+
const navBackRef = React.useRef(null) // 后退动作最新闭包(窗口级键盘监听读取)
|
|
92
|
+
const navForwardRef = React.useRef(null) // 前进动作最新闭包(窗口级键盘监听读取)
|
|
93
|
+
const [navTick, setNavTick] = React.useState(0) // 历史可用性版本(按钮 disabled 重渲染)
|
|
85
94
|
const hoverRegionsRef = React.useRef([]) // 当前 pending 区域镜像(稳定回调读取)
|
|
86
95
|
const lineRegionMapRef = React.useRef(new Map()) // 行 → 区域 映射(hover 命中)
|
|
87
96
|
const hoverKeyRef = React.useRef(null) // 当前 hover 区域 key(区域不变不重渲染)
|
|
@@ -164,9 +173,97 @@ export function EditorView(props) {
|
|
|
164
173
|
try { ed.restoreViewState(saved) } catch (e) { /* 恢复失败忽略(行号越界自动兜底) */ }
|
|
165
174
|
}
|
|
166
175
|
|
|
176
|
+
/**
|
|
177
|
+
* 采集当前焦点条目:路径从编辑器 model 实时读取(避免空依赖闭包读到过期 active),
|
|
178
|
+
* 位置/viewState 从编辑器读取;编辑器或模型未就绪 → null。
|
|
179
|
+
* @author ddj 2026年09月04号
|
|
180
|
+
* @returns 导航历史条目或 null
|
|
181
|
+
*/
|
|
182
|
+
const captureNavEntry = () => {
|
|
183
|
+
const ed = editorRef.current
|
|
184
|
+
const model = ed?.getModel?.()
|
|
185
|
+
const uriPath = model?.uri?.path
|
|
186
|
+
if (!ed || !uriPath) return null
|
|
187
|
+
let line = null
|
|
188
|
+
let column = null
|
|
189
|
+
try {
|
|
190
|
+
const pos = ed.getPosition?.()
|
|
191
|
+
if (pos) { line = pos.lineNumber; column = pos.column }
|
|
192
|
+
} catch (e) { /* 位置读取失败降级为仅路径 */ }
|
|
193
|
+
let viewState = null
|
|
194
|
+
try { viewState = ed.saveViewState() } catch (e) { /* 快照失败忽略 */ }
|
|
195
|
+
return { path: decodeURIComponent(String(uriPath).replace(/^\//, '')), line, column, viewState }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 记录当前位置到导航历史(同位置去重由历史模块处理;可用性变化时刷新按钮)。
|
|
200
|
+
* @author ddj 2026年09月04号
|
|
201
|
+
*/
|
|
202
|
+
const recordNav = () => {
|
|
203
|
+
const entry = captureNavEntry()
|
|
204
|
+
if (!entry) return
|
|
205
|
+
const wasBack = navRef.current.canBack()
|
|
206
|
+
const wasForward = navRef.current.canForward()
|
|
207
|
+
navRef.current.record(entry)
|
|
208
|
+
if (navRef.current.canBack() !== wasBack || navRef.current.canForward() !== wasForward) {
|
|
209
|
+
setNavTick((v) => v + 1)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 跳到历史条目:保存当前视图状态后打开目标文件,内容就绪后恢复焦点。
|
|
215
|
+
* @author ddj 2026年09月04号
|
|
216
|
+
* @param entry 目标历史条目
|
|
217
|
+
*/
|
|
218
|
+
const navigateTo = (entry) => {
|
|
219
|
+
if (!entry || !entry.path) return
|
|
220
|
+
flushSave()
|
|
221
|
+
saveViewState(active)
|
|
222
|
+
navPendingRef.current = entry
|
|
223
|
+
addTab(entry.path, true)
|
|
224
|
+
setFocusRequest((value) => value + 1) // 目标已是活动文件时也触发恢复
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 立即落盘待记录的光标位置(后退/前进前调用:<500ms 内刚移动的光标也算入历史)。
|
|
229
|
+
* @author ddj 2026年09月04号
|
|
230
|
+
*/
|
|
231
|
+
const flushNavCursor = () => {
|
|
232
|
+
if (!navCursorTimerRef.current) return
|
|
233
|
+
clearTimeout(navCursorTimerRef.current)
|
|
234
|
+
navCursorTimerRef.current = null
|
|
235
|
+
recordNav()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 后退:恢复上一处焦点(跨文件)。
|
|
240
|
+
* @author ddj 2026年09月04号
|
|
241
|
+
*/
|
|
242
|
+
const navBack = () => {
|
|
243
|
+
flushNavCursor()
|
|
244
|
+
const entry = navRef.current.back()
|
|
245
|
+
if (!entry) return
|
|
246
|
+
navigateTo(entry)
|
|
247
|
+
setNavTick((v) => v + 1)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 前进:恢复下一处焦点(跨文件)。
|
|
252
|
+
* @author ddj 2026年09月04号
|
|
253
|
+
*/
|
|
254
|
+
const navForward = () => {
|
|
255
|
+
flushNavCursor()
|
|
256
|
+
const entry = navRef.current.forward()
|
|
257
|
+
if (!entry) return
|
|
258
|
+
navigateTo(entry)
|
|
259
|
+
setNavTick((v) => v + 1)
|
|
260
|
+
}
|
|
261
|
+
navBackRef.current = navBack
|
|
262
|
+
navForwardRef.current = navForward
|
|
263
|
+
|
|
167
264
|
const closeTab = (path) => {
|
|
168
265
|
flushSave()
|
|
169
|
-
if (path === active) saveViewState(path)
|
|
266
|
+
if (path === active) { saveViewState(path); recordNav() }
|
|
170
267
|
setTabs((prev) => {
|
|
171
268
|
const idx = prev.findIndex((t) => t.path === path)
|
|
172
269
|
if (idx < 0) return prev
|
|
@@ -284,6 +381,7 @@ export function EditorView(props) {
|
|
|
284
381
|
const p = e?.detail?.path
|
|
285
382
|
if (!p) return
|
|
286
383
|
if (e?.detail?.focusDiff === true) {
|
|
384
|
+
recordNav()
|
|
287
385
|
pendingFocusRef.current = { path: p, region: null }
|
|
288
386
|
setFocusRequest((value) => value + 1)
|
|
289
387
|
addTab(p, true)
|
|
@@ -294,6 +392,7 @@ export function EditorView(props) {
|
|
|
294
392
|
openFileAt(p, e?.detail?.line, e?.detail?.column)
|
|
295
393
|
return
|
|
296
394
|
}
|
|
395
|
+
recordNav()
|
|
297
396
|
addTab(p, true)
|
|
298
397
|
}
|
|
299
398
|
const onShowLauncher = (event) => {
|
|
@@ -392,6 +491,14 @@ export function EditorView(props) {
|
|
|
392
491
|
} catch (e) { /* 损坏忽略 */ }
|
|
393
492
|
}, [sessionId])
|
|
394
493
|
|
|
494
|
+
// 导航历史会话隔离:切换会话时重置(历史仅会话内存,不跨会话串扰)
|
|
495
|
+
React.useEffect(() => {
|
|
496
|
+
navRef.current = createNavHistory()
|
|
497
|
+
navPendingRef.current = null
|
|
498
|
+
if (navCursorTimerRef.current) { clearTimeout(navCursorTimerRef.current); navCursorTimerRef.current = null }
|
|
499
|
+
setNavTick((v) => v + 1)
|
|
500
|
+
}, [sessionId])
|
|
501
|
+
|
|
395
502
|
React.useEffect(() => {
|
|
396
503
|
if (!sessionId) return
|
|
397
504
|
try { localStorage.setItem(CACHE_KEY.editor + String(sessionId), JSON.stringify({ tabs: tabs.map((t) => t.path), active })) }
|
|
@@ -423,7 +530,7 @@ export function EditorView(props) {
|
|
|
423
530
|
// 切换侧边栏(capture 抢占,避免与 DSH 全局冲突;键位随快捷键配置)
|
|
424
531
|
React.useEffect(() => {
|
|
425
532
|
const onKey = (e) => {
|
|
426
|
-
if (!matchEvent(e,
|
|
533
|
+
if (!matchEvent(e, bindingsOf('edrv.toggleSidebar'))) return
|
|
427
534
|
e.preventDefault(); e.stopPropagation()
|
|
428
535
|
setSidebarOn((v) => !v)
|
|
429
536
|
}
|
|
@@ -434,7 +541,7 @@ export function EditorView(props) {
|
|
|
434
541
|
// 保存文件(窗口级):编辑器有活动模型才拦截执行,无文件时放行(浏览器默认行为)
|
|
435
542
|
React.useEffect(() => {
|
|
436
543
|
const onKey = (e) => {
|
|
437
|
-
if (!matchEvent(e,
|
|
544
|
+
if (!matchEvent(e, bindingsOf('edrv.save'))) return
|
|
438
545
|
if (!editorRef.current?.getModel?.()) return
|
|
439
546
|
e.preventDefault(); e.stopPropagation()
|
|
440
547
|
flushSave()
|
|
@@ -447,7 +554,7 @@ export function EditorView(props) {
|
|
|
447
554
|
// Ctrl+Shift+F 全局搜索:展开侧边栏 + 激活搜索页签,随后聚焦搜索输入框
|
|
448
555
|
React.useEffect(() => {
|
|
449
556
|
const onKey = (e) => {
|
|
450
|
-
if (!matchEvent(e,
|
|
557
|
+
if (!matchEvent(e, bindingsOf('edrv.searchInFiles'))) return
|
|
451
558
|
e.preventDefault(); e.stopPropagation()
|
|
452
559
|
setSidebarOn(true)
|
|
453
560
|
setActivePanel('search')
|
|
@@ -457,6 +564,49 @@ export function EditorView(props) {
|
|
|
457
564
|
return () => window.removeEventListener('keydown', onKey, true)
|
|
458
565
|
}, [])
|
|
459
566
|
|
|
567
|
+
// 后退/前进:导航历史(跨文件焦点位置;经 ref 读最新闭包,避免空依赖过期)
|
|
568
|
+
React.useEffect(() => {
|
|
569
|
+
const onKey = (e) => {
|
|
570
|
+
if (!matchEvent(e, bindingsOf('edrv.navigateBack'))) return
|
|
571
|
+
e.preventDefault(); e.stopPropagation()
|
|
572
|
+
if (navBackRef.current) navBackRef.current()
|
|
573
|
+
}
|
|
574
|
+
window.addEventListener('keydown', onKey, true)
|
|
575
|
+
return () => window.removeEventListener('keydown', onKey, true)
|
|
576
|
+
}, [])
|
|
577
|
+
React.useEffect(() => {
|
|
578
|
+
const onKey = (e) => {
|
|
579
|
+
if (!matchEvent(e, bindingsOf('edrv.navigateForward'))) return
|
|
580
|
+
e.preventDefault(); e.stopPropagation()
|
|
581
|
+
if (navForwardRef.current) navForwardRef.current()
|
|
582
|
+
}
|
|
583
|
+
window.addEventListener('keydown', onKey, true)
|
|
584
|
+
return () => window.removeEventListener('keydown', onKey, true)
|
|
585
|
+
}, [])
|
|
586
|
+
|
|
587
|
+
// 鼠标侧键后退/前进:Logitech 官方默认「后退/前进」= XButton 鼠标事件(button 3/4),不产生键盘事件。
|
|
588
|
+
// pointerdown 触发导航(preventDefault 后兼容 mousedown 可能不再触发,避免双触发);
|
|
589
|
+
// mousedown 兜底仅取消浏览器历史导航默认动作(MDN:preventDefault mousedown/pointerdown 可抑制)。
|
|
590
|
+
// @author ddj 2026年09月02号
|
|
591
|
+
React.useEffect(() => {
|
|
592
|
+
const onPointer = (e) => {
|
|
593
|
+
if (e?.button !== 3 && e?.button !== 4) return
|
|
594
|
+
e.preventDefault(); e.stopPropagation()
|
|
595
|
+
const ref = e.button === 3 ? navBackRef : navForwardRef
|
|
596
|
+
if (ref.current) ref.current()
|
|
597
|
+
}
|
|
598
|
+
const onMouse = (e) => {
|
|
599
|
+
if (e?.button !== 3 && e?.button !== 4) return
|
|
600
|
+
e.preventDefault(); e.stopPropagation()
|
|
601
|
+
}
|
|
602
|
+
window.addEventListener('pointerdown', onPointer, true)
|
|
603
|
+
window.addEventListener('mousedown', onMouse, true)
|
|
604
|
+
return () => {
|
|
605
|
+
window.removeEventListener('pointerdown', onPointer, true)
|
|
606
|
+
window.removeEventListener('mousedown', onMouse, true)
|
|
607
|
+
}
|
|
608
|
+
}, [])
|
|
609
|
+
|
|
460
610
|
// 文件 → 待处理差异数 映射(侧边栏角标)
|
|
461
611
|
const pendingByPath = React.useMemo(() => {
|
|
462
612
|
const out = {}
|
|
@@ -629,6 +779,12 @@ export function EditorView(props) {
|
|
|
629
779
|
})
|
|
630
780
|
ed.onDidChangeCursorPosition((e) => {
|
|
631
781
|
setCursor('Ln ' + e.position.lineNumber + ', Col ' + e.position.column)
|
|
782
|
+
// 导航历史:同文件内光标移动防抖记录(程序化恢复触发的位置与栈顶去重,无副作用)
|
|
783
|
+
if (navCursorTimerRef.current) clearTimeout(navCursorTimerRef.current)
|
|
784
|
+
navCursorTimerRef.current = setTimeout(() => {
|
|
785
|
+
navCursorTimerRef.current = null
|
|
786
|
+
recordNav()
|
|
787
|
+
}, 500)
|
|
632
788
|
})
|
|
633
789
|
// 编辑区右键 → 注入 Monaco 原生 context menu(与 Go to Definition 等同菜单,避免分离浮层)。
|
|
634
790
|
// 有选区时额外显示「选中内容」项:context key edrvSelection 由光标选区变化驱动。
|
|
@@ -720,12 +876,26 @@ export function EditorView(props) {
|
|
|
720
876
|
editorRef.current = ed
|
|
721
877
|
}, [])
|
|
722
878
|
|
|
723
|
-
//
|
|
879
|
+
// 打开文件后的跳转(导航历史恢复/差异聚焦/搜索行列跳转,内容就绪后执行一次)
|
|
724
880
|
React.useEffect(() => {
|
|
725
|
-
const pf = pendingFocusRef.current
|
|
726
|
-
if (!pf || pf.path !== active || content === null) return
|
|
727
881
|
const ed = editorRef.current
|
|
728
|
-
if (!ed) return
|
|
882
|
+
if (!ed || content === null) return
|
|
883
|
+
// 导航历史恢复优先:viewState 快照恢复滚动/折叠,行列补定位
|
|
884
|
+
const nav = navPendingRef.current
|
|
885
|
+
if (nav && nav.path === active) {
|
|
886
|
+
navPendingRef.current = null
|
|
887
|
+
if (nav.viewState) {
|
|
888
|
+
try { ed.restoreViewState(nav.viewState) } catch (e) { /* 非法快照忽略(行号越界自动兜底) */ }
|
|
889
|
+
}
|
|
890
|
+
if (nav.line != null) {
|
|
891
|
+
ed.revealLineInCenter(Math.max(1, nav.line))
|
|
892
|
+
ed.setPosition({ lineNumber: Math.max(1, nav.line), column: Math.max(1, nav.column ?? 1) })
|
|
893
|
+
}
|
|
894
|
+
ed.focus()
|
|
895
|
+
return
|
|
896
|
+
}
|
|
897
|
+
const pf = pendingFocusRef.current
|
|
898
|
+
if (!pf || pf.path !== active) return
|
|
729
899
|
if (pf.line != null) {
|
|
730
900
|
// 搜索命中跳转:直接定位到行/列(不依赖差异区域)
|
|
731
901
|
pendingFocusRef.current = null
|
|
@@ -982,6 +1152,7 @@ export function EditorView(props) {
|
|
|
982
1152
|
|
|
983
1153
|
const openFile = (path, focusDiff) => {
|
|
984
1154
|
if (!path) return
|
|
1155
|
+
recordNav()
|
|
985
1156
|
addTab(path, true)
|
|
986
1157
|
if (focusDiff) pendingFocusRef.current = { path, region: null }
|
|
987
1158
|
}
|
|
@@ -995,6 +1166,7 @@ export function EditorView(props) {
|
|
|
995
1166
|
*/
|
|
996
1167
|
const openFileAt = (path, line, column) => {
|
|
997
1168
|
if (!path) return
|
|
1169
|
+
recordNav()
|
|
998
1170
|
addTab(path, true)
|
|
999
1171
|
pendingFocusRef.current = { path, region: null, line: line ?? null, column: column ?? 1 }
|
|
1000
1172
|
setFocusRequest((value) => value + 1)
|
|
@@ -1017,7 +1189,7 @@ export function EditorView(props) {
|
|
|
1017
1189
|
key: t.path,
|
|
1018
1190
|
className: 'edrv-tab' + (t.path === active ? ' edrv-tab-active' : ''),
|
|
1019
1191
|
title: t.path,
|
|
1020
|
-
onClick: () => { if (t.path !== active) { flushSave(); saveViewState(active); setActive(t.path) } },
|
|
1192
|
+
onClick: () => { if (t.path !== active) { flushSave(); saveViewState(active); recordNav(); setActive(t.path) } },
|
|
1021
1193
|
onContextMenu: (e) => {
|
|
1022
1194
|
e.preventDefault()
|
|
1023
1195
|
e.stopPropagation()
|
|
@@ -1054,11 +1226,29 @@ export function EditorView(props) {
|
|
|
1054
1226
|
lspProgress !== null ? React.createElement('span', { className: 'edrv-sp-progress' }, lspProgress + '%') : null)
|
|
1055
1227
|
: null
|
|
1056
1228
|
|
|
1229
|
+
// 导航历史按钮:目标条目名(tooltip 提示下一步会回到哪个文件)
|
|
1230
|
+
const navBackTarget = navRef.current.peekBack()
|
|
1231
|
+
const navForwardTarget = navRef.current.peekForward()
|
|
1232
|
+
const navNameOf = (entry) => (entry && entry.path ? String(entry.path).split(/[\\/]/).pop() : '')
|
|
1057
1233
|
const tabRow = React.createElement('div', { style: { display: 'flex', alignItems: 'center', background: 'var(--dsw-alias-bg-layer-1,transparent)', flexShrink: 0 } },
|
|
1058
1234
|
tabsEl,
|
|
1059
1235
|
(sum.totalFiles > 0
|
|
1060
1236
|
? React.createElement('button', { className: 'edrv-diffchip', title: '打开差异总览/归档', onClick: () => setLauncherOpen((o) => !o) }, '⚠ 差异 ' + sum.totalFiles + ' 文件')
|
|
1061
1237
|
: null),
|
|
1238
|
+
React.createElement('button', {
|
|
1239
|
+
className: 'edrv-chip-btn',
|
|
1240
|
+
title: '后退(' + (chordOf('edrv.navigateBack') ?? '未绑定') + ')' + (navBackTarget ? ' · ' + navNameOf(navBackTarget) : ''),
|
|
1241
|
+
disabled: !navRef.current.canBack(),
|
|
1242
|
+
'aria-label': '后退',
|
|
1243
|
+
onClick: navBack,
|
|
1244
|
+
}, '←'),
|
|
1245
|
+
React.createElement('button', {
|
|
1246
|
+
className: 'edrv-chip-btn',
|
|
1247
|
+
title: '前进(' + (chordOf('edrv.navigateForward') ?? '未绑定') + ')' + (navForwardTarget ? ' · ' + navNameOf(navForwardTarget) : ''),
|
|
1248
|
+
disabled: !navRef.current.canForward(),
|
|
1249
|
+
'aria-label': '前进',
|
|
1250
|
+
onClick: navForward,
|
|
1251
|
+
}, '→'),
|
|
1062
1252
|
React.createElement('button', { className: 'edrv-chip-btn' + (sidebarOn ? ' edrv-chip-on' : ''), title: '切换侧边栏 (' + (chordOf('edrv.toggleSidebar') ?? 'Ctrl+B') + ')', onClick: () => setSidebarOn((v) => !v) }, '☰'),
|
|
1063
1253
|
React.createElement('button', { className: 'edrv-chip-btn', title: '刷新', onClick: reloadFile }, '⟳'),
|
|
1064
1254
|
React.createElement(QuickOpen, { sessionId, onOpen: (p) => openFile(p, false) }))
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react'
|
|
11
11
|
import { SettingsContext } from '../settingsContext.js'
|
|
12
|
-
import { COMMANDS, chordFromEvent,
|
|
12
|
+
import { COMMANDS, chordFromEvent, parseChords } from '../keybindings.js'
|
|
13
13
|
import { defaultKeybindings, KEYBINDING_DEFAULTS, normalizeKeybindings } from '../../shared/keybindings.js'
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -148,7 +148,7 @@ export function KeybindingsPanel() {
|
|
|
148
148
|
const rows = COMMANDS.map((command) => {
|
|
149
149
|
const chord = draft[command.id] ?? null
|
|
150
150
|
const conflict = chord !== null && conflicts.has(command.id)
|
|
151
|
-
const invalid = chord !== null &&
|
|
151
|
+
const invalid = chord !== null && parseChords(chord).length === 0
|
|
152
152
|
const recordingRow = recording === command.id
|
|
153
153
|
let display
|
|
154
154
|
if (recordingRow) {
|
|
@@ -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, '配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索 / 后退 / 前进)。修改即时写入配置,重新聚焦编辑器后生效。'),
|
|
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),
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react'
|
|
9
9
|
import { rpc } from '../rpc.js'
|
|
10
|
-
import {
|
|
10
|
+
import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* 快速打开搜索框:Ctrl+P 聚焦,输入 ≥2 字符经 edrv.searchFiles 搜索,
|
|
@@ -50,7 +50,7 @@ export function QuickOpen(props) {
|
|
|
50
50
|
|
|
51
51
|
React.useEffect(() => {
|
|
52
52
|
const onKey = (e) => {
|
|
53
|
-
if (matchEvent(e,
|
|
53
|
+
if (matchEvent(e, bindingsOf('edrv.quickOpen'))) {
|
|
54
54
|
e.preventDefault(); e.stopPropagation()
|
|
55
55
|
inputRef.current?.focus?.()
|
|
56
56
|
setOpen(true)
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-vscode-mode shared — 快捷键命令目录与默认键位(双面契约)。
|
|
3
3
|
* 纯数据模块:host(settings schema 默认值)与 client(执行匹配/设置页)共用。
|
|
4
|
-
* 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 =
|
|
4
|
+
* 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 = 未绑定;
|
|
5
|
+
* `|` 连接多个候选(任一命中即触发),如 `Alt+ArrowLeft|Ctrl+Alt+-`。
|
|
5
6
|
* 作者 ddj 2026年08月26号
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
/** 命令 id →
|
|
9
|
+
/** 命令 id → 默认键位(可为多候选)。命令目录以此为准,新增命令只需加一项。 */
|
|
9
10
|
export const KEYBINDING_DEFAULTS: Record<string, string> = {
|
|
10
11
|
'edrv.save': 'Ctrl+S',
|
|
11
12
|
'edrv.quickOpen': 'Ctrl+P',
|
|
12
13
|
'edrv.toggleSidebar': 'Ctrl+B',
|
|
13
14
|
'edrv.searchInFiles': 'Ctrl+Shift+F',
|
|
15
|
+
'edrv.navigateBack': 'Alt+ArrowLeft|Ctrl+Alt+-',
|
|
16
|
+
'edrv.navigateForward': 'Alt+ArrowRight|Ctrl+Shift+-',
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
/**
|