Keybindings PR first section - Updating keyutil (#384)

* updated keyutil

* added keybindings file

* export checkKeyPressed
This commit is contained in:
Cole Lashley
2024-03-05 18:19:47 -08:00
committed by GitHub
parent bde2a9ccfe
commit 7bf1a259a2
2 changed files with 564 additions and 18 deletions
+258
View File
@@ -0,0 +1,258 @@
[
{
"command": "system:toggleDeveloperTools",
"keys": ["Cmd:Option:i"]
},
{
"command": "generic:cancel",
"keys": ["Escape"]
},
{
"command": "generic:confirm",
"keys": ["Enter"]
},
{
"command": "generic:deleteItem",
"keys": ["Backspace", "Delete"]
},
{
"command": "generic:selectAbove",
"keys": ["ArrowUp"]
},
{
"command": "generic:selectBelow",
"keys": ["ArrowDown"]
},
{
"command": "generic:selectPageAbove",
"keys": ["PageUp"]
},
{
"command": "generic:selectPageBelow",
"keys": ["PageDown"]
},
{
"command": "app:openHistory",
"keys": ["Cmd:h"]
},
{
"command": "app:openTabSearchModal",
"keys": ["Cmd:p"]
},
{
"command": "app:newTab",
"keys": ["Cmd:t"]
},
{
"command": "app:focusCmdInput",
"keys": ["Cmd:i"]
},
{
"command": "app:focusSelectedLine",
"keys": ["Cmd:l"]
},
{
"command": "app:restartCommand",
"keys": ["Cmd:r"]
},
{
"command": "app:restartLastCommand",
"keys": ["Cmd:Shift:r"]
},
{
"command": "app:closeCurrentTab",
"keys": ["Cmd:w"]
},
{
"command": "app:selectLineAbove",
"keys": ["Cmd:ArrowUp", "Cmd:PageUp"]
},
{
"command": "app:selectLineBelow",
"keys": ["Cmd:ArrowDown", "Cmd:PageDown"]
},
{
"command": "app:selectTab-1",
"keys": ["Cmd:1"]
},
{
"command": "app:selectTab-2",
"keys": ["Cmd:2"]
},
{
"command": "app:selectTab-3",
"keys": ["Cmd:3"]
},
{
"command": "app:selectTab-4",
"keys": ["Cmd:4"]
},
{
"command": "app:selectTab-5",
"keys": ["Cmd:5"]
},
{
"command": "app:selectTab-6",
"keys": ["Cmd:6"]
},
{
"command": "app:selectTab-7",
"keys": ["Cmd:7"]
},
{
"command": "app:selectTab-8",
"keys": ["Cmd:8"]
},
{
"command": "app:selectTab-9",
"keys": ["Cmd:9"]
},
{
"command": "app:selectTabLeft",
"keys": ["Cmd:["]
},
{
"command": "app:selectTabRight",
"keys": ["Cmd:]"]
},
{
"command": "app:selectWorkspace-1",
"keys": ["Cmd:Ctrl:1"]
},
{
"command": "app:selectWorkspace-2",
"keys": ["Cmd:Ctrl:2"]
},
{
"command": "app:selectWorkspace-3",
"keys": ["Cmd:Ctrl:3"]
},
{
"command": "app:selectWorkspace-4",
"keys": ["Cmd:Ctrl:4"]
},
{
"command": "app:selectWorkspace-5",
"keys": ["Cmd:Ctrl:5"]
},
{
"command": "app:selectWorkspace-6",
"keys": ["Cmd:Ctrl:6"]
},
{
"command": "app:selectWorkspace-7",
"keys": ["Cmd:Ctrl:7"]
},
{
"command": "app:selectWorkspace-8",
"keys": ["Cmd:Ctrl:8"]
},
{
"command": "app:selectWorkspace-9",
"keys": ["Cmd:Ctrl:9"]
},
{
"command": "app:toggleSidebar",
"keys": ["Cmd:Ctrl:s"]
},
{
"command": "app:deleteActiveLine",
"keys": ["Cmd:d"]
},
{
"command": "app:bookmarkActiveLine",
"keys": ["Cmd:b"]
},
{
"command": "bookmarks:edit",
"keys": ["e"]
},
{
"command": "bookmarks:copy",
"keys": ["c"]
},
{
"command": "cmdinput:autocomplete",
"keys": ["Tab"]
},
{
"command": "cmdinput:expandInput",
"keys": ["Cmd:e"]
},
{
"command": "cmdinput:clearInput",
"keys": ["Ctrl:c"]
},
{
"command": "cmdinput:cutLineLeftOfCursor",
"keys": ["Ctrl:u"]
},
{
"command": "cmdinput:previousHistoryItem",
"keys": ["Ctrl:p"]
},
{
"command": "cmdinput:nextHistoryItem",
"keys": ["Ctrl:n"]
},
{
"command": "cmdinput:cutWordLeftOfCursor",
"keys": ["Ctrl:w"]
},
{
"command": "cmdinput:paste",
"keys": ["Ctrl:y"]
},
{
"command": "cmdinput:openHistory",
"keys": ["Ctrl:r"]
},
{
"command": "cmdinput:openAIChat",
"keys": ["Ctrl:Space"]
},
{
"command": "history:closeHistory",
"keys": ["Ctrl:g", "Ctrl:c"]
},
{
"command": "history:toggleShowRemotes",
"keys": ["Cmd:r", "Ctrl:r"]
},
{
"command": "history:changeScope",
"keys": ["Ctrl:s", "Cmd:s"]
},
{
"command": "history:selectNextItem",
"keys": ["Ctrl:n"]
},
{
"command": "history:selectPreviousItem",
"keys": ["Ctrl:p"]
},
{
"command": "aichat:clearHistory",
"keys": ["Ctrl:l"]
},
{
"command": "terminal:copy",
"keys": ["Ctrl:Shift:c"]
},
{
"command": "terminal:paste",
"keys": ["Ctrl:Shift:v"]
},
{
"command": "codeedit:save",
"keys": ["Cmd:s"]
},
{
"command": "codeedit:close",
"keys": ["Cmd:d"]
},
{
"command": "codeedit:togglePreview",
"keys": ["Cmd:p"]
}
]
+306 -18
View File
@@ -1,5 +1,8 @@
import * as React from "react";
import * as electron from "electron";
import { parse } from "node:path";
import { v4 as uuidv4 } from "uuid";
import keybindings from "../../assets/keybindings.json";
type KeyPressDecl = {
mods: {
@@ -11,8 +14,264 @@ type KeyPressDecl = {
Meta?: boolean;
};
key: string;
keyType: string;
};
const KeyTypeCodeRegex = /:c{(.*)}/;
const KeyTypeKey = "key";
const KeyTypeCode = "code";
type KeybindCallback = (event: WaveKeyboardEvent) => boolean;
type Keybind = {
domain: string;
keybinding: string;
callback: KeybindCallback;
};
const KeybindLevels = ["system", "modal", "app", "pane", "plugin"];
class KeybindManager {
domainCallbacks: Map<string, KeybindCallback>;
levelMap: Map<string, Array<Keybind>>;
levelArray: Array<string>;
keyDescriptionsMap: Map<string, Array<string>>;
processLevel(nativeEvent: any, event: WaveKeyboardEvent, keybindsArray: Array<Keybind>): boolean {
// iterate through keybinds in backwards order
for (let index = keybindsArray.length - 1; index >= 0; index--) {
let curKeybind = keybindsArray[index];
if (this.checkKeyPressed(event, curKeybind.keybinding)) {
let shouldReturn = false;
if (curKeybind.callback != null) {
console.log("Calling callback", curKeybind.domain);
shouldReturn = curKeybind.callback(event);
console.log("callback return value", shouldReturn);
}
if (!shouldReturn && this.domainCallbacks.has(curKeybind.domain)) {
let curDomainCallback = this.domainCallbacks.get(curKeybind.domain);
if (curDomainCallback != null) {
shouldReturn = curDomainCallback(event);
} else {
console.log("domain callback for ", curKeybind.domain, " is null. This should never happen");
}
}
if (shouldReturn) {
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
return true;
}
}
}
return false;
}
processKeyEvent(nativeEvent: any, event: WaveKeyboardEvent) {
let modalLevel = this.levelMap.get("modal");
if (modalLevel.length != 0) {
console.log("processing modal");
// special case when modal keybindings are present
let shouldReturn = this.processLevel(nativeEvent, event, modalLevel);
if (shouldReturn) {
return;
}
let systemLevel = this.levelMap.get("system");
this.processLevel(nativeEvent, event, systemLevel);
return;
}
for (let index = this.levelArray.length - 1; index >= 0; index--) {
let curLevel = this.levelArray[index];
let curKeybindsArray;
if (this.levelMap.has(curLevel)) {
curKeybindsArray = this.levelMap.get(curLevel);
} else {
console.error("error processing key event: couldn't find level: ", curLevel);
continue;
}
let shouldReturn = this.processLevel(nativeEvent, event, curKeybindsArray);
if (shouldReturn) {
return;
}
}
}
keybindingAlreadyAdded(level: string, domain: string, keybinding: string) {
if (!this.levelMap.has(level)) {
return false;
}
let keybindsArray = this.levelMap.get(level);
for (let index = 0; index < keybindsArray.length; index++) {
let curKeybind = keybindsArray[index];
if (curKeybind.domain == domain && keybindingIsEqual(curKeybind.keybinding, keybinding)) {
console.log("keybinding is equal: ", curKeybind.keybinding, keybinding, curKeybind.domain, domain);
return true;
}
}
return false;
}
registerKeybinding(level: string, domain: string, keybinding: string, callback: KeybindCallback): boolean {
if (domain == "" || this.keybindingAlreadyAdded(level, domain, keybinding)) {
return false;
}
// TODO: check if keybinding is valid
let newKeybind = { domain: domain, keybinding: keybinding, callback: callback } as Keybind;
if (!this.levelMap.has(level)) {
return false;
}
let curKeybindArray = this.levelMap.get(level);
curKeybindArray.push(newKeybind);
this.levelMap.set(level, curKeybindArray);
return true;
}
registerAndCheckKeyPressed(
nativeEvent: any,
waveEvent: WaveKeyboardEvent,
level: string,
keybinding: string
): boolean {
let rtn = false;
let curDomain = String(uuidv4());
this.registerKeybinding(level, curDomain, keybinding, (waveEvent) => {
rtn = true;
return true;
});
this.processKeyEvent(nativeEvent, waveEvent);
let didUnregister = this.unregisterKeybinding(level, curDomain, keybinding);
return rtn;
}
registerAndProcessKeyEvent(
nativeEvent: any,
waveEvent: WaveKeyboardEvent,
level: string,
domain: string,
keybinding: string,
callback: KeybindCallback
) {
this.registerKeybinding(level, domain, keybinding, callback);
this.processKeyEvent(nativeEvent, waveEvent);
this.unregisterKeybinding(level, domain, keybinding);
}
unregisterKeybinding(level: string, domain: string, keybinding: string): boolean {
if (!this.levelMap.has(level)) {
return false;
}
let keybindsArray = this.levelMap.get(level);
for (let index = 0; index < keybindsArray.length; index++) {
let curKeybind = keybindsArray[index];
if (curKeybind.domain == domain && keybindingIsEqual(curKeybind.keybinding, keybinding)) {
console.log("unregistering keybinding");
keybindsArray.splice(index, 1);
index--;
this.levelMap.set(level, keybindsArray);
}
return true;
}
return false;
}
unregisterDomain(domain: string) {
let foundKeybind = false;
for (let levelIndex = 0; levelIndex < this.levelArray.length; levelIndex++) {
let curLevel = this.levelArray[levelIndex];
let curKeybindArray = this.levelMap.get(curLevel);
for (let curArrayIndex = 0; curArrayIndex < curKeybindArray.length; curArrayIndex++) {
let curKeybind = curKeybindArray[curArrayIndex];
if (curKeybind.domain == domain) {
curKeybindArray.splice(curArrayIndex, 1);
curArrayIndex--;
foundKeybind = true;
}
}
}
this.domainCallbacks.delete(domain);
return foundKeybind;
}
getKeyPressEventForDomain(domain: string, callback: KeybindCallback) {
if (callback == null) {
console.log("domain callback can't be null");
}
this.domainCallbacks.set(domain, callback);
}
constructor() {
this.levelMap = new Map();
this.domainCallbacks = new Map();
this.levelArray = KeybindLevels;
for (let index = 0; index < this.levelArray.length; index++) {
let curLevel = this.levelArray[index];
this.levelMap.set(curLevel, new Array<Keybind>());
}
this.initKeyDescriptionsMap();
}
initKeyDescriptionsMap() {
this.keyDescriptionsMap = new Map();
for (let index = 0; index < keybindings.length; index++) {
let curKeybind = keybindings[index];
this.keyDescriptionsMap.set(curKeybind.command, curKeybind.keys);
}
let error = false;
let numberedTabKeybinds = [];
for (let index = 1; index <= 9; index++) {
let curKeybind = this.keyDescriptionsMap.get("app:selectTab-" + index);
if (curKeybind == null) {
error = true;
break;
}
numberedTabKeybinds = numberedTabKeybinds.concat(curKeybind);
}
if (!error) {
this.keyDescriptionsMap.set("app:selectNumberedTab", numberedTabKeybinds);
}
let numberedWorkspaceKeybinds = [];
for (let index = 1; index <= 9; index++) {
let curKeybind = this.keyDescriptionsMap.get("app:selectTab-" + index);
if (curKeybind == null) {
error = true;
break;
}
numberedWorkspaceKeybinds = numberedWorkspaceKeybinds.concat(curKeybind);
}
if (!error) {
this.keyDescriptionsMap.set("app:selectNumberedTab", numberedWorkspaceKeybinds);
}
}
checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean {
if (keyDescription == "any") {
return true;
}
if (!this.keyDescriptionsMap.has(keyDescription)) {
return false;
}
let keyPressArray = this.keyDescriptionsMap.get(keyDescription);
for (let index = 0; index < keyPressArray.length; index++) {
let curKeyPress = keyPressArray[index];
let pressed = checkKeyPressed(event, curKeyPress);
if (pressed) {
return true;
}
}
return false;
}
checkKeysPressed(event: WaveKeyboardEvent, keyDescriptions: Array<string>) {
for (let index = 0; index < keyDescriptions.length; index++) {
let curKeyDesc = keyDescriptions[index];
let pressed = this.checkKeyPressed(event, curKeyDesc);
if (pressed) {
return true;
}
}
return false;
}
}
var PLATFORM: string;
const PlatformMacOS: string = "darwin";
@@ -20,6 +279,10 @@ function setKeyUtilPlatform(platform: string) {
PLATFORM = platform;
}
function keybindingIsEqual(bind1: string, bind2: string) {
return bind1 == bind2;
}
function parseKeyDescription(keyDescription: string): KeyPressDecl {
let rtn = { key: "", mods: {} } as KeyPressDecl;
let keys = keyDescription.replace(/[()]/g, "").split(":");
@@ -37,8 +300,10 @@ function parseKeyDescription(keyDescription: string): KeyPressDecl {
} else if (key == "Meta") {
rtn.mods.Meta = true;
} else {
rtn.key = key;
if (key.length == 1) {
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
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
@@ -53,8 +318,19 @@ function parseKeyDescription(keyDescription: string): KeyPressDecl {
return rtn;
}
function checkKeyPressed(event: WaveKeyboardEvent, description: string): boolean {
let keyPress = parseKeyDescription(description);
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 checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean {
let keyPress = parseKeyDescription(keyDescription);
if (keyPress.mods.Option && !event.option) {
return false;
}
@@ -73,16 +349,22 @@ function checkKeyPressed(event: WaveKeyboardEvent, description: string): boolean
if (keyPress.mods.Meta && !event.meta) {
return false;
}
let eventKey = event.key;
let eventKey = "";
let descKey = keyPress.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 (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;
@@ -139,8 +421,8 @@ function adaptFromReactOrNativeKeyEvent(event: React.KeyboardEvent | KeyboardEve
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.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;
@@ -155,8 +437,8 @@ 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.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;
@@ -167,5 +449,11 @@ function adaptFromElectronKeyEvent(event: any): WaveKeyboardEvent {
return rtn;
}
export { adaptFromElectronKeyEvent, adaptFromReactOrNativeKeyEvent, checkKeyPressed, setKeyUtilPlatform };
export {
KeybindManager,
adaptFromElectronKeyEvent,
adaptFromReactOrNativeKeyEvent,
setKeyUtilPlatform,
checkKeyPressed,
};
export type { WaveKeyboardEvent };