mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Slash Commands for keybindings (#441)
* first pass of slash commands * added mainview slashcommand * added focus cmd input, added sleep * addressed review comments * addressed feedback * demo idea of changing hide do cmd+m, can remove if we decide we don't like it * added new keybinding for minimize * addressed feedback * added hide label * fix hide (use app.hid(), not window.hide() * fix history keybinding, make mainview command consistent
This commit is contained in:
parent
fc0b82836c
commit
f87cc42ab9
@ -3,6 +3,10 @@
|
||||
"command": "system:toggleDeveloperTools",
|
||||
"keys": ["Cmd:Option:i"]
|
||||
},
|
||||
{
|
||||
"command": "system:hideWindow",
|
||||
"keys": ["Cmd:m"]
|
||||
},
|
||||
{
|
||||
"command": "generic:cancel",
|
||||
"keys": ["Escape"]
|
||||
@ -32,7 +36,7 @@
|
||||
"keys": ["PageDown"]
|
||||
},
|
||||
{
|
||||
"command": "app:openHistory",
|
||||
"command": "app:openHistoryView",
|
||||
"keys": ["Cmd:h"]
|
||||
},
|
||||
{
|
||||
@ -41,11 +45,13 @@
|
||||
},
|
||||
{
|
||||
"command": "app:openConnectionsView",
|
||||
"keys": []
|
||||
"keys": [],
|
||||
"commandStr": "/mainview connections"
|
||||
},
|
||||
{
|
||||
"command": "app:openSettingsView",
|
||||
"keys": []
|
||||
"keys": [],
|
||||
"commandStr": "/mainview clientsettings"
|
||||
},
|
||||
{
|
||||
"command": "app:newTab",
|
||||
@ -125,39 +131,48 @@
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-1",
|
||||
"keys": ["Cmd:Ctrl:1"]
|
||||
"keys": ["Cmd:Ctrl:1"],
|
||||
"commandStr": "/session 1"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-2",
|
||||
"keys": ["Cmd:Ctrl:2"]
|
||||
"keys": ["Cmd:Ctrl:2"],
|
||||
"commandStr": "/session 2"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-3",
|
||||
"keys": ["Cmd:Ctrl:3"]
|
||||
"keys": ["Cmd:Ctrl:3"],
|
||||
"commandStr": "/session 3"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-4",
|
||||
"keys": ["Cmd:Ctrl:4"]
|
||||
"keys": ["Cmd:Ctrl:4"],
|
||||
"commandStr": "/session 4"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-5",
|
||||
"keys": ["Cmd:Ctrl:5"]
|
||||
"keys": ["Cmd:Ctrl:5"],
|
||||
"commandStr": "/session 5"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-6",
|
||||
"keys": ["Cmd:Ctrl:6"]
|
||||
"keys": ["Cmd:Ctrl:6"],
|
||||
"commandStr": "/session 6"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-7",
|
||||
"keys": ["Cmd:Ctrl:7"]
|
||||
"keys": ["Cmd:Ctrl:7"],
|
||||
"commandStr": "/session 7"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-8",
|
||||
"keys": ["Cmd:Ctrl:8"]
|
||||
"keys": ["Cmd:Ctrl:8"],
|
||||
"commandStr": "/session 8"
|
||||
},
|
||||
{
|
||||
"command": "app:selectWorkspace-9",
|
||||
"keys": ["Cmd:Ctrl:9"]
|
||||
"keys": ["Cmd:Ctrl:9"],
|
||||
"commandStr": "/session 9"
|
||||
},
|
||||
{
|
||||
"command": "app:toggleSidebar",
|
||||
@ -168,8 +183,9 @@
|
||||
"keys": ["Cmd:d"]
|
||||
},
|
||||
{
|
||||
"command": "app:bookmarkActiveLine",
|
||||
"keys": ["Cmd:b"]
|
||||
"command": "app:openBookmarksView",
|
||||
"keys": ["Cmd:b"],
|
||||
"commandStr": "/bookmarks:show"
|
||||
},
|
||||
{
|
||||
"command": "bookmarks:edit",
|
||||
@ -213,7 +229,8 @@
|
||||
},
|
||||
{
|
||||
"command": "cmdinput:openHistory",
|
||||
"keys": ["Ctrl:r"]
|
||||
"keys": ["Ctrl:r"],
|
||||
"commandStr": "/history"
|
||||
},
|
||||
{
|
||||
"command": "cmdinput:openAIChat",
|
||||
|
@ -263,7 +263,12 @@ const menuTemplate: Electron.MenuItemConstructorOptions[] = [
|
||||
{ type: "separator" },
|
||||
{ role: "services" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{
|
||||
label: "Hide",
|
||||
click: () => {
|
||||
app.hide();
|
||||
},
|
||||
},
|
||||
{ role: "hideOthers" },
|
||||
{ type: "separator" },
|
||||
{ role: "quit" },
|
||||
@ -513,6 +518,13 @@ electron.ipcMain.on("toggle-developer-tools", (event) => {
|
||||
event.returnValue = true;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("hide-window", (event) => {
|
||||
if (MainWindow != null) {
|
||||
MainWindow.hide();
|
||||
}
|
||||
event.returnValue = true;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-id", (event) => {
|
||||
event.returnValue = instanceId + ":" + event.processId;
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
let { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("api", {
|
||||
hideWindow: () => ipc.Renderer.send("hide-window"),
|
||||
toggleDeveloperTools: () => ipcRenderer.send("toggle-developer-tools"),
|
||||
getId: () => ipcRenderer.sendSync("get-id"),
|
||||
getPlatform: () => ipcRenderer.sendSync("get-platform"),
|
||||
|
@ -143,7 +143,7 @@ class Model {
|
||||
this.runUpdate(message, interactive);
|
||||
});
|
||||
this.ws.reconnect();
|
||||
this.keybindManager = new KeybindManager();
|
||||
this.keybindManager = new KeybindManager(this);
|
||||
this.readConfigKeybindings();
|
||||
this.initSystemKeybindings();
|
||||
this.initAppKeybindings();
|
||||
@ -222,46 +222,31 @@ class Model {
|
||||
getApi().toggleDeveloperTools();
|
||||
return true;
|
||||
});
|
||||
this.keybindManager.registerKeybinding("system", "electron", "system:minimizeWindow", (waveEvent) => {
|
||||
getApi().hideWindow();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
initAppKeybindings() {
|
||||
for (let index = 1; index <= 9; index++) {
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:selectWorkspace-" + index, (waveEvent) => {
|
||||
this.onSwitchSessionCmd(index);
|
||||
return true;
|
||||
});
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:selectWorkspace-" + index, null);
|
||||
}
|
||||
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:focusCmdInput", (waveEvent) => {
|
||||
console.log("focus cmd input callback");
|
||||
this.onFocusCmdInputPressed();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:bookmarkActiveLine", (waveEvent) => {
|
||||
this.onBookmarkViewPressed();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openHistory", (waveEvent) => {
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openBookmarksView", null);
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openHistoryView", (waveEvent) => {
|
||||
this.onOpenHistoryPressed();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openTabSearchModal", (waveEvent) => {
|
||||
this.onOpenTabSearchModalPressed();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openConnectionsView", (waveEvent) => {
|
||||
this.onOpenConnectionsViewPressed();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openSettingsView", (waveEvent) => {
|
||||
this.onOpenSettingsViewPressed();
|
||||
return true;
|
||||
});
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openConnectionsView", null);
|
||||
this.keybindManager.registerKeybinding("app", "model", "app:openSettingsView", null);
|
||||
}
|
||||
|
||||
static getInstance(): Model {
|
||||
@ -1023,6 +1008,12 @@ class Model {
|
||||
console.warn("invalid bookmarksview in update:", update.mainview);
|
||||
}
|
||||
break;
|
||||
case "clientsettings":
|
||||
this.activeMainView.set("clientsettings");
|
||||
break;
|
||||
case "connections":
|
||||
this.activeMainView.set("connections");
|
||||
break;
|
||||
case "plugins":
|
||||
this.pluginsModel.showPluginsView();
|
||||
break;
|
||||
|
1
src/types/custom.d.ts
vendored
1
src/types/custom.d.ts
vendored
@ -880,6 +880,7 @@ declare global {
|
||||
};
|
||||
|
||||
type ElectronApi = {
|
||||
hideWindow: () => void;
|
||||
toggleDeveloperTools: () => void;
|
||||
getId: () => string;
|
||||
getIsDev: () => boolean;
|
||||
|
@ -4,7 +4,7 @@ import * as electron from "electron";
|
||||
import { parse } from "node:path";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import defaultKeybindingsFile from "../../assets/default-keybindings.json";
|
||||
const defaultKeybindings: KeybindConfig = defaultKeybindingsFile;
|
||||
const defaultKeybindings: KeybindConfigArray = defaultKeybindingsFile;
|
||||
|
||||
type KeyPressDecl = {
|
||||
mods: {
|
||||
@ -24,12 +24,18 @@ const KeyTypeKey = "key";
|
||||
const KeyTypeCode = "code";
|
||||
|
||||
type KeybindCallback = (event: WaveKeyboardEvent) => boolean;
|
||||
type KeybindConfig = Array<{ command: string; keys: Array<string> }>;
|
||||
type KeybindConfigArray = Array<KeybindConfig>;
|
||||
type KeybindConfig = { command: string; keys: Array<string>; commandStr?: string };
|
||||
|
||||
const Callback = "callback";
|
||||
const Command = "command";
|
||||
|
||||
type Keybind = {
|
||||
domain: string;
|
||||
keybinding: string;
|
||||
action: string;
|
||||
callback: KeybindCallback;
|
||||
commandStr: string;
|
||||
};
|
||||
|
||||
const KeybindLevels = ["system", "modal", "app", "pane", "plugin"];
|
||||
@ -38,11 +44,12 @@ class KeybindManager {
|
||||
domainCallbacks: Map<string, KeybindCallback>;
|
||||
levelMap: Map<string, Array<Keybind>>;
|
||||
levelArray: Array<string>;
|
||||
keyDescriptionsMap: Map<string, Array<string>>;
|
||||
userKeybindings: KeybindConfig;
|
||||
keyDescriptionsMap: Map<string, KeybindConfig>;
|
||||
userKeybindings: KeybindConfigArray;
|
||||
userKeybindingError: OV<string>;
|
||||
globalModel: any;
|
||||
|
||||
constructor() {
|
||||
constructor(GlobalModel: any) {
|
||||
this.levelMap = new Map();
|
||||
this.domainCallbacks = new Map();
|
||||
this.levelArray = KeybindLevels;
|
||||
@ -53,6 +60,7 @@ class KeybindManager {
|
||||
this.userKeybindingError = mobx.observable.box(null, {
|
||||
name: "keyutil-userKeybindingError",
|
||||
});
|
||||
this.globalModel = GlobalModel;
|
||||
this.initKeyDescriptionsMap();
|
||||
}
|
||||
|
||||
@ -63,7 +71,7 @@ class KeybindManager {
|
||||
let newKeyDescriptions = new Map();
|
||||
for (let index = 0; index < defaultKeybindings.length; index++) {
|
||||
let curKeybind = defaultKeybindings[index];
|
||||
newKeyDescriptions.set(curKeybind.command, curKeybind.keys);
|
||||
newKeyDescriptions.set(curKeybind.command, curKeybind);
|
||||
}
|
||||
let curUserCommand = "";
|
||||
if (this.userKeybindings != null && this.userKeybindings instanceof Array) {
|
||||
@ -85,7 +93,15 @@ class KeybindManager {
|
||||
throw new Error("invalid keybind key");
|
||||
}
|
||||
}
|
||||
newKeyDescriptions.set(curKeybind.command, curKeybind.keys);
|
||||
let defaultCmd = this.keyDescriptionsMap.get(curKeybind.command);
|
||||
if (
|
||||
defaultCmd != null &&
|
||||
defaultCmd.commandStr != null &&
|
||||
(curKeybind.commandStr == null || curKeybind.commandStr == "")
|
||||
) {
|
||||
curKeybind.commandStr = this.keyDescriptionsMap.get(curKeybind.command).commandStr;
|
||||
}
|
||||
newKeyDescriptions.set(curKeybind.command, curKeybind);
|
||||
}
|
||||
} catch (e) {
|
||||
let userError = `${curUserCommand} is invalid: error: ${e}`;
|
||||
@ -98,16 +114,48 @@ class KeybindManager {
|
||||
this.keyDescriptionsMap = newKeyDescriptions;
|
||||
}
|
||||
|
||||
runSlashCommand(curKeybind: Keybind): boolean {
|
||||
let curConfigKeybind = this.keyDescriptionsMap.get(curKeybind.keybinding);
|
||||
if (curConfigKeybind == null || curConfigKeybind.commandStr == null || curKeybind.commandStr == "") {
|
||||
return false;
|
||||
}
|
||||
let commandsList = curConfigKeybind.commandStr.trim().split(";");
|
||||
this.runIndividualSlashCommand(commandsList);
|
||||
return true;
|
||||
}
|
||||
|
||||
runIndividualSlashCommand(commandsList: Array<string>): boolean {
|
||||
if (commandsList.length == 0) {
|
||||
return true;
|
||||
}
|
||||
let curCommand = commandsList.shift();
|
||||
console.log("running: ", curCommand);
|
||||
let prtn = this.globalModel.submitRawCommand(curCommand, false, false);
|
||||
prtn.then((rtn) => {
|
||||
if (!rtn.success) {
|
||||
console.log("error running command ", curCommand);
|
||||
return false;
|
||||
}
|
||||
return this.runIndividualSlashCommand(commandsList);
|
||||
}).catch((error) => {
|
||||
console.log("caught error running command ", curCommand, ": ", error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
let shouldRunCommand = true;
|
||||
if (curKeybind.callback != null) {
|
||||
shouldReturn = curKeybind.callback(event);
|
||||
shouldRunCommand = false;
|
||||
}
|
||||
if (!shouldReturn && this.domainCallbacks.has(curKeybind.domain)) {
|
||||
shouldRunCommand = false;
|
||||
let curDomainCallback = this.domainCallbacks.get(curKeybind.domain);
|
||||
if (curDomainCallback != null) {
|
||||
shouldReturn = curDomainCallback(event);
|
||||
@ -115,6 +163,9 @@ class KeybindManager {
|
||||
console.log("domain callback for ", curKeybind.domain, " is null. This should never happen");
|
||||
}
|
||||
}
|
||||
if (shouldRunCommand) {
|
||||
shouldReturn = this.runSlashCommand(curKeybind);
|
||||
}
|
||||
if (shouldReturn) {
|
||||
nativeEvent.preventDefault();
|
||||
nativeEvent.stopPropagation();
|
||||
@ -269,7 +320,7 @@ class KeybindManager {
|
||||
if (!this.keyDescriptionsMap.has(keyDescription)) {
|
||||
return false;
|
||||
}
|
||||
let keyPressArray = this.keyDescriptionsMap.get(keyDescription);
|
||||
let keyPressArray = this.keyDescriptionsMap.get(keyDescription).keys;
|
||||
for (let index = 0; index < keyPressArray.length; index++) {
|
||||
let curKeyPress = keyPressArray[index];
|
||||
let pressed = checkKeyPressed(event, curKeyPress);
|
||||
|
@ -171,6 +171,9 @@ func init() {
|
||||
registerCmdFn("reset:cwd", ResetCwdCommand)
|
||||
registerCmdFn("signal", SignalCommand)
|
||||
registerCmdFn("sync", SyncCommand)
|
||||
registerCmdFn("sleep", SleepCommand)
|
||||
|
||||
registerCmdFn("mainview", MainViewCommand)
|
||||
|
||||
registerCmdFn("session", SessionCommand)
|
||||
registerCmdFn("session:open", SessionOpenCommand)
|
||||
@ -3560,6 +3563,46 @@ func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func SleepCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
sleepTimeLimit := 10000
|
||||
if len(pk.Args) < 1 {
|
||||
return nil, fmt.Errorf("no argument found - usage: /sleep [ms]")
|
||||
}
|
||||
sleepArg := pk.Args[0]
|
||||
sleepArgInt, err := strconv.Atoi(sleepArg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse sleep arg: %v", err)
|
||||
}
|
||||
if sleepArgInt > sleepTimeLimit {
|
||||
return nil, fmt.Errorf("sleep arg is too long, max value is %v", sleepTimeLimit)
|
||||
}
|
||||
time.Sleep(time.Duration(sleepArgInt) * time.Millisecond)
|
||||
update := scbus.MakeUpdatePacket()
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MainViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) < 1 {
|
||||
return nil, fmt.Errorf("no argument found - usage: /mainview [view]")
|
||||
}
|
||||
update := scbus.MakeUpdatePacket()
|
||||
mainViewArg := pk.Args[0]
|
||||
if mainViewArg == sstore.MainViewSession {
|
||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewSession})
|
||||
} else if mainViewArg == sstore.MainViewConnections {
|
||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewConnections})
|
||||
} else if mainViewArg == sstore.MainViewSettings {
|
||||
update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewSettings})
|
||||
} else if mainViewArg == sstore.MainViewHistory {
|
||||
return nil, fmt.Errorf("use /history instead")
|
||||
} else if mainViewArg == sstore.MainViewBookmarks {
|
||||
return nil, fmt.Errorf("use /bookmarks instead")
|
||||
} else {
|
||||
return nil, fmt.Errorf("unrecognized main view")
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, 0)
|
||||
if err != nil {
|
||||
|
@ -74,6 +74,8 @@ const (
|
||||
MainViewSession = "session"
|
||||
MainViewBookmarks = "bookmarks"
|
||||
MainViewHistory = "history"
|
||||
MainViewConnections = "connections"
|
||||
MainViewSettings = "clientsettings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
Loading…
Reference in New Issue
Block a user