dsh-vscode-mode 0.12.0 → 0.13.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.
Files changed (71) hide show
  1. package/README.md +129 -103
  2. package/assets/shell/dsh-open.cs +65 -149
  3. package/assets/shell/dsh-open.mjs +241 -0
  4. package/assets/shell/dsh-open.ps1 +28 -47
  5. package/assets/shell/dsh-open.sh +25 -66
  6. package/lib/client.js +12465 -11526
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +3889 -2336
  9. package/lib/index.js.map +1 -1
  10. package/package.json +9 -8
  11. package/skills/dsh-vscodemode-mcp/SKILL.md +94 -132
  12. package/src/childEnv.ts +19 -0
  13. package/src/client/addToConversation.ts +4 -2
  14. package/src/client/ai/inlineProvider.ts +1 -8
  15. package/src/client/commandBridge.ts +6 -2
  16. package/src/client/externalOpen.ts +40 -19
  17. package/src/client/index.ts +10 -4
  18. package/src/client/keybindings.ts +18 -1
  19. package/src/client/monaco/loader.ts +3 -1
  20. package/src/client/openFlow.ts +36 -28
  21. package/src/client/openReceipt.ts +64 -0
  22. package/src/client/panelCommands.ts +101 -0
  23. package/src/client/pdf/pdfLoader.ts +4 -2
  24. package/src/client/receiptState.ts +120 -0
  25. package/src/client/rpc.ts +30 -4
  26. package/src/client/shortcutRecorder.ts +161 -0
  27. package/src/client/shortcutsOfficial.ts +13 -4
  28. package/src/client/sidebarBridge.ts +7 -0
  29. package/src/client/ui/DiffBadge.ts +2 -1
  30. package/src/client/ui/EditorView.ts +237 -55
  31. package/src/client/ui/McpSettings.ts +6 -3
  32. package/src/client/ui/OfficialSideTab.ts +5 -2
  33. package/src/client/ui/QuickOpen.ts +10 -9
  34. package/src/client/ui/ShortcutSettings.ts +34 -54
  35. package/src/client/ui/commandCatalog.ts +5 -4
  36. package/src/client/useOpenReceipt.ts +128 -0
  37. package/src/compat.ts +5 -3
  38. package/src/dap/manager.ts +46 -13
  39. package/src/devForm.ts +252 -92
  40. package/src/externalHandoff.ts +93 -52
  41. package/src/fileOpenSettings.ts +1 -24
  42. package/src/hostImport.ts +19 -0
  43. package/src/httpGuard.ts +101 -0
  44. package/src/index.ts +106 -97
  45. package/src/integrate.ts +56 -50
  46. package/src/lsp/transport.ts +84 -127
  47. package/src/mcp.ts +58 -9
  48. package/src/mcpIsolation.ts +211 -102
  49. package/src/mcpProject.ts +220 -133
  50. package/src/mcpRuntime.ts +343 -0
  51. package/src/openBridge.ts +69 -0
  52. package/src/openInbox.ts +207 -0
  53. package/src/paths.ts +8 -5
  54. package/src/processTree.ts +63 -0
  55. package/src/reveal.ts +5 -3
  56. package/src/routes.ts +156 -154
  57. package/src/rpc.ts +14 -13
  58. package/src/shared/appUrl.ts +19 -0
  59. package/src/shared/externalOpen.ts +71 -0
  60. package/src/shared/integration.ts +2 -0
  61. package/src/shared/keybindings.ts +8 -5
  62. package/src/shared/mcp.ts +4 -1
  63. package/src/shared/rpc.ts +7 -4
  64. package/src/shared/svn.ts +6 -8
  65. package/src/svnPlanTool.ts +51 -0
  66. package/src/unityBridge.ts +26 -3
  67. package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +99 -203
  68. package/unity/com.dsh.editor/Editor/DshOpenWorker.cs +166 -0
  69. package/unity/com.dsh.editor/Editor/DshOpenWorker.cs.meta +2 -0
  70. package/unity/com.dsh.editor/README.md +16 -25
  71. package/unity/com.dsh.editor/package.json +2 -2
@@ -0,0 +1,161 @@
1
+ import type { OfficialBinding, OfficialModifier } from '../shared/keybindings.js'
2
+ import type { ShortcutsServiceLike } from './shortcutsOfficial.js'
3
+
4
+ export interface RecordHooks {
5
+ ready(): void
6
+ save(binding: OfficialBinding | null): void
7
+ cancel(): void
8
+ error(error: unknown): void
9
+ }
10
+ const recorderHost = globalThis as typeof globalThis & { __edrvRecording__?: WeakMap<ShortcutsServiceLike, Promise<void>> }
11
+ const leases = recorderHost.__edrvRecording__ ??= new WeakMap<ShortcutsServiceLike, Promise<void>>()
12
+
13
+ /**
14
+ * Serialize native recorder state, including cleanup after a late ready response.
15
+ * @author ddj 2026年09月28号
16
+ * @param service Native/Web shortcut service.
17
+ * @param active Desired native interception state.
18
+ * @returns Completion of the state change; errors remain visible to the caller.
19
+ */
20
+ function recordState(service: ShortcutsServiceLike, active: boolean): Promise<void> {
21
+ const prior = leases.get(service) ?? Promise.resolve()
22
+ const next = prior.catch(() => {}).then(() => service.recording(active))
23
+ leases.set(service, next)
24
+ return next
25
+ }
26
+
27
+ /**
28
+ * Read physical modifiers without converting Control or Meta into logical primary.
29
+ * @author ddj 2026年09月28号
30
+ * @param event Keyboard event.
31
+ * @returns Modifiers in official canonical order.
32
+ */
33
+ export function eventModifiers(event: KeyboardEvent): OfficialModifier[] {
34
+ const states = { control: event.ctrlKey, alt: event.altKey, shift: event.shiftKey, meta: event.metaKey }
35
+ return (Object.keys(states) as Array<keyof typeof states>).filter((key) => states[key])
36
+ }
37
+
38
+ /** Physical key collection; native readiness and listeners are owned by startRecording. */
39
+ export class KeyCapture {
40
+ private held = new Set<string>()
41
+ private pending: OfficialBinding | null = null
42
+ private blocked = false
43
+ private dead = false
44
+ private composing = false
45
+
46
+ /**
47
+ * Create a physical collector with official platform capabilities.
48
+ * @author ddj 2026年09月28号
49
+ * @param service Official binding validator and profile.
50
+ * @param hooks Completion/error handlers.
51
+ */
52
+ constructor(private service: ShortcutsServiceLike, private hooks: RecordHooks) {}
53
+
54
+ /**
55
+ * Reset a gesture on blur or composition transitions.
56
+ * @author ddj 2026年09月28号
57
+ * @param composing Whether IME composition is currently active.
58
+ */
59
+ reset(composing = false): void {
60
+ this.held.clear(); this.pending = null; this.blocked = false; this.dead = false
61
+ this.composing = composing
62
+ }
63
+
64
+ /**
65
+ * Collect one physical press; commit only when a constituent key is released.
66
+ * @author ddj 2026年09月28号
67
+ * @param event Keyboard press; guarded IME/AltGraph input is left untouched.
68
+ */
69
+ down(event: KeyboardEvent): void {
70
+ if (this.composing || event.isComposing || event.keyCode === 229 || event.getModifierState?.('AltGraph')) {
71
+ this.pending = null; this.held.clear(); return
72
+ }
73
+ const commandDead = this.service.runtime === 'web' && this.service.platform === 'macos'
74
+ && event.code === 'KeyN' && event.metaKey && event.altKey && !event.ctrlKey && !event.shiftKey
75
+ if (event.key === 'Dead' && !commandDead) { this.reset(); this.dead = true; return }
76
+ if (this.dead) { this.dead = false; return }
77
+ event.preventDefault(); event.stopPropagation()
78
+ if (event.repeat) return
79
+ const modifiers = eventModifiers(event)
80
+ if (!modifiers.length && event.key === 'Escape') { this.hooks.cancel(); return }
81
+ if (!modifiers.length && (event.key === 'Backspace' || event.key === 'Delete')) { this.hooks.save(null); return }
82
+ if (/^(Control|Alt|Shift|Meta|AltGraph)(Left|Right)?$/u.test(event.code || event.key)) {
83
+ if (this.pending) this.reset()
84
+ return
85
+ }
86
+ const chords = this.service.runtime === 'desktop' && ['macos', 'windows'].includes(this.service.platform)
87
+ if (chords) this.held.add(event.code)
88
+ if (this.blocked) return
89
+ if (this.held.size > 2) {
90
+ this.pending = null; this.blocked = true; this.hooks.error('最多同时录制两个主键'); return
91
+ }
92
+ const codes = chords ? [...this.held] : [event.code]
93
+ try {
94
+ const probe = this.service.describeBinding({ code: codes[0], modifiers, ...(codes[1] ? { secondCode: codes[1] } : {}) })
95
+ if (!probe.binding || probe.issue) throw new Error(probe.issue ?? 'unsupported-key')
96
+ this.pending = probe.binding
97
+ } catch (error) { this.pending = null; this.hooks.error(error) }
98
+ }
99
+
100
+ /**
101
+ * Commit an accepted physical gesture once at release.
102
+ * @author ddj 2026年09月28号
103
+ * @param event Released key or modifier.
104
+ */
105
+ up(event: KeyboardEvent): void {
106
+ this.held.delete(event.code)
107
+ const binding = this.pending
108
+ const modifier = event.code.replace(/(Left|Right)$/u, '').toLowerCase() as OfficialModifier
109
+ if (!binding || !(binding.code === event.code || binding.secondCode === event.code || binding.modifiers.includes(modifier))) return
110
+ this.pending = null
111
+ event.preventDefault(); event.stopPropagation()
112
+ this.hooks.save(binding)
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Protect recording before listening, and restore interception on every exit path.
118
+ * @author ddj 2026年09月28号
119
+ * @param service Official service whose asynchronous native bridge must be ready.
120
+ * @param target Owning window, injectable for lifecycle tests.
121
+ * @param hooks Ready, save, cancel and failure callbacks.
122
+ * @returns Idempotent disposal, safe before readiness and after failure.
123
+ */
124
+ export function startRecording(service: ShortcutsServiceLike, target: EventTarget, hooks: RecordHooks): () => void {
125
+ let disposed = false
126
+ let ready = false
127
+ /** @author ddj 2026年09月28号 @param binding Accepted binding; release interception before saving. */
128
+ const save = (binding: OfficialBinding | null): void => { dispose(); hooks.save(binding) }
129
+ /** @author ddj 2026年09月28号 Cancel capture and restore native interception. */
130
+ const cancel = (): void => { dispose(); hooks.cancel() }
131
+ const capture = new KeyCapture(service, { ...hooks, save, cancel })
132
+ /** @author ddj 2026年09月28号 @param event Physical key press. */
133
+ const down = (event: Event): void => { if (ready) capture.down(event as KeyboardEvent) }
134
+ /** @author ddj 2026年09月28号 @param event Physical key release. */
135
+ const up = (event: Event): void => { if (ready) capture.up(event as KeyboardEvent) }
136
+ /** @author ddj 2026年09月28号 Reset incomplete gesture on focus loss. */
137
+ const blur = (): void => capture.reset()
138
+ /** @author ddj 2026年09月28号 Guard the complete IME composition lifecycle. */
139
+ const compose = (): void => capture.reset(true)
140
+ const listeners: Array<[string, EventListener]> = [
141
+ ['keydown', down], ['keyup', up], ['blur', blur], ['compositionstart', compose], ['compositionend', blur],
142
+ ]
143
+ /** @author ddj 2026年09月28号 Release DOM listeners and serialize native restoration. */
144
+ const dispose = (): void => {
145
+ if (disposed) return
146
+ disposed = true; ready = false
147
+ for (const [name, handler] of listeners) target.removeEventListener(name, handler, { capture: true })
148
+ void recordState(service, false).catch(hooks.error)
149
+ }
150
+ /** @author ddj 2026年09月28号 Activate capture only after native acknowledgment. */
151
+ const activate = (): void => {
152
+ if (disposed) return
153
+ ready = true
154
+ for (const [name, handler] of listeners) target.addEventListener(name, handler, { capture: true })
155
+ hooks.ready()
156
+ }
157
+ /** @author ddj 2026年09月28号 @param error Native activation failure. */
158
+ const fail = (error: unknown): void => { if (!disposed) { dispose(); hooks.error(error) } }
159
+ void recordState(service, true).then(activate, fail)
160
+ return dispose
161
+ }
@@ -8,7 +8,7 @@
8
8
  * 作者 ddj 2026年10月
9
9
  */
10
10
  import { bindingToChord, chordToBinding, defaultKeybindings, normalizeKeybindings } from '../shared/keybindings.js'
11
- import { matchEvent, parseChords } from './keybindings.js'
11
+ import { applyKeyAria, matchEvent, parseChords } from './keybindings.js'
12
12
  import type { OfficialBinding, ShortcutProfileKey } from '../shared/keybindings.js'
13
13
  import { log } from './log.js'
14
14
 
@@ -193,7 +193,9 @@ export function installLegacyKeys(
193
193
  ): () => void {
194
194
  if (!target) return () => {}
195
195
  const cache = new Map<string, ReturnType<typeof parseChords>>()
196
+ let disposed = false
196
197
  const onKey = (event: Event): void => {
198
+ if (disposed) return
197
199
  const key = event as KeyboardEvent
198
200
  const current = chords()
199
201
  for (const def of defs) {
@@ -211,8 +213,12 @@ export function installLegacyKeys(
211
213
  return
212
214
  }
213
215
  }
214
- target.addEventListener('keydown', onKey, true)
215
- return () => target.removeEventListener('keydown', onKey, true)
216
+ target.addEventListener('keydown', onKey, { capture: true })
217
+ return () => {
218
+ if (disposed) return
219
+ disposed = true
220
+ target.removeEventListener('keydown', onKey, { capture: true })
221
+ }
216
222
  }
217
223
 
218
224
  /**
@@ -283,16 +289,19 @@ export function registerOfficialShortcuts(
283
289
  export function bindCatalogChords(service: ShortcutsServiceLike, onChange: (chords: Readonly<Record<string, string>>) => void): () => void {
284
290
  const sync = (): void => {
285
291
  const chords: Record<string, string> = {}
292
+ const aria: Record<string, string | undefined> = {}
286
293
  try {
287
294
  for (const row of service.catalog.getSnapshot()) {
288
295
  if (!row.id.startsWith('edrv.') || row.binding === null) continue
289
296
  if (row.issue !== null) continue
290
- const chord = bindingToChord(row.binding)
297
+ const chord = row.keys.length ? row.keys.join(' ') : bindingToChord(row.binding)
291
298
  if (chord !== null) chords[row.id] = chord
299
+ aria[row.id] = row.aria
292
300
  }
293
301
  } catch (error) {
294
302
  bridgeLog.warn('官方目录弦表重建失败:' + String(error))
295
303
  }
304
+ applyKeyAria(aria)
296
305
  onChange(chords)
297
306
  }
298
307
  sync()
@@ -133,6 +133,13 @@ export function takePendingSideOpen(sessionId?: string): { path: string | null;
133
133
  * @param focusDiff 是否聚焦首个差异
134
134
  * @returns 是否路由成功
135
135
  */
136
+ /**
137
+ * Check the installed editor route without opening any UI.
138
+ * @author ddj 2026年09月28号
139
+ * @returns Whether a mounted sidebar adapter owns editor navigation.
140
+ */
141
+ export function hasSideRoute(): boolean { return ensureSideEditor !== null }
142
+
136
143
  export function routeSideEditor(path: string | null, focusDiff: boolean): boolean {
137
144
  if (typeof ensureSideEditor !== 'function') return false
138
145
  try {
@@ -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
  }