dsh-gufa-code 0.0.0-stage → 1.0.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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -2
  3. package/client.js +11548 -0
  4. package/cordis.patch.yml +8 -0
  5. package/docs/icon-180.png +0 -0
  6. package/docs/icon.svg +18 -0
  7. package/docs/market-submission.md +32 -0
  8. package/docs/screenshot-editor.jpg +0 -0
  9. package/docs/screenshot-ide.jpg +0 -0
  10. package/host/assets.js +31 -0
  11. package/host/chat.js +523 -0
  12. package/host/context.js +438 -0
  13. package/host/dev.js +35 -0
  14. package/host/fs.js +306 -0
  15. package/host/git.js +532 -0
  16. package/host/project.js +305 -0
  17. package/host/ssh.js +54 -0
  18. package/host/terminal-pty.js +237 -0
  19. package/host/terminal.js +947 -0
  20. package/icon.svg +18 -0
  21. package/index.js +63 -0
  22. package/locale/en.json +121 -0
  23. package/locale/zh.json +121 -0
  24. package/package.json +66 -4
  25. package/scripts/build-client.mjs +38 -0
  26. package/scripts/selftest-client.mjs +457 -0
  27. package/scripts/selftest-host.mjs +197 -0
  28. package/scripts/selftest-http.mjs +284 -0
  29. package/scripts/selftest-router.mjs +101 -0
  30. package/scripts/selftest-terminal-client.mjs +314 -0
  31. package/scripts/selftest-terminal-pty.mjs +190 -0
  32. package/scripts/selftest-terminal.mjs +351 -0
  33. package/scripts/test-chat-render.mjs +702 -0
  34. package/scripts/test-chat.mjs +761 -0
  35. package/scripts/test-editor.mjs +516 -0
  36. package/scripts/verify-workspace-sessions.mjs +135 -0
  37. package/src/client/00-core.js +1686 -0
  38. package/src/client/10-icons.js +205 -0
  39. package/src/client/20-editor.js +1318 -0
  40. package/src/client/30-terminal.js +1510 -0
  41. package/src/client/40-chat.js +1184 -0
  42. package/src/client/46-gitgraph.js +278 -0
  43. package/src/client/50-panels.js +916 -0
  44. package/src/client/55-backdrop.js +508 -0
  45. package/src/client/60-dock.js +662 -0
  46. package/src/client/85-shortcuts.js +516 -0
  47. package/src/client/90-app.js +2272 -0
  48. package/src/client/99-main.js +449 -0
  49. package/vendor/monaco/LICENSE +21 -0
  50. package/vendor/monaco/ThirdPartyNotices.txt +448 -0
  51. package/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  52. package/vendor/monaco/vs/base/worker/workerMain.js +31 -0
  53. package/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
  54. package/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
  55. package/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
  56. package/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
  57. package/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
  58. package/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
  59. package/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
  60. package/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
  61. package/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  62. package/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
  63. package/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
  64. package/vendor/monaco/vs/basic-languages/css/css.js +12 -0
  65. package/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
  66. package/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
  67. package/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  68. package/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
  69. package/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
  70. package/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
  71. package/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  72. package/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
  73. package/vendor/monaco/vs/basic-languages/go/go.js +10 -0
  74. package/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
  75. package/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
  76. package/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
  77. package/vendor/monaco/vs/basic-languages/html/html.js +10 -0
  78. package/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
  79. package/vendor/monaco/vs/basic-languages/java/java.js +10 -0
  80. package/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
  81. package/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
  82. package/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  83. package/vendor/monaco/vs/basic-languages/less/less.js +11 -0
  84. package/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
  85. package/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
  86. package/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
  87. package/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
  88. package/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  89. package/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
  90. package/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
  91. package/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
  92. package/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
  93. package/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
  94. package/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
  95. package/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  96. package/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
  97. package/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
  98. package/vendor/monaco/vs/basic-languages/php/php.js +10 -0
  99. package/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
  100. package/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
  101. package/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
  102. package/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
  103. package/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
  104. package/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
  105. package/vendor/monaco/vs/basic-languages/python/python.js +10 -0
  106. package/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
  107. package/vendor/monaco/vs/basic-languages/r/r.js +10 -0
  108. package/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
  109. package/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
  110. package/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
  111. package/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  112. package/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
  113. package/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
  114. package/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
  115. package/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
  116. package/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
  117. package/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
  118. package/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
  119. package/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
  120. package/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
  121. package/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
  122. package/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
  123. package/vendor/monaco/vs/basic-languages/st/st.js +10 -0
  124. package/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
  125. package/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  126. package/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
  127. package/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
  128. package/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
  129. package/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
  130. package/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
  131. package/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
  132. package/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
  133. package/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
  134. package/vendor/monaco/vs/editor/editor.main.css +8 -0
  135. package/vendor/monaco/vs/editor/editor.main.js +798 -0
  136. package/vendor/monaco/vs/language/css/cssMode.js +13 -0
  137. package/vendor/monaco/vs/language/css/cssWorker.js +77 -0
  138. package/vendor/monaco/vs/language/html/htmlMode.js +13 -0
  139. package/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
  140. package/vendor/monaco/vs/language/json/jsonMode.js +19 -0
  141. package/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
  142. package/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
  143. package/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
  144. package/vendor/monaco/vs/loader.js +11 -0
  145. package/vendor/monaco/vs/nls.messages.de.js +21 -0
  146. package/vendor/monaco/vs/nls.messages.es.js +21 -0
  147. package/vendor/monaco/vs/nls.messages.fr.js +19 -0
  148. package/vendor/monaco/vs/nls.messages.it.js +19 -0
  149. package/vendor/monaco/vs/nls.messages.ja.js +21 -0
  150. package/vendor/monaco/vs/nls.messages.ko.js +19 -0
  151. package/vendor/monaco/vs/nls.messages.ru.js +21 -0
  152. package/vendor/monaco/vs/nls.messages.zh-cn.js +21 -0
  153. package/vendor/monaco/vs/nls.messages.zh-tw.js +19 -0
  154. package/vendor/xterm/LICENSE +21 -0
  155. package/vendor/xterm/LICENSE.addon-fit +19 -0
  156. package/vendor/xterm/addon-fit.js +2 -0
  157. package/vendor/xterm/xterm.css +285 -0
  158. package/vendor/xterm/xterm.js +2 -0
@@ -0,0 +1,516 @@
1
+ /* ══════════════════════════════════════════════════════════════════════════
2
+ * 古法编程模式专属快捷键(参考 VS Code)。
3
+ *
4
+ * 只在 IDE 覆盖层挂载期间生效(GufaApp 内部调用 `G.useIdeShortcuts`),
5
+ * 经典模式完全不受影响;命中时在 window 捕获阶段 preventDefault + 停止传播,
6
+ * 把按键从 DSH/Monaco 的默认处理里截走。
7
+ *
8
+ * 主修饰键:macOS 用 ⌘,其他平台用 Ctrl。
9
+ * ══════════════════════════════════════════════════════════════════════════ */
10
+
11
+ /** 是否 macOS(决定主修饰键与展示文案)。 */
12
+ G.isMacPlatform = function isMacPlatform() {
13
+ try {
14
+ if (typeof navigator === 'undefined' || navigator === null) return false
15
+ const platform = typeof navigator.platform === 'string' ? navigator.platform : ''
16
+ if (platform !== '') return /Mac|iPhone|iPad|iPod/.test(platform)
17
+ return /Mac OS X|Macintosh/.test(typeof navigator.userAgent === 'string' ? navigator.userAgent : '')
18
+ } catch {
19
+ return false
20
+ }
21
+ }
22
+
23
+ /** 主修饰键是否按下。 */
24
+ function gufaShortcutModifier(event) {
25
+ return G.isMacPlatform() ? event.metaKey === true : event.ctrlKey === true
26
+ }
27
+
28
+ /**
29
+ * 把键盘事件映射成快捷键动作 id(未命中返回 null)。
30
+ * @param {KeyboardEvent|{key?:string, code?:string, metaKey?:boolean, ctrlKey?:boolean, shiftKey?:boolean, altKey?:boolean}} event
31
+ * @returns {string|null}
32
+ */
33
+ G.shortcutAction = function shortcutAction(event) {
34
+ if (event === null || event === undefined) return null
35
+ if (event.altKey === true) return null
36
+ const key = typeof event.key === 'string' ? event.key.toLowerCase() : ''
37
+ const code = typeof event.code === 'string' ? event.code : ''
38
+ const isBackquote = code === 'Backquote' || key === '`' || key === '~'
39
+ const mac = G.isMacPlatform()
40
+ if (gufaShortcutModifier(event) !== true) {
41
+ // macOS 上 ⌘` 是系统「切换窗口」,终端开关按 VS Code 惯例用 Ctrl+`。
42
+ if (mac === true && event.ctrlKey === true && isBackquote === true) {
43
+ return event.shiftKey === true ? 'newTerminal' : 'toggleTerminal'
44
+ }
45
+ return null
46
+ }
47
+ if (event.shiftKey === true) {
48
+ if (key === 'e') return 'panelExplorer'
49
+ if (key === 'g') return 'panelGit'
50
+ if (key === 'h' || key === 'u') return 'panelSsh'
51
+ if (key === 'b') return 'toggleSidebar'
52
+ if (key === 'w') return 'closeTab'
53
+ if (isBackquote) return 'newTerminal'
54
+ if (key === '/' || key === '?') return 'shortcuts'
55
+ if (key === 'enter' || code === 'Enter' || code === 'NumpadEnter') return 'commit'
56
+ return null
57
+ }
58
+ if (key === 's') return 'save'
59
+ if (key === 'k') return 'clearTerminal'
60
+ if (key === 'j' || isBackquote) return 'toggleTerminal'
61
+ if (key === 'b') return 'toggleSidebar'
62
+ if (key === 'w') return 'closeTab'
63
+ if (key === 'e' || key === 'g' || key === 'u') return null
64
+ if (key === '/') return 'shortcuts'
65
+ if (key === '?') return 'shortcuts'
66
+ return null
67
+ }
68
+
69
+ /* 当前 IDE 的快捷键处理表:由 GufaApp 挂载时发布、卸载时清空。 */
70
+ let gufaShortcutHandlers = null
71
+ /** 原生快捷键服务的注册结果(null 表示尚未注册)。 */
72
+ G.shortcutServiceActive = null
73
+ let gufaShortcutBridgeInstalled = false
74
+
75
+ /** 在插件启动时安装一次全局捕获监听(早于任何 React 组件,稳定可复用)。 */
76
+ G.installShortcutBridge = function installShortcutBridge() {
77
+ if (typeof window === 'undefined' || window === null || typeof window.addEventListener !== 'function') return
78
+ // 插件热重载会重新执行本模块:先移除上一代留下的监听器,避免多个桥重复拦截同一按键
79
+ try {
80
+ if (typeof window.__gufaShortcutBridgeDispose === 'function') window.__gufaShortcutBridgeDispose()
81
+ } catch {
82
+ /* ignore */
83
+ }
84
+ window.__gufaShortcutBridgeDispose = null
85
+ gufaShortcutBridgeInstalled = true
86
+ try {
87
+ window.__gufaShortcutBridge = true
88
+ } catch {
89
+ /* ignore */
90
+ }
91
+ const onKeyDown = (event) => {
92
+ if (event === null || event === undefined) return
93
+ try {
94
+ if (gufaKeyDiagnosticsLeft > 0 && (event.metaKey === true || event.ctrlKey === true)) {
95
+ gufaKeyDiagnosticsLeft -= 1
96
+ const target = event.target
97
+ void G.api('dev.log', {
98
+ message: `key key=${String(event.key)} code=${String(event.code)} meta=${event.metaKey === true} ctrl=${event.ctrlKey === true} shift=${event.shiftKey === true} alt=${event.altKey === true} prevented=${event.defaultPrevented === true} target=${target === null || target === undefined ? '?' : String(target.tagName ?? '?')} platform=${typeof navigator === 'undefined' ? '?' : String(navigator.platform ?? '?')} bridge=1`,
99
+ }).catch(() => {})
100
+ }
101
+ } catch {
102
+ /* 诊断失败不影响快捷键 */
103
+ }
104
+ if (event.defaultPrevented === true) return
105
+ const handlers = gufaShortcutHandlers
106
+ if (handlers === null) return
107
+ let action = null
108
+ try {
109
+ action = G.shortcutAction(event)
110
+ } catch {
111
+ return
112
+ }
113
+ if (action === null) return
114
+ if (G.shortcutServiceActive !== null && G.shortcutServiceActive !== undefined && G.shortcutServiceActive.runtime === 'web' && G.shortcutServiceActive.actions.has(action)) return
115
+ const handler = handlers[action]
116
+ if (typeof handler !== 'function') return
117
+ try {
118
+ const modalOpen = document.querySelector('.gufa-modal-backdrop, .gufa-session-backdrop') !== null
119
+ if (modalOpen && action !== 'shortcuts') return
120
+ } catch {
121
+ /* ignore */
122
+ }
123
+ event.preventDefault()
124
+ event.stopPropagation()
125
+ try {
126
+ handler()
127
+ if (gufaShortcutFiredLogsLeft > 0) {
128
+ gufaShortcutFiredLogsLeft -= 1
129
+ void G.api('dev.log', { message: `shortcut-fired ${action} key=${String(event.key)}` }).catch(() => {})
130
+ }
131
+ } catch (error) {
132
+ console.error('[gufa-code] shortcut handler failed', error)
133
+ }
134
+ }
135
+ window.addEventListener('keydown', onKeyDown, true)
136
+ window.__gufaShortcutBridgeDispose = () => {
137
+ try {
138
+ window.removeEventListener('keydown', onKeyDown, true)
139
+ } catch {
140
+ /* ignore */
141
+ }
142
+ }
143
+ }
144
+
145
+ /** 剩余按键诊断次数(排查用,命中后清零;无按键则自然闲置)。 */
146
+ let gufaKeyDiagnosticsLeft = 80
147
+ /** 剩余「快捷键命中」日志次数。 */
148
+ let gufaShortcutFiredLogsLeft = 40
149
+
150
+ /* ── 注册到 DSH 原生快捷键服务(桌面端由 Electron main 拦截物理按键) ──────
151
+ * 桌面端配置型快捷键由 main 进程的 before-input-event 拦截后通过 IPC 回送,
152
+ * 页面里根本收不到 keydown(这正是之前「快捷键全不好用、⌘B 收经典侧栏」的原因)。
153
+ * 通过 ctx.shortcuts.register 注册后:绑定进入 main 的按键索引 + DSH 快捷键设置页。 */
154
+
155
+ /** 本插件的 IDE 命令表:action ↔ 默认绑定。 */
156
+ const GUFA_SHORTCUT_COMMANDS = [
157
+ { action: 'save', id: 'gufa.ide.save', labelKey: 'shortcuts.save', code: 'KeyS', modifiers: ['primary'], regions: ['page', 'editable', 'terminal'] },
158
+ { action: 'clearTerminal', id: 'gufa.ide.clearTerminal', labelKey: 'shortcuts.clearTerminal', code: 'KeyK', modifiers: ['primary'], regions: ['page', 'editable', 'terminal'] },
159
+ { action: 'toggleTerminal', id: 'gufa.ide.toggleTerminal', labelKey: 'shortcuts.toggleTerminal', code: 'KeyJ', modifiers: ['primary'], regions: ['page', 'editable', 'terminal'] },
160
+ { action: 'toggleSidebar', id: 'gufa.ide.toggleSidebar', labelKey: 'shortcuts.toggleSidebar', code: 'KeyB', modifiers: ['primary', 'shift'], regions: ['page', 'editable'] },
161
+ { action: 'closeTab', id: 'gufa.ide.closeTab', labelKey: 'shortcuts.closeTab', code: 'KeyW', modifiers: ['primary', 'shift'], regions: ['page', 'editable'] },
162
+ { action: 'panelExplorer', id: 'gufa.ide.panelExplorer', labelKey: 'shortcuts.explorer', code: 'KeyE', modifiers: ['primary', 'shift'], regions: ['page', 'editable'] },
163
+ { action: 'panelGit', id: 'gufa.ide.panelGit', labelKey: 'shortcuts.git', code: 'KeyG', modifiers: ['primary', 'shift'], regions: ['page', 'editable'] },
164
+ { action: 'panelSsh', id: 'gufa.ide.panelSsh', labelKey: 'shortcuts.ssh', code: 'KeyU', modifiers: ['primary', 'shift'], regions: ['page', 'editable'] },
165
+ { action: 'newTerminal', id: 'gufa.ide.newTerminal', labelKey: 'shortcuts.newTerminal', code: 'Backquote', modifiers: ['control', 'shift'], regions: ['page', 'editable', 'terminal'] },
166
+ { action: 'shortcuts', id: 'gufa.ide.shortcuts', labelKey: 'shortcuts.help', code: 'Slash', modifiers: ['primary', 'shift'], regions: ['page', 'editable', 'terminal'] },
167
+ ]
168
+
169
+ /** 桌面三平台的默认绑定(web 端不设默认,避免和浏览器冲突)。 */
170
+ function gufaDesktopDefaults(code, modifiers) {
171
+ const binding = { code, modifiers }
172
+ return { 'desktop:macos': binding, 'desktop:windows': binding, 'desktop:linux': binding }
173
+ }
174
+
175
+ /**
176
+ * 把 IDE 命令注册进 DSH 原生快捷键服务。
177
+ * @param {object} scope 已注入 shortcuts 服务的 Cordis 作用域
178
+ * @returns {boolean} 是否注册成功
179
+ */
180
+ /**
181
+ * 与 DSH 原生快捷键系统集成(不修改它的定义/配置,避免影响经典模式)。
182
+ *
183
+ * 桌面端 Electron main 会在按键到达页面前拦截「已配置」的快捷键并回送 IPC,
184
+ * 页面里的 keydown 收不到这些字母键。这里只做两件事:
185
+ * 1. 撤销旧版本可能残留的 registry.dispatch 补丁;
186
+ * 2. 在 IDE 模式下,把被经典模式/其它插件占用的命令(⌘K/⌘B/⌘S/⌘W/⌘/…)
187
+ * 在 resolve 处改派为 IDE 动作(经典模式下原样执行)。
188
+ * 不调用 shortcuts.register / 不写 keybindings 配置,因此不会让 main 的快捷键索引失效。
189
+ */
190
+ G.installShortcutIntegration = function installShortcutIntegration(service) {
191
+ try {
192
+ const registry = service === null || service === undefined ? undefined : service.registry
193
+ if (registry === null || registry === undefined) return false
194
+
195
+ // 1) 撤销旧版本(如果有)对 dispatch 的补丁
196
+ if (registry.__gufaPatched === true) {
197
+ if (typeof registry.__gufaOriginalDispatch === 'function') registry.dispatch = registry.__gufaOriginalDispatch
198
+ delete registry.__gufaPatched
199
+ delete registry.__gufaHandle
200
+ delete registry.__gufaOriginalDispatch
201
+ }
202
+
203
+ // 2) IDE 模式内的命令改派表(被原生占用的组合 → IDE 动作)
204
+ const commandActions = {
205
+ 'session.search': 'clearTerminal',
206
+ 'sidebar.left.toggle': 'toggleSidebar',
207
+ 'edrv.save': 'save',
208
+ 'edrv.closeTab': 'closeTab',
209
+ 'edrv.reloadWindow': 'refresh',
210
+ 'edrv.toggleSidebar': 'toggleSidebar',
211
+ 'shortcuts.open': 'shortcuts',
212
+ 'edrv.toggleComment': 'shortcuts',
213
+ 'edrv.showCommands': 'shortcutsDialog',
214
+ 'edrv.searchInFiles': 'panelExplorer',
215
+ 'terminal.new': 'toggleTerminal',
216
+ // 编辑器动作:edrv.* 抢占的 VS Code 键位,改派给 Monaco
217
+ 'edrv.addSelectionRef': 'editor:cursorUndo',
218
+ 'edrv.goToLine': 'editor:editor.action.gotoLine',
219
+ 'edrv.prevEditorRow': 'editor:editor.action.moveLinesUpAction',
220
+ 'edrv.nextEditorRow': 'editor:editor.action.moveLinesDownAction',
221
+ 'edrv.prevTab': 'prevTab',
222
+ 'edrv.nextTab': 'nextTab',
223
+ }
224
+ service.__gufaCommandActions = commandActions
225
+ const wrapped = []
226
+ const commands = registry.commands
227
+ const makeResolve = (id, originalResolve) => function gufaIdeResolve(context) {
228
+ try {
229
+ if (G.mode.get() === true) {
230
+ const actions = service.__gufaCommandActions
231
+ const mapped = actions === null || actions === undefined ? undefined : actions[id]
232
+ if (typeof mapped === 'string') {
233
+ return {
234
+ status: 'handled',
235
+ run: () => {
236
+ try {
237
+ if (mapped.startsWith('editor:')) {
238
+ if (G.bus !== null && G.bus !== undefined) G.bus.emit('editor:command', mapped.slice('editor:'.length))
239
+ return
240
+ }
241
+ if (typeof window.__gufaIdeShortcutDispatch === 'function') window.__gufaIdeShortcutDispatch(mapped)
242
+ } catch (error) {
243
+ console.error('[gufa-code] IDE shortcut dispatch failed', error)
244
+ }
245
+ },
246
+ }
247
+ }
248
+ }
249
+ } catch {
250
+ /* 改派失败则退回原命令 */
251
+ }
252
+ return originalResolve.call(this, context)
253
+ }
254
+ if (commands !== null && commands !== undefined && typeof commands.get === 'function') {
255
+ for (const [id, action] of Object.entries(commandActions)) {
256
+ const command = commands.get(id)
257
+ if (command === null || command === undefined) continue
258
+ const originalResolve = command.__gufaWrapped === true && typeof command.__gufaOriginalResolve === 'function'
259
+ ? command.__gufaOriginalResolve
260
+ : command.resolve
261
+ if (typeof originalResolve !== 'function') continue
262
+ try {
263
+ Object.defineProperty(command, '__gufaOriginalResolve', { value: originalResolve, configurable: true })
264
+ } catch {
265
+ /* ignore */
266
+ }
267
+ command.resolve = makeResolve(id, originalResolve)
268
+ try {
269
+ Object.defineProperty(command, '__gufaWrapped', { value: true, configurable: true })
270
+ } catch {
271
+ /* ignore */
272
+ }
273
+ wrapped.push(id)
274
+ }
275
+ }
276
+ // ── 命令表/配置尚未就绪时重试(插件加载可能早于 ui-layout 注册命令)──
277
+ const expected = Object.keys(commandActions).length
278
+ try {
279
+ if (typeof window !== 'undefined' && window !== null) {
280
+ if (wrapped.length < expected) {
281
+ if (service.__gufaIntegrationTimer === undefined || service.__gufaIntegrationTimer === null) {
282
+ let attempts = 0
283
+ service.__gufaIntegrationTimer = window.setInterval(() => {
284
+ attempts += 1
285
+ if (attempts > 40) {
286
+ window.clearInterval(service.__gufaIntegrationTimer)
287
+ service.__gufaIntegrationTimer = null
288
+ return
289
+ }
290
+ try {
291
+ G.installShortcutIntegration(service)
292
+ } catch {
293
+ /* ignore */
294
+ }
295
+ }, 500)
296
+ }
297
+ } else {
298
+ if (service.__gufaIntegrationTimer !== undefined && service.__gufaIntegrationTimer !== null) {
299
+ window.clearInterval(service.__gufaIntegrationTimer)
300
+ service.__gufaIntegrationTimer = null
301
+ }
302
+ if (service.__gufaIntegrationSubscribed !== true && service.config !== null && service.config !== undefined && typeof service.config.subscribe === 'function') {
303
+ service.__gufaIntegrationSubscribed = true
304
+ try {
305
+ service.config.subscribe(() => {
306
+ try {
307
+ if (service.__gufaIntegrationTimer === undefined || service.__gufaIntegrationTimer === null) G.installShortcutIntegration(service)
308
+ } catch {
309
+ /* ignore */
310
+ }
311
+ })
312
+ } catch {
313
+ /* ignore */
314
+ }
315
+ }
316
+ }
317
+ }
318
+ } catch {
319
+ /* 重试安装失败不影响主流程 */
320
+ }
321
+
322
+ // ── 一次性自检:确认改派在 IDE 模式下真的会执行(不会影响经典模式)──
323
+ try {
324
+ if (typeof window !== 'undefined' && window !== null && window.sessionStorage !== undefined) {
325
+ if (window.sessionStorage.getItem('gufa-shortcut-selftest-v2') !== '1' && wrapped.length > 0) {
326
+ window.sessionStorage.setItem('gufa-shortcut-selftest-v2', '1')
327
+ window.setTimeout(() => {
328
+ try {
329
+ const command = commands !== null && commands !== undefined && typeof commands.get === 'function' ? commands.get('session.search') : undefined
330
+ if (command === undefined || command === null) return
331
+ const result = command.resolve({ region: 'page', modal: null, target: null })
332
+ const status = result !== null && result !== undefined ? String(result.status) : 'null'
333
+ const runnable = typeof result?.run === 'function'
334
+ if (runnable) {
335
+ try {
336
+ result.run()
337
+ } catch {
338
+ /* ignore */
339
+ }
340
+ }
341
+ void G.api('dev.log', { message: `shortcut-remap-selftest ide=${G.mode.get() === true} status=${status} runnable=${runnable}` }).catch(() => {})
342
+ } catch (error) {
343
+ void G.api('dev.log', { message: `shortcut-remap-selftest-error ${String(error)}` }).catch(() => {})
344
+ }
345
+ }, 2500)
346
+ }
347
+ }
348
+ } catch {
349
+ /* 自检失败不影响快捷键 */
350
+ }
351
+
352
+ // ── ⌘/ 链路自检(替换 dispatch 回调,验证路由但不弹窗)──
353
+ try {
354
+ if (typeof window !== 'undefined' && window !== null && window.sessionStorage !== undefined) {
355
+ if (window.sessionStorage.getItem('gufa-shortcut-selftest-v3') !== '1' && wrapped.length > 0) {
356
+ window.sessionStorage.setItem('gufa-shortcut-selftest-v3', '1')
357
+ window.setTimeout(() => {
358
+ try {
359
+ const mac = G.isMacPlatform()
360
+ const gesture = { code: 'Slash', meta: mac, control: mac === false, shift: false, alt: false, repeat: false }
361
+ const saved = window.__gufaIdeShortcutDispatch
362
+ const fired = []
363
+ window.__gufaIdeShortcutDispatch = (action) => {
364
+ fired.push(action)
365
+ }
366
+ let result
367
+ try {
368
+ result = service.registry.dispatch(gesture, { region: 'page', modal: null, target: null }, () => {})
369
+ } finally {
370
+ window.__gufaIdeShortcutDispatch = saved
371
+ }
372
+ void G.api('dev.log', {
373
+ message: `shortcut-cmd-slash-selftest ide=${G.mode.get() === true} editorFocused=${typeof G.isEditorFocused === 'function' ? G.isEditorFocused() === true : '?'} fired=${JSON.stringify(fired)} result=${JSON.stringify(result).slice(0, 200)}`,
374
+ }).catch(() => {})
375
+ } catch (error) {
376
+ void G.api('dev.log', { message: `shortcut-cmd-slash-selftest-error ${String(error)}` }).catch(() => {})
377
+ }
378
+ }, 2500)
379
+ }
380
+ }
381
+ } catch {
382
+ /* 自检失败不影响快捷键 */
383
+ }
384
+
385
+ try {
386
+ let configStatus = '?'
387
+ try {
388
+ const snapshot = service.config !== null && service.config !== undefined && typeof service.config.getSnapshot === 'function' ? service.config.getSnapshot() : null
389
+ configStatus = snapshot === null ? '?' : String(snapshot.status)
390
+ } catch {
391
+ configStatus = '?'
392
+ }
393
+ const totalCommands = commands !== null && commands !== undefined && typeof commands.size === 'number' ? commands.size : '?'
394
+ let edrvBindings = []
395
+ try {
396
+ const bindingsMap = registry.bindings
397
+ if (bindingsMap !== null && bindingsMap !== undefined && typeof bindingsMap.forEach === 'function') {
398
+ bindingsMap.forEach((command, bindingKeyValue) => {
399
+ try {
400
+ if (command !== null && command !== undefined && typeof command.id === 'string' && command.id.startsWith('edrv.')) {
401
+ edrvBindings.push(`${bindingKeyValue}>${command.id}`)
402
+ }
403
+ } catch {
404
+ /* ignore */
405
+ }
406
+ })
407
+ }
408
+ } catch {
409
+ edrvBindings = []
410
+ }
411
+ let edrvIds = []
412
+ try {
413
+ if (commands !== null && commands !== undefined && typeof commands.keys === 'function') edrvIds = [...commands.keys()].filter((id) => typeof id === 'string' && id.startsWith('edrv.'))
414
+ } catch {
415
+ edrvIds = []
416
+ }
417
+ void G.api('dev.log', { message: `shortcut-integration wrapped=${JSON.stringify(wrapped)} total=${totalCommands} configStatus=${configStatus} edrv=${JSON.stringify(edrvIds).slice(0, 1500)} edrvBindings=${JSON.stringify(edrvBindings).slice(0, 2500)}` }).catch(() => {})
418
+ } catch {
419
+ /* ignore */
420
+ }
421
+ return true
422
+ } catch (error) {
423
+ console.error('[gufa-code] install shortcut integration failed', error)
424
+ return false
425
+ }
426
+ }
427
+
428
+ /** 编辑器(Monaco,默认键位与 VS Code 一致)快捷键说明表。 */
429
+ G.EDITOR_SHORTCUTS = [
430
+ { labelKey: 'editorKeys.comment', mac: '⌘/', other: 'Ctrl+/' },
431
+ { labelKey: 'editorKeys.blockComment', mac: '⇧⌥A', other: 'Shift+Alt+A' },
432
+ { labelKey: 'editorKeys.nextOccurrence', mac: '⌘D', other: 'Ctrl+D' },
433
+ { labelKey: 'editorKeys.allOccurrences', mac: '⌘⇧L', other: 'Ctrl+Shift+L' },
434
+ { labelKey: 'editorKeys.cursorAboveBelow', mac: '⌘⌥↑ / ⌘⌥↓', other: 'Ctrl+Alt+↑/↓' },
435
+ { labelKey: 'editorKeys.undoCursor', mac: '⌘U', other: 'Ctrl+U' },
436
+ { labelKey: 'editorKeys.moveLine', mac: '⌥↑ / ⌥↓', other: 'Alt+↑/↓' },
437
+ { labelKey: 'editorKeys.copyLine', mac: '⇧⌥↑ / ⇧⌥↓', other: 'Shift+Alt+↑/↓' },
438
+ { labelKey: 'editorKeys.deleteLine', mac: '⌘⇧K', other: 'Ctrl+Shift+K' },
439
+ { labelKey: 'editorKeys.insertLine', mac: '⌘↵ / ⇧⌘↵', other: 'Ctrl+Enter / Shift+Ctrl+Enter' },
440
+ { labelKey: 'editorKeys.indent', mac: '⌘] / ⌘[', other: 'Ctrl+] / Ctrl+[' },
441
+ { labelKey: 'editorKeys.columnSelect', mac: '⇧⌥ + 鼠标拖拽', other: 'Shift+Alt + drag' },
442
+ { labelKey: 'editorKeys.find', mac: '⌘F / ⌥⌘F', other: 'Ctrl+F / Ctrl+H' },
443
+ { labelKey: 'editorKeys.goToLine', mac: '⌘G', other: 'Ctrl+G' },
444
+ { labelKey: 'editorKeys.format', mac: '⇧⌥F', other: 'Shift+Alt+F' },
445
+ { labelKey: 'editorKeys.rename', mac: 'F2', other: 'F2' },
446
+ ]
447
+
448
+ /** 快捷键说明表(渲染用)。keys 只写字母/符号,修饰键由平台决定。 *//** 编辑器(Monaco,默认键位与 VS Code 一致)快捷键说明表。 */
449
+ G.EDITOR_SHORTCUTS = [
450
+ { labelKey: 'editorKeys.comment', mac: '⌘/', other: 'Ctrl+/' },
451
+ { labelKey: 'editorKeys.blockComment', mac: '⇧⌥A', other: 'Shift+Alt+A' },
452
+ { labelKey: 'editorKeys.nextOccurrence', mac: '⌘D', other: 'Ctrl+D' },
453
+ { labelKey: 'editorKeys.allOccurrences', mac: '⌘⇧L', other: 'Ctrl+Shift+L' },
454
+ { labelKey: 'editorKeys.cursorAboveBelow', mac: '⌘⌥↑ / ⌘⌥↓', other: 'Ctrl+Alt+↑/↓' },
455
+ { labelKey: 'editorKeys.undoCursor', mac: '⌘U', other: 'Ctrl+U' },
456
+ { labelKey: 'editorKeys.moveLine', mac: '⌥↑ / ⌥↓', other: 'Alt+↑/↓' },
457
+ { labelKey: 'editorKeys.copyLine', mac: '⇧⌥↑ / ⇧⌥↓', other: 'Shift+Alt+↑/↓' },
458
+ { labelKey: 'editorKeys.deleteLine', mac: '⌘⇧K', other: 'Ctrl+Shift+K' },
459
+ { labelKey: 'editorKeys.insertLine', mac: '⌘↵ / ⇧⌘↵', other: 'Ctrl+Enter / Shift+Ctrl+Enter' },
460
+ { labelKey: 'editorKeys.indent', mac: '⌘] / ⌘[', other: 'Ctrl+] / Ctrl+[' },
461
+ { labelKey: 'editorKeys.columnSelect', mac: '⇧⌥ + 鼠标拖拽', other: 'Shift+Alt + drag' },
462
+ { labelKey: 'editorKeys.find', mac: '⌘F / ⌥⌘F', other: 'Ctrl+F / Ctrl+H' },
463
+ { labelKey: 'editorKeys.goToLine', mac: '⌘G', other: 'Ctrl+G' },
464
+ { labelKey: 'editorKeys.format', mac: '⇧⌥F', other: 'Shift+Alt+F' },
465
+ { labelKey: 'editorKeys.rename', mac: 'F2', other: 'F2' },
466
+ ]
467
+
468
+ /** 快捷键说明表(渲染用)。keys 只写字母/符号,修饰键由平台决定。 */
469
+ G.SHORTCUTS = [
470
+ { action: 'save', labelKey: 'shortcuts.save', keys: ['S'], shift: false },
471
+ { action: 'commit', labelKey: 'shortcuts.commit', keys: ['Enter'], shift: true, macText: '⌘⇧↵', otherText: 'Ctrl+Shift+Enter' },
472
+ { action: 'clearTerminal', labelKey: 'shortcuts.clearTerminal', keys: ['K'], shift: false },
473
+ { action: 'toggleTerminal', labelKey: 'shortcuts.toggleTerminal', keys: ['J'], shift: false, macText: '⌘J / ⌃`', otherText: 'Ctrl+J / Ctrl+`' },
474
+ { action: 'toggleSidebar', labelKey: 'shortcuts.toggleSidebar', keys: ['B'], shift: false },
475
+ { action: 'closeTab', labelKey: 'shortcuts.closeTab', keys: ['W'], shift: false },
476
+ { action: 'panelExplorer', labelKey: 'shortcuts.explorer', keys: ['E'], shift: true },
477
+ { action: 'panelGit', labelKey: 'shortcuts.git', keys: ['G'], shift: true },
478
+ { action: 'panelSsh', labelKey: 'shortcuts.ssh', keys: ['U'], shift: true },
479
+ { action: 'newTerminal', labelKey: 'shortcuts.newTerminal', keys: ['`'], shift: true, macText: '⌃⇧`', otherText: 'Ctrl+Shift+`' },
480
+ { action: 'shortcuts', labelKey: 'shortcuts.help', keys: ['/'], shift: false },
481
+ ]
482
+
483
+ /** 渲染一个组合键文案,例如 macOS: ⌘⇧E / 其他: Ctrl+Shift+E。 */
484
+ G.shortcutKeyText = function shortcutKeyText(entry) {
485
+ if (entry === null || entry === undefined) return ''
486
+ const mac = G.isMacPlatform()
487
+ if (mac === true) {
488
+ if (typeof entry.macText === 'string') return entry.macText
489
+ } else if (typeof entry.otherText === 'string') {
490
+ return entry.otherText
491
+ }
492
+ const keys = Array.isArray(entry.keys) ? entry.keys : []
493
+ const key = keys.length === 0 ? '' : keys[0]
494
+ const shown = key === '`' ? '`' : key.toUpperCase()
495
+ if (mac === true) return `⌘${entry.shift === true ? '⇧' : ''}${shown}`
496
+ return `Ctrl+${entry.shift === true ? 'Shift+' : ''}${shown}`
497
+ }
498
+
499
+ /**
500
+ * 安装 IDE 快捷键层:handlers[action] 为对应动作。
501
+ * @param {Record<string, () => void>} handlers
502
+ */
503
+ G.useIdeShortcuts = function useIdeShortcuts(handlers) {
504
+ useEffect(() => {
505
+ gufaShortcutHandlers = handlers
506
+ try {
507
+ window.__gufaShortcutHandlers = true
508
+ } catch {
509
+ /* ignore */
510
+ }
511
+ if (typeof G.installShortcutBridge === 'function') G.installShortcutBridge()
512
+ return () => {
513
+ if (gufaShortcutHandlers === handlers) gufaShortcutHandlers = null
514
+ }
515
+ }, [handlers])
516
+ }