@theia/core 1.77.0-next.56 → 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 (102) hide show
  1. package/lib/browser/catalog.json +66 -0
  2. package/lib/browser/common-frontend-contribution.d.ts +6 -1
  3. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  4. package/lib/browser/common-frontend-contribution.js +18 -1
  5. package/lib/browser/common-frontend-contribution.js.map +1 -1
  6. package/lib/browser/keybinding.d.ts +95 -17
  7. package/lib/browser/keybinding.d.ts.map +1 -1
  8. package/lib/browser/keybinding.js +405 -75
  9. package/lib/browser/keybinding.js.map +1 -1
  10. package/lib/browser/keybinding.spec.js +786 -30
  11. package/lib/browser/keybinding.spec.js.map +1 -1
  12. package/lib/browser/keyboard/browser-keyboard-frontend-contribution.js +1 -1
  13. package/lib/browser/keyboard/browser-keyboard-frontend-contribution.js.map +1 -1
  14. package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts +3 -4
  15. package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts.map +1 -1
  16. package/lib/browser/keyboard/browser-keyboard-layout-provider.js +3 -3
  17. package/lib/browser/keyboard/browser-keyboard-layout-provider.js.map +1 -1
  18. package/lib/browser/keyboard/browser-keyboard-layout-provider.spec.js +39 -0
  19. package/lib/browser/keyboard/browser-keyboard-layout-provider.spec.js.map +1 -1
  20. package/lib/browser/keyboard/keyboard-layout-service.d.ts +67 -15
  21. package/lib/browser/keyboard/keyboard-layout-service.d.ts.map +1 -1
  22. package/lib/browser/keyboard/keyboard-layout-service.js +309 -98
  23. package/lib/browser/keyboard/keyboard-layout-service.js.map +1 -1
  24. package/lib/browser/keyboard/keyboard-layout-service.spec.js +538 -12
  25. package/lib/browser/keyboard/keyboard-layout-service.spec.js.map +1 -1
  26. package/lib/browser/keyboard/keys.spec.js +183 -0
  27. package/lib/browser/keyboard/keys.spec.js.map +1 -1
  28. package/lib/browser/menu/action-menu-node.d.ts +5 -3
  29. package/lib/browser/menu/action-menu-node.d.ts.map +1 -1
  30. package/lib/browser/menu/action-menu-node.js +2 -11
  31. package/lib/browser/menu/action-menu-node.js.map +1 -1
  32. package/lib/browser/menu/action-menu-node.spec.d.ts +2 -0
  33. package/lib/browser/menu/action-menu-node.spec.d.ts.map +1 -0
  34. package/lib/browser/menu/action-menu-node.spec.js +54 -0
  35. package/lib/browser/menu/action-menu-node.spec.js.map +1 -0
  36. package/lib/browser/menu/browser-menu-plugin.js +1 -1
  37. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  38. package/lib/browser/test/jsdom.d.ts.map +1 -1
  39. package/lib/browser/test/jsdom.js +11 -0
  40. package/lib/browser/test/jsdom.js.map +1 -1
  41. package/lib/common/keyboard/keyboard-layout-provider.d.ts +7 -0
  42. package/lib/common/keyboard/keyboard-layout-provider.d.ts.map +1 -1
  43. package/lib/common/keyboard/keyboard-layout-provider.js.map +1 -1
  44. package/lib/common/keys.d.ts +86 -3
  45. package/lib/common/keys.d.ts.map +1 -1
  46. package/lib/common/keys.js +262 -23
  47. package/lib/common/keys.js.map +1 -1
  48. package/lib/common/ternary-search-tree.js +2 -2
  49. package/lib/common/ternary-search-tree.js.map +1 -1
  50. package/lib/common/ternary-search-tree.spec.js +12 -0
  51. package/lib/common/ternary-search-tree.spec.js.map +1 -1
  52. package/lib/electron-browser/keyboard/electron-keyboard-module.d.ts +2 -0
  53. package/lib/electron-browser/keyboard/electron-keyboard-module.d.ts.map +1 -1
  54. package/lib/electron-browser/keyboard/electron-keyboard-module.js +8 -1
  55. package/lib/electron-browser/keyboard/electron-keyboard-module.js.map +1 -1
  56. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.d.ts +2 -0
  57. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.d.ts.map +1 -0
  58. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.js +28 -0
  59. package/lib/electron-browser/keyboard/electron-keyboard-module.spec.js.map +1 -0
  60. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  61. package/lib/electron-browser/menu/electron-main-menu-factory.js +3 -2
  62. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  63. package/lib/electron-browser/menu/electron-menu-contribution.d.ts.map +1 -1
  64. package/lib/electron-browser/menu/electron-menu-contribution.js +3 -0
  65. package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
  66. package/lib/electron-common/electron-api.d.ts +5 -0
  67. package/lib/electron-common/electron-api.d.ts.map +1 -1
  68. package/lib/electron-common/electron-api.js +21 -0
  69. package/lib/electron-common/electron-api.js.map +1 -1
  70. package/lib/electron-common/electron-api.spec.d.ts +2 -0
  71. package/lib/electron-common/electron-api.spec.d.ts.map +1 -0
  72. package/lib/electron-common/electron-api.spec.js +40 -0
  73. package/lib/electron-common/electron-api.spec.js.map +1 -0
  74. package/lib/electron-main/electron-api-main.d.ts +0 -1
  75. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  76. package/lib/electron-main/electron-api-main.js +1 -12
  77. package/lib/electron-main/electron-api-main.js.map +1 -1
  78. package/package.json +4 -4
  79. package/src/browser/common-frontend-contribution.ts +16 -0
  80. package/src/browser/keybinding.spec.ts +878 -34
  81. package/src/browser/keybinding.ts +470 -85
  82. package/src/browser/keyboard/browser-keyboard-frontend-contribution.ts +1 -1
  83. package/src/browser/keyboard/browser-keyboard-layout-provider.spec.ts +52 -1
  84. package/src/browser/keyboard/browser-keyboard-layout-provider.ts +5 -5
  85. package/src/browser/keyboard/keyboard-layout-service.spec.ts +640 -15
  86. package/src/browser/keyboard/keyboard-layout-service.ts +358 -106
  87. package/src/browser/keyboard/keys.spec.ts +206 -1
  88. package/src/browser/menu/action-menu-node.spec.ts +60 -0
  89. package/src/browser/menu/action-menu-node.ts +8 -14
  90. package/src/browser/menu/browser-menu-plugin.ts +1 -1
  91. package/src/browser/test/jsdom.ts +11 -0
  92. package/src/common/keyboard/keyboard-layout-provider.ts +8 -0
  93. package/src/common/keys.ts +311 -26
  94. package/src/common/ternary-search-tree.spec.ts +14 -0
  95. package/src/common/ternary-search-tree.ts +2 -2
  96. package/src/electron-browser/keyboard/electron-keyboard-module.spec.ts +29 -0
  97. package/src/electron-browser/keyboard/electron-keyboard-module.ts +9 -2
  98. package/src/electron-browser/menu/electron-main-menu-factory.ts +3 -3
  99. package/src/electron-browser/menu/electron-menu-contribution.ts +4 -0
  100. package/src/electron-common/electron-api.spec.ts +42 -0
  101. package/src/electron-common/electron-api.ts +23 -0
  102. package/src/electron-main/electron-api-main.ts +2 -13
@@ -24,10 +24,27 @@ const keyboard_layout_provider_1 = require("../../common/keyboard/keyboard-layou
24
24
  const os = tslib_1.__importStar(require("../../common/os"));
25
25
  const chai = tslib_1.__importStar(require("chai"));
26
26
  const sinon = tslib_1.__importStar(require("sinon"));
27
+ class TestKeyboardLayoutService extends keyboard_layout_service_1.KeyboardLayoutService {
28
+ getWindowsKeyCodeInterpretations(keyCode, input, layoutModifiers) {
29
+ return super.getWindowsKeyCodeInterpretations(keyCode, input, layoutModifiers);
30
+ }
31
+ }
32
+ const RUSSIAN_LAYOUT = {
33
+ info: { id: 'ru', lang: 'ru' },
34
+ mapping: {
35
+ KeyA: { value: 'ф', withShift: 'Ф', withAltGr: '', withShiftAltGr: '' },
36
+ KeyC: { value: 'с', withShift: 'С', withAltGr: '', withShiftAltGr: '' },
37
+ KeyV: { value: 'м', withShift: 'М', withAltGr: '', withShiftAltGr: '' },
38
+ KeyS: { value: 'ы', withShift: 'Ы', withAltGr: '', withShiftAltGr: '' },
39
+ Digit8: { value: '8', withShift: '*', withAltGr: '', withShiftAltGr: '' },
40
+ BracketLeft: { value: 'х', withShift: 'Х', withAltGr: '', withShiftAltGr: '' },
41
+ Comma: { value: 'б', withShift: 'Б', withAltGr: '', withShiftAltGr: '' }
42
+ }
43
+ };
27
44
  describe('keyboard layout service', function () {
28
45
  let stubOSX;
29
46
  let stubWindows;
30
- const setup = async (layout, system) => {
47
+ const setup = async (layout, system, layoutSource, keyValidator) => {
31
48
  switch (system) {
32
49
  case 'mac':
33
50
  stubOSX = sinon.stub(os, 'isOSX').value(true);
@@ -42,9 +59,10 @@ describe('keyboard layout service', function () {
42
59
  stubWindows = sinon.stub(os, 'isWindows').value(false);
43
60
  }
44
61
  const container = new inversify_1.Container();
45
- container.bind(keyboard_layout_service_1.KeyboardLayoutService).toSelf().inSingletonScope();
62
+ container.bind(keyboard_layout_service_1.KeyboardLayoutService).to(TestKeyboardLayoutService).inSingletonScope();
46
63
  let MockLayoutProvider = class MockLayoutProvider {
47
64
  constructor() {
65
+ this.layoutSource = layoutSource;
48
66
  this.emitter = new event_1.Emitter();
49
67
  }
50
68
  get onDidChangeNativeLayout() {
@@ -59,6 +77,9 @@ describe('keyboard layout service', function () {
59
77
  ], MockLayoutProvider);
60
78
  container.bind(keyboard_layout_provider_1.KeyboardLayoutProvider).to(MockLayoutProvider);
61
79
  container.bind(keyboard_layout_provider_1.KeyboardLayoutChangeNotifier).to(MockLayoutProvider);
80
+ if (keyValidator) {
81
+ container.bind(keyboard_layout_provider_1.KeyValidator).toConstantValue(keyValidator);
82
+ }
62
83
  const service = container.get(keyboard_layout_service_1.KeyboardLayoutService);
63
84
  await service.initialize();
64
85
  return service;
@@ -67,26 +88,198 @@ describe('keyboard layout service', function () {
67
88
  stubOSX.restore();
68
89
  stubWindows.restore();
69
90
  });
91
+ it('reports an unknown layout source when no source provider is configured', async () => {
92
+ const layout = { info: { id: 'mock', lang: 'en' }, mapping: {} };
93
+ chai.expect((await setup(layout, 'linux')).layoutSource).to.equal('unknown');
94
+ });
95
+ it('reports the configured layout source', async () => {
96
+ const layout = { info: { id: 'mock', lang: 'en' }, mapping: {} };
97
+ chai.expect((await setup(layout, 'linux', 'native-keymap')).layoutSource).to.equal('native-keymap');
98
+ });
70
99
  it('resolves correct key bindings with German Mac layout', async () => {
71
100
  const macGerman = require('../../../src/common/keyboard/layouts/de-German-mac.json');
72
101
  const service = await setup(macGerman, 'mac');
73
102
  const toggleComment = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('Slash+M1'));
74
- chai.expect(toggleComment.toString()).to.equal('meta+shift+7');
103
+ chai.expect(toggleComment.toString()).to.equal('meta+7');
104
+ chai.expect(toggleComment.character).to.equal('/');
105
+ chai.expect(toggleComment.layoutModifiers).to.deep.equal('shift');
75
106
  chai.expect(service.getKeyboardCharacter(toggleComment.key)).to.equal('7');
76
107
  const indentLine = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('BracketRight+M1'));
77
- chai.expect(indentLine.toString()).to.equal('meta+alt+ctrl+6');
108
+ chai.expect(indentLine.toString()).to.equal('meta+6');
109
+ chai.expect(indentLine.character).to.equal(']');
110
+ chai.expect(indentLine.layoutModifiers).to.deep.equal('altGraph');
78
111
  chai.expect(service.getKeyboardCharacter(indentLine.key)).to.equal('6');
79
112
  });
80
113
  it('resolves correct key bindings with French Mac layout', async () => {
81
114
  const macFrench = require('../../../src/common/keyboard/layouts/fr-French-mac.json');
82
115
  const service = await setup(macFrench, 'mac');
83
116
  const toggleComment = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('Slash+M1'));
84
- chai.expect(toggleComment.toString()).to.equal('meta+shift+.');
117
+ chai.expect(toggleComment.toString()).to.equal('meta+.');
118
+ chai.expect(toggleComment.character).to.equal('/');
119
+ chai.expect(toggleComment.layoutModifiers).to.deep.equal('shift');
85
120
  chai.expect(service.getKeyboardCharacter(toggleComment.key)).to.equal(':');
86
121
  const indentLine = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('BracketRight+M1'));
87
- chai.expect(indentLine.toString()).to.equal('meta+shift+alt+ctrl+-');
122
+ chai.expect(indentLine.toString()).to.equal('meta+-');
123
+ chai.expect(indentLine.character).to.equal(']');
124
+ chai.expect(indentLine.layoutModifiers).to.deep.equal('shiftAltGraph');
88
125
  chai.expect(service.getKeyboardCharacter(indentLine.key)).to.equal(')');
89
126
  });
127
+ it('uses global layout modifier priority and a stable physical-key tie-break', async () => {
128
+ const layout = {
129
+ info: { id: 'priority', lang: 'fr' },
130
+ mapping: {
131
+ Backslash: { value: '*', withShift: 'µ', withAltGr: '@', withShiftAltGr: '#' },
132
+ IntlBackslash: { value: '@', withShift: '#', withAltGr: 'µ', withShiftAltGr: '*' },
133
+ KeyB: { value: 'x', withShift: 'X', withAltGr: '', withShiftAltGr: '' },
134
+ KeyA: { value: 'x', withShift: 'X', withAltGr: '', withShiftAltGr: '' }
135
+ }
136
+ };
137
+ const service = await setup(layout, 'linux');
138
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('shift+2')).key).to.equal(keys_1.Key.INTL_BACKSLASH);
139
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('x')).key).to.equal(keys_1.Key.KEY_A);
140
+ const shifted = service.resolveKeyCode(keys_1.KeyCode.parse('shift+x'));
141
+ const event = service.getKeyCodeInterpretations({ key: 'X', code: 'KeyA', shiftKey: true }, 'code')
142
+ .find(candidate => candidate.interpretation === 'layoutModifiers');
143
+ chai.expect(event.dispatchString()).to.equal(shifted.dispatchString());
144
+ const altShifted = service.resolveKeyCode(keys_1.KeyCode.parse('alt+shift+x'));
145
+ const altEvent = service.getKeyCodeInterpretations({ key: 'X', code: 'KeyA', altKey: true, shiftKey: true }, 'code')
146
+ .find(candidate => candidate.interpretation === 'layoutModifiers');
147
+ chai.expect(altEvent.dispatchString()).to.equal(altShifted.dispatchString());
148
+ const capsLockEvent = service.getKeyCodeInterpretations({ key: 'x', code: 'KeyA', shiftKey: true }, 'code')
149
+ .find(candidate => candidate.interpretation === 'layoutModifiers');
150
+ chai.expect(capsLockEvent.dispatchString()).to.equal(shifted.dispatchString());
151
+ });
152
+ it('globally ranks case-folded fallback candidates', async () => {
153
+ const layout = {
154
+ info: { id: 'case-folded-priority', lang: 'en' },
155
+ mapping: {
156
+ KeyA: { value: 'a', withShift: 'A', withAltGr: 'ä', withShiftAltGr: '' },
157
+ KeyB: { value: 'b', withShift: 'ä', withAltGr: '', withShiftAltGr: '' }
158
+ }
159
+ };
160
+ const service = await setup(layout, 'linux');
161
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse('[char:0xC4]'));
162
+ chai.expect(resolved.key).to.equal(keys_1.Key.KEY_B);
163
+ chai.expect(resolved.layoutModifiers).to.deep.equal('shift');
164
+ });
165
+ it('matches shifted physical scan-code bindings through the command interpretation', async () => {
166
+ const us = require('../../../src/common/keyboard/layouts/en-US-pc.json');
167
+ const service = await setup(us, 'linux');
168
+ const physical = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+[BracketRight]'));
169
+ const interpretations = service.getKeyCodeInterpretations({
170
+ key: '}', code: 'BracketRight', ctrlKey: true, shiftKey: true
171
+ }, 'code');
172
+ chai.expect(physical.physical).to.be.true;
173
+ chai.expect(interpretations.some(candidate => candidate.dispatchString() === physical.dispatchString())).to.be.true;
174
+ });
175
+ it('keeps BÉPO logical bindings distinct from their former US-position collisions', async () => {
176
+ const bepo = require('../../../src/common/keyboard/layouts/fr-Bepo-pc.json');
177
+ const service = await setup(bepo, 'linux');
178
+ const undo = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+z'));
179
+ const outdent = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+['));
180
+ const close = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+w'));
181
+ const indent = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+]'));
182
+ chai.expect(undo.key).to.equal(keys_1.Key.BRACKET_LEFT);
183
+ chai.expect(outdent.key).to.equal(keys_1.Key.DIGIT4);
184
+ chai.expect(outdent.layoutModifiers).to.equal('altGraph');
185
+ chai.expect(close.key).to.equal(keys_1.Key.BRACKET_RIGHT);
186
+ chai.expect(indent.key).to.equal(keys_1.Key.DIGIT5);
187
+ chai.expect(indent.layoutModifiers).to.equal('altGraph');
188
+ });
189
+ it('falls back to the US position for Latin letters missing from a non-Latin layout', async () => {
190
+ const service = await setup(RUSSIAN_LAYOUT, 'linux');
191
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+c'));
192
+ chai.expect(resolved.key).to.equal(keys_1.Key.KEY_C);
193
+ chai.expect(resolved.supportedByLayout).to.be.true;
194
+ chai.expect(resolved.layoutModifiers).to.equal('none');
195
+ chai.expect(resolved.dispatchString()).to.equal('ctrl+c');
196
+ chai.expect(service.getLatinLetterFallback(keys_1.Key.KEY_C)).to.equal('c');
197
+ chai.expect(service.getLatinLetterFallback(keys_1.Key.DIGIT8)).to.be.undefined;
198
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('с')).key).to.equal(keys_1.Key.KEY_C);
199
+ chai.expect(service.getKeyboardCharacter(keys_1.Key.KEY_C)).to.equal('с');
200
+ });
201
+ it('absorbs Shift into the Latin fallback letter', async () => {
202
+ const service = await setup(RUSSIAN_LAYOUT, 'linux');
203
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+a'));
204
+ chai.expect(resolved.key).to.equal(keys_1.Key.KEY_A);
205
+ chai.expect(resolved.layoutModifiers).to.equal('shift');
206
+ chai.expect(resolved.shift).to.be.false;
207
+ chai.expect(resolved.character).to.equal('A');
208
+ });
209
+ it('does not fall back for punctuation on a non-Latin layout', async () => {
210
+ const service = await setup(RUSSIAN_LAYOUT, 'linux');
211
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+[')).supportedByLayout).to.be.false;
212
+ });
213
+ it('does not override Latin letters produced elsewhere on the layout', async () => {
214
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
215
+ const service = await setup(german, 'linux');
216
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+z')).key).to.equal(keys_1.Key.KEY_Y);
217
+ chai.expect(service.getLatinLetterFallback(keys_1.Key.KEY_Z)).to.be.undefined;
218
+ });
219
+ it('does not override letters produced in only one case on the base or Shift layer', async () => {
220
+ const layout = {
221
+ info: { id: 'one-case-only', lang: 'en' },
222
+ mapping: {
223
+ KeyS: { value: 'o', withShift: 'Q', withAltGr: '', withShiftAltGr: '' },
224
+ KeyO: { value: 's', withShift: 'P', withAltGr: '', withShiftAltGr: '' }
225
+ }
226
+ };
227
+ const service = await setup(layout, 'linux');
228
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('o')).key).to.equal(keys_1.Key.KEY_S);
229
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('p')).key).to.equal(keys_1.Key.KEY_O);
230
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('p')).layoutModifiers).to.equal('shift');
231
+ for (const key of [keys_1.Key.KEY_O, keys_1.Key.KEY_P, keys_1.Key.KEY_Q, keys_1.Key.KEY_S]) {
232
+ chai.expect(service.getLatinLetterFallback(key)).to.be.undefined;
233
+ }
234
+ });
235
+ it('adds a fallback when a letter is produced only on the AltGraph layer', async () => {
236
+ const layout = {
237
+ info: { id: 'altgraph-only-latin', lang: 'ru' },
238
+ mapping: {
239
+ KeyQ: { value: 'й', withShift: 'Й', withAltGr: 'a', withShiftAltGr: '' }
240
+ }
241
+ };
242
+ const service = await setup(layout, 'linux');
243
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse('a'));
244
+ chai.expect(resolved.key).to.equal(keys_1.Key.KEY_A);
245
+ chai.expect(resolved.layoutModifiers).to.equal('none');
246
+ chai.expect(service.getLatinLetterFallback(keys_1.Key.KEY_A)).to.equal('a');
247
+ });
248
+ it('adds Latin fallbacks to Windows mappings with vkeys and character values', async () => {
249
+ const layout = {
250
+ ...RUSSIAN_LAYOUT,
251
+ mapping: {
252
+ ...RUSSIAN_LAYOUT.mapping,
253
+ KeyC: { ...RUSSIAN_LAYOUT.mapping.KeyC, vkey: 'VK_C' }
254
+ }
255
+ };
256
+ const service = await setup(layout, 'win');
257
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+c')).key).to.equal(keys_1.Key.KEY_C);
258
+ chai.expect(service.getLatinLetterFallback(keys_1.Key.KEY_C)).to.equal('c');
259
+ });
260
+ it('marks printable bindings unresolved when the layout mapping is empty', async () => {
261
+ const layout = { info: { id: 'empty', lang: 'en' }, mapping: {} };
262
+ const service = await setup(layout, 'linux');
263
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+[')).supportedByLayout).to.be.false;
264
+ for (const lower of 'abcdefghijklmnopqrstuvwxyz') {
265
+ const key = keys_1.Key.getKey(`Key${lower.toUpperCase()}`);
266
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse(`ctrl+${lower}`)).key).to.equal(key);
267
+ chai.expect(service.getLatinLetterFallback(key)).to.equal(lower);
268
+ }
269
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('f1')).supportedByLayout).to.be.true;
270
+ });
271
+ it('marks unavailable logical characters unresolved instead of using the US position', async () => {
272
+ const layout = {
273
+ info: { id: 'missing-bracket', lang: 'en' },
274
+ mapping: {
275
+ KeyA: { value: 'a', withShift: 'A', withAltGr: '', withShiftAltGr: '' }
276
+ }
277
+ };
278
+ const service = await setup(layout, 'linux');
279
+ const outdent = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+['));
280
+ chai.expect(outdent.supportedByLayout).to.be.false;
281
+ chai.expect(outdent.key).to.equal(keys_1.Key.BRACKET_LEFT);
282
+ });
90
283
  it('does not let a dead key hijack a keybinding on Mac (#17677)', async () => {
91
284
  // On macOS US Extended, Option+P is the "combining comma below" dead key whose display glyph
92
285
  // is reported as ',' by native-keymap. Since KeyP is reported before Comma, without honoring the
@@ -113,25 +306,358 @@ describe('keyboard layout service', function () {
113
306
  chai.expect(openSettings.toString()).to.equal('meta+,');
114
307
  chai.expect(service.getKeyboardCharacter(openSettings.key)).to.equal(',');
115
308
  });
309
+ it('filters dead AltGraph mappings on Linux and Windows', async () => {
310
+ const layout = {
311
+ info: { id: 'dead-altgraph', lang: 'en' },
312
+ mapping: {
313
+ KeyP: {
314
+ value: 'p', withShift: 'P', withAltGr: '§', withShiftAltGr: '', withAltGrIsDeadKey: true, vkey: 'VK_P'
315
+ }
316
+ }
317
+ };
318
+ for (const system of ['linux', 'win']) {
319
+ const service = await setup(layout, system);
320
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('§')).supportedByLayout).to.be.false;
321
+ stubOSX.restore();
322
+ stubWindows.restore();
323
+ }
324
+ stubOSX = sinon.stub(os, 'isOSX').value(false);
325
+ stubWindows = sinon.stub(os, 'isWindows').value(false);
326
+ });
327
+ it('retains the Windows vkey fallback for value-less mappings', async () => {
328
+ const layout = {
329
+ info: { id: 'vkey-fallback', lang: 'en' },
330
+ mapping: { KeyA: { vkey: 'VK_A', value: '' } }
331
+ };
332
+ const service = await setup(layout, 'win');
333
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse('a'));
334
+ chai.expect(resolved.supportedByLayout).to.be.true;
335
+ chai.expect(resolved.key).to.equal(keys_1.Key.KEY_A);
336
+ });
337
+ it('absorbs authored Shift when it selects a US character or layout shift layer', async () => {
338
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
339
+ const service = await setup(german, 'linux');
340
+ const legacy = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+['));
341
+ chai.expect(legacy.shift).to.be.false;
342
+ chai.expect(legacy.dispatchString()).to.equal('ctrl+7@altgraph');
343
+ });
344
+ it('retains authored Shift when it is not consumed by the logical character', async () => {
345
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
346
+ const service = await setup(german, 'linux');
347
+ const umlaut = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+ü'));
348
+ chai.expect(umlaut.dispatchString()).to.equal('shift+ctrl+[');
349
+ chai.expect(umlaut.layoutModifiers).to.equal('none');
350
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('shift+[char:0x41]')).dispatchString()).to.equal('a@shift');
351
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('shift+[char:0x61]')).dispatchString()).to.equal('shift+a');
352
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+a')).dispatchString()).to.equal('ctrl+a@shift');
353
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+[')).dispatchString()).to.equal('ctrl+7@altgraph');
354
+ });
355
+ it('requires Option for a macOS Shift+AltGraph layer', async () => {
356
+ const macUS = require('../../../src/common/keyboard/layouts/en-US-mac.json');
357
+ const service = await setup(macUS, 'mac');
358
+ chai.expect(service.detectLayoutModifiers({ key: '±', code: 'IntlBackslash', shiftKey: true })).to.equal('shift');
359
+ chai.expect(service.detectLayoutModifiers({ key: '±', code: 'IntlBackslash', shiftKey: true, altKey: true })).to.equal('shiftAltGraph');
360
+ });
361
+ it('interprets macOS Option as a command modifier besides the Option layer', async () => {
362
+ const macUS = require('../../../src/common/keyboard/layouts/en-US-mac.json');
363
+ const service = await setup(macUS, 'mac');
364
+ const optionShift = service.getKeyCodeInterpretations({ key: '∏', code: 'KeyP', shiftKey: true, altKey: true }, 'code');
365
+ chai.expect(optionShift.map(candidate => candidate.dispatchString())).to.deep.equal(['shift+alt+p', 'alt+p@shift', 'p@shift@altgraph']);
366
+ chai.expect(optionShift.map(candidate => candidate.character)).to.deep.equal(['P', 'P', '∏']);
367
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('shift+alt+p')).dispatchString()).to.equal('alt+p@shift');
368
+ const option = service.getKeyCodeInterpretations({ key: 'π', code: 'KeyP', altKey: true }, 'code');
369
+ chai.expect(option.map(candidate => candidate.dispatchString())).to.deep.equal(['alt+p', 'p@altgraph']);
370
+ chai.expect(option.map(candidate => candidate.character)).to.deep.equal(['p', 'π']);
371
+ const shift = service.getKeyCodeInterpretations({ key: 'P', code: 'KeyP', shiftKey: true }, 'code');
372
+ chai.expect(shift.map(candidate => candidate.dispatchString())).to.deep.equal(['shift+p', 'p@shift']);
373
+ });
374
+ it('records the base character for dead Option keys on macOS', async () => {
375
+ const service = await setup(require('../../../src/common/keyboard/layouts/en-US-mac.json'), 'mac');
376
+ const option = service.getKeyCodeInterpretations({ key: 'Dead', code: 'KeyE', altKey: true }, 'code');
377
+ chai.expect(option).to.have.length(1);
378
+ chai.expect(option[0].character).to.equal('e');
379
+ chai.expect(option[0].interpretation).to.equal('commandModifiers');
380
+ chai.expect(option[0].dispatchString()).to.equal('alt+e');
381
+ const optionShift = service.getKeyCodeInterpretations({ key: 'Dead', code: 'KeyE', altKey: true, shiftKey: true }, 'code');
382
+ chai.expect(optionShift.map(candidate => candidate.character)).to.deep.equal(['E', 'E']);
383
+ chai.expect(optionShift.map(candidate => candidate.dispatchString())).to.deep.equal(['shift+alt+e', 'alt+e@shift']);
384
+ const withoutOption = service.getKeyCodeInterpretations({ key: 'Dead', code: 'KeyE' }, 'code');
385
+ chai.expect(withoutOption).to.have.length(1);
386
+ chai.expect(withoutOption[0].character).to.be.undefined;
387
+ });
388
+ it('normalizes the character to the layout layer when Caps Lock inverts the case', async () => {
389
+ const us = await setup(require('../../../src/common/keyboard/layouts/en-US-pc.json'), 'linux');
390
+ const capsShift = us.getKeyCodeInterpretations({ key: 'p', code: 'KeyP', ctrlKey: true, shiftKey: true }, 'code');
391
+ chai.expect(capsShift.map(candidate => candidate.character)).to.deep.equal(['P', 'P']);
392
+ chai.expect(capsShift.map(candidate => candidate.dispatchString())).to.deep.equal(['shift+ctrl+p', 'ctrl+p@shift']);
393
+ const shift = us.getKeyCodeInterpretations({ key: 'P', code: 'KeyP', shiftKey: true }, 'code');
394
+ chai.expect(shift.map(candidate => candidate.character)).to.deep.equal(['P', 'P']);
395
+ stubOSX.restore();
396
+ stubWindows.restore();
397
+ const german = await setup(require('../../../src/common/keyboard/layouts/de-German-pc.json'), 'linux');
398
+ const capsBase = german.getKeyCodeInterpretations({ key: 'Ü', code: 'BracketLeft', ctrlKey: true }, 'code');
399
+ chai.expect(capsBase).to.have.length(1);
400
+ chai.expect(capsBase[0].character).to.equal('ü');
401
+ chai.expect(capsBase[0].dispatchString()).to.equal('ctrl+[');
402
+ });
403
+ it('normalizes the character to the macOS Option layer when Caps Lock inverts the case', async () => {
404
+ const service = await setup(require('../../../src/common/keyboard/layouts/en-US-mac.json'), 'mac');
405
+ const capsOption = service.getKeyCodeInterpretations({ key: 'Å', code: 'KeyA', altKey: true }, 'code');
406
+ chai.expect(capsOption.map(candidate => candidate.character)).to.deep.equal(['a', 'å']);
407
+ chai.expect(capsOption.map(candidate => candidate.dispatchString())).to.deep.equal(['alt+a', 'a@altgraph']);
408
+ const capsShiftOption = service.getKeyCodeInterpretations({ key: 'å', code: 'KeyA', altKey: true, shiftKey: true }, 'code');
409
+ chai.expect(capsShiftOption[0].character).to.equal('A');
410
+ chai.expect(capsShiftOption[0].dispatchString()).to.equal('shift+alt+a');
411
+ });
412
+ it('normalizes the character to the Linux AltGraph layer when Caps Lock inverts the case', async () => {
413
+ const layout = {
414
+ info: { id: 'altgraph-letters', lang: 'pl' },
415
+ mapping: { KeyL: { value: 'l', withShift: 'L', withAltGr: 'ł', withShiftAltGr: 'Ł' } }
416
+ };
417
+ const service = await setup(layout, 'linux');
418
+ const capsAltGraph = service.getKeyCodeInterpretations({ key: 'Ł', code: 'KeyL', altGraph: true }, 'code');
419
+ chai.expect(capsAltGraph).to.have.length(1);
420
+ chai.expect(capsAltGraph[0].character).to.equal('ł');
421
+ chai.expect(capsAltGraph[0].dispatchString()).to.equal('l@altgraph');
422
+ const capsShiftAltGraph = service.getKeyCodeInterpretations({ key: 'ł', code: 'KeyL', shiftKey: true, altGraph: true }, 'code');
423
+ chai.expect(capsShiftAltGraph).to.have.length(1);
424
+ chai.expect(capsShiftAltGraph[0].character).to.equal('Ł');
425
+ });
426
+ it('does not add an AltGraph layer to Linux Alt+Shift input', async () => {
427
+ const service = await setup(require('../../../src/common/keyboard/layouts/en-US-pc.json'), 'linux');
428
+ chai.expect(service.getKeyCodeInterpretations({ key: 'P', code: 'KeyP', shiftKey: true, altKey: true }, 'code')
429
+ .map(candidate => candidate.dispatchString())).to.deep.equal(['shift+alt+p', 'alt+p@shift']);
430
+ });
431
+ it('requires AltGraph for a Linux Shift+AltGraph layer', async () => {
432
+ const layout = {
433
+ info: { id: 'same-shift-layers', lang: 'en' },
434
+ mapping: { KeyA: { value: 'a', withShift: 'A', withAltGr: 'ä', withShiftAltGr: 'A' } }
435
+ };
436
+ const service = await setup(layout, 'linux');
437
+ chai.expect(service.detectLayoutModifiers({ key: 'A', code: 'KeyA', shiftKey: true })).to.equal('shift');
438
+ chai.expect(service.detectLayoutModifiers({ key: 'A', code: 'KeyA', shiftKey: true, altGraph: true })).to.equal('shiftAltGraph');
439
+ });
440
+ it('uses Option as macOS AltGraph layer evidence', async () => {
441
+ const layout = {
442
+ info: { id: 'altgraph-evidence', lang: 'en' },
443
+ mapping: { KeyA: { value: 'a', withShift: 'A', withAltGr: 'ä', withShiftAltGr: 'Ä' } }
444
+ };
445
+ const service = await setup(layout, 'mac');
446
+ chai.expect(service.detectLayoutModifiers({ key: 'ä', code: 'KeyA', altKey: true })).to.equal('altGraph');
447
+ chai.expect(service.detectLayoutModifiers({ key: 'ä', code: 'KeyA' })).to.be.undefined;
448
+ });
449
+ it('uses emulated Windows AltGraph as platform layer evidence', async () => {
450
+ const layout = {
451
+ info: { id: 'altgraph-evidence', lang: 'en' },
452
+ mapping: { KeyA: { value: 'a', withShift: 'A', withAltGr: 'ä', withShiftAltGr: 'Ä' } }
453
+ };
454
+ const service = await setup(layout, 'win');
455
+ chai.expect(service.detectLayoutModifiers({ key: 'ä', code: 'KeyA', ctrlKey: true, altKey: true })).to.equal('altGraph');
456
+ });
457
+ it('constructs Linux layout modifier interpretations only for participating AltGraph', async () => {
458
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
459
+ const service = await setup(german, 'linux');
460
+ const participating = service.getKeyCodeInterpretations({ key: '[', code: 'Digit8', altGraph: true }, 'code');
461
+ chai.expect(participating).to.have.length(1);
462
+ chai.expect(participating[0].layoutModifiers).to.deep.equal('altGraph');
463
+ chai.expect(participating[0].dispatchString()).to.equal('8@altgraph');
464
+ const nonParticipating = service.getKeyCodeInterpretations({ key: 'o', code: 'KeyO', altGraph: true }, 'code');
465
+ chai.expect(nonParticipating[0].layoutModifiers).to.equal('none');
466
+ const withoutModifierEvidence = service.getKeyCodeInterpretations({ key: '[', code: 'Digit8' }, 'code');
467
+ chai.expect(withoutModifierEvidence[0].layoutModifiers).to.equal('none');
468
+ const withCommandAlt = service.getKeyCodeInterpretations({ key: '[', code: 'Digit8', altKey: true, altGraph: true }, 'code');
469
+ chai.expect(withCommandAlt[0].alt).to.be.true;
470
+ chai.expect(withCommandAlt[0].layoutModifiers).to.equal('altGraph');
471
+ });
472
+ it('keeps shifted printable bindings symmetric in keyCode dispatch mode', async () => {
473
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
474
+ const service = await setup(german, 'linux');
475
+ const commandPalette = keys_1.KeyCode.parse('ctrl+shift+p');
476
+ const shiftedPrintable = keys_1.KeyCode.parse('shift+/');
477
+ chai.expect(service.getKeyCodeInterpretations({ key: 'P', code: 'KeyP', keyCode: keys_1.Key.KEY_P.keyCode, ctrlKey: true, shiftKey: true }, 'keyCode')[0]
478
+ .dispatchString()).to.equal(commandPalette.dispatchString());
479
+ chai.expect(service.getKeyCodeInterpretations({ key: '/', code: 'Digit7', keyCode: keys_1.Key.SLASH.keyCode, shiftKey: true }, 'keyCode')[0]
480
+ .dispatchString()).to.equal(shiftedPrintable.dispatchString());
481
+ });
482
+ it('constructs command-first and layout modifier Windows interpretations', async () => {
483
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
484
+ const service = await setup(german, 'win');
485
+ const interpretations = service.getKeyCodeInterpretations({
486
+ key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph: false
487
+ }, 'code');
488
+ chai.expect(interpretations.map(code => code.interpretation)).to.deep.equal(['commandModifiers', 'layoutModifiers']);
489
+ chai.expect(interpretations[0].dispatchString()).to.equal('alt+ctrl+8');
490
+ chai.expect(interpretations[1].dispatchString()).to.equal('8@altgraph');
491
+ const intentional = service.getKeyCodeInterpretations({
492
+ key: 'f', code: 'KeyF', ctrlKey: true, altKey: true, altGraph: false
493
+ }, 'code');
494
+ chai.expect(intentional).to.have.length(1);
495
+ chai.expect(intentional[0].dispatchString()).to.equal('alt+ctrl+f');
496
+ const shiftedBinding = service.resolveKeyCode(keys_1.KeyCode.parse('ctrl+shift+p'));
497
+ const shiftedEvent = service.getKeyCodeInterpretations({ key: 'P', code: 'KeyP', ctrlKey: true, shiftKey: true }, 'code')
498
+ .find(candidate => candidate.interpretation === 'layoutModifiers');
499
+ chai.expect(shiftedBinding.supportedByLayout).to.be.true;
500
+ chai.expect(shiftedEvent.dispatchString()).to.equal(shiftedBinding.dispatchString());
501
+ });
502
+ it('preserves authored command modifiers alongside explicit Windows AltGraph', async () => {
503
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
504
+ const service = await setup(german, 'win');
505
+ const cases = [
506
+ { ctrlKey: false, altKey: false, metaKey: false, expected: '8@altgraph' },
507
+ { ctrlKey: true, altKey: false, metaKey: true, expected: 'meta+ctrl+8@altgraph' },
508
+ { ctrlKey: false, altKey: true, metaKey: true, expected: 'meta+alt+8@altgraph' }
509
+ ];
510
+ for (const { expected, ...modifiers } of cases) {
511
+ const input = { key: '[', code: 'Digit8', altGraph: true, ...modifiers };
512
+ const keyCode = new keys_1.KeyCode({
513
+ key: keys_1.Key.DIGIT8, character: '[', ctrl: input.ctrlKey, alt: input.altKey, meta: input.metaKey
514
+ });
515
+ const interpretations = service.getWindowsKeyCodeInterpretations(keyCode, input, 'altGraph');
516
+ chai.expect(interpretations).to.have.length(1);
517
+ chai.expect(interpretations[0].interpretation).to.equal('layoutModifiers');
518
+ chai.expect(interpretations[0].dispatchString()).to.equal(expected);
519
+ }
520
+ });
521
+ it('keeps legacy Windows AltGraph reporting typing-safe and preserves Meta', async () => {
522
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
523
+ const service = await setup(german, 'win');
524
+ for (const metaKey of [false, true]) {
525
+ const input = { key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph: true, metaKey };
526
+ const keyCode = new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, character: '[', ctrl: true, alt: true, meta: metaKey });
527
+ const interpretations = service.getWindowsKeyCodeInterpretations(keyCode, input, 'altGraph');
528
+ const prefix = metaKey ? 'meta+' : '';
529
+ chai.expect(interpretations.map(code => code.interpretation)).to.deep.equal(['commandModifiers', 'layoutModifiers']);
530
+ chai.expect(interpretations[0].dispatchString()).to.equal(`${prefix}alt+ctrl+8`);
531
+ chai.expect(interpretations[1].dispatchString()).to.equal(`${prefix}8@altgraph`);
532
+ }
533
+ });
534
+ it('uses confirmed withAltGr output as Shift+AltGraph fallback when the shifted layer is absent', async () => {
535
+ const layout = {
536
+ info: { id: 'shift-altgraph-fallback', lang: 'en' },
537
+ mapping: {
538
+ Digit8: { value: '8', withShift: '*', withAltGr: '[', withShiftAltGr: '' }
539
+ }
540
+ };
541
+ const service = await setup(layout, 'linux');
542
+ chai.expect(service.detectLayoutModifiers({ key: '[', code: 'Digit8', shiftKey: true, altGraph: true }))
543
+ .to.deep.equal('shiftAltGraph');
544
+ chai.expect(service.detectLayoutModifiers({ key: '*', code: 'Digit8', shiftKey: true, altGraph: true }))
545
+ .to.equal('shift');
546
+ });
547
+ it('passes Linux AltGraph evidence to the key validator', async () => {
548
+ let validated;
549
+ const validator = { validateKey: validation => validated = validation };
550
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
551
+ const service = await setup(german, 'linux', undefined, validator);
552
+ const input = { key: '[', code: 'Digit8', ctrlKey: true, altGraph: true, shiftKey: false };
553
+ service.validateKeyCode(service.getKeyCodeInterpretations(input, 'code')[0], input);
554
+ chai.expect(validated?.altGraph).to.be.true;
555
+ });
556
+ it('passes macOS Option evidence to the key validator', async () => {
557
+ let validated;
558
+ const validator = { validateKey: validation => validated = validation };
559
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
560
+ const service = await setup(german, 'mac', undefined, validator);
561
+ const input = { key: '[', code: 'Digit8', altKey: true, shiftKey: false };
562
+ service.validateKeyCode(service.getKeyCodeInterpretations(input, 'code')[0], input);
563
+ chai.expect(validated?.altGraph).to.be.true;
564
+ });
565
+ it('passes emulated Windows AltGraph evidence to the key validator', async () => {
566
+ let validated;
567
+ const validator = { validateKey: validation => validated = validation };
568
+ const german = require('../../../src/common/keyboard/layouts/de-German-pc.json');
569
+ const service = await setup(german, 'win', undefined, validator);
570
+ const input = { key: '[', code: 'Digit8', ctrlKey: true, altKey: true, shiftKey: false };
571
+ service.validateKeyCode(service.getKeyCodeInterpretations(input, 'code')[0], input);
572
+ chai.expect(validated?.altGraph).to.be.true;
573
+ });
574
+ it('does not treat excluded or non-printable keys as layout modifier', async () => {
575
+ const layout = {
576
+ info: { id: 'native-shaped', lang: 'en' },
577
+ mapping: {
578
+ Space: { value: ' ', withShift: ' ', withAltGr: ' ', withShiftAltGr: ' ' },
579
+ Numpad1: { value: '1', withShift: '1', withAltGr: '¹', withShiftAltGr: '¹' }
580
+ }
581
+ };
582
+ const service = await setup(layout, 'linux');
583
+ chai.expect(service.detectLayoutModifiers({ key: ' ', code: 'Space', shiftKey: true })).to.be.undefined;
584
+ chai.expect(service.detectLayoutModifiers({ key: '¹', code: 'Numpad1', altGraph: true })).to.be.undefined;
585
+ });
586
+ it('ingests all Windows layout modifier layers across representative PC layouts', async () => {
587
+ const fixtures = [
588
+ { layout: require('../../../src/common/keyboard/layouts/de-German-pc.json'), assertions: [['²', keys_1.Key.DIGIT2, false, true], ['ẞ', keys_1.Key.MINUS, true, true]] },
589
+ { layout: require('../../../src/common/keyboard/layouts/fr-French-pc.json'), assertions: [['€', keys_1.Key.KEY_E, false, true]] },
590
+ { layout: require('../../../src/common/keyboard/layouts/de-Swiss_German-pc.json'), assertions: [['@', keys_1.Key.DIGIT2, false, true]] },
591
+ { layout: require('../../../src/common/keyboard/layouts/fr-Bepo-pc.json'), assertions: [['Æ', keys_1.Key.KEY_A, true, true]] }
592
+ ];
593
+ for (const fixture of fixtures) {
594
+ const service = await setup(fixture.layout, 'win');
595
+ for (const [character, key, shift, altGraph] of fixture.assertions) {
596
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse(character));
597
+ chai.expect(resolved.key).to.equal(key);
598
+ chai.expect(resolved.character).to.equal(character);
599
+ const expected = shift ? 'shiftAltGraph' : altGraph ? 'altGraph' : 'none';
600
+ chai.expect(resolved.layoutModifiers).to.equal(expected);
601
+ }
602
+ stubOSX.restore();
603
+ stubWindows.restore();
604
+ }
605
+ stubOSX = sinon.stub(os, 'isOSX').value(false);
606
+ stubWindows = sinon.stub(os, 'isWindows').value(false);
607
+ });
608
+ it('handles missing Windows layer values defensively', async () => {
609
+ const layout = {
610
+ info: { id: 'missing-windows-layers', lang: 'en' },
611
+ mapping: { KeyA: { vkey: 'VK_A', value: 'a' } }
612
+ };
613
+ const service = await setup(layout, 'win');
614
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('a')).supportedByLayout).to.be.true;
615
+ chai.expect(service.resolveKeyCode(keys_1.KeyCode.parse('§')).supportedByLayout).to.be.false;
616
+ });
617
+ it('resolves logical plus on US, German, and macOS layouts', async () => {
618
+ const fixtures = [
619
+ { layout: require('../../../src/common/keyboard/layouts/en-US-pc.json'), system: 'win', key: keys_1.Key.EQUAL, shift: true },
620
+ { layout: require('../../../src/common/keyboard/layouts/de-German-pc.json'), system: 'win', key: keys_1.Key.BRACKET_RIGHT, shift: false },
621
+ { layout: require('../../../src/common/keyboard/layouts/en-US-mac.json'), system: 'mac', key: keys_1.Key.EQUAL, shift: true }
622
+ ];
623
+ for (const fixture of fixtures) {
624
+ const service = await setup(fixture.layout, fixture.system);
625
+ const resolved = service.resolveKeyCode(keys_1.KeyCode.parse('[char:0x2B]'));
626
+ chai.expect(resolved.key).to.equal(fixture.key);
627
+ chai.expect(resolved.layoutModifiers === 'shift' || resolved.layoutModifiers === 'shiftAltGraph').to.equal(fixture.shift);
628
+ stubOSX.restore();
629
+ stubWindows.restore();
630
+ }
631
+ stubOSX = sinon.stub(os, 'isOSX').value(false);
632
+ stubWindows = sinon.stub(os, 'isWindows').value(false);
633
+ });
116
634
  it('resolves correct key bindings with German Windows layout', async () => {
117
635
  const winGerman = require('../../../src/common/keyboard/layouts/de-German-pc.json');
118
636
  const service = await setup(winGerman, 'win');
119
637
  const toggleComment = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('Slash+M1'));
120
- chai.expect(toggleComment.toString()).to.equal('ctrl+\\');
121
- chai.expect(service.getKeyboardCharacter(toggleComment.key)).to.equal('#');
638
+ chai.expect(toggleComment.toString()).to.equal('ctrl+7');
639
+ chai.expect(toggleComment.character).to.equal('/');
640
+ chai.expect(toggleComment.layoutModifiers).to.deep.equal('shift');
641
+ chai.expect(service.getKeyboardCharacter(toggleComment.key)).to.equal('7');
122
642
  const indentLine = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('BracketRight+M1'));
123
- chai.expect(indentLine.toString()).to.equal('ctrl+=');
124
- chai.expect(service.getKeyboardCharacter(indentLine.key)).to.equal('´');
643
+ chai.expect(indentLine.toString()).to.equal('ctrl+9');
644
+ chai.expect(indentLine.character).to.equal(']');
645
+ chai.expect(indentLine.layoutModifiers).to.deep.equal('altGraph');
646
+ chai.expect(service.getKeyboardCharacter(indentLine.key)).to.equal('9');
125
647
  });
126
648
  it('resolves correct key bindings with French Windows layout', async () => {
127
649
  const winFrench = require('../../../src/common/keyboard/layouts/fr-French-pc.json');
128
650
  const service = await setup(winFrench, 'win');
129
651
  const toggleComment = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('Slash+M1'));
130
652
  chai.expect(toggleComment.toString()).to.equal('ctrl+.');
653
+ chai.expect(toggleComment.character).to.equal('/');
654
+ chai.expect(toggleComment.layoutModifiers).to.deep.equal('shift');
131
655
  chai.expect(service.getKeyboardCharacter(toggleComment.key)).to.equal(':');
132
656
  const indentLine = service.resolveKeyCode(keys_1.KeyCode.createKeyCode('BracketRight+M1'));
133
- chai.expect(indentLine.toString()).to.equal('ctrl+[');
134
- chai.expect(service.getKeyboardCharacter(indentLine.key)).to.equal('^');
657
+ chai.expect(indentLine.toString()).to.equal('ctrl+-');
658
+ chai.expect(indentLine.character).to.equal(']');
659
+ chai.expect(indentLine.layoutModifiers).to.deep.equal('altGraph');
660
+ chai.expect(service.getKeyboardCharacter(indentLine.key)).to.equal(')');
135
661
  });
136
662
  });
137
663
  //# sourceMappingURL=keyboard-layout-service.spec.js.map