@theia/core 1.77.0-next.49 → 1.77.0-next.57

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 (377) hide show
  1. package/lib/browser/breadcrumbs/breadcrumbs-renderer.js +1 -1
  2. package/lib/browser/breadcrumbs/breadcrumbs-renderer.js.map +1 -1
  3. package/lib/browser/catalog.json +81 -0
  4. package/lib/browser/color-application-contribution.d.ts +1 -1
  5. package/lib/browser/color-application-contribution.d.ts.map +1 -1
  6. package/lib/browser/color-registry.d.ts +1 -1
  7. package/lib/browser/color-registry.d.ts.map +1 -1
  8. package/lib/browser/common-frontend-contribution.d.ts +6 -1
  9. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  10. package/lib/browser/common-frontend-contribution.js +20 -3
  11. package/lib/browser/common-frontend-contribution.js.map +1 -1
  12. package/lib/browser/common-frontend-contribution.spec.js +2 -1
  13. package/lib/browser/common-frontend-contribution.spec.js.map +1 -1
  14. package/lib/browser/components/card.d.ts +1 -1
  15. package/lib/browser/components/card.d.ts.map +1 -1
  16. package/lib/browser/components/card.js +3 -2
  17. package/lib/browser/components/card.js.map +1 -1
  18. package/lib/browser/connection-status-service.spec.js +2 -1
  19. package/lib/browser/connection-status-service.spec.js.map +1 -1
  20. package/lib/browser/context-key-service.d.ts +11 -0
  21. package/lib/browser/context-key-service.d.ts.map +1 -1
  22. package/lib/browser/context-key-service.js +7 -0
  23. package/lib/browser/context-key-service.js.map +1 -1
  24. package/lib/browser/decorations-service.spec.js +2 -1
  25. package/lib/browser/decorations-service.spec.js.map +1 -1
  26. package/lib/browser/dialogs/react-dialog.spec.js +2 -1
  27. package/lib/browser/dialogs/react-dialog.spec.js.map +1 -1
  28. package/lib/browser/diff-uris.js +1 -1
  29. package/lib/browser/diff-uris.js.map +1 -1
  30. package/lib/browser/endpoint.js +2 -1
  31. package/lib/browser/endpoint.js.map +1 -1
  32. package/lib/browser/endpoint.spec.js +2 -1
  33. package/lib/browser/endpoint.spec.js.map +1 -1
  34. package/lib/browser/keybinding.d.ts +95 -17
  35. package/lib/browser/keybinding.d.ts.map +1 -1
  36. package/lib/browser/keybinding.js +406 -76
  37. package/lib/browser/keybinding.js.map +1 -1
  38. package/lib/browser/keybinding.spec.js +789 -33
  39. package/lib/browser/keybinding.spec.js.map +1 -1
  40. package/lib/browser/keyboard/browser-keyboard-frontend-contribution.js +1 -1
  41. package/lib/browser/keyboard/browser-keyboard-frontend-contribution.js.map +1 -1
  42. package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts +3 -4
  43. package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts.map +1 -1
  44. package/lib/browser/keyboard/browser-keyboard-layout-provider.js +3 -3
  45. package/lib/browser/keyboard/browser-keyboard-layout-provider.js.map +1 -1
  46. package/lib/browser/keyboard/browser-keyboard-layout-provider.spec.js +42 -3
  47. package/lib/browser/keyboard/browser-keyboard-layout-provider.spec.js.map +1 -1
  48. package/lib/browser/keyboard/keyboard-layout-service.d.ts +67 -15
  49. package/lib/browser/keyboard/keyboard-layout-service.d.ts.map +1 -1
  50. package/lib/browser/keyboard/keyboard-layout-service.js +309 -98
  51. package/lib/browser/keyboard/keyboard-layout-service.js.map +1 -1
  52. package/lib/browser/keyboard/keyboard-layout-service.spec.js +541 -15
  53. package/lib/browser/keyboard/keyboard-layout-service.spec.js.map +1 -1
  54. package/lib/browser/keyboard/keys.spec.js +187 -3
  55. package/lib/browser/keyboard/keys.spec.js.map +1 -1
  56. package/lib/browser/label-provider.d.ts.map +1 -1
  57. package/lib/browser/label-provider.js +5 -3
  58. package/lib/browser/label-provider.js.map +1 -1
  59. package/lib/browser/label-provider.spec.js +2 -1
  60. package/lib/browser/label-provider.spec.js.map +1 -1
  61. package/lib/browser/markdown-rendering/markdown-link-handler.js +2 -1
  62. package/lib/browser/markdown-rendering/markdown-link-handler.js.map +1 -1
  63. package/lib/browser/markdown-rendering/markdown-renderer.d.ts +1 -1
  64. package/lib/browser/markdown-rendering/markdown-renderer.d.ts.map +1 -1
  65. package/lib/browser/markdown-rendering/markdown-renderer.js +4 -4
  66. package/lib/browser/markdown-rendering/markdown-renderer.js.map +1 -1
  67. package/lib/browser/markdown-rendering/markdown.js +2 -1
  68. package/lib/browser/markdown-rendering/markdown.js.map +1 -1
  69. package/lib/browser/markdown-rendering/markdown.spec.js +2 -1
  70. package/lib/browser/markdown-rendering/markdown.spec.js.map +1 -1
  71. package/lib/browser/menu/action-menu-node.d.ts +5 -3
  72. package/lib/browser/menu/action-menu-node.d.ts.map +1 -1
  73. package/lib/browser/menu/action-menu-node.js +2 -11
  74. package/lib/browser/menu/action-menu-node.js.map +1 -1
  75. package/lib/browser/menu/action-menu-node.spec.d.ts +2 -0
  76. package/lib/browser/menu/action-menu-node.spec.d.ts.map +1 -0
  77. package/lib/browser/menu/action-menu-node.spec.js +54 -0
  78. package/lib/browser/menu/action-menu-node.spec.js.map +1 -0
  79. package/lib/browser/menu/browser-menu-plugin.js +1 -1
  80. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  81. package/lib/browser/menu/menu.spec.js +2 -1
  82. package/lib/browser/menu/menu.spec.js.map +1 -1
  83. package/lib/browser/opener-service.spec.js +3 -2
  84. package/lib/browser/opener-service.spec.js.map +1 -1
  85. package/lib/browser/perspective-service.spec.js +2 -1
  86. package/lib/browser/perspective-service.spec.js.map +1 -1
  87. package/lib/browser/preferences/preference-proxy.spec.js +2 -1
  88. package/lib/browser/preferences/preference-proxy.spec.js.map +1 -1
  89. package/lib/browser/preferences/preference-schema-provider.spec.js +2 -1
  90. package/lib/browser/preferences/preference-schema-provider.spec.js.map +1 -1
  91. package/lib/browser/preferences/preference-service.spec.js +2 -1
  92. package/lib/browser/preferences/preference-service.spec.js.map +1 -1
  93. package/lib/browser/preferences/preference-validation-service.spec.js +78 -77
  94. package/lib/browser/preferences/preference-validation-service.spec.js.map +1 -1
  95. package/lib/browser/quick-input/quick-command-service.d.ts +10 -1
  96. package/lib/browser/quick-input/quick-command-service.d.ts.map +1 -1
  97. package/lib/browser/quick-input/quick-command-service.js +31 -6
  98. package/lib/browser/quick-input/quick-command-service.js.map +1 -1
  99. package/lib/browser/quick-input/quick-command-service.spec.d.ts +2 -0
  100. package/lib/browser/quick-input/quick-command-service.spec.d.ts.map +1 -0
  101. package/lib/browser/quick-input/quick-command-service.spec.js +130 -0
  102. package/lib/browser/quick-input/quick-command-service.spec.js.map +1 -0
  103. package/lib/browser/quick-input/quick-help-service.js +1 -1
  104. package/lib/browser/quick-input/quick-help-service.js.map +1 -1
  105. package/lib/browser/quick-input/quick-input-service.spec.js +2 -1
  106. package/lib/browser/quick-input/quick-input-service.spec.js.map +1 -1
  107. package/lib/browser/quick-input/quick-pick-service-impl.d.ts +6 -6
  108. package/lib/browser/quick-input/quick-pick-service-impl.d.ts.map +1 -1
  109. package/lib/browser/quick-input/quick-view-service.spec.js +2 -1
  110. package/lib/browser/quick-input/quick-view-service.spec.js.map +1 -1
  111. package/lib/browser/secondary-window-handler.d.ts +4 -4
  112. package/lib/browser/secondary-window-handler.d.ts.map +1 -1
  113. package/lib/browser/shell/application-shell.js +1 -1
  114. package/lib/browser/shell/application-shell.js.map +1 -1
  115. package/lib/browser/shell/application-shell.spec.js +2 -1
  116. package/lib/browser/shell/application-shell.spec.js.map +1 -1
  117. package/lib/browser/shell/shell-layout-restorer.spec.js +2 -1
  118. package/lib/browser/shell/shell-layout-restorer.spec.js.map +1 -1
  119. package/lib/browser/shell/side-panel-toolbar.d.ts +1 -1
  120. package/lib/browser/shell/side-panel-toolbar.d.ts.map +1 -1
  121. package/lib/browser/shell/sidebar-menu-widget.js +1 -1
  122. package/lib/browser/shell/sidebar-menu-widget.js.map +1 -1
  123. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +1 -1
  124. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  125. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js +2 -1
  126. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js.map +1 -1
  127. package/lib/browser/shell/tab-bars.js +2 -1
  128. package/lib/browser/shell/tab-bars.js.map +1 -1
  129. package/lib/browser/status-bar/status-bar.js +1 -1
  130. package/lib/browser/status-bar/status-bar.js.map +1 -1
  131. package/lib/browser/storage-service.spec.js +2 -1
  132. package/lib/browser/storage-service.spec.js.map +1 -1
  133. package/lib/browser/test/jsdom.d.ts.map +1 -1
  134. package/lib/browser/test/jsdom.js +11 -0
  135. package/lib/browser/test/jsdom.js.map +1 -1
  136. package/lib/browser/tooltip-service.js +1 -1
  137. package/lib/browser/tooltip-service.js.map +1 -1
  138. package/lib/browser/tree/tree-consistency.spec.js +1 -1
  139. package/lib/browser/tree/tree-consistency.spec.js.map +1 -1
  140. package/lib/browser/tree/tree-container.spec.js +6 -5
  141. package/lib/browser/tree/tree-container.spec.js.map +1 -1
  142. package/lib/browser/tree/tree-iterator.js +1 -1
  143. package/lib/browser/tree/tree-iterator.js.map +1 -1
  144. package/lib/browser/tree/tree-model.js +2 -2
  145. package/lib/browser/tree/tree-model.js.map +1 -1
  146. package/lib/browser/tree/tree-view-welcome-widget.js +1 -1
  147. package/lib/browser/tree/tree-view-welcome-widget.js.map +1 -1
  148. package/lib/browser/tree/tree-widget.d.ts +2 -2
  149. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  150. package/lib/browser/tree/tree-widget.js +1 -1
  151. package/lib/browser/tree/tree-widget.js.map +1 -1
  152. package/lib/browser/tree/tree.spec.js +2 -1
  153. package/lib/browser/tree/tree.spec.js.map +1 -1
  154. package/lib/browser/user-working-directory-provider.js +1 -1
  155. package/lib/browser/user-working-directory-provider.js.map +1 -1
  156. package/lib/browser/widgets/react-renderer.js +1 -1
  157. package/lib/browser/widgets/react-renderer.js.map +1 -1
  158. package/lib/browser/widgets/react-widget.js +1 -1
  159. package/lib/browser/widgets/react-widget.js.map +1 -1
  160. package/lib/browser/widgets/select-component.js +4 -3
  161. package/lib/browser/widgets/select-component.js.map +1 -1
  162. package/lib/browser/widgets/select-component.spec.js +3 -2
  163. package/lib/browser/widgets/select-component.spec.js.map +1 -1
  164. package/lib/browser/widgets/widget.js +1 -1
  165. package/lib/browser/widgets/widget.js.map +1 -1
  166. package/lib/common/buffer.js +3 -2
  167. package/lib/common/buffer.js.map +1 -1
  168. package/lib/common/cancellation.js.map +1 -1
  169. package/lib/common/command.spec.js +2 -1
  170. package/lib/common/command.spec.js.map +1 -1
  171. package/lib/common/disposable.spec.js +3 -2
  172. package/lib/common/disposable.spec.js.map +1 -1
  173. package/lib/common/encoding-service.js +2 -2
  174. package/lib/common/encoding-service.js.map +1 -1
  175. package/lib/common/event.d.ts +1 -1
  176. package/lib/common/event.d.ts.map +1 -1
  177. package/lib/common/event.js +0 -1
  178. package/lib/common/event.js.map +1 -1
  179. package/lib/common/file-uri.js +2 -1
  180. package/lib/common/file-uri.js.map +1 -1
  181. package/lib/common/fuzzy-search.js +1 -1
  182. package/lib/common/fuzzy-search.js.map +1 -1
  183. package/lib/common/glob.d.ts.map +1 -1
  184. package/lib/common/glob.js +9 -15
  185. package/lib/common/glob.js.map +1 -1
  186. package/lib/common/index.js +1 -1
  187. package/lib/common/index.js.map +1 -1
  188. package/lib/common/keyboard/keyboard-layout-provider.d.ts +7 -0
  189. package/lib/common/keyboard/keyboard-layout-provider.d.ts.map +1 -1
  190. package/lib/common/keyboard/keyboard-layout-provider.js.map +1 -1
  191. package/lib/common/keys.d.ts +86 -3
  192. package/lib/common/keys.d.ts.map +1 -1
  193. package/lib/common/keys.js +263 -24
  194. package/lib/common/keys.js.map +1 -1
  195. package/lib/common/message-rpc/channel.spec.js +3 -2
  196. package/lib/common/message-rpc/channel.spec.js.map +1 -1
  197. package/lib/common/messaging/proxy-factory.js +2 -2
  198. package/lib/common/messaging/proxy-factory.js.map +1 -1
  199. package/lib/common/messaging/proxy-factory.spec.js +2 -1
  200. package/lib/common/messaging/proxy-factory.spec.js.map +1 -1
  201. package/lib/common/messaging/web-socket-channel.d.ts +1 -1
  202. package/lib/common/messaging/web-socket-channel.d.ts.map +1 -1
  203. package/lib/common/path.spec.js +2 -1
  204. package/lib/common/path.spec.js.map +1 -1
  205. package/lib/common/performance/stopwatch.spec.js +2 -1
  206. package/lib/common/performance/stopwatch.spec.js.map +1 -1
  207. package/lib/common/preferences/preference-schema-service.d.ts +2 -2
  208. package/lib/common/preferences/preference-schema-service.d.ts.map +1 -1
  209. package/lib/common/preferences/preference-service.js +1 -1
  210. package/lib/common/preferences/preference-service.js.map +1 -1
  211. package/lib/common/promise-util.spec.js +2 -1
  212. package/lib/common/promise-util.spec.js.map +1 -1
  213. package/lib/common/quick-pick-service.d.ts +5 -0
  214. package/lib/common/quick-pick-service.d.ts.map +1 -1
  215. package/lib/common/quick-pick-service.js +2 -1
  216. package/lib/common/quick-pick-service.js.map +1 -1
  217. package/lib/common/reference.spec.js +2 -1
  218. package/lib/common/reference.spec.js.map +1 -1
  219. package/lib/common/resource.js +1 -1
  220. package/lib/common/resource.js.map +1 -1
  221. package/lib/common/selection-service.spec.js +2 -1
  222. package/lib/common/selection-service.spec.js.map +1 -1
  223. package/lib/common/selection.js +2 -1
  224. package/lib/common/selection.js.map +1 -1
  225. package/lib/common/stream.d.ts.map +1 -1
  226. package/lib/common/stream.js +2 -1
  227. package/lib/common/stream.js.map +1 -1
  228. package/lib/common/strings.d.ts.map +1 -1
  229. package/lib/common/strings.js +1 -2
  230. package/lib/common/strings.js.map +1 -1
  231. package/lib/common/ternary-search-tree.js +2 -2
  232. package/lib/common/ternary-search-tree.js.map +1 -1
  233. package/lib/common/ternary-search-tree.spec.js +12 -0
  234. package/lib/common/ternary-search-tree.spec.js.map +1 -1
  235. package/lib/common/test/mock-resource-provider.js +1 -1
  236. package/lib/common/test/mock-resource-provider.js.map +1 -1
  237. package/lib/common/types.spec.js +2 -1
  238. package/lib/common/types.spec.js.map +1 -1
  239. package/lib/common/uri-command-handler.js +4 -3
  240. package/lib/common/uri-command-handler.js.map +1 -1
  241. package/lib/common/uri-command-handler.spec.js +3 -2
  242. package/lib/common/uri-command-handler.spec.js.map +1 -1
  243. package/lib/common/uri.spec.js +3 -2
  244. package/lib/common/uri.spec.js.map +1 -1
  245. package/lib/electron-browser/keyboard/electron-keyboard-module.d.ts +2 -0
  246. package/lib/electron-browser/keyboard/electron-keyboard-module.d.ts.map +1 -1
  247. package/lib/electron-browser/keyboard/electron-keyboard-module.js +8 -1
  248. package/lib/electron-browser/keyboard/electron-keyboard-module.js.map +1 -1
  249. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.d.ts +2 -0
  250. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.d.ts.map +1 -0
  251. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.js +28 -0
  252. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.js.map +1 -0
  253. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  254. package/lib/electron-browser/menu/electron-main-menu-factory.js +3 -2
  255. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  256. package/lib/electron-browser/menu/electron-menu-contribution.d.ts.map +1 -1
  257. package/lib/electron-browser/menu/electron-menu-contribution.js +3 -0
  258. package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
  259. package/lib/electron-browser/window/window-zoom-action-bar.js +2 -1
  260. package/lib/electron-browser/window/window-zoom-action-bar.js.map +1 -1
  261. package/lib/electron-common/electron-api.d.ts +5 -0
  262. package/lib/electron-common/electron-api.d.ts.map +1 -1
  263. package/lib/electron-common/electron-api.js +21 -0
  264. package/lib/electron-common/electron-api.js.map +1 -1
  265. package/lib/electron-common/electron-api.spec.d.ts +2 -0
  266. package/lib/electron-common/electron-api.spec.d.ts.map +1 -0
  267. package/lib/electron-common/electron-api.spec.js +40 -0
  268. package/lib/electron-common/electron-api.spec.js.map +1 -0
  269. package/lib/electron-main/electron-api-main.d.ts +0 -1
  270. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  271. package/lib/electron-main/electron-api-main.js +3 -14
  272. package/lib/electron-main/electron-api-main.js.map +1 -1
  273. package/lib/electron-main/electron-main-application.js +2 -2
  274. package/lib/electron-main/electron-main-application.js.map +1 -1
  275. package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js +2 -1
  276. package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js.map +1 -1
  277. package/lib/electron-node/keyboard/electron-keyboard-layout-provider.js +1 -1
  278. package/lib/electron-node/keyboard/electron-keyboard-layout-provider.js.map +1 -1
  279. package/lib/electron-node/token/electron-token-validator.js +2 -2
  280. package/lib/electron-node/token/electron-token-validator.js.map +1 -1
  281. package/lib/node/backend-application.d.ts +1 -1
  282. package/lib/node/backend-application.d.ts.map +1 -1
  283. package/lib/node/backend-application.js +7 -7
  284. package/lib/node/backend-application.js.map +1 -1
  285. package/lib/node/backend-application.spec.js +1 -1
  286. package/lib/node/backend-application.spec.js.map +1 -1
  287. package/lib/node/bundled-resource-provider.js +4 -4
  288. package/lib/node/bundled-resource-provider.js.map +1 -1
  289. package/lib/node/bundled-resource-provider.spec.js +5 -4
  290. package/lib/node/bundled-resource-provider.spec.js.map +1 -1
  291. package/lib/node/cli.d.ts +1 -1
  292. package/lib/node/cli.d.ts.map +1 -1
  293. package/lib/node/cli.js +2 -2
  294. package/lib/node/cli.js.map +1 -1
  295. package/lib/node/cli.spec.js +3 -2
  296. package/lib/node/cli.spec.js.map +1 -1
  297. package/lib/node/console-logger-server.js +1 -1
  298. package/lib/node/console-logger-server.js.map +1 -1
  299. package/lib/node/env-variables/env-variables-server.js +1 -1
  300. package/lib/node/env-variables/env-variables-server.js.map +1 -1
  301. package/lib/node/environment-utils.spec.js +2 -1
  302. package/lib/node/environment-utils.spec.js.map +1 -1
  303. package/lib/node/file-uri.spec.js +4 -3
  304. package/lib/node/file-uri.spec.js.map +1 -1
  305. package/lib/node/filesystem-locking.js +1 -1
  306. package/lib/node/filesystem-locking.js.map +1 -1
  307. package/lib/node/hosting/browser-connection-token.js +2 -2
  308. package/lib/node/hosting/browser-connection-token.js.map +1 -1
  309. package/lib/node/i18n/localization-contribution.js +1 -1
  310. package/lib/node/i18n/localization-contribution.js.map +1 -1
  311. package/lib/node/key-store-server.js +1 -1
  312. package/lib/node/key-store-server.js.map +1 -1
  313. package/lib/node/logger-cli-contribution.js +2 -2
  314. package/lib/node/logger-cli-contribution.js.map +1 -1
  315. package/lib/node/logger-cli-contribution.spec.js +17 -16
  316. package/lib/node/logger-cli-contribution.spec.js.map +1 -1
  317. package/lib/node/messaging/ipc-connection-provider.js +2 -2
  318. package/lib/node/messaging/ipc-connection-provider.js.map +1 -1
  319. package/lib/node/process-utils.js +1 -1
  320. package/lib/node/process-utils.js.map +1 -1
  321. package/lib/node/process-utils.spec.js +2 -1
  322. package/lib/node/process-utils.spec.js.map +1 -1
  323. package/lib/node/request/backend-request-facade.spec.js +2 -1
  324. package/lib/node/request/backend-request-facade.spec.js.map +1 -1
  325. package/lib/node/setting-service.spec.js +2 -1
  326. package/lib/node/setting-service.spec.js.map +1 -1
  327. package/package.json +4 -4
  328. package/src/browser/common-frontend-contribution.ts +17 -1
  329. package/src/browser/components/card.tsx +1 -1
  330. package/src/browser/context-key-service.ts +15 -0
  331. package/src/browser/keybinding.spec.ts +878 -34
  332. package/src/browser/keybinding.ts +470 -85
  333. package/src/browser/keyboard/browser-keyboard-frontend-contribution.ts +1 -1
  334. package/src/browser/keyboard/browser-keyboard-layout-provider.spec.ts +52 -1
  335. package/src/browser/keyboard/browser-keyboard-layout-provider.ts +5 -5
  336. package/src/browser/keyboard/keyboard-layout-service.spec.ts +640 -15
  337. package/src/browser/keyboard/keyboard-layout-service.ts +358 -106
  338. package/src/browser/keyboard/keys.spec.ts +206 -1
  339. package/src/browser/label-provider.ts +3 -1
  340. package/src/browser/markdown-rendering/markdown-renderer.ts +1 -1
  341. package/src/browser/menu/action-menu-node.spec.ts +60 -0
  342. package/src/browser/menu/action-menu-node.ts +8 -14
  343. package/src/browser/menu/browser-menu-plugin.ts +1 -1
  344. package/src/browser/preferences/preference-validation-service.spec.ts +1 -1
  345. package/src/browser/quick-input/quick-command-service.spec.ts +159 -0
  346. package/src/browser/quick-input/quick-command-service.ts +36 -9
  347. package/src/browser/quick-input/quick-help-service.ts +1 -1
  348. package/src/browser/test/jsdom.ts +11 -0
  349. package/src/browser/tree/tree-container.spec.ts +1 -1
  350. package/src/browser/tree/tree-iterator.ts +1 -1
  351. package/src/browser/tree/tree-model.ts +2 -2
  352. package/src/browser/tree/tree-widget.tsx +2 -2
  353. package/src/common/cancellation.ts +1 -1
  354. package/src/common/disposable.spec.ts +1 -1
  355. package/src/common/event.ts +1 -2
  356. package/src/common/glob.ts +13 -30
  357. package/src/common/json-schema.ts +1 -1
  358. package/src/common/keyboard/keyboard-layout-provider.ts +8 -0
  359. package/src/common/keys.ts +312 -27
  360. package/src/common/message-rpc/channel.spec.ts +1 -1
  361. package/src/common/messaging/proxy-factory.ts +2 -2
  362. package/src/common/quick-pick-service.ts +5 -0
  363. package/src/common/stream.ts +4 -3
  364. package/src/common/strings.ts +2 -5
  365. package/src/common/ternary-search-tree.spec.ts +14 -0
  366. package/src/common/ternary-search-tree.ts +3 -3
  367. package/src/common/uri-command-handler.ts +2 -2
  368. package/src/electron-browser/keyboard/electron-keyboard-module.spec.ts +29 -0
  369. package/src/electron-browser/keyboard/electron-keyboard-module.ts +9 -2
  370. package/src/electron-browser/menu/electron-main-menu-factory.ts +3 -3
  371. package/src/electron-browser/menu/electron-menu-contribution.ts +4 -0
  372. package/src/electron-common/electron-api.spec.ts +42 -0
  373. package/src/electron-common/electron-api.ts +23 -0
  374. package/src/electron-main/electron-api-main.ts +2 -13
  375. package/src/node/backend-application.ts +1 -1
  376. package/src/node/cli.ts +1 -1
  377. package/src/node/logger-cli-contribution.spec.ts +1 -1
@@ -15,11 +15,14 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { injectable, inject, named } from 'inversify';
18
- import { isOSX } from '../common/os';
18
+ import { isOSX, isWindows } from '../common/os';
19
19
  import { Emitter, Event } from '../common/event';
20
20
  import { CommandRegistry, Command } from '../common/command';
21
21
  import { Disposable, DisposableCollection } from '../common/disposable';
22
- import { KeyCode, KeySequence, Key } from './keyboard/keys';
22
+ import {
23
+ KeyCode, KeySequence, Key, NormalizedKeyboardInput, normalizeKeyboardInput,
24
+ layoutModifiersIncludeAltGraph, layoutModifiersIncludeShift
25
+ } from './keyboard/keys';
23
26
  import { KeyboardLayoutService } from './keyboard/keyboard-layout-service';
24
27
  import { ContributionProvider } from '../common/contribution-provider';
25
28
  import { ILogger } from '../common/logger';
@@ -46,6 +49,9 @@ export namespace KeybindingScope {
46
49
  export type Keybinding = common.Keybinding;
47
50
  export const Keybinding = common.Keybinding;
48
51
 
52
+ /** Selects whether an accelerator describes the authored character or its physical realization. */
53
+ export type AcceleratorForm = 'logical' | 'physical';
54
+
49
55
  export interface ResolvedKeybinding extends common.Keybinding {
50
56
  /**
51
57
  * The KeyboardLayoutService may transform the `keybinding` depending on the
@@ -54,6 +60,8 @@ export interface ResolvedKeybinding extends common.Keybinding {
54
60
  * has not been called yet to resolve the keybinding.
55
61
  */
56
62
  resolved?: KeyCode[];
63
+ inactive?: boolean;
64
+ inactiveReason?: string;
57
65
  }
58
66
 
59
67
  export interface ScopedKeybinding extends common.Keybinding {
@@ -99,7 +107,9 @@ export namespace KeybindingContexts {
99
107
  export class KeybindingRegistry {
100
108
 
101
109
  static readonly PASSTHROUGH_PSEUDO_COMMAND = 'passthrough';
102
- protected keySequence: KeySequence = [];
110
+ protected keySequenceCandidates: KeySequence[] = [[]];
111
+ /** Whether keyboard shortcut troubleshooting logs are enabled. */
112
+ protected keyboardShortcutsTroubleshooting = false;
103
113
 
104
114
  protected readonly contexts: { [id: string]: KeybindingContext } = {};
105
115
  protected readonly keymaps: ScopedKeybinding[][] = [...Array(KeybindingScope.length)].map(() => []);
@@ -108,7 +118,9 @@ export class KeybindingRegistry {
108
118
  * Ternary search tree for efficient keybinding lookup.
109
119
  * Maps resolved key sequences to arrays of bindings sorted by scope (highest first).
110
120
  */
111
- protected keybindingTree: TernarySearchTree<KeySequence, ScopedKeybinding[]> | undefined;
121
+ protected keybindingTree: TernarySearchTree<KeySequence, KeybindingRegistry.RuntimeKeybinding[]> | undefined;
122
+ protected readonly handledKeyboardEvents = new WeakSet<KeyboardEvent>();
123
+ protected readonly warnedAmbiguousSequences = new Set<string>();
112
124
 
113
125
  @inject(CorePreferences)
114
126
  protected readonly corePreferences: CorePreferences;
@@ -136,10 +148,11 @@ export class KeybindingRegistry {
136
148
 
137
149
  async onStart(): Promise<void> {
138
150
  await this.keyboardLayoutService.initialize();
139
- this.keyboardLayoutService.onKeyboardLayoutChanged(newLayout => {
140
- this.clearResolvedKeybindings();
141
- this.invalidateKeybindingTree();
142
- this.keybindingsChanged.fire(undefined);
151
+ this.keyboardLayoutService.onKeyboardLayoutChanged(() => this.invalidateResolvedKeybindings());
152
+ this.corePreferences.onPreferenceChanged(change => {
153
+ if (change.preferenceName === 'keyboard.dispatch') {
154
+ this.invalidateResolvedKeybindings();
155
+ }
143
156
  });
144
157
  this.registerContext(KeybindingContexts.NOOP_CONTEXT);
145
158
  this.registerContext(KeybindingContexts.DEFAULT_CONTEXT);
@@ -283,14 +296,63 @@ export class KeybindingRegistry {
283
296
  resolveKeybinding(binding: ResolvedKeybinding): KeyCode[] {
284
297
  if (!binding.resolved) {
285
298
  const sequence = KeySequence.parse(binding.keybinding);
286
- binding.resolved = sequence.map(code => this.keyboardLayoutService.resolveKeyCode(code));
299
+ const resolved = this.corePreferences['keyboard.dispatch'] === 'keyCode'
300
+ ? sequence
301
+ : sequence.map(code => this.keyboardLayoutService.resolveKeyCode(code));
302
+ binding.inactiveReason = this.getInactiveReason(resolved, this.corePreferences['keyboard.dispatch']);
303
+ binding.inactive = binding.inactiveReason !== undefined;
304
+ binding.resolved = resolved;
287
305
  }
288
306
  return binding.resolved;
289
307
  }
290
308
 
309
+ protected getInactiveReason(sequence: KeyCode[], dispatch: 'code' | 'keyCode'): string | undefined {
310
+ if (dispatch === 'keyCode' && sequence.some(code => code.character !== undefined && !code.key)) {
311
+ return nls.localize('theia/core/keybinding/characterRequiresCodeDispatch',
312
+ 'Character keybindings require the "keyboard.dispatch" preference to be set to "code".');
313
+ }
314
+ if (sequence.some(code => code.shift && layoutModifiersIncludeAltGraph(code.layoutModifiers)
315
+ && !layoutModifiersIncludeShift(code.layoutModifiers))) {
316
+ return nls.localize('theia/core/keybinding/shiftedAltGraphCharacterUnavailable',
317
+ 'The logical character and Shift command modifier cannot be produced together on the current keyboard layout.');
318
+ }
319
+ // On macOS, Option selects the AltGraph layer. On Windows, AltGr is reported as Ctrl+Alt, so both Ctrl and Alt are
320
+ // consumed by the layout layer. Linux keeps Alt and AltGraph distinct.
321
+ const altGraphCodes = isOSX || isWindows ? sequence.filter(code => layoutModifiersIncludeAltGraph(code.layoutModifiers)) : [];
322
+ const consumedModifier = altGraphCodes.some(code => code.alt) ? (isOSX ? 'Option' : 'Alt')
323
+ : isWindows && altGraphCodes.some(code => code.ctrl) ? 'Ctrl' : undefined;
324
+ if (consumedModifier) {
325
+ return nls.localize('theia/core/keybinding/commandModifierAltGraphCharacterUnavailable',
326
+ 'The logical character and the {0} command modifier cannot be produced together on the current keyboard layout.',
327
+ consumedModifier);
328
+ }
329
+ if (sequence.some(code => !code.supportedByLayout)) {
330
+ return nls.localize('theia/core/keybinding/unavailableCharacter', 'The key is not available on the current keyboard layout.');
331
+ }
332
+ return undefined;
333
+ }
334
+
335
+ /** Return the localized explanation when a binding is inactive on the current layout. */
336
+ getKeybindingInactiveReason(binding: ResolvedKeybinding): string | undefined {
337
+ this.resolveKeybinding(binding);
338
+ return binding.inactiveReason;
339
+ }
340
+
341
+ /** Return whether a binding is inactive on the current keyboard layout. */
342
+ isKeybindingInactive(binding: ResolvedKeybinding): boolean {
343
+ this.resolveKeybinding(binding);
344
+ return !!binding.inactive;
345
+ }
346
+
347
+ /** Clear cached resolutions, invalidate the lookup tree, and notify listeners. */
348
+ protected invalidateResolvedKeybindings(): void {
349
+ this.clearResolvedKeybindings();
350
+ this.invalidateKeybindingTree();
351
+ this.keybindingsChanged.fire(undefined);
352
+ }
353
+
291
354
  /**
292
- * Clear all `resolved` properties of registered keybindings so the KeyboardLayoutService is called
293
- * again to resolve them. This is necessary when the user's keyboard layout has changed.
355
+ * Clear all `resolved` properties of registered keybindings so the keyboard layout service resolves them again.
294
356
  */
295
357
  protected clearResolvedKeybindings(): void {
296
358
  for (let i = KeybindingScope.DEFAULT; i < KeybindingScope.END; i++) {
@@ -298,6 +360,8 @@ export class KeybindingRegistry {
298
360
  for (let j = 0; j < bindings.length; j++) {
299
361
  const binding = bindings[j] as ResolvedKeybinding;
300
362
  binding.resolved = undefined;
363
+ binding.inactive = undefined;
364
+ binding.inactiveReason = undefined;
301
365
  }
302
366
  }
303
367
  }
@@ -307,13 +371,14 @@ export class KeybindingRegistry {
307
371
  */
308
372
  protected invalidateKeybindingTree(): void {
309
373
  this.keybindingTree = undefined;
374
+ this.warnedAmbiguousSequences.clear();
310
375
  }
311
376
 
312
377
  /**
313
378
  * Gets or builds the keybinding tree for efficient lookup.
314
379
  * The tree maps resolved key sequences to arrays of bindings sorted by scope (highest first).
315
380
  */
316
- protected getKeybindingTree(): TernarySearchTree<KeySequence, ScopedKeybinding[]> {
381
+ protected getKeybindingTree(): TernarySearchTree<KeySequence, KeybindingRegistry.RuntimeKeybinding[]> {
317
382
  if (!this.keybindingTree) {
318
383
  this.keybindingTree = this.buildKeybindingTree();
319
384
  }
@@ -326,8 +391,8 @@ export class KeybindingRegistry {
326
391
  * Bindings are stored in the order they should be checked: highest scope first,
327
392
  * and within the same scope, in the order they appear in keymaps (later-registered first).
328
393
  */
329
- protected buildKeybindingTree(): TernarySearchTree<KeySequence, ScopedKeybinding[]> {
330
- const tree = TernarySearchTree.forKeySequences<ScopedKeybinding[]>();
394
+ protected buildKeybindingTree(): TernarySearchTree<KeySequence, KeybindingRegistry.RuntimeKeybinding[]> {
395
+ const tree = TernarySearchTree.forKeySequences<KeybindingRegistry.RuntimeKeybinding[]>();
331
396
 
332
397
  // Process scopes from highest to lowest priority (WORKSPACE > USER > DEFAULT)
333
398
  // Within each scope, maintain the keymaps order (later-registered bindings are at the front)
@@ -335,12 +400,15 @@ export class KeybindingRegistry {
335
400
  for (const binding of this.keymaps[scope]) {
336
401
  try {
337
402
  const resolved = this.resolveKeybinding(binding);
403
+ if (this.isKeybindingInactive(binding)) {
404
+ continue;
405
+ }
406
+ const runtime: KeybindingRegistry.RuntimeKeybinding = { binding, sequence: resolved };
338
407
  const existing = tree.get(resolved);
339
408
  if (existing) {
340
- // Append to maintain correct order: higher scopes first, then keymaps order within scope
341
- existing.push(binding);
409
+ existing.push(runtime);
342
410
  } else {
343
- tree.set(resolved, [binding]);
411
+ tree.set(resolved, [runtime]);
344
412
  }
345
413
  } catch (error) {
346
414
  // Skip bindings that can't be resolved
@@ -359,19 +427,60 @@ export class KeybindingRegistry {
359
427
  * @returns true if there is a colliding keybinding
360
428
  */
361
429
  containsKeybindingInScope(binding: common.Keybinding, scope = KeybindingScope.USER): boolean {
362
- const bindingKeySequence = this.resolveKeybinding(binding);
363
- const collisions = this.getKeySequenceCollisions(this.getUsableBindings(this.keymaps[scope]), bindingKeySequence)
364
- .filter(b => b.context === binding.context && !b.when && !binding.when);
365
- if (collisions.full.length > 0) {
366
- return true;
430
+ const diagnostics = this.getKeybindingCollisionDiagnostics(binding, scope);
431
+ return [diagnostics.authored, diagnostics.layoutDerived].some(result =>
432
+ result.full.length > 0 || result.partial.length > 0 || result.shadow.length > 0);
433
+ }
434
+
435
+ /** Return authored, layout-resolved, and shadowing collision diagnostics for a binding. */
436
+ getKeybindingCollisionDiagnostics(binding: common.Keybinding, scope = KeybindingScope.USER): KeybindingRegistry.CollisionDiagnostics {
437
+ const diagnostics: KeybindingRegistry.CollisionDiagnostics = {
438
+ authored: new KeybindingRegistry.KeybindingsResult(),
439
+ layoutDerived: new KeybindingRegistry.KeybindingsResult(),
440
+ shadowedBy: []
441
+ };
442
+ const sequence = this.resolveKeybinding(binding);
443
+ if (this.isKeybindingInactive(binding)) {
444
+ return diagnostics;
367
445
  }
368
- if (collisions.partial.length > 0) {
369
- return true;
446
+ const candidates = this.getUsableBindings(this.keymaps[scope]).filter(candidate => candidate !== binding
447
+ && candidate.context === binding.context && !candidate.when && !binding.when);
448
+ for (const candidate of candidates) {
449
+ const resolved = this.resolveKeybinding(candidate);
450
+ const target = resolved.some(code => code.layoutModifiers !== 'none')
451
+ ? diagnostics.layoutDerived
452
+ : diagnostics.authored;
453
+ target.merge(this.getKeySequenceCollisions([candidate], sequence));
370
454
  }
371
- if (collisions.shadow.length > 0) {
372
- return true;
455
+ diagnostics.shadowedBy.push(...this.getShadowingKeybindings(binding, scope));
456
+ return diagnostics;
457
+ }
458
+
459
+ /** Return bindings whose command-modifier interpretation takes the keystroke instead of the supplied binding. */
460
+ getShadowingKeybindings(binding: common.Keybinding, scope?: KeybindingScope): ScopedKeybinding[] {
461
+ const sequence = this.resolveKeybinding(binding);
462
+ if (!sequence.some(code => code.layoutModifiers !== 'none')) {
463
+ return [];
464
+ }
465
+ const commandSequence = sequence.map(code => code.layoutModifiers !== 'none' ? new KeyCode({
466
+ key: code.key,
467
+ ctrl: code.ctrl,
468
+ shift: code.shift || layoutModifiersIncludeShift(code.layoutModifiers),
469
+ alt: code.alt || layoutModifiersIncludeAltGraph(code.layoutModifiers),
470
+ meta: code.meta,
471
+ character: code.key?.easyString,
472
+ interpretation: 'commandModifiers'
473
+ }) : code);
474
+ const result: ScopedKeybinding[] = [];
475
+ const matchesContext = (candidate: ScopedKeybinding) => candidate !== binding
476
+ && candidate.context === binding.context && candidate.when === binding.when;
477
+ const firstScope = scope ?? KeybindingScope.END - 1;
478
+ const lastScope = scope ?? KeybindingScope.DEFAULT;
479
+ for (let currentScope = firstScope; currentScope >= lastScope; currentScope--) {
480
+ const collisions = this.getKeySequenceCollisions(this.getUsableBindings(this.keymaps[currentScope]), commandSequence);
481
+ result.push(...collisions.full.filter(matchesContext));
373
482
  }
374
- return false;
483
+ return result;
375
484
  }
376
485
 
377
486
  /**
@@ -379,10 +488,12 @@ export class KeybindingRegistry {
379
488
  * @returns an array of strings representing all elements of the {@link KeySequence} defined by the {@link common.Keybinding}
380
489
  * @param keybinding the keybinding
381
490
  * @param separator the separator to be used to stringify {@link KeyCode}s that are part of the {@link KeySequence}
491
+ * @param form whether to render logical characters or their physical realization
382
492
  */
383
- acceleratorFor(keybinding: common.Keybinding, separator: string = ' ', asciiOnly = false): string[] {
493
+ acceleratorFor(keybinding: common.Keybinding, separator: string = ' ', asciiOnly = false,
494
+ form: AcceleratorForm = 'logical'): string[] {
384
495
  const bindingKeySequence = this.resolveKeybinding(keybinding);
385
- return this.acceleratorForSequence(bindingKeySequence, separator, asciiOnly);
496
+ return this.acceleratorForSequence(bindingKeySequence, separator, asciiOnly, form);
386
497
  }
387
498
 
388
499
  /**
@@ -390,9 +501,11 @@ export class KeybindingRegistry {
390
501
  * @returns an array of strings representing all elements of the {@link KeySequence}
391
502
  * @param keySequence the keysequence
392
503
  * @param separator the separator to be used to stringify {@link KeyCode}s that are part of the {@link KeySequence}
504
+ * @param form whether to render logical characters or their physical realization
393
505
  */
394
- acceleratorForSequence(keySequence: KeySequence, separator: string = ' ', asciiOnly = false): string[] {
395
- return keySequence.map(keyCode => this.acceleratorForKeyCode(keyCode, separator, asciiOnly));
506
+ acceleratorForSequence(keySequence: KeySequence, separator: string = ' ', asciiOnly = false,
507
+ form: AcceleratorForm = 'logical'): string[] {
508
+ return keySequence.map(keyCode => this.acceleratorForKeyCode(keyCode, separator, asciiOnly, form));
396
509
  }
397
510
 
398
511
  /**
@@ -403,13 +516,33 @@ export class KeybindingRegistry {
403
516
  * Ignored when rendering macOS symbols (i.e. on macOS with `asciiOnly=false`), as the convention is
404
517
  * to juxtapose modifier symbols without a separator (e.g. `⌃⌘V`).
405
518
  * @param asciiOnly if `true`, no special characters will be substituted into the string returned. Ensures correct keyboard shortcuts in Electron menus.
519
+ * @param form whether to render the logical character or its physical realization
406
520
  */
407
- acceleratorForKeyCode(keyCode: KeyCode, separator: string = ' ', asciiOnly = false): string {
521
+ acceleratorForKeyCode(keyCode: KeyCode, separator: string = ' ', asciiOnly = false,
522
+ form: AcceleratorForm = 'logical'): string {
408
523
  const useSymbols = isOSX && !asciiOnly;
409
- return this.componentsForKeyCode(keyCode, asciiOnly).join(useSymbols ? '' : separator);
524
+ return this.componentsForKeyCode(keyCode, asciiOnly, form).join(useSymbols ? '' : separator);
410
525
  }
411
526
 
412
- componentsForKeyCode(keyCode: KeyCode, asciiOnly = false): string[] {
527
+ componentsForKeyCode(keyCode: KeyCode, asciiOnly = false, form: AcceleratorForm = 'logical'): string[] {
528
+ if (form === 'physical') {
529
+ const physical = new KeyCode({
530
+ key: keyCode.key,
531
+ meta: keyCode.meta,
532
+ ctrl: keyCode.ctrl,
533
+ alt: keyCode.alt,
534
+ shift: keyCode.shift || layoutModifiersIncludeShift(keyCode.layoutModifiers)
535
+ });
536
+ const result = this.componentsForKeyCode(physical, asciiOnly);
537
+ if (layoutModifiersIncludeAltGraph(keyCode.layoutModifiers)) {
538
+ const layoutModifier = isOSX ? (asciiOnly ? 'Option' : '⌥') : 'AltGr';
539
+ const keyIndex = result.length - (physical.key ? 1 : 0);
540
+ if (!(isOSX && keyCode.alt)) {
541
+ result.splice(keyIndex, 0, layoutModifier);
542
+ }
543
+ }
544
+ return result;
545
+ }
413
546
  const keyCodeResult = [];
414
547
  const useSymbols = isOSX && !asciiOnly;
415
548
  if (keyCode.meta && isOSX) {
@@ -421,15 +554,29 @@ export class KeybindingRegistry {
421
554
  if (keyCode.alt) {
422
555
  keyCodeResult.push(useSymbols ? '⌥' : 'Alt');
423
556
  }
424
- if (keyCode.shift) {
557
+ if (this.displaysShift(keyCode)) {
425
558
  keyCodeResult.push(useSymbols ? '⇧' : 'Shift');
426
559
  }
427
- if (keyCode.key) {
560
+ if (keyCode.character) {
561
+ keyCodeResult.push(this.acceleratorForCharacter(keyCode.character));
562
+ } else if (keyCode.key) {
428
563
  keyCodeResult.push(this.acceleratorForKey(keyCode.key, asciiOnly));
429
564
  }
430
565
  return keyCodeResult;
431
566
  }
432
567
 
568
+ /**
569
+ * Whether the logical label of a key code shows Shift: when Shift is a command modifier or produces an uppercase letter.
570
+ * Shift that only selects a layout layer for other characters (e.g. `/` on German layouts) is not shown.
571
+ */
572
+ displaysShift(keyCode: KeyCode): boolean {
573
+ return keyCode.shift || layoutModifiersIncludeShift(keyCode.layoutModifiers) && !!keyCode.character && /^[A-Z]$/.test(keyCode.character);
574
+ }
575
+
576
+ protected acceleratorForCharacter(character: string): string {
577
+ return /^[a-z]$/i.test(character) ? character.toUpperCase() : character;
578
+ }
579
+
433
580
  /**
434
581
  * @param asciiOnly if `true`, no special characters will be substituted into the string returned. Ensures correct keyboard shortcuts in Electron menus.
435
582
  *
@@ -472,6 +619,9 @@ export class KeybindingRegistry {
472
619
  for (const binding of bindings) {
473
620
  try {
474
621
  const bindingKeySequence = this.resolveKeybinding(binding);
622
+ if (this.isKeybindingInactive(binding)) {
623
+ continue;
624
+ }
475
625
  const compareResult = KeySequence.compare(candidate, bindingKeySequence);
476
626
  switch (compareResult) {
477
627
  case KeySequence.CompareResult.FULL: {
@@ -520,6 +670,21 @@ export class KeybindingRegistry {
520
670
  return result;
521
671
  }
522
672
 
673
+ /**
674
+ * Get the accelerator of the first keybinding of a command that is enabled in the given context.
675
+ * Physical accelerators (for native menus) are omitted for inactive keybindings.
676
+ */
677
+ acceleratorForCommand(commandId: string, context: HTMLElement | undefined, form: AcceleratorForm = 'logical'): string[] {
678
+ const binding = this.getKeybindingsForCommand(commandId).find(candidate => this.isEnabledInScope(candidate, context));
679
+ if (!binding) {
680
+ return [];
681
+ }
682
+ if (form === 'physical') {
683
+ return this.isKeybindingInactive(binding) ? [] : this.acceleratorFor(binding, '+', true, 'physical');
684
+ }
685
+ return this.acceleratorFor(binding, '+');
686
+ }
687
+
523
688
  protected isActive(binding: common.Keybinding): boolean {
524
689
  /* Pseudo commands like "passthrough" are always active (and not found
525
690
  in the command registry). */
@@ -577,20 +742,65 @@ export class KeybindingRegistry {
577
742
  return true;
578
743
  }
579
744
 
745
+ /**
746
+ * Executes the command by dispatching its first keybinding: the resolved key codes are matched directly,
747
+ * and a DOM keydown marked as already handled is dispatched to the target for other listeners to observe.
748
+ */
580
749
  dispatchCommand(id: string, target?: EventTarget): void {
581
750
  const keybindings = this.getKeybindingsForCommand(id);
582
- if (keybindings.length) {
751
+ if (keybindings.length && !this.isKeybindingInactive(keybindings[0])) {
583
752
  for (const keyCode of this.resolveKeybinding(keybindings[0])) {
584
- this.dispatchKeyDown(keyCode, target);
753
+ const input: NormalizedKeyboardInput = {
754
+ key: keyCode.character ?? keyCode.key?.code,
755
+ code: keyCode.key?.code,
756
+ keyCode: keyCode.key?.keyCode,
757
+ ctrlKey: keyCode.ctrl,
758
+ shiftKey: keyCode.shift || layoutModifiersIncludeShift(keyCode.layoutModifiers),
759
+ altKey: keyCode.alt,
760
+ metaKey: keyCode.meta,
761
+ altGraph: layoutModifiersIncludeAltGraph(keyCode.layoutModifiers)
762
+ };
763
+ this.dispatchNormalizedKeyDown(input, target, [keyCode]);
764
+ this.dispatchKeyDown({ ...input, modifierAltGraph: input.altGraph }, target, true);
585
765
  }
586
766
  }
587
767
  }
588
768
 
589
- dispatchKeyDown(input: KeyboardEventInit | KeyCode | string, target: EventTarget = document.activeElement || window): void {
769
+ /** Dispatch a DOM keydown, optionally marking it as already handled by direct normalized dispatch. */
770
+ dispatchKeyDown(input: KeyboardEventInit | KeyCode | string, target: EventTarget = document.activeElement || window, alreadyHandled = false): void {
590
771
  const eventInit = this.asKeyboardEventInit(input);
591
- const emulatedKeyboardEvent = new KeyboardEvent('keydown', eventInit);
772
+ const emulatedKeyboardEvent = new KeyboardEvent('keydown', { ...eventInit, cancelable: true });
773
+ if (alreadyHandled) {
774
+ this.handledKeyboardEvents.add(emulatedKeyboardEvent);
775
+ }
592
776
  target.dispatchEvent(emulatedKeyboardEvent);
593
777
  }
778
+
779
+ /**
780
+ * Runs normalized keyboard data directly through keybinding matching without dispatching a DOM event.
781
+ */
782
+ dispatchNormalizedKeyDown(input: NormalizedKeyboardInput, target?: EventTarget, keyCodes?: KeyCode[]): void {
783
+ const normalizedInput = normalizeKeyboardInput(input);
784
+ const eventTarget = target ?? (typeof document === 'undefined' ? new EventTarget() : document.activeElement || window);
785
+ const event = new KeyboardEvent('keydown', this.asKeyboardEventInit(normalizedInput));
786
+ Object.defineProperty(event, 'target', { value: eventTarget });
787
+ this.runNormalizedKeyboardInput(normalizedInput, event, keyCodes);
788
+ }
789
+
790
+ isKeyboardShortcutsTroubleshooting(): boolean {
791
+ return this.keyboardShortcutsTroubleshooting;
792
+ }
793
+
794
+ /**
795
+ * Mirrors VS Code's `workbench.action.toggleKeybindingsLog` command.
796
+ * It logs how each key press was interpreted so keyboard-layout problems can be diagnosed on other machines.
797
+ */
798
+ toggleKeyboardShortcutsTroubleshooting(): boolean {
799
+ this.keyboardShortcutsTroubleshooting = !this.keyboardShortcutsTroubleshooting;
800
+ this.logger.info(`Keyboard shortcuts troubleshooting is now ${this.keyboardShortcutsTroubleshooting ? 'active' : 'inactive'}.`);
801
+ return this.keyboardShortcutsTroubleshooting;
802
+ }
803
+
594
804
  protected asKeyboardEventInit(input: KeyboardEventInit | KeyCode | string): KeyboardEventInit & Partial<{ keyCode: number }> {
595
805
  if (typeof input === 'string') {
596
806
  return this.asKeyboardEventInit(KeyCode.createKeyCode(input));
@@ -625,17 +835,7 @@ export class KeybindingRegistry {
625
835
  };
626
836
  win.document.addEventListener('compositionend', compositionEnd);
627
837
 
628
- const keydown = (event: KeyboardEvent) => {
629
- // Skip IME-related keydowns. `inComposition` covers the textarea input path;
630
- // `isComposing` and `keyCode === 229` cover the native EditContext path where
631
- // composition events don't reach the DOM. The keyCode check catches keys that
632
- // finalize a composition (e.g. arrow keys) where isComposing is already false.
633
- // eslint-disable-next-line deprecation/deprecation
634
- if (inComposition || event.isComposing || event.keyCode === 229) {
635
- return;
636
- }
637
- this.run(event);
638
- };
838
+ const keydown = (event: KeyboardEvent) => this.run(event, inComposition);
639
839
  win.document.addEventListener('keydown', keydown, true);
640
840
 
641
841
  return Disposable.create(() => {
@@ -647,42 +847,210 @@ export class KeybindingRegistry {
647
847
  /**
648
848
  * Run the command matching to the given keyboard event.
649
849
  */
650
- run(event: KeyboardEvent): void {
651
- if (event.defaultPrevented) {
850
+ run(event: KeyboardEvent, inComposition = false): void {
851
+ const input = normalizeKeyboardInput(event);
852
+ // The textarea input path reports composition through separate DOM events.
853
+ if (inComposition) {
854
+ this.logKeyboardTroubleshooting(input, undefined, undefined, 'composition');
855
+ return;
856
+ }
857
+ this.runNormalizedKeyboardInput(input, event);
858
+ }
859
+
860
+ /** Return all runtime interpretations of normalized keyboard input. */
861
+ getKeyCodeInterpretations(input: NormalizedKeyboardInput): KeyCode[] {
862
+ return this.keyboardLayoutService.getKeyCodeInterpretations(normalizeKeyboardInput(input), this.corePreferences['keyboard.dispatch']);
863
+ }
864
+
865
+ /** Return the authored representation that should be persisted for normalized keyboard input. */
866
+ authoredKeyCodeForKeyboardInput(input: NormalizedKeyboardInput): KeyCode | undefined {
867
+ const interpretations = this.getKeyCodeInterpretations(input);
868
+ // macOS Option is recorded as the `alt` command modifier rather than as the character it produces.
869
+ const optionAsCommandModifier = isOSX && !!input.altKey;
870
+ const keyCode = interpretations.find(candidate => candidate.interpretation === 'layoutModifiers' && (!optionAsCommandModifier || candidate.alt))
871
+ ?? interpretations[0];
872
+ if (!keyCode || keyCode.isModifierOnly()) {
873
+ return keyCode;
874
+ }
875
+ const fallbackLetter = keyCode.key && this.keyboardLayoutService.getLatinLetterFallback(keyCode.key);
876
+ if (fallbackLetter && keyCode.character && !layoutModifiersIncludeAltGraph(keyCode.layoutModifiers)
877
+ && keyCode.character.toLocaleLowerCase() !== fallbackLetter) {
878
+ const shift = keyCode.shift || layoutModifiersIncludeShift(keyCode.layoutModifiers);
879
+ return new KeyCode({
880
+ key: keyCode.key,
881
+ ctrl: keyCode.ctrl,
882
+ alt: keyCode.alt,
883
+ meta: keyCode.meta,
884
+ shift,
885
+ character: shift ? fallbackLetter.toLocaleUpperCase() : fallbackLetter
886
+ });
887
+ }
888
+ if (keyCode.interpretation !== 'layoutModifiers' || !keyCode.character) {
889
+ return keyCode;
890
+ }
891
+ try {
892
+ const parsedCharacter = KeyCode.parse(keyCode.character);
893
+ const logical = new KeyCode({
894
+ key: parsedCharacter.key,
895
+ ctrl: keyCode.ctrl,
896
+ shift: keyCode.shift,
897
+ alt: keyCode.alt,
898
+ meta: keyCode.meta,
899
+ character: keyCode.character,
900
+ authoredToken: parsedCharacter.authoredToken
901
+ });
902
+ const persisted = KeyCode.parse(logical.toString());
903
+ if (this.keyboardLayoutService.resolveKeyCode(persisted).dispatchString() === keyCode.dispatchString()) {
904
+ return logical;
905
+ }
906
+ return new KeyCode({
907
+ key: logical.key,
908
+ ctrl: logical.ctrl,
909
+ shift: logical.shift || layoutModifiersIncludeShift(keyCode.layoutModifiers),
910
+ alt: logical.alt,
911
+ meta: logical.meta,
912
+ character: logical.character
913
+ });
914
+ } catch {
915
+ return new KeyCode({
916
+ key: keyCode.key,
917
+ ctrl: keyCode.ctrl,
918
+ shift: keyCode.shift || layoutModifiersIncludeShift(keyCode.layoutModifiers),
919
+ alt: keyCode.alt,
920
+ meta: keyCode.meta,
921
+ character: keyCode.key?.easyString
922
+ });
923
+ }
924
+ }
925
+
926
+ /** Return the keybinding stroke the keybinding recorder persists for normalized keyboard input, or `undefined` for modifier-only input. */
927
+ authoredKeybindingStringForKeyboardInput(input: NormalizedKeyboardInput): string | undefined {
928
+ const keyCode = this.authoredKeyCodeForKeyboardInput(input);
929
+ return keyCode && !keyCode.isModifierOnly() ? keyCode.toAuthoredKeybindingString() : undefined;
930
+ }
931
+
932
+ protected runNormalizedKeyboardInput(input: NormalizedKeyboardInput, event: KeyboardEvent, keyCodesOverride?: KeyCode[]): void {
933
+ // Skip IME-related keydowns. `isComposing` and `keyCode === 229` cover the native EditContext path where
934
+ // composition events do not reach the DOM. The keyCode check catches keys that finalize a composition,
935
+ // such as arrow keys, when isComposing is already false.
936
+ if (input.isComposing || input.keyCode === 229) {
937
+ this.logKeyboardTroubleshooting(input, undefined, undefined, 'composition');
938
+ return;
939
+ }
940
+ if (this.handledKeyboardEvents.has(event) || event.defaultPrevented) {
941
+ this.logKeyboardTroubleshooting(input, undefined, undefined, this.handledKeyboardEvents.has(event) ? 'alreadyHandled' : 'defaultPrevented');
652
942
  return;
653
943
  }
654
944
 
655
- const eventDispatch = this.corePreferences['keyboard.dispatch'];
656
- const keyCode = KeyCode.createKeyCode(event, eventDispatch);
945
+ const keyCodes = keyCodesOverride ?? this.getKeyCodeInterpretations(input);
946
+ const keyCode = keyCodes[0];
657
947
  /* Keycode is only a modifier, next keycode will be modifier + key.
658
948
  Ignore this one. */
659
949
  if (keyCode.isModifierOnly()) {
950
+ this.logKeyboardTroubleshooting(input, keyCode, undefined, 'modifierOnly');
660
951
  return;
661
952
  }
662
953
 
663
- this.keyboardLayoutService.validateKeyCode(keyCode);
664
- this.keySequence.push(keyCode);
665
- const match = this.matchKeybinding(this.keySequence, event);
954
+ this.keyboardLayoutService.validateKeyCode(keyCode, input);
955
+ const candidates = this.keySequenceCandidates.flatMap(sequence => keyCodes.flatMap(keyCodeCandidate => {
956
+ const candidateSequence = [...sequence, keyCodeCandidate];
957
+ const candidateMatch = this.matchKeybinding(candidateSequence, event);
958
+ return candidateMatch ? [{ sequence: candidateSequence, keyCode: keyCodeCandidate, match: candidateMatch }] : [];
959
+ }));
960
+ const prioritized = candidates.map(candidate => ({ candidate, priority: this.matchPriority(candidate) }))
961
+ .sort((left, right) => right.priority - left.priority);
962
+ const selectedEntry = prioritized[0];
963
+ const selected = selectedEntry?.candidate;
964
+ const equallyPreferred = prioritized.filter(entry => entry.priority === selectedEntry?.priority).map(entry => entry.candidate);
965
+ const ambiguous = selected?.match.kind === 'full'
966
+ && equallyPreferred.some(candidate => candidate.match.kind === 'full'
967
+ && candidate.match.runtime.binding !== selected.match.runtime.binding);
968
+ const match = ambiguous ? undefined : selected?.match;
969
+ if (ambiguous) {
970
+ const sequence = selected.sequence.map(code => code.dispatchString()).join(' ');
971
+ if (!this.warnedAmbiguousSequences.has(sequence)) {
972
+ this.warnedAmbiguousSequences.add(sequence);
973
+ this.logger.warn(`Ambiguous keybinding sequence ignored: ${sequence}`);
974
+ }
975
+ }
976
+ this.logKeyboardTroubleshooting(input, selected?.keyCode ?? keyCode, match, ambiguous ? 'ambiguous' : undefined);
666
977
 
667
- if (match && match.kind === 'partial') {
668
- /* Accumulate the keysequence */
978
+ if (match?.kind === 'partial') {
979
+ this.keySequenceCandidates = candidates.filter(candidate => candidate.match.kind === 'partial').map(candidate => candidate.sequence);
669
980
  event.preventDefault();
670
981
  event.stopPropagation();
671
982
 
672
983
  this.statusBar.setElement('keybinding-status', {
673
- text: nls.localize('theia/core/keybindingStatus', '{0} was pressed, waiting for more keys', `(${this.acceleratorForSequence(this.keySequence, '+')})`),
984
+ text: nls.localize('theia/core/keybindingStatus', '{0} was pressed, waiting for more keys', `(${this.acceleratorForSequence(selected.sequence, '+')})`),
674
985
  alignment: StatusBarAlignment.LEFT,
675
986
  priority: 2
676
987
  });
677
988
  } else {
678
- if (match && match.kind === 'full') {
679
- this.executeKeyBinding(match.binding, event);
989
+ if (match?.kind === 'full') {
990
+ this.executeKeyBinding(match.runtime.binding, event);
680
991
  }
681
- this.keySequence = [];
992
+ this.keySequenceCandidates = [[]];
682
993
  this.statusBar.removeElement('keybinding-status');
683
994
  }
684
995
  }
685
996
 
997
+ /** Higher values win; explicit command modifiers take precedence over layout-modifier interpretations. */
998
+ protected matchPriority(candidate: KeybindingRegistry.MatchedCandidate): number {
999
+ const usesLayoutModifiers = candidate.keyCode.interpretation === 'layoutModifiers';
1000
+ return (usesLayoutModifiers ? 0 : 2) + (candidate.match.kind === 'full' ? 1 : 0);
1001
+ }
1002
+
1003
+ protected logKeyboardTroubleshooting(
1004
+ input: NormalizedKeyboardInput,
1005
+ keyCode?: KeyCode,
1006
+ match?: KeybindingRegistry.Match,
1007
+ skipped?: string
1008
+ ): void {
1009
+ if (!this.keyboardShortcutsTroubleshooting) {
1010
+ return;
1011
+ }
1012
+ this.logger.info('Keyboard shortcut troubleshooting', {
1013
+ input: {
1014
+ key: input.key,
1015
+ code: input.code,
1016
+ keyCode: input.keyCode,
1017
+ ctrlKey: input.ctrlKey,
1018
+ shiftKey: input.shiftKey,
1019
+ altKey: input.altKey,
1020
+ metaKey: input.metaKey,
1021
+ altGraph: input.altGraph,
1022
+ controlModifier: input.getModifierState?.('Control'),
1023
+ altModifier: input.getModifierState?.('Alt'),
1024
+ repeat: input.repeat,
1025
+ isComposing: input.isComposing,
1026
+ location: input.location,
1027
+ timeStamp: input.timeStamp
1028
+ },
1029
+ normalized: keyCode && {
1030
+ key: keyCode.key?.code,
1031
+ character: keyCode.character,
1032
+ ctrl: keyCode.ctrl,
1033
+ shift: keyCode.shift,
1034
+ alt: keyCode.alt,
1035
+ meta: keyCode.meta,
1036
+ layoutModifiers: keyCode.layoutModifiers,
1037
+ interpretation: keyCode.interpretation,
1038
+ dispatch: keyCode.dispatchString()
1039
+ },
1040
+ layout: {
1041
+ info: this.keyboardLayoutService.layoutInfo,
1042
+ source: this.keyboardLayoutService.layoutSource
1043
+ },
1044
+ match: match && {
1045
+ kind: match.kind,
1046
+ command: match.runtime.binding.command,
1047
+ keybinding: match.runtime.binding.keybinding,
1048
+ interpretations: match.runtime.sequence.map(code => code.interpretation)
1049
+ },
1050
+ skipped
1051
+ });
1052
+ }
1053
+
686
1054
  /**
687
1055
  * Match first binding in the current context.
688
1056
  * Keybindings ordered by a scope and by a registration order within the scope.
@@ -690,10 +1058,14 @@ export class KeybindingRegistry {
690
1058
  * Uses a ternary search tree for efficient O(log n) lookup instead of O(n) iteration.
691
1059
  * The tree is lazily built and invalidated when keybindings change.
692
1060
  *
1061
+ * `keySequence` must be a resolved sequence from {@link resolveKeybinding} or a runtime interpretation from
1062
+ * {@link getKeyCodeInterpretations}. Lookup uses {@link KeyCode.dispatchString} identity, so raw
1063
+ * {@link KeySequence.parse} output only matches when parsing and resolution coincide, such as `keyboard.dispatch: 'keyCode'`.
1064
+ *
693
1065
  * When multiple bindings match, bindings that use context keys local to the focused
694
1066
  * element are given priority over bindings that only use global context keys.
695
1067
  */
696
- matchKeybinding(keySequence: KeySequence, event?: KeyboardEvent): KeybindingRegistry.Match {
1068
+ matchKeybinding(keySequence: KeySequence, event?: KeyboardEvent): KeybindingRegistry.Match | undefined {
697
1069
  let disabled: Set<string> | undefined;
698
1070
  const isEnabled = (binding: ScopedKeybinding) => {
699
1071
  const { command, context, when, keybinding } = binding;
@@ -713,36 +1085,32 @@ export class KeybindingRegistry {
713
1085
  // Check for exact (FULL) matches first
714
1086
  const fullMatches = tree.get(keySequence);
715
1087
  if (fullMatches) {
716
- // Collect all enabled bindings
717
- const enabledBindings = fullMatches.filter(isEnabled);
1088
+ const enabledForms = fullMatches.filter(runtimeForm => isEnabled(runtimeForm.binding));
718
1089
 
719
- if (enabledBindings.length > 0) {
720
- // If we have multiple enabled bindings, prioritize those using local context keys
721
- const selectedBinding = this.selectBindingByLocalContext(enabledBindings, event);
722
- return { kind: 'full', binding: selectedBinding };
1090
+ if (enabledForms.length > 0) {
1091
+ const selectedBinding = this.selectBindingByLocalContext(enabledForms.map(candidate => candidate.binding), event);
1092
+ const runtimeForm = enabledForms.find(candidate => candidate.binding === selectedBinding)!;
1093
+ return { kind: 'full', runtime: runtimeForm };
723
1094
  }
724
1095
  }
725
1096
 
726
1097
  // Check for PARTIAL matches (bindings that start with keySequence but are longer)
727
1098
  const partialIterator = tree.findSuperstr(keySequence);
728
1099
  if (partialIterator) {
729
- // Collect all partial match bindings and sort by scope
730
- const partialBindings: ScopedKeybinding[] = [];
1100
+ const partialForms: KeybindingRegistry.RuntimeKeybinding[] = [];
731
1101
  let result = partialIterator.next();
732
1102
  while (!result.done) {
733
- partialBindings.push(...result.value);
1103
+ partialForms.push(...result.value);
734
1104
  result = partialIterator.next();
735
1105
  }
736
- // Sort by scope descending (highest scope first)
737
- partialBindings.sort((a, b) => b.scope - a.scope);
1106
+ partialForms.sort((a, b) => b.binding.scope - a.binding.scope);
738
1107
 
739
- // Collect all enabled bindings
740
- const enabledBindings = partialBindings.filter(isEnabled);
1108
+ const enabledForms = partialForms.filter(runtimeForm => isEnabled(runtimeForm.binding));
741
1109
 
742
- if (enabledBindings.length > 0) {
743
- // If we have multiple enabled bindings, prioritize those using local context keys
744
- const selectedBinding = this.selectBindingByLocalContext(enabledBindings, event);
745
- return { kind: 'partial', binding: selectedBinding };
1110
+ if (enabledForms.length > 0) {
1111
+ const selectedBinding = this.selectBindingByLocalContext(enabledForms.map(candidate => candidate.binding), event);
1112
+ const runtimeForm = enabledForms.find(candidate => candidate.binding === selectedBinding)!;
1113
+ return { kind: 'partial', runtime: runtimeForm };
746
1114
  }
747
1115
  }
748
1116
 
@@ -867,10 +1235,27 @@ export class KeybindingRegistry {
867
1235
  }
868
1236
 
869
1237
  export namespace KeybindingRegistry {
870
- export type Match = {
1238
+ /** A binding paired with the key sequence resolved for runtime matching. */
1239
+ export interface RuntimeKeybinding {
1240
+ binding: ScopedKeybinding;
1241
+ sequence: KeySequence;
1242
+ }
1243
+ export interface Match {
871
1244
  kind: 'full' | 'partial'
872
- binding: ScopedKeybinding
873
- } | undefined;
1245
+ runtime: RuntimeKeybinding
1246
+ }
1247
+ /** A runtime interpretation and its match while selecting a keyboard input candidate. */
1248
+ export interface MatchedCandidate {
1249
+ sequence: KeySequence;
1250
+ keyCode: KeyCode;
1251
+ match: Match;
1252
+ }
1253
+ /** Collisions grouped by authored and layout-resolved identity, plus bindings that shadow the supplied binding. */
1254
+ export interface CollisionDiagnostics {
1255
+ authored: KeybindingsResult;
1256
+ layoutDerived: KeybindingsResult;
1257
+ shadowedBy: ScopedKeybinding[];
1258
+ }
874
1259
  export class KeybindingsResult {
875
1260
  full: ScopedKeybinding[] = [];
876
1261
  partial: ScopedKeybinding[] = [];