mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-01 05:57:21 +01:00
750 lines
25 KiB
TypeScript
750 lines
25 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
'use strict';
|
|
|
|
import { OperatingSystem } from 'vs/base/common/platform';
|
|
import { KeyCode, ResolvedKeybinding, KeyCodeUtils, SimpleKeybinding, Keybinding, KeybindingType, USER_SETTINGS } from 'vs/base/common/keyCodes';
|
|
import { KeyboardEventCode, KeyboardEventCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/workbench/services/keybinding/common/keyboardEventCode';
|
|
import { CharCode } from 'vs/base/common/charCode';
|
|
import { IHTMLContentElement } from 'vs/base/common/htmlContent';
|
|
import { UILabelProvider, AriaLabelProvider, UserSettingsLabelProvider, ElectronAcceleratorLabelProvider } from 'vs/platform/keybinding/common/keybindingLabels';
|
|
import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper';
|
|
|
|
export interface IKeyMapping {
|
|
value: string;
|
|
withShift: string;
|
|
withAltGr: string;
|
|
withShiftAltGr: string;
|
|
|
|
valueIsDeadKey?: boolean;
|
|
withShiftIsDeadKey?: boolean;
|
|
withAltGrIsDeadKey?: boolean;
|
|
withShiftAltGrIsDeadKey?: boolean;
|
|
}
|
|
|
|
export interface IKeyboardMapping {
|
|
[code: string]: IKeyMapping;
|
|
}
|
|
|
|
const LOG = false;
|
|
function log(str: string): void {
|
|
if (LOG) {
|
|
console.info(str);
|
|
}
|
|
}
|
|
|
|
const CHAR_CODE_TO_KEY_CODE: { keyCode: KeyCode; shiftKey: boolean }[] = [];
|
|
|
|
export class HardwareKeypress {
|
|
public readonly ctrlKey: boolean;
|
|
public readonly shiftKey: boolean;
|
|
public readonly altKey: boolean;
|
|
public readonly metaKey: boolean;
|
|
public readonly code: KeyboardEventCode;
|
|
|
|
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, code: KeyboardEventCode) {
|
|
this.ctrlKey = ctrlKey;
|
|
this.shiftKey = shiftKey;
|
|
this.altKey = altKey;
|
|
this.metaKey = metaKey;
|
|
this.code = code;
|
|
}
|
|
}
|
|
|
|
export class NativeResolvedKeybinding extends ResolvedKeybinding {
|
|
|
|
private readonly _mapper: MacLinuxKeyboardMapper;
|
|
private readonly _OS: OperatingSystem;
|
|
private readonly _firstPart: HardwareKeypress;
|
|
private readonly _chordPart: HardwareKeypress;
|
|
|
|
constructor(mapper: MacLinuxKeyboardMapper, OS: OperatingSystem, firstPart: HardwareKeypress, chordPart: HardwareKeypress) {
|
|
super();
|
|
this._mapper = mapper;
|
|
this._OS = OS;
|
|
this._firstPart = firstPart;
|
|
this._chordPart = chordPart;
|
|
}
|
|
|
|
public getLabel(): string {
|
|
let firstPart = this._firstPart ? this._mapper.getUILabelForHardwareCode(this._firstPart.code) : null;
|
|
let chordPart = this._chordPart ? this._mapper.getUILabelForHardwareCode(this._chordPart.code) : null;
|
|
return UILabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS);
|
|
}
|
|
|
|
public getAriaLabel(): string {
|
|
let firstPart = this._firstPart ? this._mapper.getAriaLabelForHardwareCode(this._firstPart.code) : null;
|
|
let chordPart = this._chordPart ? this._mapper.getAriaLabelForHardwareCode(this._chordPart.code) : null;
|
|
return AriaLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS);
|
|
}
|
|
|
|
public getHTMLLabel(): IHTMLContentElement[] {
|
|
let firstPart = this._firstPart ? this._mapper.getUILabelForHardwareCode(this._firstPart.code) : null;
|
|
let chordPart = this._chordPart ? this._mapper.getUILabelForHardwareCode(this._chordPart.code) : null;
|
|
return UILabelProvider.toHTMLLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS);
|
|
}
|
|
|
|
public getElectronAccelerator(): string {
|
|
if (this._chordPart !== null) {
|
|
// Electron cannot handle chords
|
|
return null;
|
|
}
|
|
|
|
let firstPart = this._firstPart ? this._mapper.getElectronLabelForHardwareCode(this._firstPart.code) : null;
|
|
return ElectronAcceleratorLabelProvider.toLabel(this._firstPart, firstPart, null, null, this._OS);
|
|
}
|
|
|
|
public getUserSettingsLabel(): string {
|
|
let firstPart = this._firstPart ? this._mapper.getUserSettingsLabel(this._firstPart.code) : null;
|
|
let chordPart = this._chordPart ? this._mapper.getUserSettingsLabel(this._chordPart.code) : null;
|
|
return UserSettingsLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS);
|
|
}
|
|
|
|
public isChord(): boolean {
|
|
return (this._chordPart ? true : false);
|
|
}
|
|
|
|
public hasCtrlModifier(): boolean {
|
|
if (this._chordPart) {
|
|
return false;
|
|
}
|
|
return this._firstPart.ctrlKey;
|
|
}
|
|
|
|
public hasShiftModifier(): boolean {
|
|
if (this._chordPart) {
|
|
return false;
|
|
}
|
|
return this._firstPart.shiftKey;
|
|
}
|
|
|
|
public hasAltModifier(): boolean {
|
|
if (this._chordPart) {
|
|
return false;
|
|
}
|
|
return this._firstPart.altKey;
|
|
}
|
|
|
|
public hasMetaModifier(): boolean {
|
|
if (this._chordPart) {
|
|
return false;
|
|
}
|
|
return this._firstPart.metaKey;
|
|
}
|
|
|
|
public getDispatchParts(): [string, string] {
|
|
let firstPart = this._firstPart ? this._mapper.getDispatchStrForHardwareKeypress(this._firstPart) : null;
|
|
let chordPart = this._chordPart ? this._mapper.getDispatchStrForHardwareKeypress(this._chordPart) : null;
|
|
return [firstPart, chordPart];
|
|
}
|
|
}
|
|
|
|
interface IHardwareCodeMapping {
|
|
code: KeyboardEventCode;
|
|
value: number;
|
|
withShift: number;
|
|
withAltGr: number;
|
|
withShiftAltGr: number;
|
|
}
|
|
|
|
export class MacLinuxKeyboardMapper implements IKeyboardMapper {
|
|
|
|
private readonly _OS: OperatingSystem;
|
|
private readonly _codeInfo: IHardwareCodeMapping[];
|
|
private readonly _hwToKb: number[] = [];
|
|
private readonly _hwToLabel: string[] = [];
|
|
private readonly _hwToDispatch: string[] = [];
|
|
private readonly _kbToHw: number[][] = [];
|
|
|
|
constructor(rawMappings: IKeyboardMapping, OS: OperatingSystem) {
|
|
|
|
this._OS = OS;
|
|
|
|
this._hwToKb = [];
|
|
this._kbToHw = [];
|
|
this._hwToLabel = [];
|
|
this._hwToDispatch = [];
|
|
|
|
for (let code = KeyboardEventCode.None; code < KeyboardEventCode.MAX_VALUE; code++) {
|
|
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[code];
|
|
if (immutableKeyCode !== -1) {
|
|
this._registerAllCombos1(false, false, false, code, immutableKeyCode);
|
|
this._hwToLabel[code] = KeyCodeUtils.toString(immutableKeyCode);
|
|
|
|
if (immutableKeyCode === KeyCode.Unknown || immutableKeyCode === KeyCode.Ctrl || immutableKeyCode === KeyCode.Meta || immutableKeyCode === KeyCode.Alt || immutableKeyCode === KeyCode.Shift) {
|
|
this._hwToDispatch[code] = null; // cannot dispatch on this hw code
|
|
} else {
|
|
this._hwToDispatch[code] = `[${KeyboardEventCodeUtils.toString(code)}]`;
|
|
}
|
|
}
|
|
}
|
|
|
|
this._codeInfo = [];
|
|
let mappings: IHardwareCodeMapping[] = [], mappingsLen = 0;
|
|
for (let strCode in rawMappings) {
|
|
if (rawMappings.hasOwnProperty(strCode)) {
|
|
const code = KeyboardEventCodeUtils.toEnum(strCode);
|
|
if (code === KeyboardEventCode.None) {
|
|
log(`Unknown code ${strCode} in mapping.`);
|
|
continue;
|
|
}
|
|
if (IMMUTABLE_CODE_TO_KEY_CODE[code] !== -1) {
|
|
continue;
|
|
}
|
|
|
|
const rawMapping = rawMappings[strCode];
|
|
const value = MacLinuxKeyboardMapper._getCharCode(rawMapping.value);
|
|
const withShift = MacLinuxKeyboardMapper._getCharCode(rawMapping.withShift);
|
|
const withAltGr = MacLinuxKeyboardMapper._getCharCode(rawMapping.withAltGr);
|
|
const withShiftAltGr = MacLinuxKeyboardMapper._getCharCode(rawMapping.withShiftAltGr);
|
|
|
|
const mapping: IHardwareCodeMapping = {
|
|
code: code,
|
|
value: value,
|
|
withShift: withShift,
|
|
withAltGr: withAltGr,
|
|
withShiftAltGr: withShiftAltGr,
|
|
};
|
|
mappings[mappingsLen++] = mapping;
|
|
this._codeInfo[code] = mapping;
|
|
|
|
this._hwToDispatch[code] = `[${KeyboardEventCodeUtils.toString(code)}]`;
|
|
|
|
if (value >= CharCode.a && value <= CharCode.z) {
|
|
this._hwToLabel[code] = String.fromCharCode(CharCode.A + (value - CharCode.a));
|
|
} else if (value) {
|
|
this._hwToLabel[code] = String.fromCharCode(value);
|
|
} else {
|
|
this._hwToLabel[code] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle all `withShiftAltGr` entries
|
|
for (let i = mappings.length - 1; i >= 0; i--) {
|
|
const mapping = mappings[i];
|
|
const withShiftAltGr = mapping.withShiftAltGr;
|
|
if (withShiftAltGr === mapping.withAltGr || withShiftAltGr === mapping.withShift || withShiftAltGr === mapping.value) {
|
|
// handled below
|
|
continue;
|
|
}
|
|
this._registerCharCode(mapping.code, true, true, true, withShiftAltGr);
|
|
}
|
|
// Handle all `withAltGr` entries
|
|
for (let i = mappings.length - 1; i >= 0; i--) {
|
|
const mapping = mappings[i];
|
|
const withAltGr = mapping.withAltGr;
|
|
if (withAltGr === mapping.withShift || withAltGr === mapping.value) {
|
|
// handled below
|
|
continue;
|
|
}
|
|
this._registerCharCode(mapping.code, true, false, true, withAltGr);
|
|
}
|
|
// Handle all `withShift` entries
|
|
for (let i = mappings.length - 1; i >= 0; i--) {
|
|
const mapping = mappings[i];
|
|
const withShift = mapping.withShift;
|
|
if (withShift === mapping.value) {
|
|
// handled below
|
|
continue;
|
|
}
|
|
this._registerCharCode(mapping.code, false, true, false, withShift);
|
|
}
|
|
// Handle all `value` entries
|
|
for (let i = mappings.length - 1; i >= 0; i--) {
|
|
const mapping = mappings[i];
|
|
this._registerCharCode(mapping.code, false, false, false, mapping.value);
|
|
}
|
|
// // Handle all left-over available digits
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit1, KeyCode.KEY_1);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit2, KeyCode.KEY_2);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit3, KeyCode.KEY_3);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit4, KeyCode.KEY_4);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit5, KeyCode.KEY_5);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit6, KeyCode.KEY_6);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit7, KeyCode.KEY_7);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit8, KeyCode.KEY_8);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit9, KeyCode.KEY_9);
|
|
this._registerAllCombos1(false, false, false, KeyboardEventCode.Digit0, KeyCode.KEY_0);
|
|
|
|
for (let i = 0; i < KeyboardEventCode.MAX_VALUE; i++) {
|
|
let base = (i << 3);
|
|
for (let j = 0; j < 8; j++) {
|
|
let actual = base + j;
|
|
let entry = this._hwToKb[actual];
|
|
if (typeof entry === 'undefined') {
|
|
log(`${KeyboardEventCodeUtils.toString(i)} - ${j.toString(2)} --- is missing`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public dumpDebugInfo(): string {
|
|
let result: string[] = [];
|
|
|
|
let cnt = 0;
|
|
result.push(`-----------------------------------------------------------------------------------------------------------------------------------`);
|
|
for (let code = KeyboardEventCode.None; code < KeyboardEventCode.MAX_VALUE; code++) {
|
|
if (IMMUTABLE_CODE_TO_KEY_CODE[code] !== -1) {
|
|
continue;
|
|
}
|
|
|
|
if (cnt % 4 === 0) {
|
|
result.push(`| HW Code combination | Key | KeyCode combination | UI label | Dispatching string |`);
|
|
result.push(`-----------------------------------------------------------------------------------------------------------------------------------`);
|
|
}
|
|
cnt++;
|
|
|
|
const mapping = this._codeInfo[code];
|
|
const strCode = KeyboardEventCodeUtils.toString(code);
|
|
const uiLabel = this._hwToLabel[code];
|
|
|
|
for (let mod = 0; mod < 8; mod++) {
|
|
const hwCtrlKey = (mod & 0b0001) ? true : false;
|
|
const hwShiftKey = (mod & 0b0010) ? true : false;
|
|
const hwAltKey = (mod & 0b0100) ? true : false;
|
|
const strHw = `${hwCtrlKey ? 'Ctrl+' : ''}${hwShiftKey ? 'Shift+' : ''}${hwAltKey ? 'Alt+' : ''}${strCode}`;
|
|
const uiHwLabel = `${hwCtrlKey ? 'Ctrl+' : ''}${hwShiftKey ? 'Shift+' : ''}${hwAltKey ? 'Alt+' : ''}${uiLabel}`;
|
|
|
|
let key = 0;
|
|
if (mapping) {
|
|
if (hwCtrlKey && hwShiftKey && hwAltKey) {
|
|
key = mapping.withShiftAltGr;
|
|
} else if (hwCtrlKey && hwAltKey) {
|
|
key = mapping.withAltGr;
|
|
} else if (hwShiftKey) {
|
|
key = mapping.withShift;
|
|
} else {
|
|
key = mapping.value;
|
|
}
|
|
}
|
|
let strKey: string = ' --- ';
|
|
if (key !== 0) {
|
|
if (key >= CharCode.U_Combining_Grave_Accent && key <= CharCode.U_Combining_Latin_Small_Letter_X) {
|
|
// combining
|
|
strKey = 'U+' + key.toString(16);
|
|
} else {
|
|
strKey = ' ' + String.fromCharCode(key) + ' ';
|
|
}
|
|
}
|
|
|
|
const hwEncoded = this._encode(hwCtrlKey, hwShiftKey, hwAltKey, code);
|
|
const kbEncoded = this._hwToKb[hwEncoded];
|
|
const kbCtrlKey = (kbEncoded & 0b0001) ? true : false;
|
|
const kbShiftKey = (kbEncoded & 0b0010) ? true : false;
|
|
const kbAltKey = (kbEncoded & 0b0100) ? true : false;
|
|
const keyCode = (kbEncoded >>> 3);
|
|
const strKb = `${kbCtrlKey ? 'Ctrl+' : ''}${kbShiftKey ? 'Shift+' : ''}${kbAltKey ? 'Alt+' : ''}${KeyCodeUtils.toString(keyCode)}`;
|
|
|
|
const hwKeyPress = new HardwareKeypress(hwCtrlKey, hwShiftKey, hwAltKey, false, code);
|
|
const dispatchStr = this.getDispatchStrForHardwareKeypress(hwKeyPress);
|
|
|
|
result.push(`| ${this._leftPad(strHw, 30)} | ${strKey} | ${this._leftPad(strKb, 25)} | ${this._leftPad(uiHwLabel, 25)} | ${this._leftPad(dispatchStr, 30)} |`);
|
|
|
|
}
|
|
result.push(`-----------------------------------------------------------------------------------------------------------------------------------`);
|
|
}
|
|
|
|
return result.join('\n');
|
|
}
|
|
|
|
private _leftPad(str: string, cnt: number): string {
|
|
if (str === null) {
|
|
str = 'null';
|
|
}
|
|
while (str.length < cnt) {
|
|
str = ' ' + str;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
private _registerIfUnknown(
|
|
hwCtrlKey: boolean, hwShiftKey: boolean, hwAltKey: boolean, code: KeyboardEventCode,
|
|
kbCtrlKey: boolean, kbShiftKey: boolean, kbAltKey: boolean, keyCode: KeyCode,
|
|
): void {
|
|
let hwEncoded = this._encode(hwCtrlKey, hwShiftKey, hwAltKey, code);
|
|
let kbEncoded = this._encode(kbCtrlKey, kbShiftKey, kbAltKey, keyCode);
|
|
|
|
let existing = this._hwToKb[hwEncoded] | 0;
|
|
let existingKeyCode = existing >>> 3;
|
|
|
|
if (existingKeyCode !== 0) {
|
|
return;
|
|
}
|
|
|
|
this._hwToKb[hwEncoded] = kbEncoded;
|
|
|
|
if (keyCode !== KeyCode.Unknown) {
|
|
// Do not save an inverse lookup for Unknown
|
|
this._kbToHw[kbEncoded] = this._kbToHw[kbEncoded] || [];
|
|
this._kbToHw[kbEncoded].unshift(hwEncoded);
|
|
}
|
|
}
|
|
|
|
private _registerAllCombos1(
|
|
_ctrlKey: boolean, _shiftKey: boolean, _altKey: boolean, code: KeyboardEventCode,
|
|
keyCode: KeyCode,
|
|
): void {
|
|
for (let _ctrl = (_ctrlKey ? 1 : 0); _ctrl <= 1; _ctrl++) {
|
|
const ctrlKey = (_ctrl ? true : false);
|
|
for (let _shift = (_shiftKey ? 1 : 0); _shift <= 1; _shift++) {
|
|
const shiftKey = (_shift ? true : false);
|
|
for (let _alt = (_altKey ? 1 : 0); _alt <= 1; _alt++) {
|
|
const altKey = (_alt ? true : false);
|
|
this._registerIfUnknown(
|
|
ctrlKey, shiftKey, altKey, code,
|
|
ctrlKey, shiftKey, altKey, keyCode
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private _registerAllCombos2(
|
|
hwCtrlKey: boolean, hwShiftKey: boolean, hwAltKey: boolean, code: KeyboardEventCode,
|
|
kbShiftKey: boolean, keyCode: KeyCode,
|
|
): void {
|
|
this._registerIfUnknown(
|
|
hwCtrlKey, hwShiftKey, hwAltKey, code,
|
|
false, kbShiftKey, false, keyCode
|
|
);
|
|
|
|
if (!kbShiftKey) {
|
|
for (let _ctrl = (hwCtrlKey ? 1 : 0); _ctrl <= 1; _ctrl++) {
|
|
const ctrlKey = (_ctrl ? true : false);
|
|
for (let _alt = (hwAltKey ? 1 : 0); _alt <= 1; _alt++) {
|
|
const altKey = (_alt ? true : false);
|
|
this._registerIfUnknown(
|
|
ctrlKey, hwShiftKey, altKey, code,
|
|
ctrlKey, kbShiftKey, altKey, keyCode
|
|
);
|
|
this._registerIfUnknown(
|
|
ctrlKey, true, altKey, code,
|
|
ctrlKey, true, altKey, keyCode
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
for (let _ctrl = (hwCtrlKey ? 1 : 0); _ctrl <= 1; _ctrl++) {
|
|
const ctrlKey = (_ctrl ? true : false);
|
|
for (let _alt = (hwAltKey ? 1 : 0); _alt <= 1; _alt++) {
|
|
const altKey = (_alt ? true : false);
|
|
this._registerIfUnknown(
|
|
ctrlKey, hwShiftKey, altKey, code,
|
|
ctrlKey, kbShiftKey, altKey, keyCode
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private _registerCharCode(code: KeyboardEventCode, ctrlKey: boolean, shiftKey: boolean, altKey: boolean, charCode: number): void {
|
|
|
|
let _kb = MacLinuxKeyboardMapper._charCodeToKb(charCode);
|
|
let kb = _kb ? {
|
|
ctrlKey: false,
|
|
shiftKey: _kb.shiftKey,
|
|
altKey: false,
|
|
keyCode: _kb.keyCode
|
|
} : null;
|
|
|
|
if (!_kb) {
|
|
this._registerAllCombos1(ctrlKey, shiftKey, altKey, code, KeyCode.Unknown);
|
|
return;
|
|
}
|
|
|
|
this._registerAllCombos2(
|
|
ctrlKey, shiftKey, altKey, code,
|
|
kb.shiftKey, kb.keyCode
|
|
);
|
|
}
|
|
|
|
public simpleKeybindingToHardwareKeypress(keybinding: SimpleKeybinding): HardwareKeypress[] {
|
|
const kbEncoded = this._encode(keybinding.ctrlKey, keybinding.shiftKey, keybinding.altKey, keybinding.keyCode);
|
|
const hwEncoded = this._kbToHw[kbEncoded];
|
|
|
|
let result: HardwareKeypress[] = [];
|
|
if (hwEncoded) {
|
|
for (let i = 0, len = hwEncoded.length; i < len; i++) {
|
|
result[i] = this._decodeHw(hwEncoded[i], keybinding.metaKey);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public getUILabelForHardwareCode(code: KeyboardEventCode): string {
|
|
if (this._OS === OperatingSystem.Macintosh) {
|
|
switch (code) {
|
|
case KeyboardEventCode.ArrowLeft:
|
|
return '←';
|
|
case KeyboardEventCode.ArrowUp:
|
|
return '↑';
|
|
case KeyboardEventCode.ArrowRight:
|
|
return '→';
|
|
case KeyboardEventCode.ArrowDown:
|
|
return '↓';
|
|
}
|
|
}
|
|
return this._hwToLabel[code];
|
|
}
|
|
|
|
public getAriaLabelForHardwareCode(code: KeyboardEventCode): string {
|
|
return this._hwToLabel[code];
|
|
}
|
|
|
|
public getDispatchStrForHardwareKeypress(keypress: HardwareKeypress): string {
|
|
const codeDispatch = this._hwToDispatch[keypress.code];
|
|
if (!codeDispatch) {
|
|
return null;
|
|
}
|
|
let result = '';
|
|
|
|
if (keypress.ctrlKey) {
|
|
result += 'ctrl+';
|
|
}
|
|
if (keypress.shiftKey) {
|
|
result += 'shift+';
|
|
}
|
|
if (keypress.altKey) {
|
|
result += 'alt+';
|
|
}
|
|
if (keypress.metaKey) {
|
|
result += 'meta+';
|
|
}
|
|
result += codeDispatch;
|
|
|
|
return result;
|
|
}
|
|
|
|
public getUserSettingsLabel(code: KeyboardEventCode): string {
|
|
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[code];
|
|
if (immutableKeyCode !== -1) {
|
|
return USER_SETTINGS.fromKeyCode(immutableKeyCode).toLowerCase();
|
|
}
|
|
|
|
// Check if this hw code always maps to the same kb code and back
|
|
let constantKeyCode: KeyCode = this._getStableKeyCodeForHWCode(code);
|
|
if (constantKeyCode !== -1) {
|
|
return USER_SETTINGS.fromKeyCode(constantKeyCode).toLowerCase();
|
|
}
|
|
|
|
return this._hwToDispatch[code];
|
|
}
|
|
|
|
private _getElectronLabelForKeyCode(keyCode: KeyCode): string {
|
|
if (keyCode >= KeyCode.NUMPAD_0 && keyCode <= KeyCode.NUMPAD_DIVIDE) {
|
|
// Electron cannot handle numpad keys
|
|
return null;
|
|
}
|
|
|
|
switch (keyCode) {
|
|
case KeyCode.UpArrow:
|
|
return 'Up';
|
|
case KeyCode.DownArrow:
|
|
return 'Down';
|
|
case KeyCode.LeftArrow:
|
|
return 'Left';
|
|
case KeyCode.RightArrow:
|
|
return 'Right';
|
|
}
|
|
|
|
// electron menus always do the correct rendering on Windows
|
|
return KeyCodeUtils.toString(keyCode);
|
|
}
|
|
|
|
public getElectronLabelForHardwareCode(code: KeyboardEventCode): string {
|
|
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[code];
|
|
if (immutableKeyCode !== -1) {
|
|
return this._getElectronLabelForKeyCode(immutableKeyCode);
|
|
}
|
|
|
|
// Check if this hw code always maps to the same kb code and back
|
|
let constantKeyCode: KeyCode = this._getStableKeyCodeForHWCode(code);
|
|
if (constantKeyCode !== -1) {
|
|
return this._getElectronLabelForKeyCode(constantKeyCode);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private _getStableKeyCodeForHWCode(code: KeyboardEventCode): KeyCode {
|
|
if (code >= KeyboardEventCode.Digit1 && code <= KeyboardEventCode.Digit0) {
|
|
// digits are ok
|
|
switch (code) {
|
|
case KeyboardEventCode.Digit1: return KeyCode.KEY_1;
|
|
case KeyboardEventCode.Digit2: return KeyCode.KEY_2;
|
|
case KeyboardEventCode.Digit3: return KeyCode.KEY_3;
|
|
case KeyboardEventCode.Digit4: return KeyCode.KEY_4;
|
|
case KeyboardEventCode.Digit5: return KeyCode.KEY_5;
|
|
case KeyboardEventCode.Digit6: return KeyCode.KEY_6;
|
|
case KeyboardEventCode.Digit7: return KeyCode.KEY_7;
|
|
case KeyboardEventCode.Digit8: return KeyCode.KEY_8;
|
|
case KeyboardEventCode.Digit9: return KeyCode.KEY_9;
|
|
case KeyboardEventCode.Digit0: return KeyCode.KEY_0;
|
|
}
|
|
}
|
|
|
|
// Check if this hw code always maps to the same kb code and back
|
|
let constantKeyCode: KeyCode = -1;
|
|
for (let mod = 0; mod < 8; mod++) {
|
|
const hwEncoded = ((code << 3) + mod) >>> 0;
|
|
const kbEncoded = this._hwToKb[hwEncoded];
|
|
const keyCode = (kbEncoded >>> 3);
|
|
|
|
if (keyCode === KeyCode.Unknown) {
|
|
// maps to unknown keyCode
|
|
return -1;
|
|
}
|
|
|
|
if (constantKeyCode === -1) {
|
|
constantKeyCode = keyCode;
|
|
} else if (constantKeyCode !== keyCode) {
|
|
// maps to different keyCode
|
|
return -1;
|
|
}
|
|
|
|
// Check that the inverse is true
|
|
const inverse = this._kbToHw[kbEncoded];
|
|
if (inverse.length !== 1) {
|
|
// multiple hw keypresses map to this kb
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return constantKeyCode;
|
|
}
|
|
|
|
public resolveKeybinding(keybinding: Keybinding): NativeResolvedKeybinding[] {
|
|
let result: NativeResolvedKeybinding[] = [], resultLen = 0;
|
|
|
|
if (keybinding.type === KeybindingType.Chord) {
|
|
const firstParts = this.simpleKeybindingToHardwareKeypress(keybinding.firstPart);
|
|
const chordParts = this.simpleKeybindingToHardwareKeypress(keybinding.chordPart);
|
|
|
|
for (let i = 0, len = firstParts.length; i < len; i++) {
|
|
const firstPart = firstParts[i];
|
|
for (let j = 0, lenJ = chordParts.length; j < lenJ; j++) {
|
|
const chordPart = chordParts[j];
|
|
|
|
result[resultLen++] = new NativeResolvedKeybinding(this, this._OS, firstPart, chordPart);
|
|
}
|
|
}
|
|
} else {
|
|
const firstParts = this.simpleKeybindingToHardwareKeypress(keybinding);
|
|
|
|
for (let i = 0, len = firstParts.length; i < len; i++) {
|
|
const firstPart = firstParts[i];
|
|
|
|
result[resultLen++] = new NativeResolvedKeybinding(this, this._OS, firstPart, null);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static _charCodeToKb(charCode: number): { keyCode: KeyCode; shiftKey: boolean } {
|
|
if (charCode < CHAR_CODE_TO_KEY_CODE.length) {
|
|
return CHAR_CODE_TO_KEY_CODE[charCode];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Attempt to map a combining character to a regular one that renders the same way.
|
|
*
|
|
* To the brave person following me: Good Luck!
|
|
* https://www.compart.com/en/unicode/bidiclass/NSM
|
|
*/
|
|
private static _getCharCode(char: string): number {
|
|
if (char.length === 0) {
|
|
return 0;
|
|
}
|
|
const charCode = char.charCodeAt(0);
|
|
switch (charCode) {
|
|
case CharCode.U_Combining_Grave_Accent: return CharCode.U_GRAVE_ACCENT;
|
|
case CharCode.U_Combining_Acute_Accent: return CharCode.U_ACUTE_ACCENT;
|
|
case CharCode.U_Combining_Circumflex_Accent: return CharCode.U_CIRCUMFLEX;
|
|
case CharCode.U_Combining_Tilde: return CharCode.U_SMALL_TILDE;
|
|
case CharCode.U_Combining_Macron: return CharCode.U_MACRON;
|
|
case CharCode.U_Combining_Overline: return CharCode.U_OVERLINE;
|
|
case CharCode.U_Combining_Breve: return CharCode.U_BREVE;
|
|
case CharCode.U_Combining_Dot_Above: return CharCode.U_DOT_ABOVE;
|
|
case CharCode.U_Combining_Diaeresis: return CharCode.U_DIAERESIS;
|
|
case CharCode.U_Combining_Ring_Above: return CharCode.U_RING_ABOVE;
|
|
case CharCode.U_Combining_Double_Acute_Accent: return CharCode.U_DOUBLE_ACUTE_ACCENT;
|
|
}
|
|
return charCode;
|
|
}
|
|
|
|
private _encode(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, principal: number): number {
|
|
return (
|
|
((ctrlKey ? 1 : 0) << 0)
|
|
| ((shiftKey ? 1 : 0) << 1)
|
|
| ((altKey ? 1 : 0) << 2)
|
|
| principal << 3
|
|
) >>> 0;
|
|
}
|
|
|
|
private _decodeHw(hwEncoded: number, metaKey: boolean): HardwareKeypress {
|
|
const ctrlKey = (hwEncoded & 0b0001) ? true : false;
|
|
const shiftKey = (hwEncoded & 0b0010) ? true : false;
|
|
const altKey = (hwEncoded & 0b0100) ? true : false;
|
|
const code = (hwEncoded >>> 3);
|
|
|
|
return new HardwareKeypress(ctrlKey, shiftKey, altKey, metaKey, code);
|
|
}
|
|
}
|
|
|
|
(function () {
|
|
function define(charCode: number, keyCode: KeyCode, shiftKey: boolean): void {
|
|
for (let i = CHAR_CODE_TO_KEY_CODE.length; i < charCode; i++) {
|
|
CHAR_CODE_TO_KEY_CODE[i] = null;
|
|
}
|
|
CHAR_CODE_TO_KEY_CODE[charCode] = { keyCode: keyCode, shiftKey: shiftKey };
|
|
}
|
|
|
|
for (let chCode = CharCode.A; chCode <= CharCode.Z; chCode++) {
|
|
define(chCode, KeyCode.KEY_A + (chCode - CharCode.A), true);
|
|
}
|
|
|
|
for (let chCode = CharCode.a; chCode <= CharCode.z; chCode++) {
|
|
define(chCode, KeyCode.KEY_A + (chCode - CharCode.a), false);
|
|
}
|
|
|
|
define(CharCode.Semicolon, KeyCode.US_SEMICOLON, false);
|
|
define(CharCode.Colon, KeyCode.US_SEMICOLON, true);
|
|
|
|
define(CharCode.Equals, KeyCode.US_EQUAL, false);
|
|
define(CharCode.Plus, KeyCode.US_EQUAL, true);
|
|
|
|
define(CharCode.Comma, KeyCode.US_COMMA, false);
|
|
define(CharCode.LessThan, KeyCode.US_COMMA, true);
|
|
|
|
define(CharCode.Dash, KeyCode.US_MINUS, false);
|
|
define(CharCode.Underline, KeyCode.US_MINUS, true);
|
|
|
|
define(CharCode.Period, KeyCode.US_DOT, false);
|
|
define(CharCode.GreaterThan, KeyCode.US_DOT, true);
|
|
|
|
define(CharCode.Slash, KeyCode.US_SLASH, false);
|
|
define(CharCode.QuestionMark, KeyCode.US_SLASH, true);
|
|
|
|
define(CharCode.BackTick, KeyCode.US_BACKTICK, false);
|
|
define(CharCode.Tilde, KeyCode.US_BACKTICK, true);
|
|
|
|
define(CharCode.OpenSquareBracket, KeyCode.US_OPEN_SQUARE_BRACKET, false);
|
|
define(CharCode.OpenCurlyBrace, KeyCode.US_OPEN_SQUARE_BRACKET, true);
|
|
|
|
define(CharCode.Backslash, KeyCode.US_BACKSLASH, false);
|
|
define(CharCode.Pipe, KeyCode.US_BACKSLASH, true);
|
|
|
|
define(CharCode.CloseSquareBracket, KeyCode.US_CLOSE_SQUARE_BRACKET, false);
|
|
define(CharCode.CloseCurlyBrace, KeyCode.US_CLOSE_SQUARE_BRACKET, true);
|
|
|
|
define(CharCode.SingleQuote, KeyCode.US_QUOTE, false);
|
|
define(CharCode.DoubleQuote, KeyCode.US_QUOTE, true);
|
|
})();
|