waveterm/frontend/util/keyutil.ts

148 lines
4.9 KiB
TypeScript

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
const KeyTypeCodeRegex = /c{(.*)}/;
const KeyTypeKey = "key";
const KeyTypeCode = "code";
let PLATFORM: NodeJS.Platform = "darwin";
const PlatformMacOS = "darwin";
function setKeyUtilPlatform(platform: NodeJS.Platform) {
PLATFORM = platform;
}
function parseKey(key: string): { key: string; type: string } {
let regexMatch = key.match(KeyTypeCodeRegex);
if (regexMatch != null && regexMatch.length > 1) {
let code = regexMatch[1];
return { key: code, type: KeyTypeCode };
} else if (regexMatch != null) {
console.log("error: regexMatch is not null yet there is no captured group: ", regexMatch, key);
}
return { key: key, type: KeyTypeKey };
}
function parseKeyDescription(keyDescription: string): KeyPressDecl {
let rtn = { key: "", mods: {} } as KeyPressDecl;
let keys = keyDescription.replace(/[()]/g, "").split(":");
for (let key of keys) {
if (key == "Cmd") {
rtn.mods.Cmd = true;
} else if (key == "Shift") {
rtn.mods.Shift = true;
} else if (key == "Ctrl") {
rtn.mods.Ctrl = true;
} else if (key == "Option") {
rtn.mods.Option = true;
} else if (key == "Alt") {
rtn.mods.Alt = true;
} else if (key == "Meta") {
rtn.mods.Meta = true;
} else {
let { key: parsedKey, type: keyType } = parseKey(key);
rtn.key = parsedKey;
rtn.keyType = keyType;
if (rtn.keyType == KeyTypeKey && key.length == 1) {
// check for if key is upper case
// TODO what about unicode upper case?
if (/[A-Z]/.test(key.charAt(0))) {
// this key is an upper case A - Z - we should apply the shift key, even if it wasn't specified
rtn.mods.Shift = true;
} else if (key == " ") {
rtn.key = "Space";
// we allow " " and "Space" to be mapped to Space key
}
}
}
}
return rtn;
}
function notMod(keyPressMod: boolean, eventMod: boolean) {
return (keyPressMod && !eventMod) || (eventMod && !keyPressMod);
}
function checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean {
let keyPress = parseKeyDescription(keyDescription);
if (!keyPress.mods.Alt && notMod(keyPress.mods.Option, event.option)) {
return false;
}
if (!keyPress.mods.Meta && notMod(keyPress.mods.Cmd, event.cmd)) {
return false;
}
if (notMod(keyPress.mods.Shift, event.shift)) {
return false;
}
if (notMod(keyPress.mods.Ctrl, event.control)) {
return false;
}
if (keyPress.mods.Alt && !event.alt) {
return false;
}
if (keyPress.mods.Meta && !event.meta) {
return false;
}
let eventKey = "";
let descKey = keyPress.key;
if (keyPress.keyType == KeyTypeCode) {
eventKey = event.code;
}
if (keyPress.keyType == KeyTypeKey) {
eventKey = event.key;
if (eventKey.length == 1 && /[A-Z]/.test(eventKey.charAt(0))) {
// key is upper case A-Z, this means shift is applied, we want to allow
// "Shift:e" as well as "Shift:E" or "E"
eventKey = eventKey.toLocaleLowerCase();
descKey = descKey.toLocaleLowerCase();
} else if (eventKey == " ") {
eventKey = "Space";
// a space key is shown as " ", we want users to be able to set space key as "Space" or " ", whichever they prefer
}
}
if (descKey != eventKey) {
return false;
}
return true;
}
function adaptFromReactOrNativeKeyEvent(event: React.KeyboardEvent | KeyboardEvent): WaveKeyboardEvent {
let rtn: WaveKeyboardEvent = {} as WaveKeyboardEvent;
rtn.control = event.ctrlKey;
rtn.shift = event.shiftKey;
rtn.cmd = PLATFORM == PlatformMacOS ? event.metaKey : event.altKey;
rtn.option = PLATFORM == PlatformMacOS ? event.altKey : event.metaKey;
rtn.meta = event.metaKey;
rtn.alt = event.altKey;
rtn.code = event.code;
rtn.key = event.key;
rtn.location = event.location;
rtn.type = event.type;
rtn.repeat = event.repeat;
return rtn;
}
function adaptFromElectronKeyEvent(event: any): WaveKeyboardEvent {
let rtn: WaveKeyboardEvent = {} as WaveKeyboardEvent;
rtn.type = event.type;
rtn.control = event.control;
rtn.cmd = PLATFORM == PlatformMacOS ? event.meta : event.alt;
rtn.option = PLATFORM == PlatformMacOS ? event.alt : event.meta;
rtn.meta = event.meta;
rtn.alt = event.alt;
rtn.shift = event.shift;
rtn.repeat = event.isAutoRepeat;
rtn.location = event.location;
rtn.code = event.code;
rtn.key = event.key;
return rtn;
}
export {
adaptFromElectronKeyEvent,
adaptFromReactOrNativeKeyEvent,
checkKeyPressed,
parseKeyDescription,
setKeyUtilPlatform,
};