@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.
- package/lib/browser/catalog.json +66 -0
- package/lib/browser/common-frontend-contribution.d.ts +6 -1
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +18 -1
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/keybinding.d.ts +95 -17
- package/lib/browser/keybinding.d.ts.map +1 -1
- package/lib/browser/keybinding.js +405 -75
- package/lib/browser/keybinding.js.map +1 -1
- package/lib/browser/keybinding.spec.js +786 -30
- package/lib/browser/keybinding.spec.js.map +1 -1
- package/lib/browser/keyboard/browser-keyboard-frontend-contribution.js +1 -1
- package/lib/browser/keyboard/browser-keyboard-frontend-contribution.js.map +1 -1
- package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts +3 -4
- package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts.map +1 -1
- package/lib/browser/keyboard/browser-keyboard-layout-provider.js +3 -3
- package/lib/browser/keyboard/browser-keyboard-layout-provider.js.map +1 -1
- package/lib/browser/keyboard/browser-keyboard-layout-provider.spec.js +39 -0
- package/lib/browser/keyboard/browser-keyboard-layout-provider.spec.js.map +1 -1
- package/lib/browser/keyboard/keyboard-layout-service.d.ts +67 -15
- package/lib/browser/keyboard/keyboard-layout-service.d.ts.map +1 -1
- package/lib/browser/keyboard/keyboard-layout-service.js +309 -98
- package/lib/browser/keyboard/keyboard-layout-service.js.map +1 -1
- package/lib/browser/keyboard/keyboard-layout-service.spec.js +538 -12
- package/lib/browser/keyboard/keyboard-layout-service.spec.js.map +1 -1
- package/lib/browser/keyboard/keys.spec.js +183 -0
- package/lib/browser/keyboard/keys.spec.js.map +1 -1
- package/lib/browser/menu/action-menu-node.d.ts +5 -3
- package/lib/browser/menu/action-menu-node.d.ts.map +1 -1
- package/lib/browser/menu/action-menu-node.js +2 -11
- package/lib/browser/menu/action-menu-node.js.map +1 -1
- package/lib/browser/menu/action-menu-node.spec.d.ts +2 -0
- package/lib/browser/menu/action-menu-node.spec.d.ts.map +1 -0
- package/lib/browser/menu/action-menu-node.spec.js +54 -0
- package/lib/browser/menu/action-menu-node.spec.js.map +1 -0
- package/lib/browser/menu/browser-menu-plugin.js +1 -1
- package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
- package/lib/browser/test/jsdom.d.ts.map +1 -1
- package/lib/browser/test/jsdom.js +11 -0
- package/lib/browser/test/jsdom.js.map +1 -1
- package/lib/common/keyboard/keyboard-layout-provider.d.ts +7 -0
- package/lib/common/keyboard/keyboard-layout-provider.d.ts.map +1 -1
- package/lib/common/keyboard/keyboard-layout-provider.js.map +1 -1
- package/lib/common/keys.d.ts +86 -3
- package/lib/common/keys.d.ts.map +1 -1
- package/lib/common/keys.js +262 -23
- package/lib/common/keys.js.map +1 -1
- package/lib/common/ternary-search-tree.js +2 -2
- package/lib/common/ternary-search-tree.js.map +1 -1
- package/lib/common/ternary-search-tree.spec.js +12 -0
- package/lib/common/ternary-search-tree.spec.js.map +1 -1
- package/lib/electron-browser/keyboard/electron-keyboard-module.d.ts +2 -0
- package/lib/electron-browser/keyboard/electron-keyboard-module.d.ts.map +1 -1
- package/lib/electron-browser/keyboard/electron-keyboard-module.js +8 -1
- package/lib/electron-browser/keyboard/electron-keyboard-module.js.map +1 -1
- package/lib/electron-browser/keyboard/electron-keyboard-module.spec.d.ts +2 -0
- package/lib/electron-browser/keyboard/electron-keyboard-module.spec.d.ts.map +1 -0
- package/lib/electron-browser/keyboard/electron-keyboard-module.spec.js +28 -0
- package/lib/electron-browser/keyboard/electron-keyboard-module.spec.js.map +1 -0
- package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.js +3 -2
- package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.d.ts.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js +3 -0
- package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +5 -0
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-common/electron-api.js +21 -0
- package/lib/electron-common/electron-api.js.map +1 -1
- package/lib/electron-common/electron-api.spec.d.ts +2 -0
- package/lib/electron-common/electron-api.spec.d.ts.map +1 -0
- package/lib/electron-common/electron-api.spec.js +40 -0
- package/lib/electron-common/electron-api.spec.js.map +1 -0
- package/lib/electron-main/electron-api-main.d.ts +0 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +1 -12
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/package.json +4 -4
- package/src/browser/common-frontend-contribution.ts +16 -0
- package/src/browser/keybinding.spec.ts +878 -34
- package/src/browser/keybinding.ts +470 -85
- package/src/browser/keyboard/browser-keyboard-frontend-contribution.ts +1 -1
- package/src/browser/keyboard/browser-keyboard-layout-provider.spec.ts +52 -1
- package/src/browser/keyboard/browser-keyboard-layout-provider.ts +5 -5
- package/src/browser/keyboard/keyboard-layout-service.spec.ts +640 -15
- package/src/browser/keyboard/keyboard-layout-service.ts +358 -106
- package/src/browser/keyboard/keys.spec.ts +206 -1
- package/src/browser/menu/action-menu-node.spec.ts +60 -0
- package/src/browser/menu/action-menu-node.ts +8 -14
- package/src/browser/menu/browser-menu-plugin.ts +1 -1
- package/src/browser/test/jsdom.ts +11 -0
- package/src/common/keyboard/keyboard-layout-provider.ts +8 -0
- package/src/common/keys.ts +311 -26
- package/src/common/ternary-search-tree.spec.ts +14 -0
- package/src/common/ternary-search-tree.ts +2 -2
- package/src/electron-browser/keyboard/electron-keyboard-module.spec.ts +29 -0
- package/src/electron-browser/keyboard/electron-keyboard-module.ts +9 -2
- package/src/electron-browser/menu/electron-main-menu-factory.ts +3 -3
- package/src/electron-browser/menu/electron-menu-contribution.ts +4 -0
- package/src/electron-common/electron-api.spec.ts +42 -0
- package/src/electron-common/electron-api.ts +23 -0
- package/src/electron-main/electron-api-main.ts +2 -13
|
@@ -19,7 +19,12 @@ const tslib_1 = require("tslib");
|
|
|
19
19
|
const jsdom_1 = require("./test/jsdom");
|
|
20
20
|
let disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
21
21
|
const frontend_application_config_provider_1 = require("./frontend-application-config-provider");
|
|
22
|
-
|
|
22
|
+
try {
|
|
23
|
+
frontend_application_config_provider_1.FrontendApplicationConfigProvider.get();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
frontend_application_config_provider_1.FrontendApplicationConfigProvider.set({});
|
|
27
|
+
}
|
|
23
28
|
const inversify_1 = require("inversify");
|
|
24
29
|
const contribution_provider_1 = require("../common/contribution-provider");
|
|
25
30
|
const keyboard_layout_provider_1 = require("../common/keyboard/keyboard-layout-provider");
|
|
@@ -42,19 +47,63 @@ const markdown_renderer_1 = require("./markdown-rendering/markdown-renderer");
|
|
|
42
47
|
const status_bar_1 = require("./status-bar");
|
|
43
48
|
disableJSDOM();
|
|
44
49
|
const expect = chai.expect;
|
|
50
|
+
function testKeyboardEvent(input = {}) {
|
|
51
|
+
let defaultPrevented = false;
|
|
52
|
+
return {
|
|
53
|
+
key: input.key,
|
|
54
|
+
code: input.code,
|
|
55
|
+
keyCode: input.keyCode,
|
|
56
|
+
ctrlKey: input.ctrlKey,
|
|
57
|
+
shiftKey: input.shiftKey,
|
|
58
|
+
altKey: input.altKey,
|
|
59
|
+
metaKey: input.metaKey,
|
|
60
|
+
repeat: input.repeat,
|
|
61
|
+
isComposing: input.isComposing,
|
|
62
|
+
location: input.location,
|
|
63
|
+
timeStamp: input.timeStamp,
|
|
64
|
+
target: input.target,
|
|
65
|
+
get defaultPrevented() { return defaultPrevented; },
|
|
66
|
+
preventDefault() { defaultPrevented = true; },
|
|
67
|
+
stopPropagation() { }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
45
70
|
let keybindingRegistry;
|
|
46
71
|
let commandRegistry;
|
|
47
72
|
let testContainer;
|
|
48
73
|
let stub;
|
|
74
|
+
const preferenceChanged = new event_1.Emitter();
|
|
75
|
+
const corePreferences = {
|
|
76
|
+
'keyboard.dispatch': 'code',
|
|
77
|
+
onPreferenceChanged: preferenceChanged.event
|
|
78
|
+
};
|
|
79
|
+
const RUSSIAN_LAYOUT = {
|
|
80
|
+
info: { id: 'ru', lang: 'ru' },
|
|
81
|
+
mapping: {
|
|
82
|
+
KeyA: { value: 'ф', withShift: 'Ф', withAltGr: '', withShiftAltGr: '' },
|
|
83
|
+
KeyC: { value: 'с', withShift: 'С', withAltGr: '', withShiftAltGr: '' },
|
|
84
|
+
KeyE: { value: 'у', withShift: 'У', withAltGr: '€', withShiftAltGr: '' },
|
|
85
|
+
KeyV: { value: 'м', withShift: 'М', withAltGr: '', withShiftAltGr: '' },
|
|
86
|
+
KeyS: { value: 'ы', withShift: 'Ы', withAltGr: '', withShiftAltGr: '' },
|
|
87
|
+
Digit8: { value: '8', withShift: '*', withAltGr: '', withShiftAltGr: '' },
|
|
88
|
+
BracketLeft: { value: 'х', withShift: 'Х', withAltGr: '', withShiftAltGr: '' },
|
|
89
|
+
Comma: { value: 'б', withShift: 'Б', withAltGr: '', withShiftAltGr: '' }
|
|
90
|
+
}
|
|
91
|
+
};
|
|
49
92
|
before(async () => {
|
|
50
|
-
disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
51
93
|
testContainer = new inversify_1.Container();
|
|
52
94
|
const module = new inversify_1.ContainerModule((bind, unbind, isBound, rebind) => {
|
|
53
95
|
/* Mock logger binding*/
|
|
54
96
|
bind(logger_1.ILogger).to(mock_logger_1.MockLogger);
|
|
55
97
|
bind(keyboard_layout_service_1.KeyboardLayoutService).toSelf().inSingletonScope();
|
|
56
98
|
bind(MockKeyboardLayoutProvider).toSelf().inSingletonScope();
|
|
57
|
-
bind(keyboard_layout_provider_1.KeyboardLayoutProvider).
|
|
99
|
+
bind(keyboard_layout_provider_1.KeyboardLayoutProvider).toDynamicValue(ctx => {
|
|
100
|
+
const provider = ctx.container.get(MockKeyboardLayoutProvider);
|
|
101
|
+
return new Proxy(provider, {
|
|
102
|
+
get: (target, property) => property === 'then' ? undefined
|
|
103
|
+
: property === 'layoutSource' ? 'navigator.keyboard'
|
|
104
|
+
: property in target ? Reflect.get(target, property) : () => undefined
|
|
105
|
+
});
|
|
106
|
+
});
|
|
58
107
|
bind(MockKeyboardLayoutChangeNotifier).toSelf().inSingletonScope();
|
|
59
108
|
bind(keyboard_layout_provider_1.KeyboardLayoutChangeNotifier).toService(MockKeyboardLayoutChangeNotifier);
|
|
60
109
|
(0, contribution_provider_1.bindContributionProvider)(bind, keybinding_1.KeybindingContext);
|
|
@@ -70,7 +119,10 @@ before(async () => {
|
|
|
70
119
|
return true;
|
|
71
120
|
}
|
|
72
121
|
});
|
|
73
|
-
bind(status_bar_1.StatusBar).toConstantValue({
|
|
122
|
+
bind(status_bar_1.StatusBar).toConstantValue({
|
|
123
|
+
setElement: () => Promise.resolve(),
|
|
124
|
+
removeElement: () => Promise.resolve()
|
|
125
|
+
});
|
|
74
126
|
bind(markdown_renderer_1.MarkdownRendererImpl).toSelf().inSingletonScope();
|
|
75
127
|
bind(markdown_renderer_1.MarkdownRenderer).toService(markdown_renderer_1.MarkdownRendererImpl);
|
|
76
128
|
bind(markdown_renderer_1.MarkdownRendererFactory).toFactory(({ container }) => () => container.get(markdown_renderer_1.MarkdownRenderer));
|
|
@@ -78,23 +130,28 @@ before(async () => {
|
|
|
78
130
|
bind(label_parser_1.LabelParser).toSelf().inSingletonScope();
|
|
79
131
|
bind(context_key_service_1.ContextKeyService).to(MockContextKeyService).inSingletonScope();
|
|
80
132
|
bind(frontend_application_state_1.FrontendApplicationStateService).toSelf().inSingletonScope();
|
|
81
|
-
bind(core_preferences_1.CorePreferences).toConstantValue(
|
|
133
|
+
bind(core_preferences_1.CorePreferences).toConstantValue(corePreferences);
|
|
82
134
|
(0, frontend_application_bindings_1.bindPreferenceService)(bind);
|
|
83
135
|
});
|
|
84
136
|
testContainer.load(module);
|
|
85
137
|
commandRegistry = testContainer.get(command_1.CommandRegistry);
|
|
86
138
|
commandRegistry.onStart();
|
|
87
139
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
140
|
+
beforeEach(async function () {
|
|
141
|
+
if (this.currentTest?.file?.endsWith('keybinding.spec.js')) {
|
|
142
|
+
disableJSDOM();
|
|
143
|
+
disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
144
|
+
}
|
|
92
145
|
stub = sinon.stub(os, 'isOSX').value(false);
|
|
93
146
|
keybindingRegistry = testContainer.get(keybinding_1.KeybindingRegistry);
|
|
147
|
+
corePreferences['keyboard.dispatch'] = 'code';
|
|
94
148
|
await keybindingRegistry.onStart();
|
|
95
149
|
});
|
|
96
|
-
afterEach(()
|
|
150
|
+
afterEach(function () {
|
|
97
151
|
stub.restore();
|
|
152
|
+
if (this.currentTest?.file?.endsWith('keybinding.spec.js')) {
|
|
153
|
+
disableJSDOM();
|
|
154
|
+
}
|
|
98
155
|
});
|
|
99
156
|
describe('keybindings', () => {
|
|
100
157
|
it('should register the default keybindings', () => {
|
|
@@ -278,7 +335,7 @@ describe('keybindings', () => {
|
|
|
278
335
|
match = keybindingRegistry.matchKeybinding([keys_1.KeyCode.createKeyCode({ first: keys_1.Key.KEY_B, modifiers: [keys_1.KeyModifier.CtrlCmd] })]);
|
|
279
336
|
expect(match && match.kind).to.be.equal('full');
|
|
280
337
|
match = keybindingRegistry.matchKeybinding([keys_1.KeyCode.createKeyCode({ first: keys_1.Key.KEY_C, modifiers: [keys_1.KeyModifier.CtrlCmd] })]);
|
|
281
|
-
const keyCode = match && keys_1.KeyCode.parse(match.binding.keybinding);
|
|
338
|
+
const keyCode = match && keys_1.KeyCode.parse(match.runtime.binding.keybinding);
|
|
282
339
|
expect(keyCode?.key).to.be.equal(validKeyCode.key);
|
|
283
340
|
});
|
|
284
341
|
it('should return partial keybinding matches', () => {
|
|
@@ -334,19 +391,19 @@ describe('keybindings', () => {
|
|
|
334
391
|
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [userBinding]);
|
|
335
392
|
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.WORKSPACE, [workspaceBinding]);
|
|
336
393
|
// now WORKSPACE bindings are overriding the other scopes
|
|
337
|
-
let match = keybindingRegistry.matchKeybinding(
|
|
394
|
+
let match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(workspaceBinding));
|
|
338
395
|
expect(match?.kind).to.be.equal('full');
|
|
339
|
-
expect(match?.binding?.command).to.be.equal(workspaceBinding.command);
|
|
396
|
+
expect(match?.runtime.binding?.command).to.be.equal(workspaceBinding.command);
|
|
340
397
|
keybindingRegistry.resetKeybindingsForScope(keybinding_1.KeybindingScope.WORKSPACE);
|
|
341
398
|
// now it should find USER bindings
|
|
342
|
-
match = keybindingRegistry.matchKeybinding(
|
|
399
|
+
match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(userBinding));
|
|
343
400
|
expect(match?.kind).to.be.equal('full');
|
|
344
|
-
expect(match?.binding?.command).to.be.equal(userBinding.command);
|
|
401
|
+
expect(match?.runtime.binding?.command).to.be.equal(userBinding.command);
|
|
345
402
|
keybindingRegistry.resetKeybindingsForScope(keybinding_1.KeybindingScope.USER);
|
|
346
403
|
// and finally it should fallback to DEFAULT bindings.
|
|
347
|
-
match = keybindingRegistry.matchKeybinding(
|
|
404
|
+
match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
|
|
348
405
|
expect(match?.kind).to.be.equal('full');
|
|
349
|
-
expect(match?.binding?.command).to.be.equal(defaultBinding.command);
|
|
406
|
+
expect(match?.runtime.binding?.command).to.be.equal(defaultBinding.command);
|
|
350
407
|
keybindingRegistry.resetKeybindingsForScope(keybinding_1.KeybindingScope.DEFAULT);
|
|
351
408
|
// now the registry should be empty
|
|
352
409
|
match = keybindingRegistry.matchKeybinding([keyCode]);
|
|
@@ -363,16 +420,676 @@ describe('keybindings', () => {
|
|
|
363
420
|
command: '-test.workspace-command'
|
|
364
421
|
};
|
|
365
422
|
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.DEFAULT, [defaultBinding]);
|
|
366
|
-
let match = keybindingRegistry.matchKeybinding(
|
|
423
|
+
let match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
|
|
367
424
|
expect(match?.kind).to.be.equal('full');
|
|
368
|
-
expect(match?.binding?.command).to.be.equal(defaultBinding.command);
|
|
425
|
+
expect(match?.runtime.binding?.command).to.be.equal(defaultBinding.command);
|
|
369
426
|
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [disableDefaultBinding]);
|
|
370
|
-
match = keybindingRegistry.matchKeybinding(
|
|
427
|
+
match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
|
|
371
428
|
expect(match).to.be.undefined;
|
|
372
429
|
keybindingRegistry.resetKeybindingsForScope(keybinding_1.KeybindingScope.USER);
|
|
373
|
-
match = keybindingRegistry.matchKeybinding(
|
|
430
|
+
match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(defaultBinding));
|
|
374
431
|
expect(match?.kind).to.be.equal('full');
|
|
375
|
-
expect(match?.binding?.command).to.be.equal(defaultBinding.command);
|
|
432
|
+
expect(match?.runtime.binding?.command).to.be.equal(defaultBinding.command);
|
|
433
|
+
});
|
|
434
|
+
it('should dispatch already-handled compatibility events to capture listeners', () => {
|
|
435
|
+
const target = document.createElement('iframe');
|
|
436
|
+
document.body.appendChild(target);
|
|
437
|
+
const captured = sinon.spy();
|
|
438
|
+
document.addEventListener('keydown', captured, true);
|
|
439
|
+
const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
|
|
440
|
+
keybindingRegistry.dispatchKeyDown({ key: 'a', code: 'KeyA' }, target, true);
|
|
441
|
+
expect(captured.calledOnce).to.be.true;
|
|
442
|
+
expect(captured.firstCall.args[0].defaultPrevented).to.be.false;
|
|
443
|
+
expect(match.called).to.be.false;
|
|
444
|
+
document.removeEventListener('keydown', captured, true);
|
|
445
|
+
target.remove();
|
|
446
|
+
});
|
|
447
|
+
it('should dispatch normalized keyboard data directly', () => {
|
|
448
|
+
const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
|
|
449
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
450
|
+
key: 'a',
|
|
451
|
+
code: 'KeyA',
|
|
452
|
+
keyCode: 65,
|
|
453
|
+
ctrlKey: true
|
|
454
|
+
}, new EventTarget());
|
|
455
|
+
expect(match.calledOnce).to.be.true;
|
|
456
|
+
expect(match.firstCall.args[0][0].dispatchString()).to.equal('ctrl+a');
|
|
457
|
+
});
|
|
458
|
+
it('should dispatch a DOM keydown for dispatched commands without executing them twice', async () => {
|
|
459
|
+
const target = document.createElement('div');
|
|
460
|
+
document.body.appendChild(target);
|
|
461
|
+
const captured = sinon.spy();
|
|
462
|
+
const run = (event) => keybindingRegistry.run(event);
|
|
463
|
+
document.addEventListener('keydown', captured, true);
|
|
464
|
+
document.addEventListener('keydown', run, true);
|
|
465
|
+
const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
|
|
466
|
+
const execute = sinon.spy();
|
|
467
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND.id, { execute });
|
|
468
|
+
try {
|
|
469
|
+
keybindingRegistry.dispatchCommand(TEST_COMMAND.id, target);
|
|
470
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
471
|
+
expect(captured.calledOnce).to.be.true;
|
|
472
|
+
expect(captured.firstCall.args[0].ctrlKey).to.be.true;
|
|
473
|
+
expect(execute.calledOnce).to.be.true;
|
|
474
|
+
expect(match.calledOnce).to.be.true;
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
handler.dispose();
|
|
478
|
+
document.removeEventListener('keydown', captured, true);
|
|
479
|
+
document.removeEventListener('keydown', run, true);
|
|
480
|
+
target.remove();
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
it('should preserve authored modifiers when dispatching commands through normalized Windows layout modifier data', async () => {
|
|
484
|
+
const windows = sinon.stub(os, 'isWindows').value(true);
|
|
485
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
486
|
+
const dispatch = sinon.spy(keybindingRegistry, 'dispatchNormalizedKeyDown');
|
|
487
|
+
const target = document.createElement('div');
|
|
488
|
+
const cases = [
|
|
489
|
+
{ keybinding: '[', ctrlKey: false, altKey: false },
|
|
490
|
+
{ keybinding: 'ctrl+[', ctrlKey: true, altKey: false, inactive: true },
|
|
491
|
+
{ keybinding: 'alt+[', ctrlKey: false, altKey: true, inactive: true },
|
|
492
|
+
{ keybinding: 'ctrl+alt+[', ctrlKey: true, altKey: true, inactive: true }
|
|
493
|
+
];
|
|
494
|
+
try {
|
|
495
|
+
for (const testCase of cases) {
|
|
496
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: testCase.keybinding };
|
|
497
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
498
|
+
const execute = sinon.spy();
|
|
499
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
500
|
+
dispatch.resetHistory();
|
|
501
|
+
try {
|
|
502
|
+
keybindingRegistry.dispatchCommand(TEST_COMMAND_SHADOW.id, target);
|
|
503
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
504
|
+
if (testCase.inactive) {
|
|
505
|
+
// Windows reports AltGr as Ctrl+Alt, so neither command Ctrl nor Alt can accompany an AltGraph character.
|
|
506
|
+
expect(keybindingRegistry.isKeybindingInactive(binding)).to.be.true;
|
|
507
|
+
expect(dispatch.called).to.be.false;
|
|
508
|
+
expect(execute.called).to.be.false;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
expect(dispatch.calledOnce).to.be.true;
|
|
512
|
+
expect(dispatch.firstCall.args[0]).to.include({
|
|
513
|
+
key: '[',
|
|
514
|
+
code: 'Digit8',
|
|
515
|
+
keyCode: 56,
|
|
516
|
+
ctrlKey: testCase.ctrlKey,
|
|
517
|
+
altKey: testCase.altKey,
|
|
518
|
+
altGraph: true
|
|
519
|
+
});
|
|
520
|
+
expect(execute.calledOnce).to.be.true;
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
handler.dispose();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
windows.restore();
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
it('should interpret native and transported AltGraph input identically', () => {
|
|
532
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
533
|
+
const native = keybindingRegistry.getKeyCodeInterpretations({
|
|
534
|
+
key: '[',
|
|
535
|
+
code: 'Digit8',
|
|
536
|
+
keyCode: 56,
|
|
537
|
+
ctrlKey: true,
|
|
538
|
+
getModifierState: modifier => modifier === 'AltGraph'
|
|
539
|
+
});
|
|
540
|
+
const transported = keybindingRegistry.getKeyCodeInterpretations({
|
|
541
|
+
key: '[',
|
|
542
|
+
code: 'Digit8',
|
|
543
|
+
keyCode: 56,
|
|
544
|
+
ctrlKey: true,
|
|
545
|
+
altGraph: true
|
|
546
|
+
});
|
|
547
|
+
expect(transported.map(code => code.dispatchString())).to.deep.equal(native.map(code => code.dispatchString()));
|
|
548
|
+
});
|
|
549
|
+
it('should log normalized keyboard troubleshooting data when enabled', () => {
|
|
550
|
+
const logger = keybindingRegistry.logger;
|
|
551
|
+
const info = sinon.spy(logger, 'info');
|
|
552
|
+
keybindingRegistry.toggleKeyboardShortcutsTroubleshooting();
|
|
553
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
554
|
+
key: '[',
|
|
555
|
+
code: 'Digit8',
|
|
556
|
+
keyCode: 56,
|
|
557
|
+
getModifierState: modifier => modifier === 'AltGraph',
|
|
558
|
+
timeStamp: 42
|
|
559
|
+
}, new EventTarget());
|
|
560
|
+
expect(info.calledTwice).to.be.true;
|
|
561
|
+
expect(info.secondCall.args[1].input).to.include({
|
|
562
|
+
key: '[',
|
|
563
|
+
code: 'Digit8',
|
|
564
|
+
altGraph: true,
|
|
565
|
+
timeStamp: 42
|
|
566
|
+
});
|
|
567
|
+
expect(info.secondCall.args[1].layout.source).to.equal('navigator.keyboard');
|
|
568
|
+
});
|
|
569
|
+
it('should skip composing normalized keyboard data', () => {
|
|
570
|
+
const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
|
|
571
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
572
|
+
key: 'a',
|
|
573
|
+
code: 'KeyA',
|
|
574
|
+
keyCode: 65,
|
|
575
|
+
isComposing: true
|
|
576
|
+
}, new EventTarget());
|
|
577
|
+
expect(match.called).to.be.false;
|
|
578
|
+
});
|
|
579
|
+
it('should exclude inactive bindings and reactivate them after a layout change', () => {
|
|
580
|
+
const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+[' };
|
|
581
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
582
|
+
expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(binding))?.kind).to.equal('full');
|
|
583
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
584
|
+
notifier.emitter.fire({
|
|
585
|
+
info: { id: 'missing-bracket', lang: 'en' },
|
|
586
|
+
mapping: { KeyA: { value: 'a', withShift: 'A', withAltGr: '', withShiftAltGr: '' } }
|
|
587
|
+
});
|
|
588
|
+
expect(keybindingRegistry.getKeybindingsForCommand(TEST_COMMAND.id).some(candidate => candidate.keybinding === 'ctrl+[')).to.be.true;
|
|
589
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
|
|
590
|
+
expect(keybindingRegistry.matchKeybinding(keys_1.KeySequence.parse('ctrl+['))).to.be.undefined;
|
|
591
|
+
expect(keybindingRegistry.containsKeybindingInScope({ command: TEST_COMMAND2.id, keybinding: 'ctrl+[' })).to.be.false;
|
|
592
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
593
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
|
|
594
|
+
expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(binding))?.kind).to.equal('full');
|
|
595
|
+
});
|
|
596
|
+
it('should keep default Latin-letter bindings active on a non-Latin layout', async () => {
|
|
597
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+c' };
|
|
598
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
599
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
600
|
+
notifier.emitter.fire(RUSSIAN_LAYOUT);
|
|
601
|
+
const execute = sinon.spy();
|
|
602
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
603
|
+
try {
|
|
604
|
+
expect(keybindingRegistry.isKeybindingInactive(binding)).to.be.false;
|
|
605
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
606
|
+
key: 'с', code: 'KeyC', ctrlKey: true
|
|
607
|
+
}, new EventTarget());
|
|
608
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
609
|
+
expect(execute.calledOnce).to.be.true;
|
|
610
|
+
}
|
|
611
|
+
finally {
|
|
612
|
+
handler.dispose();
|
|
613
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
it('should dispatch a shifted Latin-letter binding on a non-Latin layout', async () => {
|
|
617
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+shift+a' };
|
|
618
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
619
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
620
|
+
notifier.emitter.fire(RUSSIAN_LAYOUT);
|
|
621
|
+
const execute = sinon.spy();
|
|
622
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
623
|
+
try {
|
|
624
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
625
|
+
key: 'Ф', code: 'KeyA', ctrlKey: true, shiftKey: true
|
|
626
|
+
}, new EventTarget());
|
|
627
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
628
|
+
expect(execute.calledOnce).to.be.true;
|
|
629
|
+
}
|
|
630
|
+
finally {
|
|
631
|
+
handler.dispose();
|
|
632
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
it('should keep punctuation bindings inactive on a non-Latin layout', () => {
|
|
636
|
+
const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+[' };
|
|
637
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
638
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
639
|
+
notifier.emitter.fire(RUSSIAN_LAYOUT);
|
|
640
|
+
try {
|
|
641
|
+
expect(keybindingRegistry.isKeybindingInactive(binding)).to.be.true;
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
it('should execute an authored Shift command modifier on a non-US logical character', async () => {
|
|
648
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
649
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+shift+ü' };
|
|
650
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
651
|
+
const execute = sinon.spy();
|
|
652
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
653
|
+
try {
|
|
654
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
655
|
+
key: 'Ü', code: 'BracketLeft', ctrlKey: true, shiftKey: true
|
|
656
|
+
}, new EventTarget());
|
|
657
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
658
|
+
expect(execute.calledOnce).to.be.true;
|
|
659
|
+
}
|
|
660
|
+
finally {
|
|
661
|
+
handler.dispose();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
it('should mark an unmatchable Shift plus AltGraph character binding inactive', async () => {
|
|
665
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
666
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+shift+[char:0x7B]' };
|
|
667
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
668
|
+
const execute = sinon.spy();
|
|
669
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
670
|
+
try {
|
|
671
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
|
|
672
|
+
keybindingRegistry.dispatchNormalizedKeyDown({
|
|
673
|
+
key: '{', code: 'Digit7', ctrlKey: true, shiftKey: true, altGraph: true
|
|
674
|
+
}, new EventTarget());
|
|
675
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
676
|
+
expect(execute.called).to.be.false;
|
|
677
|
+
}
|
|
678
|
+
finally {
|
|
679
|
+
handler.dispose();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
it('should mark command Alt plus AltGraph character bindings inactive on macOS and Windows', async () => {
|
|
683
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
684
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'alt+[' };
|
|
685
|
+
const execute = sinon.spy();
|
|
686
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
687
|
+
const windows = sinon.stub(os, 'isWindows').value(false);
|
|
688
|
+
const dispatch = async (input) => {
|
|
689
|
+
keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
|
|
690
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
691
|
+
};
|
|
692
|
+
try {
|
|
693
|
+
stub.value(true);
|
|
694
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-mac.json'));
|
|
695
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
696
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
|
|
697
|
+
expect(keybindingRegistry.acceleratorForCommand(TEST_COMMAND_SHADOW.id, undefined, 'physical')).to.deep.equal([]);
|
|
698
|
+
await dispatch({ key: '[', code: 'Digit5', altKey: true });
|
|
699
|
+
expect(execute.called).to.be.false;
|
|
700
|
+
stub.value(false);
|
|
701
|
+
windows.value(true);
|
|
702
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
703
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
704
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
|
|
705
|
+
await dispatch({ key: '[', code: 'Digit8', ctrlKey: true, altKey: true });
|
|
706
|
+
expect(execute.called).to.be.false;
|
|
707
|
+
windows.value(false);
|
|
708
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
709
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
710
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
|
|
711
|
+
await dispatch({ key: '[', code: 'Digit8', altKey: true, altGraph: true });
|
|
712
|
+
expect(execute.calledOnce).to.be.true;
|
|
713
|
+
}
|
|
714
|
+
finally {
|
|
715
|
+
windows.restore();
|
|
716
|
+
handler.dispose();
|
|
717
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
it('should mark command Ctrl plus AltGraph character bindings inactive on Windows', async () => {
|
|
721
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
722
|
+
const binding = { command: TEST_COMMAND_SHADOW.id, keybinding: 'ctrl+[' };
|
|
723
|
+
const typed = { command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: '[' };
|
|
724
|
+
const execute = sinon.spy();
|
|
725
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
726
|
+
const windows = sinon.stub(os, 'isWindows').value(true);
|
|
727
|
+
const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
|
|
728
|
+
const dispatch = async (input) => {
|
|
729
|
+
keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
|
|
730
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
731
|
+
};
|
|
732
|
+
try {
|
|
733
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
734
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding, typed]);
|
|
735
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.a('string');
|
|
736
|
+
expect(keybindingRegistry.acceleratorForCommand(TEST_COMMAND_SHADOW.id, undefined, 'physical')).to.deep.equal([]);
|
|
737
|
+
// Chromium reports Ctrl+AltGr+8 on Windows exactly like AltGr+8, so the input types `[`.
|
|
738
|
+
await dispatch({ key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph: true });
|
|
739
|
+
expect(execute.called).to.be.false;
|
|
740
|
+
const fullMatches = match.returnValues.filter(result => result?.kind === 'full').map(result => result.runtime.binding.keybinding);
|
|
741
|
+
expect(fullMatches).to.deep.equal(['[']);
|
|
742
|
+
windows.value(false);
|
|
743
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
744
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
745
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
|
|
746
|
+
await dispatch({ key: '[', code: 'Digit8', ctrlKey: true, altGraph: true });
|
|
747
|
+
expect(execute.calledOnce).to.be.true;
|
|
748
|
+
}
|
|
749
|
+
finally {
|
|
750
|
+
windows.restore();
|
|
751
|
+
handler.dispose();
|
|
752
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
it('should route Windows Ctrl+Alt candidates through the registry with and without reported AltGraph', () => {
|
|
756
|
+
const windows = sinon.stub(os, 'isWindows').value(true);
|
|
757
|
+
try {
|
|
758
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
759
|
+
const match = sinon.spy(keybindingRegistry, 'matchKeybinding');
|
|
760
|
+
const logicalBinding = {
|
|
761
|
+
command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
762
|
+
keybinding: '[',
|
|
763
|
+
resolved: [new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, character: '[', layoutModifiers: 'altGraph' })]
|
|
764
|
+
};
|
|
765
|
+
for (const altGraph of [true, false]) {
|
|
766
|
+
const input = { key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph };
|
|
767
|
+
match.resetHistory();
|
|
768
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [logicalBinding]);
|
|
769
|
+
keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
|
|
770
|
+
expect(match.callCount).to.equal(2);
|
|
771
|
+
expect(match.secondCall.returnValue?.kind).to.equal('full');
|
|
772
|
+
expect(match.secondCall.returnValue?.runtime.binding.keybinding).to.equal('[');
|
|
773
|
+
match.resetHistory();
|
|
774
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [
|
|
775
|
+
logicalBinding,
|
|
776
|
+
{ command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrl+alt+8' }
|
|
777
|
+
]);
|
|
778
|
+
keybindingRegistry.dispatchNormalizedKeyDown(input, new EventTarget());
|
|
779
|
+
expect(match.callCount).to.equal(2);
|
|
780
|
+
expect(match.firstCall.returnValue?.runtime.binding.keybinding).to.equal('ctrl+alt+8');
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
finally {
|
|
784
|
+
windows.restore();
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
it('should prefer a command chord prefix over a layout modifier full match', () => {
|
|
788
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
|
|
789
|
+
new keys_1.KeyCode({ key: keys_1.Key.KEY_A, ctrl: true, interpretation: 'commandModifiers' }),
|
|
790
|
+
new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, ctrl: true, layoutModifiers: 'altGraph', interpretation: 'layoutModifiers' })
|
|
791
|
+
]);
|
|
792
|
+
const commandChord = {
|
|
793
|
+
command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
794
|
+
keybinding: 'ctrl+a b'
|
|
795
|
+
};
|
|
796
|
+
const layoutFull = {
|
|
797
|
+
command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
798
|
+
keybinding: 'ctrl+['
|
|
799
|
+
};
|
|
800
|
+
keybindingRegistry.resolveKeybinding(commandChord).splice(0, 2, ...keys_1.KeySequence.parse('ctrl+a b'));
|
|
801
|
+
keybindingRegistry.resolveKeybinding(layoutFull).splice(0, 1, new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, ctrl: true, character: '[', layoutModifiers: 'altGraph' }));
|
|
802
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [commandChord, layoutFull]);
|
|
803
|
+
const event = testKeyboardEvent({ key: '[', code: 'Digit8' });
|
|
804
|
+
keybindingRegistry.run(event);
|
|
805
|
+
expect(event.defaultPrevented).to.be.true;
|
|
806
|
+
expect(keybindingRegistry.keySequenceCandidates).to.have.length(1);
|
|
807
|
+
expect(keybindingRegistry.keySequenceCandidates[0][0].interpretation).to.equal('commandModifiers');
|
|
808
|
+
interpretations.restore();
|
|
809
|
+
});
|
|
810
|
+
it('should retain alternative interpretation prefixes across a chord', () => {
|
|
811
|
+
const commandPrefix = new keys_1.KeyCode({ key: keys_1.Key.KEY_A, ctrl: true, interpretation: 'commandModifiers' });
|
|
812
|
+
const layoutPrefix = new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, ctrl: true, layoutModifiers: 'altGraph', interpretation: 'layoutModifiers' });
|
|
813
|
+
const layoutSecond = new keys_1.KeyCode({ key: keys_1.Key.KEY_C, interpretation: 'layoutModifiers' });
|
|
814
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations');
|
|
815
|
+
interpretations.onFirstCall().returns([commandPrefix, layoutPrefix]);
|
|
816
|
+
interpretations.onSecondCall().returns([
|
|
817
|
+
new keys_1.KeyCode({ key: keys_1.Key.KEY_X, interpretation: 'commandModifiers' }),
|
|
818
|
+
layoutSecond
|
|
819
|
+
]);
|
|
820
|
+
const commandBinding = {
|
|
821
|
+
command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
822
|
+
keybinding: 'ctrl+a b'
|
|
823
|
+
};
|
|
824
|
+
const layoutBinding = {
|
|
825
|
+
command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
826
|
+
keybinding: 'ctrl+[ c'
|
|
827
|
+
};
|
|
828
|
+
keybindingRegistry.resolveKeybinding(commandBinding).splice(0, 2, commandPrefix, new keys_1.KeyCode({ key: keys_1.Key.KEY_B }));
|
|
829
|
+
keybindingRegistry.resolveKeybinding(layoutBinding).splice(0, 2, layoutPrefix, layoutSecond);
|
|
830
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [commandBinding, layoutBinding]);
|
|
831
|
+
keybindingRegistry.run(testKeyboardEvent({ key: '[', code: 'Digit8' }));
|
|
832
|
+
expect(keybindingRegistry.keySequenceCandidates).to.have.length(2);
|
|
833
|
+
const second = testKeyboardEvent({ key: 'c', code: 'KeyC' });
|
|
834
|
+
keybindingRegistry.run(second);
|
|
835
|
+
expect(second.defaultPrevented).to.be.false;
|
|
836
|
+
expect(keybindingRegistry.keySequenceCandidates).to.deep.equal([[]]);
|
|
837
|
+
interpretations.restore();
|
|
838
|
+
});
|
|
839
|
+
it('should decline equal-priority full matches from different interpretations', () => {
|
|
840
|
+
const first = new keys_1.KeyCode({ key: keys_1.Key.KEY_A, ctrl: true, interpretation: 'commandModifiers' });
|
|
841
|
+
const second = new keys_1.KeyCode({ key: keys_1.Key.KEY_B, ctrl: true, interpretation: 'commandModifiers' });
|
|
842
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([first, second]);
|
|
843
|
+
const troubleshooting = sinon.spy(keybindingRegistry, 'logKeyboardTroubleshooting');
|
|
844
|
+
const logger = keybindingRegistry.logger;
|
|
845
|
+
const warning = sinon.spy(logger, 'warn');
|
|
846
|
+
const firstBinding = { command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrl+a' };
|
|
847
|
+
const secondBinding = { command: keybinding_1.KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrl+b' };
|
|
848
|
+
keybindingRegistry.resolveKeybinding(firstBinding).splice(0, 1, first);
|
|
849
|
+
keybindingRegistry.resolveKeybinding(secondBinding).splice(0, 1, second);
|
|
850
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [firstBinding, secondBinding]);
|
|
851
|
+
keybindingRegistry.run(testKeyboardEvent({ key: 'a', code: 'KeyA' }));
|
|
852
|
+
expect(troubleshooting.lastCall.args[3]).to.equal('ambiguous');
|
|
853
|
+
expect(troubleshooting.lastCall.args[2]).to.be.undefined;
|
|
854
|
+
expect(warning.calledOnce).to.be.true;
|
|
855
|
+
keybindingRegistry.run(testKeyboardEvent({ key: 'a', code: 'KeyA' }));
|
|
856
|
+
expect(warning.calledOnce).to.be.true;
|
|
857
|
+
interpretations.restore();
|
|
858
|
+
});
|
|
859
|
+
it('should canonicalize layout modifier input for recorder persistence', () => {
|
|
860
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
|
|
861
|
+
new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, ctrl: true, alt: true, interpretation: 'commandModifiers' }),
|
|
862
|
+
new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, ctrl: true, character: '[', layoutModifiers: 'altGraph', interpretation: 'layoutModifiers' })
|
|
863
|
+
]);
|
|
864
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: '[', code: 'Digit8' })?.toString()).to.equal('ctrl+[');
|
|
865
|
+
interpretations.restore();
|
|
866
|
+
});
|
|
867
|
+
it('should record the Latin fallback letter on a non-Latin layout', () => {
|
|
868
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
869
|
+
notifier.emitter.fire(RUSSIAN_LAYOUT);
|
|
870
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
|
|
871
|
+
key: 'с', code: 'KeyC', ctrlKey: true
|
|
872
|
+
})?.toAuthoredKeybindingString()).to.equal('ctrl+c');
|
|
873
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
|
|
874
|
+
key: 'С', code: 'KeyC', ctrlKey: true, shiftKey: true
|
|
875
|
+
})?.toAuthoredKeybindingString()).to.equal('shift+ctrl+c');
|
|
876
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
|
|
877
|
+
key: '€', code: 'KeyE', ctrlKey: true, altGraph: true
|
|
878
|
+
})?.toAuthoredKeybindingString()).to.equal('ctrl+€');
|
|
879
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
|
|
880
|
+
key: 'у', code: 'KeyE', ctrlKey: true
|
|
881
|
+
})?.toAuthoredKeybindingString()).to.equal('ctrl+e');
|
|
882
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
883
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
|
|
884
|
+
key: 'c', code: 'KeyC', ctrlKey: true
|
|
885
|
+
})?.toAuthoredKeybindingString()).to.equal('ctrl+c');
|
|
886
|
+
});
|
|
887
|
+
it('should record legacy Windows AltGraph input as its logical character', () => {
|
|
888
|
+
const windows = sinon.stub(os, 'isWindows').value(true);
|
|
889
|
+
try {
|
|
890
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
891
|
+
for (const altGraph of [true, false]) {
|
|
892
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({
|
|
893
|
+
key: '[', code: 'Digit8', ctrlKey: true, altKey: true, altGraph
|
|
894
|
+
})?.toString()).to.equal('[');
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
finally {
|
|
898
|
+
windows.restore();
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
it('should retain command Shift when layout modifier Shift is not the logical binding', () => {
|
|
902
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
|
|
903
|
+
new keys_1.KeyCode({ key: keys_1.Key.KEY_P, ctrl: true, character: 'P', layoutModifiers: 'shift', interpretation: 'layoutModifiers' })
|
|
904
|
+
]);
|
|
905
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: 'P', code: 'KeyP' })?.toString()).to.equal('shift+ctrl+p');
|
|
906
|
+
interpretations.restore();
|
|
907
|
+
});
|
|
908
|
+
it('should persist a parseable logical character produced by Shift', () => {
|
|
909
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([
|
|
910
|
+
new keys_1.KeyCode({ key: keys_1.Key.DIGIT7, character: '/', layoutModifiers: 'shift', interpretation: 'layoutModifiers' })
|
|
911
|
+
]);
|
|
912
|
+
const resolve = sinon.stub(testContainer.get(keyboard_layout_service_1.KeyboardLayoutService), 'resolveKeyCode').callsFake(code => code.key === keys_1.Key.SLASH
|
|
913
|
+
? new keys_1.KeyCode({ key: keys_1.Key.DIGIT7, character: '/', layoutModifiers: 'shift' })
|
|
914
|
+
: code);
|
|
915
|
+
expect(keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: '/', code: 'Digit7' })?.toString()).to.equal('/');
|
|
916
|
+
resolve.restore();
|
|
917
|
+
interpretations.restore();
|
|
918
|
+
});
|
|
919
|
+
it('should record newly authorable printable characters logically', () => {
|
|
920
|
+
const layoutCode = new keys_1.KeyCode({ key: keys_1.Key.SLASH, ctrl: true, character: '?', layoutModifiers: 'shift', interpretation: 'layoutModifiers' });
|
|
921
|
+
const interpretations = sinon.stub(keybindingRegistry, 'getKeyCodeInterpretations').returns([layoutCode]);
|
|
922
|
+
const recorded = keybindingRegistry.authoredKeyCodeForKeyboardInput({ key: '?', code: 'Slash' });
|
|
923
|
+
expect(recorded?.toString()).to.equal('ctrl+?');
|
|
924
|
+
expect(keybindingRegistry.resolveKeybinding({ command: TEST_COMMAND.id, keybinding: recorded.toString() })[0].dispatchString())
|
|
925
|
+
.to.equal(layoutCode.dispatchString());
|
|
926
|
+
interpretations.restore();
|
|
927
|
+
});
|
|
928
|
+
it('should record logical characters, physical non-printables, and ignore modifier-only input', () => {
|
|
929
|
+
const registry = Object.create(keybinding_1.KeybindingRegistry.prototype);
|
|
930
|
+
registry.authoredKeyCodeForKeyboardInput = input => {
|
|
931
|
+
if (input.code === 'ControlLeft') {
|
|
932
|
+
return new keys_1.KeyCode({ ctrl: true });
|
|
933
|
+
}
|
|
934
|
+
if (input.code === 'F1') {
|
|
935
|
+
return new keys_1.KeyCode({ key: keys_1.Key.F1, ctrl: true });
|
|
936
|
+
}
|
|
937
|
+
if (input.code === 'Delete') {
|
|
938
|
+
return new keys_1.KeyCode({ key: keys_1.Key.DELETE, ctrl: true });
|
|
939
|
+
}
|
|
940
|
+
if (input.code === 'KeyP') {
|
|
941
|
+
return new keys_1.KeyCode({ key: keys_1.Key.KEY_P, ctrl: true, shift: true, character: 'P' });
|
|
942
|
+
}
|
|
943
|
+
return new keys_1.KeyCode({ key: input.code === 'Equal' ? keys_1.Key.EQUAL : keys_1.Key.BRACKET_LEFT, ctrl: true, character: input.key });
|
|
944
|
+
};
|
|
945
|
+
expect(registry.authoredKeybindingStringForKeyboardInput({ key: '[', code: 'Digit8', ctrlKey: true })).to.equal('ctrl+[');
|
|
946
|
+
expect(registry.authoredKeybindingStringForKeyboardInput({ key: '+', code: 'Equal', ctrlKey: true })).to.equal('ctrl+[char:0x2B]');
|
|
947
|
+
expect(registry.authoredKeybindingStringForKeyboardInput({ key: 'F1', code: 'F1', ctrlKey: true })).to.equal('ctrl+f1');
|
|
948
|
+
expect(registry.authoredKeybindingStringForKeyboardInput({ key: 'Delete', code: 'Delete', ctrlKey: true })).to.equal('ctrl+delete');
|
|
949
|
+
const shifted = registry.authoredKeybindingStringForKeyboardInput({ key: 'P', code: 'KeyP', ctrlKey: true, shiftKey: true });
|
|
950
|
+
expect(shifted).to.equal('shift+ctrl+p');
|
|
951
|
+
expect(keys_1.KeyCode.parse(shifted).dispatchString()).to.equal('shift+ctrl+p');
|
|
952
|
+
expect(registry.authoredKeybindingStringForKeyboardInput({ key: 'Control', code: 'ControlLeft', ctrlKey: true })).to.be.undefined;
|
|
953
|
+
});
|
|
954
|
+
it('should record Space and numpad keys by name', () => {
|
|
955
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: ' ', code: 'Space', ctrlKey: true, shiftKey: true }))
|
|
956
|
+
.to.equal('shift+ctrl+space');
|
|
957
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '+', code: 'NumpadAdd', ctrlKey: true })).to.equal('ctrl+add');
|
|
958
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '-', code: 'NumpadSubtract', ctrlKey: true })).to.equal('ctrl+subtract');
|
|
959
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '.', code: 'NumpadDecimal', ctrlKey: true })).to.equal('ctrl+decimal');
|
|
960
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '1', code: 'Numpad1', ctrlKey: true })).to.equal('ctrl+1');
|
|
961
|
+
});
|
|
962
|
+
it('should dispatch and record Option bindings as command modifiers on macOS', async () => {
|
|
963
|
+
stub.value(true);
|
|
964
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
965
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-mac.json'));
|
|
966
|
+
const execute = sinon.spy();
|
|
967
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
968
|
+
try {
|
|
969
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: 'shift+alt+p' }]);
|
|
970
|
+
keybindingRegistry.run(testKeyboardEvent({ key: '∏', code: 'KeyP', shiftKey: true, altKey: true }));
|
|
971
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
972
|
+
expect(execute.calledOnce).to.be.true;
|
|
973
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: '∏', code: 'KeyP', shiftKey: true, altKey: true }))
|
|
974
|
+
.to.equal('shift+alt+p');
|
|
975
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'π', code: 'KeyP', altKey: true })).to.equal('alt+p');
|
|
976
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'P', code: 'KeyP', shiftKey: true })).to.equal('shift+p');
|
|
977
|
+
}
|
|
978
|
+
finally {
|
|
979
|
+
handler.dispose();
|
|
980
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
it('should dispatch and record dead Option keys as their base character on macOS', async () => {
|
|
984
|
+
stub.value(true);
|
|
985
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
986
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-mac.json'));
|
|
987
|
+
const execute = sinon.spy();
|
|
988
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
989
|
+
try {
|
|
990
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: 'alt+e' }]);
|
|
991
|
+
keybindingRegistry.run(testKeyboardEvent({ key: 'Dead', code: 'KeyE', altKey: true }));
|
|
992
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
993
|
+
expect(execute.calledOnce).to.be.true;
|
|
994
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Dead', code: 'KeyE', altKey: true })).to.equal('alt+e');
|
|
995
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Dead', code: 'KeyE', altKey: true, shiftKey: true }))
|
|
996
|
+
.to.equal('shift+alt+e');
|
|
997
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Dead', code: 'KeyE', altKey: true, metaKey: true }))
|
|
998
|
+
.to.equal('meta+alt+e');
|
|
999
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'F1', code: 'F1', altKey: true })).to.equal('alt+f1');
|
|
1000
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: 'shift+alt+e' }]);
|
|
1001
|
+
keybindingRegistry.run(testKeyboardEvent({ key: 'Dead', code: 'KeyE', altKey: true, shiftKey: true }));
|
|
1002
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1003
|
+
expect(execute.calledTwice).to.be.true;
|
|
1004
|
+
}
|
|
1005
|
+
finally {
|
|
1006
|
+
handler.dispose();
|
|
1007
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
it('should record Caps Lock input with the layout layer case', async () => {
|
|
1011
|
+
const notifier = testContainer.get(MockKeyboardLayoutChangeNotifier);
|
|
1012
|
+
const execute = sinon.spy();
|
|
1013
|
+
const handler = commandRegistry.registerHandler(TEST_COMMAND_SHADOW.id, { execute });
|
|
1014
|
+
try {
|
|
1015
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'p', code: 'KeyP', ctrlKey: true, shiftKey: true }))
|
|
1016
|
+
.to.equal('shift+ctrl+p');
|
|
1017
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-pc.json'));
|
|
1018
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'ü', code: 'BracketLeft', ctrlKey: true, shiftKey: true }))
|
|
1019
|
+
.to.equal('ctrl+Ü');
|
|
1020
|
+
const recorded = keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Ü', code: 'BracketLeft', ctrlKey: true });
|
|
1021
|
+
expect(recorded).to.equal('ctrl+ü');
|
|
1022
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [{ command: TEST_COMMAND_SHADOW.id, keybinding: recorded }]);
|
|
1023
|
+
keybindingRegistry.dispatchNormalizedKeyDown({ key: 'ü', code: 'BracketLeft', ctrlKey: true }, new EventTarget());
|
|
1024
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1025
|
+
expect(execute.calledOnce).to.be.true;
|
|
1026
|
+
notifier.emitter.fire({
|
|
1027
|
+
info: { id: 'altgraph-letters', lang: 'pl' },
|
|
1028
|
+
mapping: { KeyL: { value: 'l', withShift: 'L', withAltGr: 'ł', withShiftAltGr: 'Ł' } }
|
|
1029
|
+
});
|
|
1030
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Ł', code: 'KeyL', ctrlKey: true, altGraph: true }))
|
|
1031
|
+
.to.equal('ctrl+ł');
|
|
1032
|
+
stub.value(true);
|
|
1033
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/de-German-mac.json'));
|
|
1034
|
+
expect(keybindingRegistry.authoredKeybindingStringForKeyboardInput({ key: 'Å', code: 'KeyA', altKey: true })).to.equal('alt+a');
|
|
1035
|
+
}
|
|
1036
|
+
finally {
|
|
1037
|
+
handler.dispose();
|
|
1038
|
+
notifier.emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
it('should classify interpretation shadowing separately from canonical collisions', () => {
|
|
1042
|
+
const logical = {
|
|
1043
|
+
command: TEST_COMMAND.id,
|
|
1044
|
+
keybinding: 'ctrl+[',
|
|
1045
|
+
resolved: [new keys_1.KeyCode({ key: keys_1.Key.DIGIT8, ctrl: true, character: '[', layoutModifiers: 'altGraph' })]
|
|
1046
|
+
};
|
|
1047
|
+
const command = { command: TEST_COMMAND2.id, keybinding: 'ctrl+alt+8' };
|
|
1048
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [logical, command]);
|
|
1049
|
+
const diagnostics = keybindingRegistry.getKeybindingCollisionDiagnostics(logical);
|
|
1050
|
+
expect(diagnostics.shadowedBy.map(binding => binding.command)).to.include(TEST_COMMAND2.id);
|
|
1051
|
+
expect(diagnostics.authored.full).to.be.empty;
|
|
1052
|
+
expect(diagnostics.layoutDerived.full).to.be.empty;
|
|
1053
|
+
expect(keybindingRegistry.containsKeybindingInScope(logical)).to.be.false;
|
|
1054
|
+
});
|
|
1055
|
+
it('should keep shifted printable bindings symmetric in keyCode dispatch mode', () => {
|
|
1056
|
+
corePreferences['keyboard.dispatch'] = 'keyCode';
|
|
1057
|
+
const commandPalette = { command: TEST_COMMAND.id, keybinding: 'ctrl+shift+p' };
|
|
1058
|
+
const shiftedPrintable = { command: TEST_COMMAND2.id, keybinding: 'shift+/' };
|
|
1059
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [commandPalette, shiftedPrintable]);
|
|
1060
|
+
expect(keybindingRegistry.matchKeybinding(keys_1.KeySequence.parse('ctrl+shift+p'))?.runtime.binding.command).to.equal(TEST_COMMAND.id);
|
|
1061
|
+
expect(keybindingRegistry.matchKeybinding(keys_1.KeySequence.parse('shift+/'))?.runtime.binding.command).to.equal(TEST_COMMAND2.id);
|
|
1062
|
+
});
|
|
1063
|
+
it('should mark character-only bindings inactive in keyCode dispatch mode', () => {
|
|
1064
|
+
corePreferences['keyboard.dispatch'] = 'keyCode';
|
|
1065
|
+
const characterOnly = { command: TEST_COMMAND.id, keybinding: 'ctrl+ü' };
|
|
1066
|
+
const positional = { command: TEST_COMMAND2.id, keybinding: 'ctrl+[' };
|
|
1067
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [characterOnly, positional]);
|
|
1068
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(characterOnly)).to.be.a('string');
|
|
1069
|
+
expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(characterOnly))).to.be.undefined;
|
|
1070
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(positional)).to.be.undefined;
|
|
1071
|
+
expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(positional))?.kind).to.equal('full');
|
|
1072
|
+
});
|
|
1073
|
+
it('should require resolved sequences for logical matching in code dispatch mode', () => {
|
|
1074
|
+
testContainer.get(MockKeyboardLayoutChangeNotifier).emitter.fire(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
1075
|
+
const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+shift+a' };
|
|
1076
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
1077
|
+
expect(keys_1.KeySequence.parse('ctrl+shift+a')[0].dispatchString()).to.equal('shift+ctrl+a');
|
|
1078
|
+
expect(keybindingRegistry.resolveKeybinding(binding)[0].dispatchString()).to.equal('ctrl+a@shift');
|
|
1079
|
+
expect(keybindingRegistry.matchKeybinding(keys_1.KeySequence.parse('ctrl+shift+a'))).to.be.undefined;
|
|
1080
|
+
expect(keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(binding))?.kind).to.equal('full');
|
|
1081
|
+
});
|
|
1082
|
+
it('should invalidate resolved bindings when keyboard dispatch changes', () => {
|
|
1083
|
+
const binding = { command: TEST_COMMAND.id, keybinding: 'ctrl+[' };
|
|
1084
|
+
keybindingRegistry.setKeymap(keybinding_1.KeybindingScope.USER, [binding]);
|
|
1085
|
+
expect(keybindingRegistry.getKeybindingInactiveReason(binding)).to.be.undefined;
|
|
1086
|
+
const changed = sinon.spy();
|
|
1087
|
+
keybindingRegistry.onKeybindingsChanged(changed);
|
|
1088
|
+
corePreferences['keyboard.dispatch'] = 'keyCode';
|
|
1089
|
+
preferenceChanged.fire({ preferenceName: 'keyboard.dispatch' });
|
|
1090
|
+
expect(changed.calledOnce).to.be.true;
|
|
1091
|
+
expect(keybindingRegistry.resolveKeybinding(binding)[0].dispatchString()).to.equal('ctrl+[');
|
|
1092
|
+
expect(keybindingRegistry.matchKeybinding(keys_1.KeySequence.parse('ctrl+['))?.kind).to.equal('full');
|
|
376
1093
|
});
|
|
377
1094
|
it('should prioritize bindings that use local context keys', () => {
|
|
378
1095
|
// Ensure JSDOM is enabled for this test since it uses document
|
|
@@ -407,10 +1124,10 @@ describe('keybindings', () => {
|
|
|
407
1124
|
shiftKey: true
|
|
408
1125
|
});
|
|
409
1126
|
Object.defineProperty(mockEvent, 'target', { value: targetElement });
|
|
410
|
-
const match = keybindingRegistry.matchKeybinding(
|
|
1127
|
+
const match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(localBinding), mockEvent);
|
|
411
1128
|
// The localBinding should be selected because it uses a local context key
|
|
412
1129
|
expect(match?.kind).to.be.equal('full');
|
|
413
|
-
expect(match?.binding?.command).to.be.equal('test.local-command');
|
|
1130
|
+
expect(match?.runtime.binding?.command).to.be.equal('test.local-command');
|
|
414
1131
|
disable();
|
|
415
1132
|
});
|
|
416
1133
|
it('should fall back to first binding when no local context keys exist', () => {
|
|
@@ -442,10 +1159,10 @@ describe('keybindings', () => {
|
|
|
442
1159
|
shiftKey: true
|
|
443
1160
|
});
|
|
444
1161
|
Object.defineProperty(mockEvent, 'target', { value: targetElement });
|
|
445
|
-
const match = keybindingRegistry.matchKeybinding(
|
|
1162
|
+
const match = keybindingRegistry.matchKeybinding(keybindingRegistry.resolveKeybinding(secondBinding), mockEvent);
|
|
446
1163
|
// Should fall back to first enabled binding in priority order (secondBinding was registered later, so it wins)
|
|
447
1164
|
expect(match?.kind).to.be.equal('full');
|
|
448
|
-
expect(match?.binding?.command).to.be.equal('test.second-command');
|
|
1165
|
+
expect(match?.runtime.binding?.command).to.be.equal('test.second-command');
|
|
449
1166
|
disable();
|
|
450
1167
|
});
|
|
451
1168
|
});
|
|
@@ -462,6 +1179,48 @@ describe('acceleratorForKeyCode', () => {
|
|
|
462
1179
|
const accelerator = keybindingRegistry.acceleratorForKeyCode(metaV, '+');
|
|
463
1180
|
expect(accelerator).to.equal('⌘V');
|
|
464
1181
|
});
|
|
1182
|
+
it('separates logical labels from physical layout modifier realizations', () => {
|
|
1183
|
+
const logicalBracket = new keys_1.KeyCode({
|
|
1184
|
+
key: keys_1.Key.DIGIT8,
|
|
1185
|
+
ctrl: true,
|
|
1186
|
+
character: '[',
|
|
1187
|
+
layoutModifiers: 'altGraph'
|
|
1188
|
+
});
|
|
1189
|
+
const logicalSlash = new keys_1.KeyCode({
|
|
1190
|
+
key: keys_1.Key.DIGIT7,
|
|
1191
|
+
character: '/',
|
|
1192
|
+
layoutModifiers: 'shift'
|
|
1193
|
+
});
|
|
1194
|
+
expect(keybindingRegistry.componentsForKeyCode(logicalBracket)).to.deep.equal(['Ctrl', '[']);
|
|
1195
|
+
expect(keybindingRegistry.componentsForKeyCode(logicalBracket, false, 'physical')).to.deep.equal(['Ctrl', 'AltGr', '8']);
|
|
1196
|
+
expect(keybindingRegistry.acceleratorForKeyCode(logicalBracket, '+', true, 'physical')).to.equal('Ctrl+AltGr+8');
|
|
1197
|
+
expect(keybindingRegistry.componentsForKeyCode(logicalSlash)).to.deep.equal(['/']);
|
|
1198
|
+
expect(keybindingRegistry.componentsForKeyCode(logicalSlash, false, 'physical')).to.deep.equal(['Shift', '7']);
|
|
1199
|
+
expect(keybindingRegistry.acceleratorForKeyCode(logicalSlash, '+', true, 'physical')).to.equal('Shift+7');
|
|
1200
|
+
});
|
|
1201
|
+
it('keeps command Shift in physical realizations', () => {
|
|
1202
|
+
const commandShift = new keys_1.KeyCode({ key: keys_1.Key.TAB, ctrl: true, shift: true });
|
|
1203
|
+
expect(keybindingRegistry.componentsForKeyCode(commandShift, false, 'physical')).to.deep.equal(['Ctrl', 'Shift', 'Tab']);
|
|
1204
|
+
const shiftedLetter = new keys_1.KeyCode({ key: keys_1.Key.KEY_P, ctrl: true, character: 'P', layoutModifiers: 'shift' });
|
|
1205
|
+
expect(keybindingRegistry.componentsForKeyCode(shiftedLetter)).to.deep.equal(['Ctrl', 'Shift', 'P']);
|
|
1206
|
+
const unresolvedCharacter = new keys_1.KeyCode({ ctrl: true, character: '§', supportedByLayout: false });
|
|
1207
|
+
expect(keybindingRegistry.componentsForKeyCode(unresolvedCharacter)).to.deep.equal(['Ctrl', '§']);
|
|
1208
|
+
});
|
|
1209
|
+
it('deduplicates command Alt and layout modifier Option on macOS', () => {
|
|
1210
|
+
stub.value(true);
|
|
1211
|
+
const commandAndLayoutOption = new keys_1.KeyCode({
|
|
1212
|
+
key: keys_1.Key.DIGIT6,
|
|
1213
|
+
meta: true,
|
|
1214
|
+
ctrl: true,
|
|
1215
|
+
alt: true,
|
|
1216
|
+
layoutModifiers: 'altGraph'
|
|
1217
|
+
});
|
|
1218
|
+
const layoutOption = new keys_1.KeyCode({ key: keys_1.Key.DIGIT6, layoutModifiers: 'altGraph' });
|
|
1219
|
+
expect(keybindingRegistry.componentsForKeyCode(commandAndLayoutOption, false, 'physical')).to.deep.equal(['⌘', '⌃', '⌥', '6']);
|
|
1220
|
+
expect(keybindingRegistry.componentsForKeyCode(commandAndLayoutOption, true, 'physical')).to.deep.equal(['Cmd', 'Ctrl', 'Alt', '6']);
|
|
1221
|
+
expect(keybindingRegistry.componentsForKeyCode(layoutOption, false, 'physical')).to.deep.equal(['⌥', '6']);
|
|
1222
|
+
expect(keybindingRegistry.componentsForKeyCode(layoutOption, true, 'physical')).to.deep.equal(['Option', '6']);
|
|
1223
|
+
});
|
|
465
1224
|
it('uses ASCII names with the separator on macOS Electron (asciiOnly)', () => {
|
|
466
1225
|
stub.value(true);
|
|
467
1226
|
const accelerator = keybindingRegistry.acceleratorForKeyCode(metaV, '+', true);
|
|
@@ -514,10 +1273,7 @@ MockContextKeyService = tslib_1.__decorate([
|
|
|
514
1273
|
], MockContextKeyService);
|
|
515
1274
|
let MockKeyboardLayoutProvider = class MockKeyboardLayoutProvider {
|
|
516
1275
|
getNativeLayout() {
|
|
517
|
-
return Promise.resolve(
|
|
518
|
-
info: { id: 'mock', lang: 'en' },
|
|
519
|
-
mapping: {}
|
|
520
|
-
});
|
|
1276
|
+
return Promise.resolve(require('../../src/common/keyboard/layouts/en-US-pc.json'));
|
|
521
1277
|
}
|
|
522
1278
|
};
|
|
523
1279
|
MockKeyboardLayoutProvider = tslib_1.__decorate([
|