@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
@@ -18,7 +18,11 @@ import { enableJSDOM } from './test/jsdom';
18
18
  let disableJSDOM = enableJSDOM();
19
19
 
20
20
  import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
21
- FrontendApplicationConfigProvider.set({});
21
+ try {
22
+ FrontendApplicationConfigProvider.get();
23
+ } catch {
24
+ FrontendApplicationConfigProvider.set({});
25
+ }
22
26
 
23
27
  import { Container, injectable, ContainerModule } from 'inversify';
24
28
  import { bindContributionProvider } from '../common/contribution-provider';
@@ -26,7 +30,7 @@ import { KeyboardLayoutProvider, NativeKeyboardLayout, KeyboardLayoutChangeNotif
26
30
  import { ILogger } from '../common/logger';
27
31
  import { KeybindingRegistry, KeybindingContext, KeybindingContribution, KeybindingScope } from './keybinding';
28
32
  import { Keybinding } from '../common/keybinding';
29
- import { KeyCode, Key, KeyModifier, KeySequence } from './keyboard/keys';
33
+ import { KeyCode, Key, KeyModifier, KeySequence, NormalizedKeyboardInput } from './keyboard/keys';
30
34
  import { KeyboardLayoutService } from './keyboard/keyboard-layout-service';
31
35
  import { CommandRegistry, CommandService, CommandContribution, Command } from '../common/command';
32
36
  import { LabelParser } from './label-parser';
@@ -46,15 +50,53 @@ disableJSDOM();
46
50
 
47
51
  const expect = chai.expect;
48
52
 
53
+ function testKeyboardEvent(input: Partial<KeyboardEvent> = {}): KeyboardEvent {
54
+ let defaultPrevented = false;
55
+ return {
56
+ key: input.key,
57
+ code: input.code,
58
+ keyCode: input.keyCode,
59
+ ctrlKey: input.ctrlKey,
60
+ shiftKey: input.shiftKey,
61
+ altKey: input.altKey,
62
+ metaKey: input.metaKey,
63
+ repeat: input.repeat,
64
+ isComposing: input.isComposing,
65
+ location: input.location,
66
+ timeStamp: input.timeStamp,
67
+ target: input.target,
68
+ get defaultPrevented(): boolean { return defaultPrevented; },
69
+ preventDefault(): void { defaultPrevented = true; },
70
+ stopPropagation(): void { }
71
+ } as KeyboardEvent;
72
+ }
73
+
49
74
  let keybindingRegistry: KeybindingRegistry;
50
75
  let commandRegistry: CommandRegistry;
51
76
  let testContainer: Container;
52
77
 
53
78
  let stub: sinon.SinonStub;
79
+ const preferenceChanged = new Emitter<{ preferenceName: string }>();
80
+ const corePreferences = {
81
+ 'keyboard.dispatch': 'code',
82
+ onPreferenceChanged: preferenceChanged.event
83
+ } as unknown as CorePreferences;
84
+
85
+ const RUSSIAN_LAYOUT: NativeKeyboardLayout = {
86
+ info: { id: 'ru', lang: 'ru' },
87
+ mapping: {
88
+ KeyA: { value: 'ф', withShift: 'Ф', withAltGr: '', withShiftAltGr: '' },
89
+ KeyC: { value: 'с', withShift: 'С', withAltGr: '', withShiftAltGr: '' },
90
+ KeyE: { value: 'у', withShift: 'У', withAltGr: '€', withShiftAltGr: '' },
91
+ KeyV: { value: 'м', withShift: 'М', withAltGr: '', withShiftAltGr: '' },
92
+ KeyS: { value: 'ы', withShift: 'Ы', withAltGr: '', withShiftAltGr: '' },
93
+ Digit8: { value: '8', withShift: '*', withAltGr: '', withShiftAltGr: '' },
94
+ BracketLeft: { value: 'х', withShift: 'Х', withAltGr: '', withShiftAltGr: '' },
95
+ Comma: { value: 'б', withShift: 'Б', withAltGr: '', withShiftAltGr: '' }
96
+ }
97
+ };
54
98
 
55
99
  before(async () => {
56
- disableJSDOM = enableJSDOM();
57
-
58
100
  testContainer = new Container();
59
101
  const module = new ContainerModule((bind, unbind, isBound, rebind) => {
60
102
 
@@ -63,7 +105,14 @@ before(async () => {
63
105
 
64
106
  bind(KeyboardLayoutService).toSelf().inSingletonScope();
65
107
  bind(MockKeyboardLayoutProvider).toSelf().inSingletonScope();
66
- bind(KeyboardLayoutProvider).toService(MockKeyboardLayoutProvider);
108
+ bind(KeyboardLayoutProvider).toDynamicValue(ctx => {
109
+ const provider = ctx.container.get(MockKeyboardLayoutProvider);
110
+ return new Proxy(provider, {
111
+ get: (target, property) => property === 'then' ? undefined
112
+ : property === 'layoutSource' ? 'navigator.keyboard'
113
+ : property in target ? Reflect.get(target, property) : () => undefined
114
+ });
115
+ });
67
116
  bind(MockKeyboardLayoutChangeNotifier).toSelf().inSingletonScope();
68
117
  bind(KeyboardLayoutChangeNotifier).toService(MockKeyboardLayoutChangeNotifier);
69
118
 
@@ -87,7 +136,10 @@ before(async () => {
87
136
  }
88
137
  });
89
138
 
90
- bind(StatusBar).toConstantValue({} as StatusBar);
139
+ bind(StatusBar).toConstantValue({
140
+ setElement: () => Promise.resolve(),
141
+ removeElement: () => Promise.resolve()
142
+ } as unknown as StatusBar);
91
143
  bind(MarkdownRendererImpl).toSelf().inSingletonScope();
92
144
  bind(MarkdownRenderer).toService(MarkdownRendererImpl);
93
145
  bind(MarkdownRendererFactory).toFactory(({ container }) => () => container.get(MarkdownRenderer));
@@ -96,7 +148,7 @@ before(async () => {
96
148
  bind(LabelParser).toSelf().inSingletonScope();
97
149
  bind(ContextKeyService).to(MockContextKeyService).inSingletonScope();
98
150
  bind(FrontendApplicationStateService).toSelf().inSingletonScope();
99
- bind(CorePreferences).toConstantValue(<CorePreferences>{});
151
+ bind(CorePreferences).toConstantValue(corePreferences);
100
152
  bindPreferenceService(bind);
101
153
  });
102
154
 
@@ -107,18 +159,22 @@ before(async () => {
107
159
 
108
160
  });
109
161
 
110
- after(() => {
111
- disableJSDOM();
112
- });
113
-
114
- beforeEach(async () => {
162
+ beforeEach(async function (): Promise<void> {
163
+ if (this.currentTest?.file?.endsWith('keybinding.spec.js')) {
164
+ disableJSDOM();
165
+ disableJSDOM = enableJSDOM();
166
+ }
115
167
  stub = sinon.stub(os, 'isOSX').value(false);
116
168
  keybindingRegistry = testContainer.get<KeybindingRegistry>(KeybindingRegistry);
169
+ (corePreferences as unknown as Record<string, unknown>)['keyboard.dispatch'] = 'code';
117
170
  await keybindingRegistry.onStart();
118
171
  });
119
172
 
120
- afterEach(() => {
173
+ afterEach(function (): void {
121
174
  stub.restore();
175
+ if (this.currentTest?.file?.endsWith('keybinding.spec.js')) {
176
+ disableJSDOM();
177
+ }
122
178
  });
123
179
 
124
180
  describe('keybindings', () => {
@@ -353,7 +409,7 @@ describe('keybindings', () => {
353
409
  expect(match && match.kind).to.be.equal('full');
354
410
 
355
411
  match = keybindingRegistry.matchKeybinding([KeyCode.createKeyCode({ first: Key.KEY_C, modifiers: [KeyModifier.CtrlCmd] })]);
356
- const keyCode = match && KeyCode.parse(match.binding.keybinding);
412
+ const keyCode = match && KeyCode.parse(match.runtime.binding.keybinding);
357
413
  expect(keyCode?.key).to.be.equal(validKeyCode.key);
358
414
  });
359
415
 
@@ -423,23 +479,23 @@ describe('keybindings', () => {
423
479
  keybindingRegistry.setKeymap(KeybindingScope.WORKSPACE, [workspaceBinding]);
424
480
  // now WORKSPACE bindings are overriding the other scopes
425
481
 
426
- let match = keybindingRegistry.matchKeybinding([keyCode]);
482
+ let match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(workspaceBinding));
427
483
  expect(match?.kind).to.be.equal('full');
428
- expect(match?.binding?.command).to.be.equal(workspaceBinding.command);
484
+ expect(match?.runtime.binding?.command).to.be.equal(workspaceBinding.command);
429
485
 
430
486
  keybindingRegistry.resetKeybindingsForScope(KeybindingScope.WORKSPACE);
431
487
  // now it should find USER bindings
432
488
 
433
- match = keybindingRegistry.matchKeybinding([keyCode]);
489
+ match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(userBinding));
434
490
  expect(match?.kind).to.be.equal('full');
435
- expect(match?.binding?.command).to.be.equal(userBinding.command);
491
+ expect(match?.runtime.binding?.command).to.be.equal(userBinding.command);
436
492
 
437
493
  keybindingRegistry.resetKeybindingsForScope(KeybindingScope.USER);
438
494
  // and finally it should fallback to DEFAULT bindings.
439
495
 
440
- match = keybindingRegistry.matchKeybinding([keyCode]);
496
+ match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
441
497
  expect(match?.kind).to.be.equal('full');
442
- expect(match?.binding?.command).to.be.equal(defaultBinding.command);
498
+ expect(match?.runtime.binding?.command).to.be.equal(defaultBinding.command);
443
499
 
444
500
  keybindingRegistry.resetKeybindingsForScope(KeybindingScope.DEFAULT);
445
501
  // now the registry should be empty
@@ -462,18 +518,760 @@ describe('keybindings', () => {
462
518
  };
463
519
 
464
520
  keybindingRegistry.setKeymap(KeybindingScope.DEFAULT, [defaultBinding]);
465
- let match = keybindingRegistry.matchKeybinding([keyCode]);
521
+ let match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
466
522
  expect(match?.kind).to.be.equal('full');
467
- expect(match?.binding?.command).to.be.equal(defaultBinding.command);
523
+ expect(match?.runtime.binding?.command).to.be.equal(defaultBinding.command);
468
524
 
469
525
  keybindingRegistry.setKeymap(KeybindingScope.USER, [disableDefaultBinding]);
470
- match = keybindingRegistry.matchKeybinding([keyCode]);
526
+ match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
471
527
  expect(match).to.be.undefined;
472
528
 
473
529
  keybindingRegistry.resetKeybindingsForScope(KeybindingScope.USER);
474
- match = keybindingRegistry.matchKeybinding([keyCode]);
530
+ match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
475
531
  expect(match?.kind).to.be.equal('full');
476
- expect(match?.binding?.command).to.be.equal(defaultBinding.command);
532
+ expect(match?.runtime.binding?.command).to.be.equal(defaultBinding.command);
533
+ });
534
+
535
+ it('should dispatch already-handled compatibility events to capture listeners', () => {
536
+ const target = document.createElement('iframe');
537
+ document.body.appendChild(target);
538
+ const captured = sinon.spy();
539
+ document.addEventListener('keydown', captured, true);
540
+ const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
541
+
542
+ keybindingRegistry.dispatchKeyDown({ key: 'a', code: 'KeyA' }, target, true);
543
+
544
+ expect(captured.calledOnce).to.be.true;
545
+ expect(captured.firstCall.args[0].defaultPrevented).to.be.false;
546
+ expect(match.called).to.be.false;
547
+ document.removeEventListener('keydown', captured, true);
548
+ target.remove();
549
+ });
550
+
551
+ it('should dispatch normalized keyboard data directly', () => {
552
+ const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
553
+
554
+ keybindingRegistry.dispatchNormalizedKeyDown({
555
+ key: 'a',
556
+ code: 'KeyA',
557
+ keyCode: 65,
558
+ ctrlKey: true
559
+ }, new EventTarget());
560
+
561
+ expect(match.calledOnce).to.be.true;
562
+ expect(match.firstCall.args[0][0].dispatchString()).to.equal('ctrl+a');
563
+ });
564
+
565
+ it('should dispatch a DOM keydown for dispatched commands without executing them twice', async () => {
566
+ const target = document.createElement('div');
567
+ document.body.appendChild(target);
568
+ const captured = sinon.spy();
569
+ const run = (event: KeyboardEvent) => keybindingRegistry.run(event);
570
+ document.addEventListener('keydown', captured, true);
571
+ document.addEventListener('keydown', run, true);
572
+ const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
573
+ const execute = sinon.spy();
574
+ const handler = commandRegistry.registerHandler(TEST_COMMAND.id, { execute });
575
+
576
+ try {
577
+ keybindingRegistry.dispatchCommand(TEST_COMMAND.id, target);
578
+ await new Promise(resolve => setTimeout(resolve, 0));
579
+
580
+ expect(captured.calledOnce).to.be.true;
581
+ expect(captured.firstCall.args[0].ctrlKey).to.be.true;
582
+ expect(execute.calledOnce).to.be.true;
583
+ expect(match.calledOnce).to.be.true;
584
+ } finally {
585
+ handler.dispose();
586
+ document.removeEventListener('keydown', captured, true);
587
+ document.removeEventListener('keydown', run, true);
588
+ target.remove();
589
+ }
590
+ });
591
+
592
+ it('should preserve authored modifiers when dispatching commands through normalized Windows layout modifier data', async () => {
593
+ const windows = sinon.stub(os, 'isWindows').value(true);
594
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
595
+ const dispatch = sinon.spy(keybindingRegistry, 'dispatchNormalizedKeyDown');
596
+ const target = document.createElement('div');
597
+ const cases = [
598
+ { keybinding: '[', ctrlKey: false, altKey: false },
599
+ { keybinding: 'ctrl+[', ctrlKey: true, altKey: false, inactive: true },
600
+ { keybinding: 'alt+[', ctrlKey: false, altKey: true, inactive: true },
601
+ { keybinding: 'ctrl+alt+[', ctrlKey: true, altKey: true, inactive: true }
602
+ ];
603
+
604
+ try {
605
+ for (const testCase of cases) {
606
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: testCase.keybinding };
607
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
608
+ const execute = sinon.spy();
609
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
610
+ dispatch.resetHistory();
611
+ try {
612
+ keybindingRegistry.dispatchCommand(TEST_COMMAND_SHADOW.id, target);
613
+ await new Promise(resolve => setTimeout(resolve, 0));
614
+
615
+ if (testCase.inactive) {
616
+ // Windows reports AltGr as Ctrl+Alt, so neither command Ctrl nor Alt can accompany an AltGraph character.
617
+ expect(keybindingRegistry.isKeybindingInactive(binding)).to.be.true;
618
+ expect(dispatch.called).to.be.false;
619
+ expect(execute.called).to.be.false;
620
+ continue;
621
+ }
622
+ expect(dispatch.calledOnce).to.be.true;
623
+ expect(dispatch.firstCall.args[0]).to.include({
624
+ key: '[',
625
+ code: 'Digit8',
626
+ keyCode: 56,
627
+ ctrlKey: testCase.ctrlKey,
628
+ altKey: testCase.altKey,
629
+ altGraph: true
630
+ });
631
+ expect(execute.calledOnce).to.be.true;
632
+ } finally {
633
+ handler.dispose();
634
+ }
635
+ }
636
+ } finally {
637
+ windows.restore();
638
+ }
639
+ });
640
+
641
+ it('should interpret native and transported AltGraph input identically', () => {
642
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
643
+ const native = keybindingRegistry.getKeyCodeInterpretations({
644
+ key: '[',
645
+ code: 'Digit8',
646
+ keyCode: 56,
647
+ ctrlKey: true,
648
+ getModifierState: modifier => modifier === 'AltGraph'
649
+ });
650
+ const transported = keybindingRegistry.getKeyCodeInterpretations({
651
+ key: '[',
652
+ code: 'Digit8',
653
+ keyCode: 56,
654
+ ctrlKey: true,
655
+ altGraph: true
656
+ });
657
+
658
+ expect(transported.map(code => code.dispatchString())).to.deep.equal(native.map(code => code.dispatchString()));
659
+ });
660
+
661
+ it('should log normalized keyboard troubleshooting data when enabled', () => {
662
+ const logger = (keybindingRegistry as unknown as { logger: ILogger }).logger;
663
+ const info = sinon.spy(logger, 'info');
664
+ keybindingRegistry.toggleKeyboardShortcutsTroubleshooting();
665
+
666
+ keybindingRegistry.dispatchNormalizedKeyDown({
667
+ key: '[',
668
+ code: 'Digit8',
669
+ keyCode: 56,
670
+ getModifierState: modifier => modifier === 'AltGraph',
671
+ timeStamp: 42
672
+ }, new EventTarget());
673
+
674
+ expect(info.calledTwice).to.be.true;
675
+ expect(info.secondCall.args[1].input).to.include({
676
+ key: '[',
677
+ code: 'Digit8',
678
+ altGraph: true,
679
+ timeStamp: 42
680
+ });
681
+ expect(info.secondCall.args[1].layout.source).to.equal('navigator.keyboard');
682
+ });
683
+
684
+ it('should skip composing normalized keyboard data', () => {
685
+ const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
686
+
687
+ keybindingRegistry.dispatchNormalizedKeyDown({
688
+ key: 'a',
689
+ code: 'KeyA',
690
+ keyCode: 65,
691
+ isComposing: true
692
+ }, new EventTarget());
693
+
694
+ expect(match.called).to.be.false;
695
+ });
696
+
697
+ it('should exclude inactive bindings and reactivate them after a layout change', () => {
698
+ const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+[' };
699
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
700
+ expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(binding))?.kind).to.equal('full');
701
+
702
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
703
+ notifier.emitter.fire({
704
+ info: { id: 'missing-bracket', lang: 'en' },
705
+ mapping: { KeyA: { value: 'a', withShift: 'A', withAltGr: '', withShiftAltGr: '' } }
706
+ });
707
+ expect(keybindingRegistry.getKeybindingsForCommand(TEST_COMMAND.id).some(candidate => candidate.keybinding === 'ctrl+[')).to.be.true;
708
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
709
+ expect(keybindingRegistry.matchKeybinding(KeySequence.parse('ctrl+['))).to.be.undefined;
710
+ expect(keybindingRegistry.containsKeybindingInScope({ command: TEST_COMMAND2.id, keybinding: 'ctrl+[' })).to.be.false;
711
+
712
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
713
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
714
+ expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(binding))?.kind).to.equal('full');
715
+ });
716
+
717
+ it('should keep default Latin-letter bindings active on a non-Latin layout', async () => {
718
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+c' };
719
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
720
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
721
+ notifier.emitter.fire(RUSSIAN_LAYOUT);
722
+ const execute = sinon.spy();
723
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
724
+
725
+ try {
726
+ expect(keybindingRegistry.isKeybindingInactive(binding)).to.be.false;
727
+ keybindingRegistry.dispatchNormalizedKeyDown({
728
+ key: 'с', code: 'KeyC', ctrlKey: true
729
+ }, new EventTarget());
730
+ await new Promise(resolve => setTimeout(resolve, 0));
731
+
732
+ expect(execute.calledOnce).to.be.true;
733
+ } finally {
734
+ handler.dispose();
735
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
736
+ }
737
+ });
738
+
739
+ it('should dispatch a shifted Latin-letter binding on a non-Latin layout', async () => {
740
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+shift+a' };
741
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
742
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
743
+ notifier.emitter.fire(RUSSIAN_LAYOUT);
744
+ const execute = sinon.spy();
745
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
746
+
747
+ try {
748
+ keybindingRegistry.dispatchNormalizedKeyDown({
749
+ key: 'Ф', code: 'KeyA', ctrlKey: true, shiftKey: true
750
+ }, new EventTarget());
751
+ await new Promise(resolve => setTimeout(resolve, 0));
752
+
753
+ expect(execute.calledOnce).to.be.true;
754
+ } finally {
755
+ handler.dispose();
756
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
757
+ }
758
+ });
759
+
760
+ it('should keep punctuation bindings inactive on a non-Latin layout', () => {
761
+ const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+[' };
762
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
763
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
764
+ notifier.emitter.fire(RUSSIAN_LAYOUT);
765
+
766
+ try {
767
+ expect(keybindingRegistry.isKeybindingInactive(binding)).to.be.true;
768
+ } finally {
769
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
770
+ }
771
+ });
772
+
773
+ it('should execute an authored Shift command modifier on a non-US logical character', async () => {
774
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
775
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+shift+ü' };
776
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
777
+ const execute = sinon.spy();
778
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
779
+
780
+ try {
781
+ keybindingRegistry.dispatchNormalizedKeyDown({
782
+ key: 'Ü', code: 'BracketLeft', ctrlKey: true, shiftKey: true
783
+ }, new EventTarget());
784
+ await new Promise(resolve => setTimeout(resolve, 0));
785
+
786
+ expect(execute.calledOnce).to.be.true;
787
+ } finally {
788
+ handler.dispose();
789
+ }
790
+ });
791
+
792
+ it('should mark an unmatchable Shift plus AltGraph character binding inactive', async () => {
793
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
794
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+shift+[char:0x7B]' };
795
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
796
+ const execute = sinon.spy();
797
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
798
+
799
+ try {
800
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
801
+ keybindingRegistry.dispatchNormalizedKeyDown({
802
+ key: '{', code: 'Digit7', ctrlKey: true, shiftKey: true, altGraph: true
803
+ }, new EventTarget());
804
+ await new Promise(resolve => setTimeout(resolve, 0));
805
+
806
+ expect(execute.called).to.be.false;
807
+ } finally {
808
+ handler.dispose();
809
+ }
810
+ });
811
+
812
+ it('should mark command Alt plus AltGraph character bindings inactive on macOS and Windows', async () => {
813
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
814
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'alt+[' };
815
+ const execute = sinon.spy();
816
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
817
+ const windows = sinon.stub(os, 'isWindows').value(false);
818
+ const dispatch = async (input: NormalizedKeyboardInput) => {
819
+ keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
820
+ await new Promise(resolve => setTimeout(resolve, 0));
821
+ };
822
+
823
+ try {
824
+ stub.value(true);
825
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-mac.json'));
826
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
827
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
828
+ expect(keybindingRegistry.acceleratorForCommand(TEST_COMMAND_SHADOW.id, undefined, 'physical')).to.deep.equal([]);
829
+ await dispatch({ key: '[', code: 'Digit5', altKey: true });
830
+ expect(execute.called).to.be.false;
831
+
832
+ stub.value(false);
833
+ windows.value(true);
834
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
835
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
836
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
837
+ await dispatch({ key: '[', code: 'Digit8', ctrlKey: true, altKey: true });
838
+ expect(execute.called).to.be.false;
839
+
840
+ windows.value(false);
841
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
842
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
843
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
844
+ await dispatch({ key: '[', code: 'Digit8', altKey: true, altGraph: true });
845
+ expect(execute.calledOnce).to.be.true;
846
+ } finally {
847
+ windows.restore();
848
+ handler.dispose();
849
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
850
+ }
851
+ });
852
+
853
+ it('should mark command Ctrl plus AltGraph character bindings inactive on Windows', async () => {
854
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
855
+ const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+[' };
856
+ const typed = { command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: '[' };
857
+ const execute = sinon.spy();
858
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
859
+ const windows = sinon.stub(os, 'isWindows').value(true);
860
+ const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
861
+ const dispatch = async (input: NormalizedKeyboardInput) => {
862
+ keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
863
+ await new Promise(resolve => setTimeout(resolve, 0));
864
+ };
865
+
866
+ try {
867
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
868
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding, typed]);
869
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
870
+ expect(keybindingRegistry.acceleratorForCommand(TEST_COMMAND_SHADOW.id, undefined, 'physical')).to.deep.equal([]);
871
+ // Chromium reports Ctrl+AltGr+8 on Windows exactly like AltGr+8, so the input types `[`.
872
+ await dispatch({ key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph: true });
873
+ expect(execute.called).to.be.false;
874
+ const fullMatches = match.returnValues.filter(result => result?.kind === 'full').map(result => result!.runtime.binding.keybinding);
875
+ expect(fullMatches).to.deep.equal(['[']);
876
+
877
+ windows.value(false);
878
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
879
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
880
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
881
+ await dispatch({ key: '[', code: 'Digit8', ctrlKey: true, altGraph: true });
882
+ expect(execute.calledOnce).to.be.true;
883
+ } finally {
884
+ windows.restore();
885
+ handler.dispose();
886
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
887
+ }
888
+ });
889
+
890
+ it('should route Windows Ctrl+Alt candidates through the registry with and without reported AltGraph', () => {
891
+ const windows = sinon.stub(os, 'isWindows').value(true);
892
+ try {
893
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
894
+ const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
895
+ const logicalBinding = {
896
+ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
897
+ keybinding: '[',
898
+ resolved: [new KeyCode({ key: Key.DIGIT8, character: '[', layoutModifiers: 'altGraph' })]
899
+ };
900
+ for (const altGraph of [true, false]) {
901
+ const input = { key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph };
902
+ match.resetHistory();
903
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [logicalBinding]);
904
+ keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
905
+
906
+ expect(match.callCount).to.equal(2);
907
+ expect(match.secondCall.returnValue?.kind).to.equal('full');
908
+ expect(match.secondCall.returnValue?.runtime.binding.keybinding).to.equal('[');
909
+
910
+ match.resetHistory();
911
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [
912
+ logicalBinding,
913
+ { command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrl+alt+8' }
914
+ ]);
915
+ keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
916
+
917
+ expect(match.callCount).to.equal(2);
918
+ expect(match.firstCall.returnValue?.runtime.binding.keybinding).to.equal('ctrl+alt+8');
919
+ }
920
+ } finally {
921
+ windows.restore();
922
+ }
923
+ });
924
+
925
+ it('should prefer a command chord prefix over a layout modifier full match', () => {
926
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
927
+ new KeyCode({ key: Key.KEY_A, ctrl: true, interpretation: 'commandModifiers' }),
928
+ new KeyCode({ key: Key.DIGIT8, ctrl: true, layoutModifiers: 'altGraph', interpretation: 'layoutModifiers' })
929
+ ]);
930
+ const commandChord = {
931
+ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
932
+ keybinding: 'ctrl+a b'
933
+ };
934
+ const layoutFull = {
935
+ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
936
+ keybinding: 'ctrl+['
937
+ };
938
+ keybindingRegistry.resolveKeybinding(commandChord).splice(0, 2, ...KeySequence.parse('ctrl+a b'));
939
+ keybindingRegistry.resolveKeybinding(layoutFull).splice(0, 1,
940
+ new KeyCode({ key: Key.DIGIT8, ctrl: true, character: '[', layoutModifiers: 'altGraph' }));
941
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [commandChord, layoutFull]);
942
+
943
+ const event = testKeyboardEvent({ key: '[', code: 'Digit8' });
944
+ keybindingRegistry.run(event);
945
+
946
+ expect(event.defaultPrevented).to.be.true;
947
+ expect((keybindingRegistry as unknown as { keySequenceCandidates: KeySequence[] }).keySequenceCandidates).to.have.length(1);
948
+ expect((keybindingRegistry as unknown as { keySequenceCandidates: KeySequence[] }).keySequenceCandidates[0][0].interpretation).to.equal('commandModifiers');
949
+ interpretations.restore();
950
+ });
951
+
952
+ it('should retain alternative interpretation prefixes across a chord', () => {
953
+ const commandPrefix = new KeyCode({ key: Key.KEY_A, ctrl: true, interpretation: 'commandModifiers' });
954
+ const layoutPrefix = new KeyCode({ key: Key.DIGIT8, ctrl: true, layoutModifiers: 'altGraph', interpretation: 'layoutModifiers' });
955
+ const layoutSecond = new KeyCode({ key: Key.KEY_C, interpretation: 'layoutModifiers' });
956
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations');
957
+ interpretations.onFirstCall().returns([commandPrefix, layoutPrefix]);
958
+ interpretations.onSecondCall().returns([
959
+ new KeyCode({ key: Key.KEY_X, interpretation: 'commandModifiers' }),
960
+ layoutSecond
961
+ ]);
962
+ const commandBinding = {
963
+ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
964
+ keybinding: 'ctrl+a b'
965
+ };
966
+ const layoutBinding = {
967
+ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
968
+ keybinding: 'ctrl+[ c'
969
+ };
970
+ keybindingRegistry.resolveKeybinding(commandBinding).splice(0, 2, commandPrefix, new KeyCode({ key: Key.KEY_B }));
971
+ keybindingRegistry.resolveKeybinding(layoutBinding).splice(0, 2, layoutPrefix, layoutSecond);
972
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [commandBinding, layoutBinding]);
973
+
974
+ keybindingRegistry.run(testKeyboardEvent({ key: '[', code: 'Digit8' }));
975
+ expect((keybindingRegistry as unknown as { keySequenceCandidates: KeySequence[] }).keySequenceCandidates).to.have.length(2);
976
+ const second = testKeyboardEvent({ key: 'c', code: 'KeyC' });
977
+ keybindingRegistry.run(second);
978
+
979
+ expect(second.defaultPrevented).to.be.false;
980
+ expect((keybindingRegistry as unknown as { keySequenceCandidates: KeySequence[] }).keySequenceCandidates).to.deep.equal([[]]);
981
+ interpretations.restore();
982
+ });
983
+
984
+ it('should decline equal-priority full matches from different interpretations', () => {
985
+ const first = new KeyCode({ key: Key.KEY_A, ctrl: true, interpretation: 'commandModifiers' });
986
+ const second = new KeyCode({ key: Key.KEY_B, ctrl: true, interpretation: 'commandModifiers' });
987
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([first, second]);
988
+ const troubleshooting = sinon.spy(keybindingRegistry as unknown as {
989
+ logKeyboardTroubleshooting: (input: unknown, keyCode: KeyCode, match: unknown, skipped?: string) => void
990
+ }, 'logKeyboardTroubleshooting');
991
+ const logger = (keybindingRegistry as unknown as { logger: ILogger }).logger;
992
+ const warning = sinon.spy(logger, 'warn');
993
+ const firstBinding = { command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrl+a' };
994
+ const secondBinding = { command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrl+b' };
995
+ keybindingRegistry.resolveKeybinding(firstBinding).splice(0, 1, first);
996
+ keybindingRegistry.resolveKeybinding(secondBinding).splice(0, 1, second);
997
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [firstBinding, secondBinding]);
998
+
999
+ keybindingRegistry.run(testKeyboardEvent({ key: 'a', code: 'KeyA' }));
1000
+
1001
+ expect(troubleshooting.lastCall.args[3]).to.equal('ambiguous');
1002
+ expect(troubleshooting.lastCall.args[2]).to.be.undefined;
1003
+ expect(warning.calledOnce).to.be.true;
1004
+ keybindingRegistry.run(testKeyboardEvent({ key: 'a', code: 'KeyA' }));
1005
+ expect(warning.calledOnce).to.be.true;
1006
+ interpretations.restore();
1007
+ });
1008
+
1009
+ it('should canonicalize layout modifier input for recorder persistence', () => {
1010
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
1011
+ new KeyCode({ key: Key.DIGIT8, ctrl: true, alt: true, interpretation: 'commandModifiers' }),
1012
+ new KeyCode({ key: Key.DIGIT8, ctrl: true, character: '[', layoutModifiers: 'altGraph', interpretation: 'layoutModifiers' })
1013
+ ]);
1014
+
1015
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: '[', code: 'Digit8' })?.toString()).to.equal('ctrl+[');
1016
+ interpretations.restore();
1017
+ });
1018
+
1019
+ it('should record the Latin fallback letter on a non-Latin layout', () => {
1020
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
1021
+ notifier.emitter.fire(RUSSIAN_LAYOUT);
1022
+
1023
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
1024
+ key: 'с', code: 'KeyC', ctrlKey: true
1025
+ })?.toAuthoredKeybindingString()).to.equal('ctrl+c');
1026
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
1027
+ key: 'С', code: 'KeyC', ctrlKey: true, shiftKey: true
1028
+ })?.toAuthoredKeybindingString()).to.equal('shift+ctrl+c');
1029
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
1030
+ key: '€', code: 'KeyE', ctrlKey: true, altGraph: true
1031
+ })?.toAuthoredKeybindingString()).to.equal('ctrl+€');
1032
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
1033
+ key: 'у', code: 'KeyE', ctrlKey: true
1034
+ })?.toAuthoredKeybindingString()).to.equal('ctrl+e');
1035
+
1036
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
1037
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
1038
+ key: 'c', code: 'KeyC', ctrlKey: true
1039
+ })?.toAuthoredKeybindingString()).to.equal('ctrl+c');
1040
+ });
1041
+
1042
+ it('should record legacy Windows AltGraph input as its logical character', () => {
1043
+ const windows = sinon.stub(os, 'isWindows').value(true);
1044
+ try {
1045
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
1046
+
1047
+ for (const altGraph of [true, false]) {
1048
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
1049
+ key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph
1050
+ })?.toString()).to.equal('[');
1051
+ }
1052
+ } finally {
1053
+ windows.restore();
1054
+ }
1055
+ });
1056
+
1057
+ it('should retain command Shift when layout modifier Shift is not the logical binding', () => {
1058
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
1059
+ new KeyCode({ key: Key.KEY_P, ctrl: true, character: 'P', layoutModifiers: 'shift', interpretation: 'layoutModifiers' })
1060
+ ]);
1061
+
1062
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: 'P', code: 'KeyP' })?.toString()).to.equal('shift+ctrl+p');
1063
+ interpretations.restore();
1064
+ });
1065
+
1066
+ it('should persist a parseable logical character produced by Shift', () => {
1067
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
1068
+ new KeyCode({ key: Key.DIGIT7, character: '/', layoutModifiers: 'shift', interpretation: 'layoutModifiers' })
1069
+ ]);
1070
+ const resolve = sinon.stub(testContainer.get(KeyboardLayoutService), 'resolveKeyCode').callsFake(code => code.key === Key.SLASH
1071
+ ? new KeyCode({ key: Key.DIGIT7, character: '/', layoutModifiers: 'shift' })
1072
+ : code);
1073
+
1074
+ expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: '/', code: 'Digit7' })?.toString()).to.equal('/');
1075
+ resolve.restore();
1076
+ interpretations.restore();
1077
+ });
1078
+
1079
+ it('should record newly authorable printable characters logically', () => {
1080
+ const layoutCode = new KeyCode({ key: Key.SLASH, ctrl: true, character: '?', layoutModifiers: 'shift', interpretation: 'layoutModifiers' });
1081
+ const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([layoutCode]);
1082
+
1083
+ const recorded = keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: '?', code: 'Slash' });
1084
+ expect(recorded?.toString()).to.equal('ctrl+?');
1085
+ expect(keybindingRegistry.resolveKeybinding({ command: TEST_COMMAND.id, keybinding: recorded!.toString() })[0].dispatchString())
1086
+ .to.equal(layoutCode.dispatchString());
1087
+ interpretations.restore();
1088
+ });
1089
+
1090
+ it('should record logical characters, physical non-printables, and ignore modifier-only input', () => {
1091
+ const registry = Object.create(KeybindingRegistry.prototype) as KeybindingRegistry;
1092
+ registry.authoredKeyCodeForKeyboardInput = input => {
1093
+ if (input.code === 'ControlLeft') {
1094
+ return new KeyCode({ ctrl: true });
1095
+ }
1096
+ if (input.code === 'F1') {
1097
+ return new KeyCode({ key: Key.F1, ctrl: true });
1098
+ }
1099
+ if (input.code === 'Delete') {
1100
+ return new KeyCode({ key: Key.DELETE, ctrl: true });
1101
+ }
1102
+ if (input.code === 'KeyP') {
1103
+ return new KeyCode({ key: Key.KEY_P, ctrl: true, shift: true, character: 'P' });
1104
+ }
1105
+ return new KeyCode({ key: input.code === 'Equal' ? Key.EQUAL : Key.BRACKET_LEFT, ctrl: true, character: input.key });
1106
+ };
1107
+
1108
+ expect(registry.authoredKeybindingStringForKeyboardInput({ key: '[', code: 'Digit8', ctrlKey: true })).to.equal('ctrl+[');
1109
+ expect(registry.authoredKeybindingStringForKeyboardInput({ key: '+', code: 'Equal', ctrlKey: true })).to.equal('ctrl+[char:0x2B]');
1110
+ expect(registry.authoredKeybindingStringForKeyboardInput({ key: 'F1', code: 'F1', ctrlKey: true })).to.equal('ctrl+f1');
1111
+ expect(registry.authoredKeybindingStringForKeyboardInput({ key: 'Delete', code: 'Delete', ctrlKey: true })).to.equal('ctrl+delete');
1112
+ const shifted = registry.authoredKeybindingStringForKeyboardInput({ key: 'P', code: 'KeyP', ctrlKey: true, shiftKey: true });
1113
+ expect(shifted).to.equal('shift+ctrl+p');
1114
+ expect(KeyCode.parse(shifted!).dispatchString()).to.equal('shift+ctrl+p');
1115
+ expect(registry.authoredKeybindingStringForKeyboardInput({ key: 'Control', code: 'ControlLeft', ctrlKey: true })).to.be.undefined;
1116
+ });
1117
+
1118
+ it('should record Space and numpad keys by name', () => {
1119
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: ' ', code: 'Space', ctrlKey: true, shiftKey: true }))
1120
+ .to.equal('shift+ctrl+space');
1121
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '+', code: 'NumpadAdd', ctrlKey: true })).to.equal('ctrl+add');
1122
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '-', code: 'NumpadSubtract', ctrlKey: true })).to.equal('ctrl+subtract');
1123
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '.', code: 'NumpadDecimal', ctrlKey: true })).to.equal('ctrl+decimal');
1124
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '1', code: 'Numpad1', ctrlKey: true })).to.equal('ctrl+1');
1125
+ });
1126
+
1127
+ it('should dispatch and record Option bindings as command modifiers on macOS', async () => {
1128
+ stub.value(true);
1129
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
1130
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-mac.json'));
1131
+ const execute = sinon.spy();
1132
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
1133
+ try {
1134
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: 'shift+alt+p' }]);
1135
+ keybindingRegistry.run(testKeyboardEvent({ key: '∏', code: 'KeyP', shiftKey: true, altKey: true }));
1136
+ await new Promise(resolve => setTimeout(resolve, 0));
1137
+
1138
+ expect(execute.calledOnce).to.be.true;
1139
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '∏', code: 'KeyP', shiftKey: true, altKey: true }))
1140
+ .to.equal('shift+alt+p');
1141
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'π', code: 'KeyP', altKey: true })).to.equal('alt+p');
1142
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'P', code: 'KeyP', shiftKey: true })).to.equal('shift+p');
1143
+ } finally {
1144
+ handler.dispose();
1145
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
1146
+ }
1147
+ });
1148
+
1149
+ it('should dispatch and record dead Option keys as their base character on macOS', async () => {
1150
+ stub.value(true);
1151
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
1152
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-mac.json'));
1153
+ const execute = sinon.spy();
1154
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
1155
+ try {
1156
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: 'alt+e' }]);
1157
+ keybindingRegistry.run(testKeyboardEvent({ key: 'Dead', code: 'KeyE', altKey: true }));
1158
+ await new Promise(resolve => setTimeout(resolve, 0));
1159
+
1160
+ expect(execute.calledOnce).to.be.true;
1161
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Dead', code: 'KeyE', altKey: true })).to.equal('alt+e');
1162
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Dead', code: 'KeyE', altKey: true, shiftKey: true }))
1163
+ .to.equal('shift+alt+e');
1164
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Dead', code: 'KeyE', altKey: true, metaKey: true }))
1165
+ .to.equal('meta+alt+e');
1166
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'F1', code: 'F1', altKey: true })).to.equal('alt+f1');
1167
+
1168
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: 'shift+alt+e' }]);
1169
+ keybindingRegistry.run(testKeyboardEvent({ key: 'Dead', code: 'KeyE', altKey: true, shiftKey: true }));
1170
+ await new Promise(resolve => setTimeout(resolve, 0));
1171
+ expect(execute.calledTwice).to.be.true;
1172
+ } finally {
1173
+ handler.dispose();
1174
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
1175
+ }
1176
+ });
1177
+
1178
+ it('should record Caps Lock input with the layout layer case', async () => {
1179
+ const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
1180
+ const execute = sinon.spy();
1181
+ const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
1182
+ try {
1183
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'p', code: 'KeyP', ctrlKey: true, shiftKey: true }))
1184
+ .to.equal('shift+ctrl+p');
1185
+
1186
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
1187
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'ü', code: 'BracketLeft', ctrlKey: true, shiftKey: true }))
1188
+ .to.equal('ctrl+Ü');
1189
+ const recorded = keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Ü', code: 'BracketLeft', ctrlKey: true });
1190
+ expect(recorded).to.equal('ctrl+ü');
1191
+
1192
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: recorded! }]);
1193
+ keybindingRegistry.dispatchNormalizedKeyDown({ key: 'ü', code: 'BracketLeft', ctrlKey: true }, new EventTarget());
1194
+ await new Promise(resolve => setTimeout(resolve, 0));
1195
+ expect(execute.calledOnce).to.be.true;
1196
+
1197
+ notifier.emitter.fire({
1198
+ info: { id: 'altgraph-letters', lang: 'pl' },
1199
+ mapping: { KeyL: { value: 'l', withShift: 'L', withAltGr: 'ł', withShiftAltGr: 'Ł' } }
1200
+ });
1201
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Ł', code: 'KeyL', ctrlKey: true, altGraph: true }))
1202
+ .to.equal('ctrl+ł');
1203
+
1204
+ stub.value(true);
1205
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-mac.json'));
1206
+ expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Å', code: 'KeyA', altKey: true })).to.equal('alt+a');
1207
+ } finally {
1208
+ handler.dispose();
1209
+ notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
1210
+ }
1211
+ });
1212
+
1213
+ it('should classify interpretation shadowing separately from canonical collisions', () => {
1214
+ const logical = {
1215
+ command: TEST_COMMAND.id,
1216
+ keybinding: 'ctrl+[',
1217
+ resolved: [new KeyCode({ key: Key.DIGIT8, ctrl: true, character: '[', layoutModifiers: 'altGraph' })]
1218
+ };
1219
+ const command = { command: TEST_COMMAND2.id, keybinding: 'ctrl+alt+8' };
1220
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [logical, command]);
1221
+
1222
+ const diagnostics = keybindingRegistry.getKeybindingCollisionDiagnostics(logical);
1223
+ expect(diagnostics.shadowedBy.map(binding => binding.command)).to.include(TEST_COMMAND2.id);
1224
+ expect(diagnostics.authored.full).to.be.empty;
1225
+ expect(diagnostics.layoutDerived.full).to.be.empty;
1226
+ expect(keybindingRegistry.containsKeybindingInScope(logical)).to.be.false;
1227
+ });
1228
+
1229
+ it('should keep shifted printable bindings symmetric in keyCode dispatch mode', () => {
1230
+ (corePreferences as unknown as Record<string, unknown>)['keyboard.dispatch'] = 'keyCode';
1231
+ const commandPalette = { command: TEST_COMMAND.id, keybinding: 'ctrl+shift+p' };
1232
+ const shiftedPrintable = { command: TEST_COMMAND2.id, keybinding: 'shift+/' };
1233
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [commandPalette, shiftedPrintable]);
1234
+
1235
+ expect(keybindingRegistry.matchKeybinding(KeySequence.parse('ctrl+shift+p'))?.runtime.binding.command).to.equal(TEST_COMMAND.id);
1236
+ expect(keybindingRegistry.matchKeybinding(KeySequence.parse('shift+/'))?.runtime.binding.command).to.equal(TEST_COMMAND2.id);
1237
+ });
1238
+
1239
+ it('should mark character-only bindings inactive in keyCode dispatch mode', () => {
1240
+ (corePreferences as unknown as Record<string, unknown>)['keyboard.dispatch'] = 'keyCode';
1241
+ const characterOnly = { command: TEST_COMMAND.id, keybinding: 'ctrl+ü' };
1242
+ const positional = { command: TEST_COMMAND2.id, keybinding: 'ctrl+[' };
1243
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [characterOnly, positional]);
1244
+
1245
+ expect(keybindingRegistry.getKeybindingInactiveReason(characterOnly)).to.be.a('string');
1246
+ expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(characterOnly))).to.be.undefined;
1247
+ expect(keybindingRegistry.getKeybindingInactiveReason(positional)).to.be.undefined;
1248
+ expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(positional))?.kind).to.equal('full');
1249
+ });
1250
+
1251
+ it('should require resolved sequences for logical matching in code dispatch mode', () => {
1252
+ testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
1253
+ const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+shift+a' };
1254
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
1255
+
1256
+ expect(KeySequence.parse('ctrl+shift+a')[0].dispatchString()).to.equal('shift+ctrl+a');
1257
+ expect(keybindingRegistry.resolveKeybinding(binding)[0].dispatchString()).to.equal('ctrl+a@shift');
1258
+ expect(keybindingRegistry.matchKeybinding(KeySequence.parse('ctrl+shift+a'))).to.be.undefined;
1259
+ expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(binding))?.kind).to.equal('full');
1260
+ });
1261
+
1262
+ it('should invalidate resolved bindings when keyboard dispatch changes', () => {
1263
+ const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+[' };
1264
+ keybindingRegistry.setKeymap(KeybindingScope.USER, [binding]);
1265
+ expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
1266
+ const changed = sinon.spy();
1267
+ keybindingRegistry.onKeybindingsChanged(changed);
1268
+
1269
+ (corePreferences as unknown as Record<string, unknown>)['keyboard.dispatch'] = 'keyCode';
1270
+ preferenceChanged.fire({ preferenceName: 'keyboard.dispatch' });
1271
+
1272
+ expect(changed.calledOnce).to.be.true;
1273
+ expect(keybindingRegistry.resolveKeybinding(binding)[0].dispatchString()).to.equal('ctrl+[');
1274
+ expect(keybindingRegistry.matchKeybinding(KeySequence.parse('ctrl+['))?.kind).to.equal('full');
477
1275
  });
478
1276
 
479
1277
  it('should prioritize bindings that use local context keys', () => {
@@ -519,11 +1317,11 @@ describe('keybindings', () => {
519
1317
  });
520
1318
  Object.defineProperty(mockEvent, 'target', { value: targetElement });
521
1319
 
522
- const match = keybindingRegistry.matchKeybinding([keyCode], mockEvent);
1320
+ const match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(localBinding), mockEvent);
523
1321
 
524
1322
  // The localBinding should be selected because it uses a local context key
525
1323
  expect(match?.kind).to.be.equal('full');
526
- expect(match?.binding?.command).to.be.equal('test.local-command');
1324
+ expect(match?.runtime.binding?.command).to.be.equal('test.local-command');
527
1325
 
528
1326
  disable();
529
1327
  });
@@ -566,11 +1364,11 @@ describe('keybindings', () => {
566
1364
  });
567
1365
  Object.defineProperty(mockEvent, 'target', { value: targetElement });
568
1366
 
569
- const match = keybindingRegistry.matchKeybinding([keyCode], mockEvent);
1367
+ const match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(secondBinding), mockEvent);
570
1368
 
571
1369
  // Should fall back to first enabled binding in priority order (secondBinding was registered later, so it wins)
572
1370
  expect(match?.kind).to.be.equal('full');
573
- expect(match?.binding?.command).to.be.equal('test.second-command');
1371
+ expect(match?.runtime.binding?.command).to.be.equal('test.second-command');
574
1372
 
575
1373
  disable();
576
1374
  });
@@ -593,6 +1391,55 @@ describe('acceleratorForKeyCode', () => {
593
1391
  expect(accelerator).to.equal('⌘V');
594
1392
  });
595
1393
 
1394
+ it('separates logical labels from physical layout modifier realizations', () => {
1395
+ const logicalBracket = new KeyCode({
1396
+ key: Key.DIGIT8,
1397
+ ctrl: true,
1398
+ character: '[',
1399
+ layoutModifiers: 'altGraph'
1400
+ });
1401
+ const logicalSlash = new KeyCode({
1402
+ key: Key.DIGIT7,
1403
+ character: '/',
1404
+ layoutModifiers: 'shift'
1405
+ });
1406
+
1407
+ expect(keybindingRegistry.componentsForKeyCode(logicalBracket)).to.deep.equal(['Ctrl', '[']);
1408
+ expect(keybindingRegistry.componentsForKeyCode(logicalBracket, false, 'physical')).to.deep.equal(['Ctrl', 'AltGr', '8']);
1409
+ expect(keybindingRegistry.acceleratorForKeyCode(logicalBracket, '+', true, 'physical')).to.equal('Ctrl+AltGr+8');
1410
+ expect(keybindingRegistry.componentsForKeyCode(logicalSlash)).to.deep.equal(['/']);
1411
+ expect(keybindingRegistry.componentsForKeyCode(logicalSlash, false, 'physical')).to.deep.equal(['Shift', '7']);
1412
+ expect(keybindingRegistry.acceleratorForKeyCode(logicalSlash, '+', true, 'physical')).to.equal('Shift+7');
1413
+ });
1414
+
1415
+ it('keeps command Shift in physical realizations', () => {
1416
+ const commandShift = new KeyCode({ key: Key.TAB, ctrl: true, shift: true });
1417
+ expect(keybindingRegistry.componentsForKeyCode(commandShift, false, 'physical')).to.deep.equal(['Ctrl', 'Shift', 'Tab']);
1418
+
1419
+ const shiftedLetter = new KeyCode({ key: Key.KEY_P, ctrl: true, character: 'P', layoutModifiers: 'shift' });
1420
+ expect(keybindingRegistry.componentsForKeyCode(shiftedLetter)).to.deep.equal(['Ctrl', 'Shift', 'P']);
1421
+
1422
+ const unresolvedCharacter = new KeyCode({ ctrl: true, character: '§', supportedByLayout: false });
1423
+ expect(keybindingRegistry.componentsForKeyCode(unresolvedCharacter)).to.deep.equal(['Ctrl', '§']);
1424
+ });
1425
+
1426
+ it('deduplicates command Alt and layout modifier Option on macOS', () => {
1427
+ stub.value(true);
1428
+ const commandAndLayoutOption = new KeyCode({
1429
+ key: Key.DIGIT6,
1430
+ meta: true,
1431
+ ctrl: true,
1432
+ alt: true,
1433
+ layoutModifiers: 'altGraph'
1434
+ });
1435
+ const layoutOption = new KeyCode({ key: Key.DIGIT6, layoutModifiers: 'altGraph' });
1436
+
1437
+ expect(keybindingRegistry.componentsForKeyCode(commandAndLayoutOption, false, 'physical')).to.deep.equal(['⌘', '⌃', '⌥', '6']);
1438
+ expect(keybindingRegistry.componentsForKeyCode(commandAndLayoutOption, true, 'physical')).to.deep.equal(['Cmd', 'Ctrl', 'Alt', '6']);
1439
+ expect(keybindingRegistry.componentsForKeyCode(layoutOption, false, 'physical')).to.deep.equal(['⌥', '6']);
1440
+ expect(keybindingRegistry.componentsForKeyCode(layoutOption, true, 'physical')).to.deep.equal(['Option', '6']);
1441
+ });
1442
+
596
1443
  it('uses ASCII names with the separator on macOS Electron (asciiOnly)', () => {
597
1444
  stub.value(true);
598
1445
  const accelerator = keybindingRegistry.acceleratorForKeyCode(metaV, '+', true);
@@ -655,16 +1502,13 @@ class MockContextKeyService extends ContextKeyServiceDummyImpl {
655
1502
  @injectable()
656
1503
  class MockKeyboardLayoutProvider implements KeyboardLayoutProvider {
657
1504
  getNativeLayout(): Promise<NativeKeyboardLayout> {
658
- return Promise.resolve({
659
- info: { id: 'mock', lang: 'en' },
660
- mapping: {}
661
- });
1505
+ return Promise.resolve(require('../../src/common/keyboard/layouts/en-US-pc.json'));
662
1506
  }
663
1507
  }
664
1508
 
665
1509
  @injectable()
666
1510
  class MockKeyboardLayoutChangeNotifier implements KeyboardLayoutChangeNotifier {
667
- private emitter = new Emitter<NativeKeyboardLayout>();
1511
+ readonly emitter = new Emitter<NativeKeyboardLayout>();
668
1512
  get onDidChangeNativeLayout(): Event<NativeKeyboardLayout> {
669
1513
  return this.emitter.event;
670
1514
  }