dsh-vscode-mode 0.11.0 → 0.12.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.
@@ -1,8 +1,9 @@
1
1
  // @ts-nocheck
2
2
  /**
3
- * dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
3
+ * dsh-vscode-mode client — VSCodeMode 设置区:通用 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
4
+ * (快捷键管理已迁出为独立顶层设置区 ui/ShortcutSettings.ts,与 VSCodeMode 平级。)
4
5
  * MCP 管理 Tab 下含三个子页签:我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)。
5
- * 作者 ddj 2026年08月22号 / 2026年08月26号 / 2026年08月27号
6
+ * 作者 ddj 2026年08月22号 / 2026年08月26号 / 2026年08月27号 / 2026年10月
6
7
  */
7
8
  import React from 'react'
8
9
  import { rpc } from '../rpc.js'
@@ -14,7 +15,6 @@ import { normalizeSidebarMinWidth, SIDEBAR_MIN_DEFAULT } from '../sidebarMin.js'
14
15
  import { EDITOR_LIMIT_CEIL, EDITOR_LIMIT_DEFAULT, normalizeMaxOpenEditors } from '../../shared/editorLimit.js'
15
16
  import { TORTOISE_DIR_DEFAULT } from '../../shared/svn.js'
16
17
  import { DEFAULT_NATIVE_CSV } from '../../shared/nativeOpen.js'
17
- import { KeybindingsPanel } from './KeybindingsPanel.js'
18
18
  import { LspSettings } from './LspSettings.js'
19
19
  import { PerfSettings } from './PerfSettings.js'
20
20
  import { IntegrationSettings } from './IntegrationSettings.js'
@@ -581,7 +581,6 @@ export function McpSettings(props) {
581
581
  let body
582
582
  if (loading) body = React.createElement('div', { className: 'vsm-mcp-empty' }, '正在读取 MCP 服务…')
583
583
  else if (tab === 'general') body = React.createElement(GeneralSettings, { registry: openerRegistry })
584
- else if (tab === 'keys') body = React.createElement(KeybindingsPanel, null)
585
584
  else if (tab === 'mcp') body = React.createElement(McpManagePanel, { servers, projects, busy, draft, edit, resetDraft,
586
585
  saveGlobal: (close) => saveGlobal(close), saveProject: (workspacePath, close) => saveProject(workspacePath, close),
587
586
  refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject })
@@ -591,7 +590,7 @@ export function McpSettings(props) {
591
590
  else body = React.createElement(PerfSettings, null)
592
591
  return React.createElement('section', { className: 'vsm-mcp-page' },
593
592
  React.createElement('header', { className: 'vsm-mcp-header' }, React.createElement('div', null, React.createElement('h2', null, 'VSCodeMode'), React.createElement('p', null, '管理当前 profile 与各项目的 Model Context Protocol 服务。'))),
594
- React.createElement('nav', { className: 'vsm-mcp-tabs' }, React.createElement('button', { className: tab === 'general' ? 'active' : '', onClick: () => setTab('general') }, '通用'), React.createElement('button', { className: tab === 'keys' ? 'active' : '', onClick: () => setTab('keys') }, '快捷键'), React.createElement('button', { className: tab === 'mcp' ? 'active' : '', onClick: () => setTab('mcp') }, 'MCP 管理'), React.createElement('button', { className: tab === 'lsp' ? 'active' : '', onClick: () => setTab('lsp') }, '语言服务器'), React.createElement('button', { className: tab === 'ai' ? 'active' : '', onClick: () => setTab('ai') }, 'AI 补全'), React.createElement('button', { className: tab === 'perf' ? 'active' : '', onClick: () => setTab('perf') }, '性能优化'), React.createElement('button', { className: tab === 'compat' ? 'active' : '', onClick: () => setTab('compat') }, '兼容性')),
593
+ React.createElement('nav', { className: 'vsm-mcp-tabs' }, React.createElement('button', { className: tab === 'general' ? 'active' : '', onClick: () => setTab('general') }, '通用'), React.createElement('button', { className: tab === 'mcp' ? 'active' : '', onClick: () => setTab('mcp') }, 'MCP 管理'), React.createElement('button', { className: tab === 'lsp' ? 'active' : '', onClick: () => setTab('lsp') }, '语言服务器'), React.createElement('button', { className: tab === 'ai' ? 'active' : '', onClick: () => setTab('ai') }, 'AI 补全'), React.createElement('button', { className: tab === 'perf' ? 'active' : '', onClick: () => setTab('perf') }, '性能优化'), React.createElement('button', { className: tab === 'compat' ? 'active' : '', onClick: () => setTab('compat') }, '兼容性')),
595
594
  error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
596
595
  body,
597
596
  )
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import React from 'react'
11
11
  import { rpc } from '../rpc.js'
12
- import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
12
+ import { chordOf, useKeybindingsVersion } from '../keybindings.js'
13
13
  import { stepIndex } from './searchNav.js'
14
14
 
15
15
  /**
@@ -64,19 +64,18 @@ export function QuickOpen(props) {
64
64
  inputRef.current?.focus?.()
65
65
  setOpen(true)
66
66
  }
67
- const onKey = (e) => {
68
- if (matchEvent(e, bindingsOf('edrv.quickOpen'))) {
69
- e.preventDefault(); e.stopPropagation()
70
- openBox()
71
- } else if (e.key === 'Escape') {
72
- setOpen(false); inputRef.current?.blur?.()
73
- }
67
+ // Esc 关闭(仅收起浮窗;历史 capture 行为保留,不参与命令派发、不吞键)
68
+ const onEscape = (e) => {
69
+ if (e.key !== 'Escape') return
70
+ setOpen(false)
71
+ inputRef.current?.blur?.()
74
72
  }
75
- window.addEventListener('keydown', onKey, true)
76
- // 指令系统入口(命令栏「快速打开文件」):与键位复用同一动作
73
+ // 快速打开键位(Ctrl+P)已归官方 shortcuts 机制派发:官方 resolve → 注册表 run →
74
+ // 派发 `edrv.command.quickOpen` 事件到此处(历史窗口 capture 键位自检移除,防双开)。
75
+ window.addEventListener('keydown', onEscape, true)
77
76
  window.addEventListener('edrv.command.quickOpen', openBox)
78
77
  return () => {
79
- window.removeEventListener('keydown', onKey, true)
78
+ window.removeEventListener('keydown', onEscape, true)
80
79
  window.removeEventListener('edrv.command.quickOpen', openBox)
81
80
  }
82
81
  }, [])
@@ -0,0 +1,294 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 独立「快捷键」设置区(与 VSCodeMode 平级,settings.section slot)。
4
+ * 子页一「官方快捷键」:官方 shortcuts 目录只读总览(官方 + 插件命令)+ 一键打开官方快捷键弹窗;
5
+ * 子页二「插件快捷键」:插件命令(edrv.*)行内录键编辑,持久化经官方 edit API
6
+ * (Desktop: userData/keybindings.json;Web: localStorage dsh.keybindings.v1),不再写插件设置。
7
+ * 录制期间调 service.recording(true):Desktop 原生键盘桥暂停拦截,否则录不到键。
8
+ * 样式复用 vsm-kb-* / vsm-panel-* / vsm-mcp-subtabs(styles/mcp.css 随 client bundle 注入)。
9
+ * 作者 ddj 2026年10月
10
+ */
11
+ import React from 'react'
12
+ import { COMMANDS } from '../keybindings.js'
13
+ import { log } from '../log.js'
14
+ import { resetPluginKeys } from '../shortcutsOfficial.js'
15
+ import type { ShortcutsServiceLike, ShortcutCatalogRow } from '../shortcutsOfficial.js'
16
+
17
+ const settingsLog = log.child('shortcut-settings')
18
+
19
+ const MODIFIER_ONLY = new Set(['Control', 'Shift', 'Alt', 'Meta', 'AltGraph'])
20
+
21
+ /** 录制键 → 官方绑定(物理 code + 修饰符;Ctrl/Cmd 同为 primary,与官方互认语义一致)。 */
22
+ function bindingFromEvent(e) {
23
+ const modifiers = []
24
+ if (e.ctrlKey || e.metaKey) modifiers.push('primary')
25
+ if (e.altKey) modifiers.push('alt')
26
+ if (e.shiftKey) modifiers.push('shift')
27
+ return { code: e.code, modifiers }
28
+ }
29
+
30
+ /** 目录行展示键位(keys 已按平台格式化;binding null = 未绑定)。 */
31
+ function keysText(row) {
32
+ if (!row || row.binding === null) return '未绑定'
33
+ return row.keys.join(' ')
34
+ }
35
+
36
+ /** 插件命令行 = COMMANDS 目录序 × 官方目录行(行缺失时占位「未注册」)。 */
37
+ function catalogRowsOf(service) {
38
+ const byId = new Map(service.catalog.getSnapshot().map((row) => [row.id, row]))
39
+ return COMMANDS.map((command) => ({ id: command.id, label: command.label, row: byId.get(command.id) ?? null }))
40
+ }
41
+
42
+ /**
43
+ * 「快捷键」设置区主体:子页切换 + 服务就绪探测(晚到自愈,有界重试)。
44
+ * @author ddj 2026年10月
45
+ * @param props.getShortcuts 官方服务运行时探测函数(client/index.ts 注入)
46
+ */
47
+ export function ShortcutSettings(props) {
48
+ const getShortcuts = props?.getShortcuts
49
+ const [service, setService] = React.useState(() => (typeof getShortcuts === 'function' ? getShortcuts() : null))
50
+ const [tab, setTab] = React.useState('official')
51
+ const [, setVersion] = React.useState(0)
52
+
53
+ // 服务就绪有界重试(服务晚到自愈;用尽保持提示文案)
54
+ React.useEffect(() => {
55
+ if (service) return undefined
56
+ let attempts = 0
57
+ let cancelled = false
58
+ const tick = () => {
59
+ if (cancelled) return
60
+ const found = typeof getShortcuts === 'function' ? getShortcuts() : null
61
+ if (found) {
62
+ setService(found)
63
+ return
64
+ }
65
+ if (attempts >= 15) return
66
+ attempts += 1
67
+ setTimeout(tick, 2000)
68
+ }
69
+ tick()
70
+ return () => {
71
+ cancelled = true
72
+ }
73
+ }, [service])
74
+
75
+ // 目录/配置变化即重渲染(官方 catalog/config 快照存储订阅)
76
+ React.useEffect(() => {
77
+ if (!service) return undefined
78
+ const bump = () => setVersion((v) => v + 1)
79
+ const offCatalog = service.catalog.subscribe(bump)
80
+ const offConfig = service.config.subscribe(bump)
81
+ return () => {
82
+ offCatalog()
83
+ offConfig()
84
+ }
85
+ }, [service])
86
+
87
+ let body
88
+ if (!service) {
89
+ body = React.createElement('div', { className: 'vsm-mcp-empty' },
90
+ '官方快捷键服务未就绪(正在探测;若长期无响应,说明当前 DSH 版本不含官方快捷键机制)。')
91
+ } else if (tab === 'plugin') {
92
+ body = React.createElement(PluginShortcutEditor, { service })
93
+ } else {
94
+ body = React.createElement(OfficialShortcutsPane, { service })
95
+ }
96
+
97
+ return React.createElement('section', { className: 'vsm-general-page' },
98
+ React.createElement('header', { className: 'vsm-mcp-header' }, React.createElement('div', null,
99
+ React.createElement('h2', null, '快捷键'),
100
+ React.createElement('p', null, '基于官方 shortcuts 机制:统一注册、冲突校验、按平台差异化默认值;Desktop 持久化到 userData/keybindings.json,Web 持久化到 localStorage(dsh.keybindings.v1)。'))),
101
+ React.createElement('nav', { className: 'vsm-mcp-subtabs' },
102
+ React.createElement('button', { className: tab === 'official' ? 'active' : '', onClick: () => setTab('official') }, '官方快捷键'),
103
+ React.createElement('button', { className: tab === 'plugin' ? 'active' : '', onClick: () => setTab('plugin') }, '插件快捷键')),
104
+ body,
105
+ )
106
+ }
107
+
108
+ /** 官方目录行(只读总览行)。 */
109
+ function OfficialRow({ row }) {
110
+ return React.createElement('div', { className: 'vsm-kb-row' },
111
+ React.createElement('div', { className: 'vsm-kb-label' },
112
+ React.createElement('span', null, row.label),
113
+ React.createElement('small', null, row.id),
114
+ row.modified && React.createElement('small', { className: 'vsm-kb-warn' }, '已自定义'),
115
+ row.issue && React.createElement('small', { className: 'vsm-kb-warn' }, '键位不受当前环境支持'),
116
+ row.conflicts.length > 0 && React.createElement('small', { className: 'vsm-kb-warn' }, '与 ' + row.conflicts.join('、') + ' 冲突')),
117
+ React.createElement('kbd', { className: 'vsm-kb-key' + (row.conflicts.length ? ' warn' : '') }, keysText(row)),
118
+ )
119
+ }
120
+
121
+ /** 子页一:官方快捷键(弹窗入口 + 目录总览)。 */
122
+ function OfficialShortcutsPane({ service }) {
123
+ const [error, setError] = React.useState('')
124
+ const rows = service.catalog.getSnapshot()
125
+ const config = service.config.getSnapshot()
126
+ const openOfficial = () => {
127
+ setError('')
128
+ if (!service.registry || typeof service.registry.invoke !== 'function') {
129
+ setError('官方弹窗入口不可用;可按 Ctrl+/ 或到 设置 → 通用 → 快捷键 打开')
130
+ return
131
+ }
132
+ try {
133
+ service.registry.invoke('shortcuts.open', {
134
+ region: 'page',
135
+ modal: null,
136
+ target: typeof document !== 'undefined' ? document.activeElement : null,
137
+ })
138
+ } catch (e) {
139
+ settingsLog.warn('打开官方快捷键弹窗失败:' + String(e))
140
+ setError('打开官方弹窗失败:' + String(e) + ';可按 Ctrl+/ 或到 设置 → 通用 → 快捷键 打开')
141
+ }
142
+ }
143
+ return React.createElement(React.Fragment, null,
144
+ React.createElement('section', { className: 'vsm-panel' },
145
+ React.createElement('h3', { className: 'vsm-panel-title' }, '官方快捷键配置'),
146
+ React.createElement('div', { className: 'vsm-panel-body' },
147
+ React.createElement('p', null, '官方快捷键弹窗(Ctrl+/ 或 设置 → 通用 → 快捷键)提供全部命令的查看、录键与恢复默认;下方为当前目录只读总览(含插件注册的命令)。'),
148
+ React.createElement('button', { className: 'vsm-primary vsm-small', onClick: openOfficial }, '打开官方快捷键设置(Ctrl+/)'),
149
+ error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
150
+ config?.status === 'unreadable' && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' },
151
+ '快捷键配置读取失败(内容损坏或由更新版本写入),当前使用默认键位;请备份后清理配置,或升级 DSH。')),
152
+ ),
153
+ React.createElement('section', { className: 'vsm-panel' },
154
+ React.createElement('h3', { className: 'vsm-panel-title' }, '命令目录(只读总览)'),
155
+ React.createElement('div', { className: 'vsm-panel-body' },
156
+ React.createElement('div', { className: 'vsm-kb-list' }, rows.map((row) => React.createElement(OfficialRow, { key: row.id, row }))),
157
+ ),
158
+ ),
159
+ )
160
+ }
161
+
162
+ /** 子页二:插件命令键位(行内录键 + 行级清除 + 恢复默认;写入走官方 edit API)。 */
163
+ function PluginShortcutEditor({ service }) {
164
+ const [recording, setRecording] = React.useState(null) // 正在录制的命令 id
165
+ const [busy, setBusy] = React.useState(false)
166
+ const [message, setMessage] = React.useState('')
167
+ const [error, setError] = React.useState('')
168
+ const rows = catalogRowsOf(service)
169
+ const config = service.config.getSnapshot()
170
+
171
+ const saveBinding = async (id, binding) => {
172
+ setBusy(true)
173
+ setError('')
174
+ setMessage('')
175
+ const buildEdit = () => (binding === null ? { type: 'reset', id } : { type: 'set', id, binding })
176
+ try {
177
+ const result = await service.edit(buildEdit(), service.config.getSnapshot().revision)
178
+ if (result.status === 'saved') {
179
+ setMessage('已保存')
180
+ } else if (result.status === 'stale') {
181
+ setError('快捷键配置已在其他窗口更新,请检查当前键位后重新录制')
182
+ } else if (result.status === 'conflict') {
183
+ // 官方按原因分类拒绝:issue = 键位本身不合法(如 web 白名单外),conflicts = 与其他命令撞键
184
+ if (result.issue) {
185
+ const reasons = {
186
+ 'unsupported-browser': 'web 模式白名单外的组合(单修饰符组合在浏览器环境不可靠)',
187
+ 'reserved': '官方保留键',
188
+ 'unsupported-key': '官方不支持的键',
189
+ 'modifier-required': '至少需要一个修饰键',
190
+ }
191
+ setError('键位被官方拒绝:' + (reasons[result.issue] ?? result.issue)
192
+ + '。web 模式可用 Ctrl+Alt+X 风格组合;升级桌面外壳进入桌面模式后不受此限。')
193
+ } else {
194
+ setError('键位冲突:与 ' + (result.conflicts?.join('、') ?? '其他命令') + ' 冲突,请换一个组合')
195
+ }
196
+ } else if (result.status === 'not-ready') {
197
+ setError('快捷键配置尚未就绪,请稍后重试')
198
+ } else if (result.status === 'unreadable') {
199
+ setError('快捷键配置读取失败,编辑已禁用(见官方提示)')
200
+ } else {
201
+ setError('写入失败,请重试')
202
+ }
203
+ } catch (e) {
204
+ settingsLog.warn('快捷键保存失败:' + String(e))
205
+ setError('保存失败:' + String(e))
206
+ } finally {
207
+ setBusy(false)
208
+ }
209
+ }
210
+
211
+ const resetAll = async () => {
212
+ if (!window.confirm('恢复全部插件快捷键为默认值?(只影响本插件命令的键位覆盖)')) return
213
+ setBusy(true)
214
+ setError('')
215
+ setMessage('')
216
+ try {
217
+ const result = await resetPluginKeys(service)
218
+ if (result.failed) setError('已恢复 ' + result.reset + ' 条,' + result.failed + ' 恢复失败;请重试')
219
+ else setMessage('已恢复 ' + result.reset + ' 条插件快捷键')
220
+ } catch (e) {
221
+ settingsLog.warn('快捷键恢复默认失败:' + String(e))
222
+ setError('恢复失败:' + String(e))
223
+ } finally {
224
+ setBusy(false)
225
+ }
226
+ }
227
+
228
+ // 录制态:窗口捕获下一次组合键(Desktop 原生桥已经 recording(true) 暂停拦截)。
229
+ // capture 阶段 preventDefault → 官方 DOM 派发因 defaultPrevented 放行,不会误触发命令。
230
+ React.useEffect(() => {
231
+ if (!recording) return undefined
232
+ const onKey = (e) => {
233
+ e.preventDefault()
234
+ e.stopPropagation()
235
+ if (e.key === 'Escape') {
236
+ setRecording(null)
237
+ return
238
+ }
239
+ if (e.key === 'Backspace' || e.key === 'Delete') {
240
+ const id = recording
241
+ setRecording(null)
242
+ void saveBinding(id, null)
243
+ return
244
+ }
245
+ if (MODIFIER_ONLY.has(e.key)) return
246
+ const id = recording
247
+ setRecording(null)
248
+ void saveBinding(id, bindingFromEvent(e))
249
+ }
250
+ window.addEventListener('keydown', onKey, true)
251
+ return () => window.removeEventListener('keydown', onKey, true)
252
+ }, [recording])
253
+
254
+ // 录制期间通知 Desktop 原生桥暂停拦截(Web 适配器为空实现)
255
+ React.useEffect(() => {
256
+ if (!service || typeof service.recording !== 'function') return undefined
257
+ void service.recording(Boolean(recording)).catch(() => {})
258
+ return () => {
259
+ if (recording) void service.recording(false).catch(() => {})
260
+ }
261
+ }, [recording, service])
262
+
263
+ const rowsView = rows.map(({ id, label, row }) => {
264
+ const recordingRow = recording === id
265
+ const display = recordingRow
266
+ ? React.createElement('span', { className: 'vsm-kb-rec' }, '按下组合键…(Esc 取消 / Backspace 清除)')
267
+ : React.createElement('kbd', { className: 'vsm-kb-key' + (row && row.conflicts.length ? ' warn' : '') }, keysText(row))
268
+ return React.createElement('div', { key: id, className: 'vsm-kb-row' + (recordingRow ? ' rec' : '') },
269
+ React.createElement('div', { className: 'vsm-kb-label' },
270
+ React.createElement('span', null, label),
271
+ row === null && React.createElement('small', { className: 'vsm-kb-warn' }, '未注册(官方服务未同步)'),
272
+ row && row.modified && React.createElement('small', { className: 'vsm-kb-warn' }, '已自定义'),
273
+ row && row.conflicts.length > 0 && React.createElement('small', { className: 'vsm-kb-warn' }, '与 ' + row.conflicts.join('、') + ' 冲突')),
274
+ display,
275
+ React.createElement('div', { className: 'vsm-kb-actions' },
276
+ recordingRow
277
+ ? React.createElement('button', { onClick: () => setRecording(null) }, '取消')
278
+ : React.createElement(React.Fragment, null,
279
+ React.createElement('button', { disabled: busy || config?.status !== 'ready', onClick: () => { setRecording(id); setMessage('') } }, '编辑'),
280
+ React.createElement('button', { disabled: busy || config?.status !== 'ready', title: '清除该命令的键位(恢复默认)', onClick: () => void saveBinding(id, null) }, '清除'))),
281
+ )
282
+ })
283
+
284
+ return React.createElement('section', { className: 'vsm-panel' },
285
+ React.createElement('h3', { className: 'vsm-panel-title' }, '插件命令键位'),
286
+ React.createElement('div', { className: 'vsm-panel-body' },
287
+ React.createElement('div', { className: 'vsm-kb-list' }, rowsView)),
288
+ React.createElement('div', { className: 'vsm-panel-body vsm-kb-actionsbar' },
289
+ React.createElement('span', { className: 'vsm-kb-dirty' }, recording ? '录制中…' : ''),
290
+ message && React.createElement('span', { className: 'vsm-kb-message' }, message),
291
+ error && React.createElement('span', { className: 'vsm-kb-warn' }, error),
292
+ React.createElement('button', { className: 'vsm-small', disabled: busy || config?.status !== 'ready', onClick: () => void resetAll() }, '恢复默认')),
293
+ )
294
+ }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * dsh-vscode-mode client — 编辑器指令目录(纯元数据)。
3
- * 「一条命令 = 一条注册数据」:命令栏、快捷键设置页、快捷键监听与 Monaco 右键菜单
4
- * 全部从本表读取;新增一条能力只需在 EDITOR_COMMANDS 追加一项(可选 keybinding)。
3
+ * 「一条命令 = 一条注册数据」:命令栏、快捷键设置页与 Monaco 右键菜单
4
+ * 全部从本表读取;新增一条能力只需在 EDITOR_COMMANDS 追加一项(默认键位见 shared/keybindings SHORTCUT_PROFILES)。
5
5
  * run 只派发 `edrv.command.*` 窗口事件,不直接触碰 React/Monaco(`edrv.command.` 前缀
6
6
  * 与既有 `edrv:` 刷新/主题事件分属不同命名空间,互不干扰)。
7
7
  * 作者 ddj 2026年09月10号
8
8
  */
9
9
  import { hasEditorModel, hasOpenTabs } from '../editorModelState.js'
10
+ import { activeEditorPath } from '../activePathStore.js'
11
+ import { isMarkdownPath } from '../markdownPreview.js'
10
12
  import { svnCurrentStatus } from '../svnStatus.js'
11
13
  import { dapStore } from '../dap/store.js'
12
14
  import { svnActionOn, svnActionsFor } from '../../shared/svnActions.js'
@@ -22,8 +24,6 @@ export interface CommandDef {
22
24
  category: string
23
25
  /** 组内排序(小者优先)。 */
24
26
  order?: number
25
- /** 默认键位弦(可选;可含 `|` 多候选)。声明后由快捷键设置页展示与录制。 */
26
- keybinding?: string
27
27
  /** 运行体:派发窗口事件或直接执行。 */
28
28
  run: () => void
29
29
  /** 可用性判定(缺省视为始终可用;返回 false 时命令栏隐藏且 run 被拒)。 */
@@ -62,7 +62,7 @@ function alwaysAvailable(): boolean {
62
62
  function nextEditorRowDef(): CommandDef {
63
63
  return {
64
64
  id: 'edrv.nextEditorRow', label: '下一编辑行(光标整行下移)', category: '导航', order: 50,
65
- keybinding: 'Ctrl+Alt+ArrowDown', available: needsModel,
65
+ available: needsModel,
66
66
  run: () => emit('nextEditorRow'),
67
67
  }
68
68
  }
@@ -75,7 +75,7 @@ function nextEditorRowDef(): CommandDef {
75
75
  function prevEditorRowDef(): CommandDef {
76
76
  return {
77
77
  id: 'edrv.prevEditorRow', label: '上一编辑行(光标整行上移)', category: '导航', order: 60,
78
- keybinding: 'Ctrl+Alt+ArrowUp', available: needsModel,
78
+ available: needsModel,
79
79
  run: () => emit('prevEditorRow'),
80
80
  }
81
81
  }
@@ -88,7 +88,7 @@ function prevEditorRowDef(): CommandDef {
88
88
  function gotoLineDef(): CommandDef {
89
89
  return {
90
90
  id: 'edrv.goToLine', label: '转到行', category: '导航', order: 45,
91
- keybinding: 'Ctrl+G', available: needsModel,
91
+ available: needsModel,
92
92
  run: () => emit('goToLine'),
93
93
  }
94
94
  }
@@ -102,7 +102,7 @@ function gotoLineDef(): CommandDef {
102
102
  function addSelectionRefDef(): CommandDef {
103
103
  return {
104
104
  id: 'edrv.addSelectionRef', label: '添加选中内容为引用', category: '编辑', order: 10,
105
- keybinding: 'Ctrl+U', available: needsModel,
105
+ available: needsModel,
106
106
  run: () => emit('addSelectionRef'),
107
107
  }
108
108
  }
@@ -117,7 +117,7 @@ function addSelectionRefDef(): CommandDef {
117
117
  function closeTabDef(): CommandDef {
118
118
  return {
119
119
  id: 'edrv.closeTab', label: '关闭当前页签', category: '文件', order: 40,
120
- keybinding: 'Ctrl+F4', available: hasOpenTabs,
120
+ available: hasOpenTabs,
121
121
  run: () => emit('closeTab'),
122
122
  }
123
123
  }
@@ -188,34 +188,39 @@ function svnEventOf(action: SvnActionDef): string {
188
188
 
189
189
  /**
190
190
  * 编辑器内置指令目录(顺序 = 快捷键设置页展示顺序)。
191
- * 前置 8 条的键位由 EditorView / QuickOpen 自行 capture 监听(历史实现),
192
- * 故不进 BRIDGE_COMMANDS,避免同一按键双执行。
193
- * @author ddj 2026年09月10号
191
+ * 键位派发已归官方 shortcuts 机制:默认键位表在 shared/keybindings.ts 的 SHORTCUT_PROFILES,
192
+ * 由 client/index.ts 经 shortcutsOfficial.registerOfficialShortcuts 注册,本目录只管
193
+ * 「标签/可用性/执行」。无默认键位的命令同样注册(官方 UI/插件子页内可自定义绑定)。
194
+ * @author ddj 2026年09月10号 / 2026年10月
194
195
  */
195
196
  export const EDITOR_COMMANDS: readonly CommandDef[] = [
196
197
  {
197
198
  id: 'edrv.save', label: '保存文件', category: '文件', order: 10,
198
- keybinding: 'Ctrl+S', available: needsModel,
199
+ available: needsModel,
199
200
  run: () => emit('save'),
200
201
  },
201
202
  {
202
203
  id: 'edrv.quickOpen', label: '快速打开文件', category: '文件', order: 20,
203
- keybinding: 'Ctrl+P', available: alwaysAvailable,
204
+ available: alwaysAvailable,
204
205
  run: () => emit('quickOpen'),
205
206
  },
206
207
  {
207
208
  id: 'edrv.toggleSidebar', label: '切换侧边栏', category: '视图', order: 10,
208
- keybinding: 'Ctrl+B', available: alwaysAvailable,
209
+ available: alwaysAvailable,
209
210
  run: () => emit('toggleSidebar'),
210
211
  },
211
212
  {
212
213
  id: 'edrv.searchInFiles', label: '在工作区中搜索', category: '视图', order: 20,
213
- keybinding: 'Ctrl+Shift+F', available: alwaysAvailable,
214
+ available: alwaysAvailable,
214
215
  run: () => emit('searchInFiles'),
215
216
  },
216
217
  {
217
218
  id: 'edrv.toggleMarkdownPreview', label: '切换 Markdown 预览', category: '视图', order: 25,
218
- keybinding: 'Ctrl+Shift+V', available: alwaysAvailable,
219
+ // 仅活动文件是 .md 时可用(不可用时官方派发返回 pass 不吞键,保留浏览器「无格式粘贴」语义)
220
+ available: () => {
221
+ const path = activeEditorPath()
222
+ return typeof path === 'string' && isMarkdownPath(path)
223
+ },
219
224
  run: () => emit('toggleMarkdownPreview'),
220
225
  },
221
226
  {
@@ -225,22 +230,22 @@ export const EDITOR_COMMANDS: readonly CommandDef[] = [
225
230
  },
226
231
  {
227
232
  id: 'edrv.navigateBack', label: '后退(导航历史)', category: '导航', order: 10,
228
- keybinding: 'Alt+ArrowLeft|Ctrl+Alt+-', available: needsModel,
233
+ available: needsModel,
229
234
  run: () => emit('navigateBack'),
230
235
  },
231
236
  {
232
237
  id: 'edrv.navigateForward', label: '前进(导航历史)', category: '导航', order: 20,
233
- keybinding: 'Alt+ArrowRight|Ctrl+Shift+-', available: needsModel,
238
+ available: needsModel,
234
239
  run: () => emit('navigateForward'),
235
240
  },
236
241
  {
237
242
  id: 'edrv.nextTab', label: '下一个页签', category: '导航', order: 30,
238
- keybinding: 'Ctrl+Alt+ArrowRight|Ctrl+PageDown', available: needsModel,
243
+ available: needsModel,
239
244
  run: () => emit('nextTab'),
240
245
  },
241
246
  {
242
247
  id: 'edrv.prevTab', label: '上一个页签', category: '导航', order: 40,
243
- keybinding: 'Ctrl+Alt+ArrowLeft|Ctrl+PageUp', available: needsModel,
248
+ available: needsModel,
244
249
  run: () => emit('prevTab'),
245
250
  },
246
251
  {
@@ -291,41 +296,41 @@ function debugPaletteDefs(): CommandDef[] {
291
296
  return [
292
297
  {
293
298
  id: 'edrv.debugToggleBreakpoint', label: '调试:切换断点(光标行)', category: '调试', order: 10,
294
- keybinding: 'F9', available: needsModel,
299
+ available: needsModel,
295
300
  run: () => emit('debugToggleBreakpoint'),
296
301
  },
297
302
  {
298
303
  id: 'edrv.debugStartContinue', label: '调试:启动 / 继续', category: '调试', order: 20,
299
- keybinding: 'F5', available: canStart,
304
+ available: canStart,
300
305
  run: () => emit('debugStartContinue'),
301
306
  },
302
307
  {
303
308
  id: 'edrv.debugStepOver', label: '调试:单步跳过', category: '调试', order: 30,
304
- keybinding: 'F10', available: paused,
309
+ available: paused,
305
310
  run: () => emit('debugStepOver'),
306
311
  },
307
312
  {
308
313
  id: 'edrv.debugStepInto', label: '调试:单步步入', category: '调试', order: 40,
309
- keybinding: 'F11', available: paused,
314
+ available: paused,
310
315
  run: () => emit('debugStepInto'),
311
316
  },
312
317
  {
313
318
  id: 'edrv.debugStepOut', label: '调试:单步步出', category: '调试', order: 50,
314
- keybinding: 'Shift+F11', available: paused,
319
+ available: paused,
315
320
  run: () => emit('debugStepOut'),
316
321
  },
317
322
  {
318
323
  id: 'edrv.debugStop', label: '调试:停止', category: '调试', order: 60,
319
- keybinding: 'Shift+F5', available: canStop,
324
+ available: canStop,
320
325
  run: () => emit('debugStop'),
321
326
  },
322
327
  ]
323
328
  }
324
329
 
325
330
  /**
326
- * 桥接派发指令(无原生监听,键位由 commandBridge 统一 capture 处理)。
327
- * 新增「只填目录、不写监听」的编辑器指令一律放这里:键位、命令栏、设置页自动可用。
328
- * @author ddj 2026年09月10号
331
+ * 非编辑器原生路径的指令(键位统一由官方 shortcuts 机制派发;历史「桥接」分组名保留)。
332
+ * 新增「只填目录」的编辑器指令一律放这里:命令栏与官方快捷键目录自动可用。
333
+ * @author ddj 2026年09月10号 / 2026年10月
329
334
  */
330
335
  export const BRIDGE_COMMANDS: readonly CommandDef[] = [
331
336
  nextEditorRowDef(),
@@ -345,16 +350,6 @@ export const BRIDGE_COMMANDS: readonly CommandDef[] = [
345
350
  export function showCommandsDef(run: () => void): CommandDef {
346
351
  return {
347
352
  id: 'edrv.showCommands', label: '显示所有命令', category: '视图', order: 1,
348
- keybinding: 'Ctrl+Shift+P|F1', available: alwaysAvailable, run,
353
+ available: alwaysAvailable, run,
349
354
  }
350
355
  }
351
-
352
- /**
353
- * 需要全局键位派发的指令(桥接类 + 命令栏自身;不含 EditorView/QuickOpen 原生监听的那些)。
354
- * @author ddj 2026年09月10号
355
- * @param showCommands 命令栏命令定义
356
- * @returns 派发集合
357
- */
358
- export function dispatchedCommands(showCommands: CommandDef): readonly CommandDef[] {
359
- return [...BRIDGE_COMMANDS, showCommands]
360
- }
@@ -407,6 +407,9 @@ export async function updateSection(provider: SettingsProvider | undefined, ns:
407
407
  * 构建设置 section schema(install 路径与插件 Config 声明共用,保证两代形状同源)。
408
408
  * 字段集 = fileOpenTool/keybindings/sidebarMinWidth/maxOpenEditors/integrationBaseUrl/
409
409
  * aiInline/aiProvider/aiModel/aiEffort/svnPath/tortoisePath/nativeOpenExts,全部带默认值
410
+ * (keybindings 自 2026-10 起废弃停写:键位持久化归官方 shortcuts 存储
411
+ * userData/keybindings.json / dsh.keybindings.v1;字段保留仅为旧档迁移读取与降级回滚,
412
+ * 见 client/shortcutsOfficial.ts 的 migrateLegacyKeybindings。)
410
413
  * (Config 启动校验在 undefined/空配置下自动填充,rc/alpha 两代 cordis 均通过)。
411
414
  * options.volatile:DSH 0.1.7 线的 Config 导出专用——SettingsForms.describe 只下发
412
415
  * 含 volatile 字段的 entry(volatileForm 门槛),不标记则 ns 永不出现在 describe、