Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CharCode } from '../../../../base/common/charCode.js';
7
import { KeyCode, KeyCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE, ScanCode, ScanCodeUtils } from '../../../../base/common/keyCodes.js';
8
import { ResolvedKeybinding, KeyCodeChord, SingleModifierChord, ScanCodeChord, Keybinding, Chord } from '../../../../base/common/keybindings.js';
9
import { OperatingSystem } from '../../../../base/common/platform.js';
10
import { IKeyboardEvent } from '../../../../platform/keybinding/common/keybinding.js';
11
import { IKeyboardMapper } from '../../../../platform/keyboardLayout/common/keyboardMapper.js';
12
import { BaseResolvedKeybinding } from '../../../../platform/keybinding/common/baseResolvedKeybinding.js';
13
import { IMacLinuxKeyboardMapping, IMacLinuxKeyMapping } from '../../../../platform/keyboardLayout/common/keyboardLayout.js';
14
15
/**
16
* A map from character to key codes.
17
* e.g. Contains entries such as:
18
* - '/' => { keyCode: KeyCode.US_SLASH, shiftKey: false }
19
* - '?' => { keyCode: KeyCode.US_SLASH, shiftKey: true }
20
*/
21
const CHAR_CODE_TO_KEY_CODE: ({ keyCode: KeyCode; shiftKey: boolean } | null)[] = [];
22
23
export class NativeResolvedKeybinding extends BaseResolvedKeybinding<ScanCodeChord> {
24
25
private readonly _mapper: MacLinuxKeyboardMapper;
26
27
constructor(mapper: MacLinuxKeyboardMapper, os: OperatingSystem, chords: ScanCodeChord[]) {
28
super(os, chords);
29
this._mapper = mapper;
30
}
31
32
protected _getLabel(chord: ScanCodeChord): string | null {
33
return this._mapper.getUILabelForScanCodeChord(chord);
34
}
35
36
protected _getAriaLabel(chord: ScanCodeChord): string | null {
37
return this._mapper.getAriaLabelForScanCodeChord(chord);
38
}
39
40
protected _getElectronAccelerator(chord: ScanCodeChord): string | null {
41
return this._mapper.getElectronAcceleratorLabelForScanCodeChord(chord);
42
}
43
44
protected _getUserSettingsLabel(chord: ScanCodeChord): string | null {
45
return this._mapper.getUserSettingsLabelForScanCodeChord(chord);
46
}
47
48
protected _isWYSIWYG(binding: ScanCodeChord | null): boolean {
49
if (!binding) {
50
return true;
51
}
52
if (IMMUTABLE_CODE_TO_KEY_CODE[binding.scanCode] !== KeyCode.DependsOnKbLayout) {
53
return true;
54
}
55
const a = this._mapper.getAriaLabelForScanCodeChord(binding);
56
const b = this._mapper.getUserSettingsLabelForScanCodeChord(binding);
57
58
if (!a && !b) {
59
return true;
60
}
61
if (!a || !b) {
62
return false;
63
}
64
return (a.toLowerCase() === b.toLowerCase());
65
}
66
67
protected _getChordDispatch(chord: ScanCodeChord): string | null {
68
return this._mapper.getDispatchStrForScanCodeChord(chord);
69
}
70
71
protected _getSingleModifierChordDispatch(chord: ScanCodeChord): SingleModifierChord | null {
72
if ((chord.scanCode === ScanCode.ControlLeft || chord.scanCode === ScanCode.ControlRight) && !chord.shiftKey && !chord.altKey && !chord.metaKey) {
73
return 'ctrl';
74
}
75
if ((chord.scanCode === ScanCode.AltLeft || chord.scanCode === ScanCode.AltRight) && !chord.ctrlKey && !chord.shiftKey && !chord.metaKey) {
76
return 'alt';
77
}
78
if ((chord.scanCode === ScanCode.ShiftLeft || chord.scanCode === ScanCode.ShiftRight) && !chord.ctrlKey && !chord.altKey && !chord.metaKey) {
79
return 'shift';
80
}
81
if ((chord.scanCode === ScanCode.MetaLeft || chord.scanCode === ScanCode.MetaRight) && !chord.ctrlKey && !chord.shiftKey && !chord.altKey) {
82
return 'meta';
83
}
84
return null;
85
}
86
}
87
88
interface IScanCodeMapping {
89
scanCode: ScanCode;
90
value: number;
91
withShift: number;
92
withAltGr: number;
93
withShiftAltGr: number;
94
}
95
96
class ScanCodeCombo {
97
public readonly ctrlKey: boolean;
98
public readonly shiftKey: boolean;
99
public readonly altKey: boolean;
100
public readonly scanCode: ScanCode;
101
102
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, scanCode: ScanCode) {
103
this.ctrlKey = ctrlKey;
104
this.shiftKey = shiftKey;
105
this.altKey = altKey;
106
this.scanCode = scanCode;
107
}
108
109
public toString(): string {
110
return `${this.ctrlKey ? 'Ctrl+' : ''}${this.shiftKey ? 'Shift+' : ''}${this.altKey ? 'Alt+' : ''}${ScanCodeUtils.toString(this.scanCode)}`;
111
}
112
113
public equals(other: ScanCodeCombo): boolean {
114
return (
115
this.ctrlKey === other.ctrlKey
116
&& this.shiftKey === other.shiftKey
117
&& this.altKey === other.altKey
118
&& this.scanCode === other.scanCode
119
);
120
}
121
122
private getProducedCharCode(mapping: IMacLinuxKeyMapping): string {
123
if (!mapping) {
124
return '';
125
}
126
if (this.ctrlKey && this.shiftKey && this.altKey) {
127
return mapping.withShiftAltGr;
128
}
129
if (this.ctrlKey && this.altKey) {
130
return mapping.withAltGr;
131
}
132
if (this.shiftKey) {
133
return mapping.withShift;
134
}
135
return mapping.value;
136
}
137
138
public getProducedChar(mapping: IMacLinuxKeyMapping): string {
139
const charCode = MacLinuxKeyboardMapper.getCharCode(this.getProducedCharCode(mapping));
140
if (charCode === 0) {
141
return ' --- ';
142
}
143
if (charCode >= CharCode.U_Combining_Grave_Accent && charCode <= CharCode.U_Combining_Latin_Small_Letter_X) {
144
// combining
145
return 'U+' + charCode.toString(16);
146
}
147
return ' ' + String.fromCharCode(charCode) + ' ';
148
}
149
}
150
151
class KeyCodeCombo {
152
public readonly ctrlKey: boolean;
153
public readonly shiftKey: boolean;
154
public readonly altKey: boolean;
155
public readonly keyCode: KeyCode;
156
157
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, keyCode: KeyCode) {
158
this.ctrlKey = ctrlKey;
159
this.shiftKey = shiftKey;
160
this.altKey = altKey;
161
this.keyCode = keyCode;
162
}
163
164
public toString(): string {
165
return `${this.ctrlKey ? 'Ctrl+' : ''}${this.shiftKey ? 'Shift+' : ''}${this.altKey ? 'Alt+' : ''}${KeyCodeUtils.toString(this.keyCode)}`;
166
}
167
}
168
169
class ScanCodeKeyCodeMapper {
170
171
/**
172
* ScanCode combination => KeyCode combination.
173
* Only covers relevant modifiers ctrl, shift, alt (since meta does not influence the mappings).
174
*/
175
private readonly _scanCodeToKeyCode: number[][] = [];
176
/**
177
* inverse of `_scanCodeToKeyCode`.
178
* KeyCode combination => ScanCode combination.
179
* Only covers relevant modifiers ctrl, shift, alt (since meta does not influence the mappings).
180
*/
181
private readonly _keyCodeToScanCode: number[][] = [];
182
183
constructor() {
184
this._scanCodeToKeyCode = [];
185
this._keyCodeToScanCode = [];
186
}
187
188
public registrationComplete(): void {
189
// IntlHash and IntlBackslash are rare keys, so ensure they don't end up being the preferred...
190
this._moveToEnd(ScanCode.IntlHash);
191
this._moveToEnd(ScanCode.IntlBackslash);
192
}
193
194
private _moveToEnd(scanCode: ScanCode): void {
195
for (let mod = 0; mod < 8; mod++) {
196
const encodedKeyCodeCombos = this._scanCodeToKeyCode[(scanCode << 3) + mod];
197
if (!encodedKeyCodeCombos) {
198
continue;
199
}
200
for (let i = 0, len = encodedKeyCodeCombos.length; i < len; i++) {
201
const encodedScanCodeCombos = this._keyCodeToScanCode[encodedKeyCodeCombos[i]];
202
if (encodedScanCodeCombos.length === 1) {
203
continue;
204
}
205
for (let j = 0, len = encodedScanCodeCombos.length; j < len; j++) {
206
const entry = encodedScanCodeCombos[j];
207
const entryScanCode = (entry >>> 3);
208
if (entryScanCode === scanCode) {
209
// Move this entry to the end
210
for (let k = j + 1; k < len; k++) {
211
encodedScanCodeCombos[k - 1] = encodedScanCodeCombos[k];
212
}
213
encodedScanCodeCombos[len - 1] = entry;
214
}
215
}
216
}
217
}
218
}
219
220
public registerIfUnknown(scanCodeCombo: ScanCodeCombo, keyCodeCombo: KeyCodeCombo): void {
221
if (keyCodeCombo.keyCode === KeyCode.Unknown) {
222
return;
223
}
224
const scanCodeComboEncoded = this._encodeScanCodeCombo(scanCodeCombo);
225
const keyCodeComboEncoded = this._encodeKeyCodeCombo(keyCodeCombo);
226
227
const keyCodeIsDigit = (keyCodeCombo.keyCode >= KeyCode.Digit0 && keyCodeCombo.keyCode <= KeyCode.Digit9);
228
const keyCodeIsLetter = (keyCodeCombo.keyCode >= KeyCode.KeyA && keyCodeCombo.keyCode <= KeyCode.KeyZ);
229
230
const existingKeyCodeCombos = this._scanCodeToKeyCode[scanCodeComboEncoded];
231
232
// Allow a scan code to map to multiple key codes if it is a digit or a letter key code
233
if (keyCodeIsDigit || keyCodeIsLetter) {
234
// Only check that we don't insert the same entry twice
235
if (existingKeyCodeCombos) {
236
for (let i = 0, len = existingKeyCodeCombos.length; i < len; i++) {
237
if (existingKeyCodeCombos[i] === keyCodeComboEncoded) {
238
// avoid duplicates
239
return;
240
}
241
}
242
}
243
} else {
244
// Don't allow multiples
245
if (existingKeyCodeCombos && existingKeyCodeCombos.length !== 0) {
246
return;
247
}
248
}
249
250
this._scanCodeToKeyCode[scanCodeComboEncoded] = this._scanCodeToKeyCode[scanCodeComboEncoded] || [];
251
this._scanCodeToKeyCode[scanCodeComboEncoded].unshift(keyCodeComboEncoded);
252
253
this._keyCodeToScanCode[keyCodeComboEncoded] = this._keyCodeToScanCode[keyCodeComboEncoded] || [];
254
this._keyCodeToScanCode[keyCodeComboEncoded].unshift(scanCodeComboEncoded);
255
}
256
257
public lookupKeyCodeCombo(keyCodeCombo: KeyCodeCombo): ScanCodeCombo[] {
258
const keyCodeComboEncoded = this._encodeKeyCodeCombo(keyCodeCombo);
259
const scanCodeCombosEncoded = this._keyCodeToScanCode[keyCodeComboEncoded];
260
if (!scanCodeCombosEncoded || scanCodeCombosEncoded.length === 0) {
261
return [];
262
}
263
264
const result: ScanCodeCombo[] = [];
265
for (let i = 0, len = scanCodeCombosEncoded.length; i < len; i++) {
266
const scanCodeComboEncoded = scanCodeCombosEncoded[i];
267
268
const ctrlKey = (scanCodeComboEncoded & 0b001) ? true : false;
269
const shiftKey = (scanCodeComboEncoded & 0b010) ? true : false;
270
const altKey = (scanCodeComboEncoded & 0b100) ? true : false;
271
const scanCode: ScanCode = (scanCodeComboEncoded >>> 3);
272
273
result[i] = new ScanCodeCombo(ctrlKey, shiftKey, altKey, scanCode);
274
}
275
return result;
276
}
277
278
public lookupScanCodeCombo(scanCodeCombo: ScanCodeCombo): KeyCodeCombo[] {
279
const scanCodeComboEncoded = this._encodeScanCodeCombo(scanCodeCombo);
280
const keyCodeCombosEncoded = this._scanCodeToKeyCode[scanCodeComboEncoded];
281
if (!keyCodeCombosEncoded || keyCodeCombosEncoded.length === 0) {
282
return [];
283
}
284
285
const result: KeyCodeCombo[] = [];
286
for (let i = 0, len = keyCodeCombosEncoded.length; i < len; i++) {
287
const keyCodeComboEncoded = keyCodeCombosEncoded[i];
288
289
const ctrlKey = (keyCodeComboEncoded & 0b001) ? true : false;
290
const shiftKey = (keyCodeComboEncoded & 0b010) ? true : false;
291
const altKey = (keyCodeComboEncoded & 0b100) ? true : false;
292
const keyCode: KeyCode = (keyCodeComboEncoded >>> 3);
293
294
result[i] = new KeyCodeCombo(ctrlKey, shiftKey, altKey, keyCode);
295
}
296
return result;
297
}
298
299
public guessStableKeyCode(scanCode: ScanCode): KeyCode {
300
if (scanCode >= ScanCode.Digit1 && scanCode <= ScanCode.Digit0) {
301
// digits are ok
302
switch (scanCode) {
303
case ScanCode.Digit1: return KeyCode.Digit1;
304
case ScanCode.Digit2: return KeyCode.Digit2;
305
case ScanCode.Digit3: return KeyCode.Digit3;
306
case ScanCode.Digit4: return KeyCode.Digit4;
307
case ScanCode.Digit5: return KeyCode.Digit5;
308
case ScanCode.Digit6: return KeyCode.Digit6;
309
case ScanCode.Digit7: return KeyCode.Digit7;
310
case ScanCode.Digit8: return KeyCode.Digit8;
311
case ScanCode.Digit9: return KeyCode.Digit9;
312
case ScanCode.Digit0: return KeyCode.Digit0;
313
}
314
}
315
316
// Lookup the scanCode with and without shift and see if the keyCode is stable
317
const keyCodeCombos1 = this.lookupScanCodeCombo(new ScanCodeCombo(false, false, false, scanCode));
318
const keyCodeCombos2 = this.lookupScanCodeCombo(new ScanCodeCombo(false, true, false, scanCode));
319
if (keyCodeCombos1.length === 1 && keyCodeCombos2.length === 1) {
320
const shiftKey1 = keyCodeCombos1[0].shiftKey;
321
const keyCode1 = keyCodeCombos1[0].keyCode;
322
const shiftKey2 = keyCodeCombos2[0].shiftKey;
323
const keyCode2 = keyCodeCombos2[0].keyCode;
324
if (keyCode1 === keyCode2 && shiftKey1 !== shiftKey2) {
325
// This looks like a stable mapping
326
return keyCode1;
327
}
328
}
329
330
return KeyCode.DependsOnKbLayout;
331
}
332
333
private _encodeScanCodeCombo(scanCodeCombo: ScanCodeCombo): number {
334
return this._encode(scanCodeCombo.ctrlKey, scanCodeCombo.shiftKey, scanCodeCombo.altKey, scanCodeCombo.scanCode);
335
}
336
337
private _encodeKeyCodeCombo(keyCodeCombo: KeyCodeCombo): number {
338
return this._encode(keyCodeCombo.ctrlKey, keyCodeCombo.shiftKey, keyCodeCombo.altKey, keyCodeCombo.keyCode);
339
}
340
341
private _encode(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, principal: number): number {
342
return (
343
((ctrlKey ? 1 : 0) << 0)
344
| ((shiftKey ? 1 : 0) << 1)
345
| ((altKey ? 1 : 0) << 2)
346
| principal << 3
347
) >>> 0;
348
}
349
}
350
351
export class MacLinuxKeyboardMapper implements IKeyboardMapper {
352
353
/**
354
* used only for debug purposes.
355
*/
356
private readonly _codeInfo: IMacLinuxKeyMapping[];
357
/**
358
* Maps ScanCode combos <-> KeyCode combos.
359
*/
360
private readonly _scanCodeKeyCodeMapper: ScanCodeKeyCodeMapper;
361
/**
362
* UI label for a ScanCode.
363
*/
364
private readonly _scanCodeToLabel: Array<string | null> = [];
365
/**
366
* Dispatching string for a ScanCode.
367
*/
368
private readonly _scanCodeToDispatch: Array<string | null> = [];
369
370
constructor(
371
private readonly _isUSStandard: boolean,
372
rawMappings: IMacLinuxKeyboardMapping,
373
private readonly _mapAltGrToCtrlAlt: boolean,
374
private readonly _OS: OperatingSystem,
375
) {
376
this._codeInfo = [];
377
this._scanCodeKeyCodeMapper = new ScanCodeKeyCodeMapper();
378
this._scanCodeToLabel = [];
379
this._scanCodeToDispatch = [];
380
381
const _registerIfUnknown = (
382
hwCtrlKey: 0 | 1, hwShiftKey: 0 | 1, hwAltKey: 0 | 1, scanCode: ScanCode,
383
kbCtrlKey: 0 | 1, kbShiftKey: 0 | 1, kbAltKey: 0 | 1, keyCode: KeyCode,
384
): void => {
385
this._scanCodeKeyCodeMapper.registerIfUnknown(
386
new ScanCodeCombo(hwCtrlKey ? true : false, hwShiftKey ? true : false, hwAltKey ? true : false, scanCode),
387
new KeyCodeCombo(kbCtrlKey ? true : false, kbShiftKey ? true : false, kbAltKey ? true : false, keyCode)
388
);
389
};
390
391
const _registerAllCombos = (_ctrlKey: 0 | 1, _shiftKey: 0 | 1, _altKey: 0 | 1, scanCode: ScanCode, keyCode: KeyCode): void => {
392
for (let ctrlKey = _ctrlKey; ctrlKey <= 1; ctrlKey++) {
393
for (let shiftKey = _shiftKey; shiftKey <= 1; shiftKey++) {
394
for (let altKey = _altKey; altKey <= 1; altKey++) {
395
_registerIfUnknown(
396
ctrlKey, shiftKey, altKey, scanCode,
397
ctrlKey, shiftKey, altKey, keyCode
398
);
399
}
400
}
401
}
402
};
403
404
// Initialize `_scanCodeToLabel`
405
for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) {
406
this._scanCodeToLabel[scanCode] = null;
407
}
408
409
// Initialize `_scanCodeToDispatch`
410
for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) {
411
this._scanCodeToDispatch[scanCode] = null;
412
}
413
414
// Handle immutable mappings
415
for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) {
416
const keyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode];
417
if (keyCode !== KeyCode.DependsOnKbLayout) {
418
_registerAllCombos(0, 0, 0, scanCode, keyCode);
419
this._scanCodeToLabel[scanCode] = KeyCodeUtils.toString(keyCode);
420
421
if (keyCode === KeyCode.Unknown || keyCode === KeyCode.Ctrl || keyCode === KeyCode.Meta || keyCode === KeyCode.Alt || keyCode === KeyCode.Shift) {
422
this._scanCodeToDispatch[scanCode] = null; // cannot dispatch on this ScanCode
423
} else {
424
this._scanCodeToDispatch[scanCode] = `[${ScanCodeUtils.toString(scanCode)}]`;
425
}
426
}
427
}
428
429
// Try to identify keyboard layouts where characters A-Z are missing
430
// and forcibly map them to their corresponding scan codes if that is the case
431
const missingLatinLettersOverride: { [scanCode: string]: IMacLinuxKeyMapping } = {};
432
433
{
434
const producesLatinLetter: boolean[] = [];
435
for (const strScanCode in rawMappings) {
436
if (rawMappings.hasOwnProperty(strScanCode)) {
437
const scanCode = ScanCodeUtils.toEnum(strScanCode);
438
if (scanCode === ScanCode.None) {
439
continue;
440
}
441
if (IMMUTABLE_CODE_TO_KEY_CODE[scanCode] !== KeyCode.DependsOnKbLayout) {
442
continue;
443
}
444
445
const rawMapping = rawMappings[strScanCode];
446
const value = MacLinuxKeyboardMapper.getCharCode(rawMapping.value);
447
448
if (value >= CharCode.a && value <= CharCode.z) {
449
const upperCaseValue = CharCode.A + (value - CharCode.a);
450
producesLatinLetter[upperCaseValue] = true;
451
}
452
}
453
}
454
455
const _registerLetterIfMissing = (charCode: CharCode, scanCode: ScanCode, value: string, withShift: string): void => {
456
if (!producesLatinLetter[charCode]) {
457
missingLatinLettersOverride[ScanCodeUtils.toString(scanCode)] = {
458
value: value,
459
withShift: withShift,
460
withAltGr: '',
461
withShiftAltGr: ''
462
};
463
}
464
};
465
466
// Ensure letters are mapped
467
_registerLetterIfMissing(CharCode.A, ScanCode.KeyA, 'a', 'A');
468
_registerLetterIfMissing(CharCode.B, ScanCode.KeyB, 'b', 'B');
469
_registerLetterIfMissing(CharCode.C, ScanCode.KeyC, 'c', 'C');
470
_registerLetterIfMissing(CharCode.D, ScanCode.KeyD, 'd', 'D');
471
_registerLetterIfMissing(CharCode.E, ScanCode.KeyE, 'e', 'E');
472
_registerLetterIfMissing(CharCode.F, ScanCode.KeyF, 'f', 'F');
473
_registerLetterIfMissing(CharCode.G, ScanCode.KeyG, 'g', 'G');
474
_registerLetterIfMissing(CharCode.H, ScanCode.KeyH, 'h', 'H');
475
_registerLetterIfMissing(CharCode.I, ScanCode.KeyI, 'i', 'I');
476
_registerLetterIfMissing(CharCode.J, ScanCode.KeyJ, 'j', 'J');
477
_registerLetterIfMissing(CharCode.K, ScanCode.KeyK, 'k', 'K');
478
_registerLetterIfMissing(CharCode.L, ScanCode.KeyL, 'l', 'L');
479
_registerLetterIfMissing(CharCode.M, ScanCode.KeyM, 'm', 'M');
480
_registerLetterIfMissing(CharCode.N, ScanCode.KeyN, 'n', 'N');
481
_registerLetterIfMissing(CharCode.O, ScanCode.KeyO, 'o', 'O');
482
_registerLetterIfMissing(CharCode.P, ScanCode.KeyP, 'p', 'P');
483
_registerLetterIfMissing(CharCode.Q, ScanCode.KeyQ, 'q', 'Q');
484
_registerLetterIfMissing(CharCode.R, ScanCode.KeyR, 'r', 'R');
485
_registerLetterIfMissing(CharCode.S, ScanCode.KeyS, 's', 'S');
486
_registerLetterIfMissing(CharCode.T, ScanCode.KeyT, 't', 'T');
487
_registerLetterIfMissing(CharCode.U, ScanCode.KeyU, 'u', 'U');
488
_registerLetterIfMissing(CharCode.V, ScanCode.KeyV, 'v', 'V');
489
_registerLetterIfMissing(CharCode.W, ScanCode.KeyW, 'w', 'W');
490
_registerLetterIfMissing(CharCode.X, ScanCode.KeyX, 'x', 'X');
491
_registerLetterIfMissing(CharCode.Y, ScanCode.KeyY, 'y', 'Y');
492
_registerLetterIfMissing(CharCode.Z, ScanCode.KeyZ, 'z', 'Z');
493
}
494
495
const mappings: IScanCodeMapping[] = [];
496
let mappingsLen = 0;
497
for (const strScanCode in rawMappings) {
498
if (rawMappings.hasOwnProperty(strScanCode)) {
499
const scanCode = ScanCodeUtils.toEnum(strScanCode);
500
if (scanCode === ScanCode.None) {
501
continue;
502
}
503
if (IMMUTABLE_CODE_TO_KEY_CODE[scanCode] !== KeyCode.DependsOnKbLayout) {
504
continue;
505
}
506
507
this._codeInfo[scanCode] = rawMappings[strScanCode];
508
509
const rawMapping = missingLatinLettersOverride[strScanCode] || rawMappings[strScanCode];
510
const value = MacLinuxKeyboardMapper.getCharCode(rawMapping.value);
511
const withShift = MacLinuxKeyboardMapper.getCharCode(rawMapping.withShift);
512
const withAltGr = MacLinuxKeyboardMapper.getCharCode(rawMapping.withAltGr);
513
const withShiftAltGr = MacLinuxKeyboardMapper.getCharCode(rawMapping.withShiftAltGr);
514
515
const mapping: IScanCodeMapping = {
516
scanCode: scanCode,
517
value: value,
518
withShift: withShift,
519
withAltGr: withAltGr,
520
withShiftAltGr: withShiftAltGr,
521
};
522
mappings[mappingsLen++] = mapping;
523
524
this._scanCodeToDispatch[scanCode] = `[${ScanCodeUtils.toString(scanCode)}]`;
525
526
if (value >= CharCode.a && value <= CharCode.z) {
527
const upperCaseValue = CharCode.A + (value - CharCode.a);
528
this._scanCodeToLabel[scanCode] = String.fromCharCode(upperCaseValue);
529
} else if (value >= CharCode.A && value <= CharCode.Z) {
530
this._scanCodeToLabel[scanCode] = String.fromCharCode(value);
531
} else if (value) {
532
this._scanCodeToLabel[scanCode] = String.fromCharCode(value);
533
} else {
534
this._scanCodeToLabel[scanCode] = null;
535
}
536
}
537
}
538
539
// Handle all `withShiftAltGr` entries
540
for (let i = mappings.length - 1; i >= 0; i--) {
541
const mapping = mappings[i];
542
const scanCode = mapping.scanCode;
543
const withShiftAltGr = mapping.withShiftAltGr;
544
if (withShiftAltGr === mapping.withAltGr || withShiftAltGr === mapping.withShift || withShiftAltGr === mapping.value) {
545
// handled below
546
continue;
547
}
548
const kb = MacLinuxKeyboardMapper._charCodeToKb(withShiftAltGr);
549
if (!kb) {
550
continue;
551
}
552
const kbShiftKey = kb.shiftKey;
553
const keyCode = kb.keyCode;
554
555
if (kbShiftKey) {
556
// Ctrl+Shift+Alt+ScanCode => Shift+KeyCode
557
_registerIfUnknown(1, 1, 1, scanCode, 0, 1, 0, keyCode); // Ctrl+Alt+ScanCode => Shift+KeyCode
558
} else {
559
// Ctrl+Shift+Alt+ScanCode => KeyCode
560
_registerIfUnknown(1, 1, 1, scanCode, 0, 0, 0, keyCode); // Ctrl+Alt+ScanCode => KeyCode
561
}
562
}
563
// Handle all `withAltGr` entries
564
for (let i = mappings.length - 1; i >= 0; i--) {
565
const mapping = mappings[i];
566
const scanCode = mapping.scanCode;
567
const withAltGr = mapping.withAltGr;
568
if (withAltGr === mapping.withShift || withAltGr === mapping.value) {
569
// handled below
570
continue;
571
}
572
const kb = MacLinuxKeyboardMapper._charCodeToKb(withAltGr);
573
if (!kb) {
574
continue;
575
}
576
const kbShiftKey = kb.shiftKey;
577
const keyCode = kb.keyCode;
578
579
if (kbShiftKey) {
580
// Ctrl+Alt+ScanCode => Shift+KeyCode
581
_registerIfUnknown(1, 0, 1, scanCode, 0, 1, 0, keyCode); // Ctrl+Alt+ScanCode => Shift+KeyCode
582
} else {
583
// Ctrl+Alt+ScanCode => KeyCode
584
_registerIfUnknown(1, 0, 1, scanCode, 0, 0, 0, keyCode); // Ctrl+Alt+ScanCode => KeyCode
585
}
586
}
587
// Handle all `withShift` entries
588
for (let i = mappings.length - 1; i >= 0; i--) {
589
const mapping = mappings[i];
590
const scanCode = mapping.scanCode;
591
const withShift = mapping.withShift;
592
if (withShift === mapping.value) {
593
// handled below
594
continue;
595
}
596
const kb = MacLinuxKeyboardMapper._charCodeToKb(withShift);
597
if (!kb) {
598
continue;
599
}
600
const kbShiftKey = kb.shiftKey;
601
const keyCode = kb.keyCode;
602
603
if (kbShiftKey) {
604
// Shift+ScanCode => Shift+KeyCode
605
_registerIfUnknown(0, 1, 0, scanCode, 0, 1, 0, keyCode); // Shift+ScanCode => Shift+KeyCode
606
_registerIfUnknown(0, 1, 1, scanCode, 0, 1, 1, keyCode); // Shift+Alt+ScanCode => Shift+Alt+KeyCode
607
_registerIfUnknown(1, 1, 0, scanCode, 1, 1, 0, keyCode); // Ctrl+Shift+ScanCode => Ctrl+Shift+KeyCode
608
_registerIfUnknown(1, 1, 1, scanCode, 1, 1, 1, keyCode); // Ctrl+Shift+Alt+ScanCode => Ctrl+Shift+Alt+KeyCode
609
} else {
610
// Shift+ScanCode => KeyCode
611
_registerIfUnknown(0, 1, 0, scanCode, 0, 0, 0, keyCode); // Shift+ScanCode => KeyCode
612
_registerIfUnknown(0, 1, 0, scanCode, 0, 1, 0, keyCode); // Shift+ScanCode => Shift+KeyCode
613
_registerIfUnknown(0, 1, 1, scanCode, 0, 0, 1, keyCode); // Shift+Alt+ScanCode => Alt+KeyCode
614
_registerIfUnknown(0, 1, 1, scanCode, 0, 1, 1, keyCode); // Shift+Alt+ScanCode => Shift+Alt+KeyCode
615
_registerIfUnknown(1, 1, 0, scanCode, 1, 0, 0, keyCode); // Ctrl+Shift+ScanCode => Ctrl+KeyCode
616
_registerIfUnknown(1, 1, 0, scanCode, 1, 1, 0, keyCode); // Ctrl+Shift+ScanCode => Ctrl+Shift+KeyCode
617
_registerIfUnknown(1, 1, 1, scanCode, 1, 0, 1, keyCode); // Ctrl+Shift+Alt+ScanCode => Ctrl+Alt+KeyCode
618
_registerIfUnknown(1, 1, 1, scanCode, 1, 1, 1, keyCode); // Ctrl+Shift+Alt+ScanCode => Ctrl+Shift+Alt+KeyCode
619
}
620
}
621
// Handle all `value` entries
622
for (let i = mappings.length - 1; i >= 0; i--) {
623
const mapping = mappings[i];
624
const scanCode = mapping.scanCode;
625
const kb = MacLinuxKeyboardMapper._charCodeToKb(mapping.value);
626
if (!kb) {
627
continue;
628
}
629
const kbShiftKey = kb.shiftKey;
630
const keyCode = kb.keyCode;
631
632
if (kbShiftKey) {
633
// ScanCode => Shift+KeyCode
634
_registerIfUnknown(0, 0, 0, scanCode, 0, 1, 0, keyCode); // ScanCode => Shift+KeyCode
635
_registerIfUnknown(0, 0, 1, scanCode, 0, 1, 1, keyCode); // Alt+ScanCode => Shift+Alt+KeyCode
636
_registerIfUnknown(1, 0, 0, scanCode, 1, 1, 0, keyCode); // Ctrl+ScanCode => Ctrl+Shift+KeyCode
637
_registerIfUnknown(1, 0, 1, scanCode, 1, 1, 1, keyCode); // Ctrl+Alt+ScanCode => Ctrl+Shift+Alt+KeyCode
638
} else {
639
// ScanCode => KeyCode
640
_registerIfUnknown(0, 0, 0, scanCode, 0, 0, 0, keyCode); // ScanCode => KeyCode
641
_registerIfUnknown(0, 0, 1, scanCode, 0, 0, 1, keyCode); // Alt+ScanCode => Alt+KeyCode
642
_registerIfUnknown(0, 1, 0, scanCode, 0, 1, 0, keyCode); // Shift+ScanCode => Shift+KeyCode
643
_registerIfUnknown(0, 1, 1, scanCode, 0, 1, 1, keyCode); // Shift+Alt+ScanCode => Shift+Alt+KeyCode
644
_registerIfUnknown(1, 0, 0, scanCode, 1, 0, 0, keyCode); // Ctrl+ScanCode => Ctrl+KeyCode
645
_registerIfUnknown(1, 0, 1, scanCode, 1, 0, 1, keyCode); // Ctrl+Alt+ScanCode => Ctrl+Alt+KeyCode
646
_registerIfUnknown(1, 1, 0, scanCode, 1, 1, 0, keyCode); // Ctrl+Shift+ScanCode => Ctrl+Shift+KeyCode
647
_registerIfUnknown(1, 1, 1, scanCode, 1, 1, 1, keyCode); // Ctrl+Shift+Alt+ScanCode => Ctrl+Shift+Alt+KeyCode
648
}
649
}
650
// Handle all left-over available digits
651
_registerAllCombos(0, 0, 0, ScanCode.Digit1, KeyCode.Digit1);
652
_registerAllCombos(0, 0, 0, ScanCode.Digit2, KeyCode.Digit2);
653
_registerAllCombos(0, 0, 0, ScanCode.Digit3, KeyCode.Digit3);
654
_registerAllCombos(0, 0, 0, ScanCode.Digit4, KeyCode.Digit4);
655
_registerAllCombos(0, 0, 0, ScanCode.Digit5, KeyCode.Digit5);
656
_registerAllCombos(0, 0, 0, ScanCode.Digit6, KeyCode.Digit6);
657
_registerAllCombos(0, 0, 0, ScanCode.Digit7, KeyCode.Digit7);
658
_registerAllCombos(0, 0, 0, ScanCode.Digit8, KeyCode.Digit8);
659
_registerAllCombos(0, 0, 0, ScanCode.Digit9, KeyCode.Digit9);
660
_registerAllCombos(0, 0, 0, ScanCode.Digit0, KeyCode.Digit0);
661
662
this._scanCodeKeyCodeMapper.registrationComplete();
663
}
664
665
public dumpDebugInfo(): string {
666
const result: string[] = [];
667
668
const immutableSamples = [
669
ScanCode.ArrowUp,
670
ScanCode.Numpad0
671
];
672
673
let cnt = 0;
674
result.push(`isUSStandard: ${this._isUSStandard}`);
675
result.push(`----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------`);
676
for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) {
677
if (IMMUTABLE_CODE_TO_KEY_CODE[scanCode] !== KeyCode.DependsOnKbLayout) {
678
if (immutableSamples.indexOf(scanCode) === -1) {
679
continue;
680
}
681
}
682
683
if (cnt % 4 === 0) {
684
result.push(`| HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG |`);
685
result.push(`----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------`);
686
}
687
cnt++;
688
689
const mapping = this._codeInfo[scanCode];
690
691
for (let mod = 0; mod < 8; mod++) {
692
const hwCtrlKey = (mod & 0b001) ? true : false;
693
const hwShiftKey = (mod & 0b010) ? true : false;
694
const hwAltKey = (mod & 0b100) ? true : false;
695
const scanCodeCombo = new ScanCodeCombo(hwCtrlKey, hwShiftKey, hwAltKey, scanCode);
696
const resolvedKb = this.resolveKeyboardEvent({
697
_standardKeyboardEventBrand: true,
698
ctrlKey: scanCodeCombo.ctrlKey,
699
shiftKey: scanCodeCombo.shiftKey,
700
altKey: scanCodeCombo.altKey,
701
metaKey: false,
702
altGraphKey: false,
703
keyCode: KeyCode.DependsOnKbLayout,
704
code: ScanCodeUtils.toString(scanCode)
705
});
706
707
const outScanCodeCombo = scanCodeCombo.toString();
708
const outKey = scanCodeCombo.getProducedChar(mapping);
709
const ariaLabel = resolvedKb.getAriaLabel();
710
const outUILabel = (ariaLabel ? ariaLabel.replace(/Control\+/, 'Ctrl+') : null);
711
const outUserSettings = resolvedKb.getUserSettingsLabel();
712
const outElectronAccelerator = resolvedKb.getElectronAccelerator();
713
const outDispatchStr = resolvedKb.getDispatchChords()[0];
714
715
const isWYSIWYG = (resolvedKb ? resolvedKb.isWYSIWYG() : false);
716
const outWYSIWYG = (isWYSIWYG ? ' ' : ' NO ');
717
718
const kbCombos = this._scanCodeKeyCodeMapper.lookupScanCodeCombo(scanCodeCombo);
719
if (kbCombos.length === 0) {
720
result.push(`| ${this._leftPad(outScanCodeCombo, 30)} | ${outKey} | ${this._leftPad('', 25)} | ${this._leftPad('', 3)} | ${this._leftPad(outUILabel, 25)} | ${this._leftPad(outUserSettings, 30)} | ${this._leftPad(outElectronAccelerator, 25)} | ${this._leftPad(outDispatchStr, 30)} | ${outWYSIWYG} |`);
721
} else {
722
for (let i = 0, len = kbCombos.length; i < len; i++) {
723
const kbCombo = kbCombos[i];
724
// find out the priority of this scan code for this key code
725
let colPriority: string;
726
727
const scanCodeCombos = this._scanCodeKeyCodeMapper.lookupKeyCodeCombo(kbCombo);
728
if (scanCodeCombos.length === 1) {
729
// no need for priority, this key code combo maps to precisely this scan code combo
730
colPriority = '';
731
} else {
732
let priority = -1;
733
for (let j = 0; j < scanCodeCombos.length; j++) {
734
if (scanCodeCombos[j].equals(scanCodeCombo)) {
735
priority = j + 1;
736
break;
737
}
738
}
739
colPriority = String(priority);
740
}
741
742
const outKeybinding = kbCombo.toString();
743
if (i === 0) {
744
result.push(`| ${this._leftPad(outScanCodeCombo, 30)} | ${outKey} | ${this._leftPad(outKeybinding, 25)} | ${this._leftPad(colPriority, 3)} | ${this._leftPad(outUILabel, 25)} | ${this._leftPad(outUserSettings, 30)} | ${this._leftPad(outElectronAccelerator, 25)} | ${this._leftPad(outDispatchStr, 30)} | ${outWYSIWYG} |`);
745
} else {
746
// secondary keybindings
747
result.push(`| ${this._leftPad('', 30)} | | ${this._leftPad(outKeybinding, 25)} | ${this._leftPad(colPriority, 3)} | ${this._leftPad('', 25)} | ${this._leftPad('', 30)} | ${this._leftPad('', 25)} | ${this._leftPad('', 30)} | |`);
748
}
749
}
750
}
751
752
}
753
result.push(`----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------`);
754
}
755
756
return result.join('\n');
757
}
758
759
private _leftPad(str: string | null, cnt: number): string {
760
if (str === null) {
761
str = 'null';
762
}
763
while (str.length < cnt) {
764
str = ' ' + str;
765
}
766
return str;
767
}
768
769
public keyCodeChordToScanCodeChord(chord: KeyCodeChord): ScanCodeChord[] {
770
// Avoid double Enter bindings (both ScanCode.NumpadEnter and ScanCode.Enter point to KeyCode.Enter)
771
if (chord.keyCode === KeyCode.Enter) {
772
return [new ScanCodeChord(chord.ctrlKey, chord.shiftKey, chord.altKey, chord.metaKey, ScanCode.Enter)];
773
}
774
775
const scanCodeCombos = this._scanCodeKeyCodeMapper.lookupKeyCodeCombo(
776
new KeyCodeCombo(chord.ctrlKey, chord.shiftKey, chord.altKey, chord.keyCode)
777
);
778
779
const result: ScanCodeChord[] = [];
780
for (let i = 0, len = scanCodeCombos.length; i < len; i++) {
781
const scanCodeCombo = scanCodeCombos[i];
782
result[i] = new ScanCodeChord(scanCodeCombo.ctrlKey, scanCodeCombo.shiftKey, scanCodeCombo.altKey, chord.metaKey, scanCodeCombo.scanCode);
783
}
784
return result;
785
}
786
787
public getUILabelForScanCodeChord(chord: ScanCodeChord | null): string | null {
788
if (!chord) {
789
return null;
790
}
791
if (chord.isDuplicateModifierCase()) {
792
return '';
793
}
794
if (this._OS === OperatingSystem.Macintosh) {
795
switch (chord.scanCode) {
796
case ScanCode.ArrowLeft:
797
return '←';
798
case ScanCode.ArrowUp:
799
return '↑';
800
case ScanCode.ArrowRight:
801
return '→';
802
case ScanCode.ArrowDown:
803
return '↓';
804
}
805
}
806
return this._scanCodeToLabel[chord.scanCode];
807
}
808
809
public getAriaLabelForScanCodeChord(chord: ScanCodeChord | null): string | null {
810
if (!chord) {
811
return null;
812
}
813
if (chord.isDuplicateModifierCase()) {
814
return '';
815
}
816
return this._scanCodeToLabel[chord.scanCode];
817
}
818
819
public getDispatchStrForScanCodeChord(chord: ScanCodeChord): string | null {
820
const codeDispatch = this._scanCodeToDispatch[chord.scanCode];
821
if (!codeDispatch) {
822
return null;
823
}
824
let result = '';
825
826
if (chord.ctrlKey) {
827
result += 'ctrl+';
828
}
829
if (chord.shiftKey) {
830
result += 'shift+';
831
}
832
if (chord.altKey) {
833
result += 'alt+';
834
}
835
if (chord.metaKey) {
836
result += 'meta+';
837
}
838
result += codeDispatch;
839
840
return result;
841
}
842
843
public getUserSettingsLabelForScanCodeChord(chord: ScanCodeChord | null): string | null {
844
if (!chord) {
845
return null;
846
}
847
if (chord.isDuplicateModifierCase()) {
848
return '';
849
}
850
851
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[chord.scanCode];
852
if (immutableKeyCode !== KeyCode.DependsOnKbLayout) {
853
return KeyCodeUtils.toUserSettingsUS(immutableKeyCode).toLowerCase();
854
}
855
856
// Check if this scanCode always maps to the same keyCode and back
857
const constantKeyCode: KeyCode = this._scanCodeKeyCodeMapper.guessStableKeyCode(chord.scanCode);
858
if (constantKeyCode !== KeyCode.DependsOnKbLayout) {
859
// Verify that this is a good key code that can be mapped back to the same scan code
860
const reverseChords = this.keyCodeChordToScanCodeChord(new KeyCodeChord(chord.ctrlKey, chord.shiftKey, chord.altKey, chord.metaKey, constantKeyCode));
861
for (let i = 0, len = reverseChords.length; i < len; i++) {
862
const reverseChord = reverseChords[i];
863
if (reverseChord.scanCode === chord.scanCode) {
864
return KeyCodeUtils.toUserSettingsUS(constantKeyCode).toLowerCase();
865
}
866
}
867
}
868
869
return this._scanCodeToDispatch[chord.scanCode];
870
}
871
872
public getElectronAcceleratorLabelForScanCodeChord(chord: ScanCodeChord | null): string | null {
873
if (!chord) {
874
return null;
875
}
876
877
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[chord.scanCode];
878
if (immutableKeyCode !== KeyCode.DependsOnKbLayout) {
879
return KeyCodeUtils.toElectronAccelerator(immutableKeyCode);
880
}
881
882
// Check if this scanCode always maps to the same keyCode and back
883
const constantKeyCode: KeyCode = this._scanCodeKeyCodeMapper.guessStableKeyCode(chord.scanCode);
884
885
if (this._OS === OperatingSystem.Linux && !this._isUSStandard) {
886
// [Electron Accelerators] On Linux, Electron does not handle correctly OEM keys.
887
// when using a different keyboard layout than US Standard.
888
// See https://github.com/microsoft/vscode/issues/23706
889
// See https://github.com/microsoft/vscode/pull/134890#issuecomment-941671791
890
const isOEMKey = (
891
constantKeyCode === KeyCode.Semicolon
892
|| constantKeyCode === KeyCode.Equal
893
|| constantKeyCode === KeyCode.Comma
894
|| constantKeyCode === KeyCode.Minus
895
|| constantKeyCode === KeyCode.Period
896
|| constantKeyCode === KeyCode.Slash
897
|| constantKeyCode === KeyCode.Backquote
898
|| constantKeyCode === KeyCode.BracketLeft
899
|| constantKeyCode === KeyCode.Backslash
900
|| constantKeyCode === KeyCode.BracketRight
901
);
902
903
if (isOEMKey) {
904
return null;
905
}
906
}
907
908
if (constantKeyCode !== KeyCode.DependsOnKbLayout) {
909
return KeyCodeUtils.toElectronAccelerator(constantKeyCode);
910
}
911
912
return null;
913
}
914
915
private _toResolvedKeybinding(chordParts: ScanCodeChord[][]): NativeResolvedKeybinding[] {
916
if (chordParts.length === 0) {
917
return [];
918
}
919
const result: NativeResolvedKeybinding[] = [];
920
this._generateResolvedKeybindings(chordParts, 0, [], result);
921
return result;
922
}
923
924
private _generateResolvedKeybindings(chordParts: ScanCodeChord[][], currentIndex: number, previousParts: ScanCodeChord[], result: NativeResolvedKeybinding[]) {
925
const chordPart = chordParts[currentIndex];
926
const isFinalIndex = currentIndex === chordParts.length - 1;
927
for (let i = 0, len = chordPart.length; i < len; i++) {
928
const chords = [...previousParts, chordPart[i]];
929
if (isFinalIndex) {
930
result.push(new NativeResolvedKeybinding(this, this._OS, chords));
931
} else {
932
this._generateResolvedKeybindings(chordParts, currentIndex + 1, chords, result);
933
}
934
}
935
}
936
937
public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): NativeResolvedKeybinding {
938
let code = ScanCodeUtils.toEnum(keyboardEvent.code);
939
940
// Treat NumpadEnter as Enter
941
if (code === ScanCode.NumpadEnter) {
942
code = ScanCode.Enter;
943
}
944
945
const keyCode = keyboardEvent.keyCode;
946
947
if (
948
(keyCode === KeyCode.LeftArrow)
949
|| (keyCode === KeyCode.UpArrow)
950
|| (keyCode === KeyCode.RightArrow)
951
|| (keyCode === KeyCode.DownArrow)
952
|| (keyCode === KeyCode.Delete)
953
|| (keyCode === KeyCode.Insert)
954
|| (keyCode === KeyCode.Home)
955
|| (keyCode === KeyCode.End)
956
|| (keyCode === KeyCode.PageDown)
957
|| (keyCode === KeyCode.PageUp)
958
|| (keyCode === KeyCode.Backspace)
959
) {
960
// "Dispatch" on keyCode for these key codes to workaround issues with remote desktoping software
961
// where the scan codes appear to be incorrect (see https://github.com/microsoft/vscode/issues/24107)
962
const immutableScanCode = IMMUTABLE_KEY_CODE_TO_CODE[keyCode];
963
if (immutableScanCode !== ScanCode.DependsOnKbLayout) {
964
code = immutableScanCode;
965
}
966
967
} else {
968
969
if (
970
(code === ScanCode.Numpad1)
971
|| (code === ScanCode.Numpad2)
972
|| (code === ScanCode.Numpad3)
973
|| (code === ScanCode.Numpad4)
974
|| (code === ScanCode.Numpad5)
975
|| (code === ScanCode.Numpad6)
976
|| (code === ScanCode.Numpad7)
977
|| (code === ScanCode.Numpad8)
978
|| (code === ScanCode.Numpad9)
979
|| (code === ScanCode.Numpad0)
980
|| (code === ScanCode.NumpadDecimal)
981
) {
982
// "Dispatch" on keyCode for all numpad keys in order for NumLock to work correctly
983
if (keyCode >= 0) {
984
const immutableScanCode = IMMUTABLE_KEY_CODE_TO_CODE[keyCode];
985
if (immutableScanCode !== ScanCode.DependsOnKbLayout) {
986
code = immutableScanCode;
987
}
988
}
989
}
990
}
991
992
const ctrlKey = keyboardEvent.ctrlKey || (this._mapAltGrToCtrlAlt && keyboardEvent.altGraphKey);
993
const altKey = keyboardEvent.altKey || (this._mapAltGrToCtrlAlt && keyboardEvent.altGraphKey);
994
const chord = new ScanCodeChord(ctrlKey, keyboardEvent.shiftKey, altKey, keyboardEvent.metaKey, code);
995
return new NativeResolvedKeybinding(this, this._OS, [chord]);
996
}
997
998
private _resolveChord(chord: Chord | null): ScanCodeChord[] {
999
if (!chord) {
1000
return [];
1001
}
1002
if (chord instanceof ScanCodeChord) {
1003
return [chord];
1004
}
1005
return this.keyCodeChordToScanCodeChord(chord);
1006
}
1007
1008
public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[] {
1009
const chords: ScanCodeChord[][] = keybinding.chords.map(chord => this._resolveChord(chord));
1010
return this._toResolvedKeybinding(chords);
1011
}
1012
1013
private static _redirectCharCode(charCode: number): number {
1014
switch (charCode) {
1015
// allow-any-unicode-next-line
1016
// CJK: 。 「 」 【 】 ; ,
1017
// map: . [ ] [ ] ; ,
1018
case CharCode.U_IDEOGRAPHIC_FULL_STOP: return CharCode.Period;
1019
case CharCode.U_LEFT_CORNER_BRACKET: return CharCode.OpenSquareBracket;
1020
case CharCode.U_RIGHT_CORNER_BRACKET: return CharCode.CloseSquareBracket;
1021
case CharCode.U_LEFT_BLACK_LENTICULAR_BRACKET: return CharCode.OpenSquareBracket;
1022
case CharCode.U_RIGHT_BLACK_LENTICULAR_BRACKET: return CharCode.CloseSquareBracket;
1023
case CharCode.U_FULLWIDTH_SEMICOLON: return CharCode.Semicolon;
1024
case CharCode.U_FULLWIDTH_COMMA: return CharCode.Comma;
1025
}
1026
return charCode;
1027
}
1028
1029
private static _charCodeToKb(charCode: number): { keyCode: KeyCode; shiftKey: boolean } | null {
1030
charCode = this._redirectCharCode(charCode);
1031
if (charCode < CHAR_CODE_TO_KEY_CODE.length) {
1032
return CHAR_CODE_TO_KEY_CODE[charCode];
1033
}
1034
return null;
1035
}
1036
1037
/**
1038
* Attempt to map a combining character to a regular one that renders the same way.
1039
*
1040
* https://www.compart.com/en/unicode/bidiclass/NSM
1041
*/
1042
public static getCharCode(char: string): number {
1043
if (char.length === 0) {
1044
return 0;
1045
}
1046
const charCode = char.charCodeAt(0);
1047
switch (charCode) {
1048
case CharCode.U_Combining_Grave_Accent: return CharCode.U_GRAVE_ACCENT;
1049
case CharCode.U_Combining_Acute_Accent: return CharCode.U_ACUTE_ACCENT;
1050
case CharCode.U_Combining_Circumflex_Accent: return CharCode.U_CIRCUMFLEX;
1051
case CharCode.U_Combining_Tilde: return CharCode.U_SMALL_TILDE;
1052
case CharCode.U_Combining_Macron: return CharCode.U_MACRON;
1053
case CharCode.U_Combining_Overline: return CharCode.U_OVERLINE;
1054
case CharCode.U_Combining_Breve: return CharCode.U_BREVE;
1055
case CharCode.U_Combining_Dot_Above: return CharCode.U_DOT_ABOVE;
1056
case CharCode.U_Combining_Diaeresis: return CharCode.U_DIAERESIS;
1057
case CharCode.U_Combining_Ring_Above: return CharCode.U_RING_ABOVE;
1058
case CharCode.U_Combining_Double_Acute_Accent: return CharCode.U_DOUBLE_ACUTE_ACCENT;
1059
}
1060
return charCode;
1061
}
1062
}
1063
1064
(function () {
1065
function define(charCode: number, keyCode: KeyCode, shiftKey: boolean): void {
1066
for (let i = CHAR_CODE_TO_KEY_CODE.length; i < charCode; i++) {
1067
CHAR_CODE_TO_KEY_CODE[i] = null;
1068
}
1069
CHAR_CODE_TO_KEY_CODE[charCode] = { keyCode: keyCode, shiftKey: shiftKey };
1070
}
1071
1072
for (let chCode = CharCode.A; chCode <= CharCode.Z; chCode++) {
1073
define(chCode, KeyCode.KeyA + (chCode - CharCode.A), true);
1074
}
1075
1076
for (let chCode = CharCode.a; chCode <= CharCode.z; chCode++) {
1077
define(chCode, KeyCode.KeyA + (chCode - CharCode.a), false);
1078
}
1079
1080
define(CharCode.Semicolon, KeyCode.Semicolon, false);
1081
define(CharCode.Colon, KeyCode.Semicolon, true);
1082
1083
define(CharCode.Equals, KeyCode.Equal, false);
1084
define(CharCode.Plus, KeyCode.Equal, true);
1085
1086
define(CharCode.Comma, KeyCode.Comma, false);
1087
define(CharCode.LessThan, KeyCode.Comma, true);
1088
1089
define(CharCode.Dash, KeyCode.Minus, false);
1090
define(CharCode.Underline, KeyCode.Minus, true);
1091
1092
define(CharCode.Period, KeyCode.Period, false);
1093
define(CharCode.GreaterThan, KeyCode.Period, true);
1094
1095
define(CharCode.Slash, KeyCode.Slash, false);
1096
define(CharCode.QuestionMark, KeyCode.Slash, true);
1097
1098
define(CharCode.BackTick, KeyCode.Backquote, false);
1099
define(CharCode.Tilde, KeyCode.Backquote, true);
1100
1101
define(CharCode.OpenSquareBracket, KeyCode.BracketLeft, false);
1102
define(CharCode.OpenCurlyBrace, KeyCode.BracketLeft, true);
1103
1104
define(CharCode.Backslash, KeyCode.Backslash, false);
1105
define(CharCode.Pipe, KeyCode.Backslash, true);
1106
1107
define(CharCode.CloseSquareBracket, KeyCode.BracketRight, false);
1108
define(CharCode.CloseCurlyBrace, KeyCode.BracketRight, true);
1109
1110
define(CharCode.SingleQuote, KeyCode.Quote, false);
1111
define(CharCode.DoubleQuote, KeyCode.Quote, true);
1112
})();
1113
1114