mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-11-28 13:10:24 +08:00
* roundtrip image filenames correctly * fix scrolling issue where the thumbs-up/thumbs-down buttons were cut off * update ai-sdk / ai-sdk react to current versions * fix a focus issue when moving to AI panel using ctrl:shift:arrowleft
706 lines
22 KiB
TypeScript
706 lines
22 KiB
TypeScript
// Copyright 2025, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import { WaveAIModel } from "@/app/aipanel/waveai-model";
|
|
import { FocusManager } from "@/app/store/focusManager";
|
|
import {
|
|
atoms,
|
|
createBlock,
|
|
createBlockSplitHorizontally,
|
|
createBlockSplitVertically,
|
|
createTab,
|
|
getAllBlockComponentModels,
|
|
getApi,
|
|
getBlockComponentModel,
|
|
getFocusedBlockId,
|
|
getSettingsKeyAtom,
|
|
globalStore,
|
|
refocusNode,
|
|
replaceBlock,
|
|
WOS,
|
|
} from "@/app/store/global";
|
|
import { TabBarModel } from "@/app/tab/tabbar-model";
|
|
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
|
|
import { deleteLayoutModelForTab, getLayoutModelForStaticTab, NavigateDirection } from "@/layout/index";
|
|
import * as keyutil from "@/util/keyutil";
|
|
import { CHORD_TIMEOUT } from "@/util/sharedconst";
|
|
import { fireAndForget } from "@/util/util";
|
|
import * as jotai from "jotai";
|
|
import { modalsModel } from "./modalmodel";
|
|
|
|
type KeyHandler = (event: WaveKeyboardEvent) => boolean;
|
|
|
|
const simpleControlShiftAtom = jotai.atom(false);
|
|
const globalKeyMap = new Map<string, (waveEvent: WaveKeyboardEvent) => boolean>();
|
|
const globalChordMap = new Map<string, Map<string, KeyHandler>>();
|
|
let globalKeybindingsDisabled = false;
|
|
|
|
// track current chord state and timeout (for resetting)
|
|
let activeChord: string | null = null;
|
|
let chordTimeout: NodeJS.Timeout = null;
|
|
|
|
function resetChord() {
|
|
activeChord = null;
|
|
if (chordTimeout) {
|
|
clearTimeout(chordTimeout);
|
|
chordTimeout = null;
|
|
}
|
|
}
|
|
|
|
function setActiveChord(activeChordArg: string) {
|
|
getApi().setKeyboardChordMode();
|
|
if (chordTimeout) {
|
|
clearTimeout(chordTimeout);
|
|
}
|
|
activeChord = activeChordArg;
|
|
chordTimeout = setTimeout(() => resetChord(), CHORD_TIMEOUT);
|
|
}
|
|
|
|
export function keyboardMouseDownHandler(e: MouseEvent) {
|
|
if (!e.ctrlKey || !e.shiftKey) {
|
|
unsetControlShift();
|
|
}
|
|
}
|
|
|
|
function getFocusedBlockInStaticTab() {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
return focusedNode.data?.blockId;
|
|
}
|
|
|
|
function getSimpleControlShiftAtom() {
|
|
return simpleControlShiftAtom;
|
|
}
|
|
|
|
function setControlShift() {
|
|
globalStore.set(simpleControlShiftAtom, true);
|
|
setTimeout(() => {
|
|
const simpleState = globalStore.get(simpleControlShiftAtom);
|
|
if (simpleState) {
|
|
globalStore.set(atoms.controlShiftDelayAtom, true);
|
|
}
|
|
}, 400);
|
|
}
|
|
|
|
function unsetControlShift() {
|
|
globalStore.set(simpleControlShiftAtom, false);
|
|
globalStore.set(atoms.controlShiftDelayAtom, false);
|
|
}
|
|
|
|
function disableGlobalKeybindings() {
|
|
globalKeybindingsDisabled = true;
|
|
}
|
|
|
|
function enableGlobalKeybindings() {
|
|
globalKeybindingsDisabled = false;
|
|
}
|
|
|
|
function shouldDispatchToBlock(e: WaveKeyboardEvent): boolean {
|
|
if (globalStore.get(atoms.modalOpen)) {
|
|
return false;
|
|
}
|
|
const activeElem = document.activeElement;
|
|
if (activeElem != null && activeElem instanceof HTMLElement) {
|
|
if (activeElem.tagName == "INPUT" || activeElem.tagName == "TEXTAREA" || activeElem.contentEditable == "true") {
|
|
if (activeElem.classList.contains("dummy-focus") || activeElem.classList.contains("dummy")) {
|
|
return true;
|
|
}
|
|
if (keyutil.isInputEvent(e)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function getStaticTabBlockCount(): number {
|
|
const tabId = globalStore.get(atoms.staticTabId);
|
|
const tabORef = WOS.makeORef("tab", tabId);
|
|
const tabAtom = WOS.getWaveObjectAtom<Tab>(tabORef);
|
|
const tabData = globalStore.get(tabAtom);
|
|
return tabData?.blockids?.length ?? 0;
|
|
}
|
|
|
|
function isStaticTabPinned(): boolean {
|
|
const ws = globalStore.get(atoms.workspace);
|
|
const tabId = globalStore.get(atoms.staticTabId);
|
|
return ws.pinnedtabids?.includes(tabId) ?? false;
|
|
}
|
|
|
|
function simpleCloseStaticTab() {
|
|
const ws = globalStore.get(atoms.workspace);
|
|
const tabId = globalStore.get(atoms.staticTabId);
|
|
getApi().closeTab(ws.oid, tabId);
|
|
deleteLayoutModelForTab(tabId);
|
|
}
|
|
|
|
function uxCloseBlock(blockId: string) {
|
|
if (isStaticTabPinned() && getStaticTabBlockCount() === 1) {
|
|
TabBarModel.getInstance().jiggleActivePinnedTab();
|
|
return;
|
|
}
|
|
|
|
const workspaceLayoutModel = WorkspaceLayoutModel.getInstance();
|
|
const isAIPanelOpen = workspaceLayoutModel.getAIPanelVisible();
|
|
if (isAIPanelOpen && getStaticTabBlockCount() === 1) {
|
|
const aiModel = WaveAIModel.getInstance();
|
|
const shouldSwitchToAI = !aiModel.isChatEmpty || aiModel.hasNonEmptyInput();
|
|
if (shouldSwitchToAI) {
|
|
replaceBlock(blockId, { meta: { view: "launcher" } }, false);
|
|
setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const blockAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", blockId));
|
|
const blockData = globalStore.get(blockAtom);
|
|
const isAIFileDiff = blockData?.meta?.view === "aifilediff";
|
|
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const node = layoutModel.getNodeByBlockId(blockId);
|
|
if (node) {
|
|
fireAndForget(() => layoutModel.closeNode(node.id));
|
|
|
|
if (isAIFileDiff && isAIPanelOpen) {
|
|
setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
|
|
}
|
|
}
|
|
}
|
|
|
|
function genericClose() {
|
|
const focusType = FocusManager.getInstance().getFocusType();
|
|
if (focusType === "waveai") {
|
|
WorkspaceLayoutModel.getInstance().setAIPanelVisible(false);
|
|
return;
|
|
}
|
|
if (isStaticTabPinned() && getStaticTabBlockCount() === 1) {
|
|
TabBarModel.getInstance().jiggleActivePinnedTab();
|
|
return;
|
|
}
|
|
|
|
const workspaceLayoutModel = WorkspaceLayoutModel.getInstance();
|
|
const isAIPanelOpen = workspaceLayoutModel.getAIPanelVisible();
|
|
if (isAIPanelOpen && getStaticTabBlockCount() === 1) {
|
|
const aiModel = WaveAIModel.getInstance();
|
|
const shouldSwitchToAI = !aiModel.isChatEmpty || aiModel.hasNonEmptyInput();
|
|
if (shouldSwitchToAI) {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
if (focusedNode) {
|
|
replaceBlock(focusedNode.data.blockId, { meta: { view: "launcher" } }, false);
|
|
setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
const blockCount = getStaticTabBlockCount();
|
|
if (blockCount === 0) {
|
|
simpleCloseStaticTab();
|
|
return;
|
|
}
|
|
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
const blockId = focusedNode?.data?.blockId;
|
|
const blockAtom = blockId ? WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", blockId)) : null;
|
|
const blockData = blockAtom ? globalStore.get(blockAtom) : null;
|
|
const isAIFileDiff = blockData?.meta?.view === "aifilediff";
|
|
|
|
fireAndForget(layoutModel.closeFocusedNode.bind(layoutModel));
|
|
|
|
if (isAIFileDiff && isAIPanelOpen) {
|
|
setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
|
|
}
|
|
}
|
|
|
|
function switchBlockByBlockNum(index: number) {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
if (!layoutModel) {
|
|
return;
|
|
}
|
|
layoutModel.switchNodeFocusByBlockNum(index);
|
|
setTimeout(() => {
|
|
globalRefocus();
|
|
}, 10);
|
|
}
|
|
|
|
function switchBlockInDirection(direction: NavigateDirection) {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusType = FocusManager.getInstance().getFocusType();
|
|
|
|
if (direction === NavigateDirection.Left) {
|
|
const numBlocks = globalStore.get(layoutModel.numLeafs);
|
|
if (focusType === "waveai") {
|
|
return;
|
|
}
|
|
if (numBlocks === 1) {
|
|
FocusManager.getInstance().requestWaveAIFocus();
|
|
setTimeout(() => {
|
|
FocusManager.getInstance().refocusNode();
|
|
}, 10);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (direction === NavigateDirection.Right && focusType === "waveai") {
|
|
FocusManager.getInstance().requestNodeFocus();
|
|
return;
|
|
}
|
|
|
|
const inWaveAI = focusType === "waveai";
|
|
const navResult = layoutModel.switchNodeFocusInDirection(direction, inWaveAI);
|
|
if (navResult.atLeft) {
|
|
FocusManager.getInstance().requestWaveAIFocus();
|
|
setTimeout(() => {
|
|
FocusManager.getInstance().refocusNode();
|
|
}, 10);
|
|
return;
|
|
}
|
|
setTimeout(() => {
|
|
globalRefocus();
|
|
}, 10);
|
|
}
|
|
|
|
function getAllTabs(ws: Workspace): string[] {
|
|
return [...(ws.pinnedtabids ?? []), ...(ws.tabids ?? [])];
|
|
}
|
|
|
|
function switchTabAbs(index: number) {
|
|
console.log("switchTabAbs", index);
|
|
const ws = globalStore.get(atoms.workspace);
|
|
const newTabIdx = index - 1;
|
|
const tabids = getAllTabs(ws);
|
|
if (newTabIdx < 0 || newTabIdx >= tabids.length) {
|
|
return;
|
|
}
|
|
const newActiveTabId = tabids[newTabIdx];
|
|
getApi().setActiveTab(newActiveTabId);
|
|
}
|
|
|
|
function switchTab(offset: number) {
|
|
console.log("switchTab", offset);
|
|
const ws = globalStore.get(atoms.workspace);
|
|
const curTabId = globalStore.get(atoms.staticTabId);
|
|
let tabIdx = -1;
|
|
const tabids = getAllTabs(ws);
|
|
for (let i = 0; i < tabids.length; i++) {
|
|
if (tabids[i] == curTabId) {
|
|
tabIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (tabIdx == -1) {
|
|
return;
|
|
}
|
|
const newTabIdx = (tabIdx + offset + tabids.length) % tabids.length;
|
|
const newActiveTabId = tabids[newTabIdx];
|
|
getApi().setActiveTab(newActiveTabId);
|
|
}
|
|
|
|
function handleCmdI() {
|
|
globalRefocus();
|
|
}
|
|
|
|
function globalRefocusWithTimeout(timeoutVal: number) {
|
|
setTimeout(() => {
|
|
globalRefocus();
|
|
}, timeoutVal);
|
|
}
|
|
|
|
function globalRefocus() {
|
|
if (globalStore.get(atoms.waveWindowType) == "builder") {
|
|
return;
|
|
}
|
|
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
if (focusedNode == null) {
|
|
// focus a node
|
|
layoutModel.focusFirstNode();
|
|
return;
|
|
}
|
|
const blockId = focusedNode?.data?.blockId;
|
|
if (blockId == null) {
|
|
return;
|
|
}
|
|
refocusNode(blockId);
|
|
}
|
|
|
|
function getDefaultNewBlockDef(): BlockDef {
|
|
const adnbAtom = getSettingsKeyAtom("app:defaultnewblock");
|
|
const adnb = globalStore.get(adnbAtom) ?? "term";
|
|
if (adnb == "launcher") {
|
|
return {
|
|
meta: {
|
|
view: "launcher",
|
|
},
|
|
};
|
|
}
|
|
// "term", blank, anything else, fall back to terminal
|
|
const termBlockDef: BlockDef = {
|
|
meta: {
|
|
view: "term",
|
|
controller: "shell",
|
|
},
|
|
};
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
if (focusedNode != null) {
|
|
const blockAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", focusedNode.data?.blockId));
|
|
const blockData = globalStore.get(blockAtom);
|
|
if (blockData?.meta?.view == "term") {
|
|
if (blockData?.meta?.["cmd:cwd"] != null) {
|
|
termBlockDef.meta["cmd:cwd"] = blockData.meta["cmd:cwd"];
|
|
}
|
|
}
|
|
if (blockData?.meta?.connection != null) {
|
|
termBlockDef.meta.connection = blockData.meta.connection;
|
|
}
|
|
}
|
|
return termBlockDef;
|
|
}
|
|
|
|
async function handleCmdN() {
|
|
const blockDef = getDefaultNewBlockDef();
|
|
await createBlock(blockDef);
|
|
}
|
|
|
|
async function handleSplitHorizontal(position: "before" | "after") {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
if (focusedNode == null) {
|
|
return;
|
|
}
|
|
const blockDef = getDefaultNewBlockDef();
|
|
await createBlockSplitHorizontally(blockDef, focusedNode.data.blockId, position);
|
|
}
|
|
|
|
async function handleSplitVertical(position: "before" | "after") {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
if (focusedNode == null) {
|
|
return;
|
|
}
|
|
const blockDef = getDefaultNewBlockDef();
|
|
await createBlockSplitVertically(blockDef, focusedNode.data.blockId, position);
|
|
}
|
|
|
|
let lastHandledEvent: KeyboardEvent | null = null;
|
|
|
|
// returns [keymatch, T]
|
|
function checkKeyMap<T>(waveEvent: WaveKeyboardEvent, keyMap: Map<string, T>): [string, T] {
|
|
for (const key of keyMap.keys()) {
|
|
if (keyutil.checkKeyPressed(waveEvent, key)) {
|
|
const val = keyMap.get(key);
|
|
return [key, val];
|
|
}
|
|
}
|
|
return [null, null];
|
|
}
|
|
|
|
function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean {
|
|
if (globalKeybindingsDisabled) {
|
|
return false;
|
|
}
|
|
const nativeEvent = (waveEvent as any).nativeEvent;
|
|
if (lastHandledEvent != null && nativeEvent != null && lastHandledEvent === nativeEvent) {
|
|
console.log("lastHandledEvent return false");
|
|
return false;
|
|
}
|
|
lastHandledEvent = nativeEvent;
|
|
if (activeChord) {
|
|
console.log("handle activeChord", activeChord);
|
|
// If we're in chord mode, look for the second key.
|
|
const chordBindings = globalChordMap.get(activeChord);
|
|
const [, handler] = checkKeyMap(waveEvent, chordBindings);
|
|
if (handler) {
|
|
resetChord();
|
|
return handler(waveEvent);
|
|
} else {
|
|
// invalid chord; reset state and consume key
|
|
resetChord();
|
|
return true;
|
|
}
|
|
}
|
|
const [chordKeyMatch] = checkKeyMap(waveEvent, globalChordMap);
|
|
if (chordKeyMatch) {
|
|
setActiveChord(chordKeyMatch);
|
|
return true;
|
|
}
|
|
|
|
const [, globalHandler] = checkKeyMap(waveEvent, globalKeyMap);
|
|
if (globalHandler) {
|
|
const handled = globalHandler(waveEvent);
|
|
if (handled) {
|
|
return true;
|
|
}
|
|
}
|
|
if (globalStore.get(atoms.waveWindowType) == "tab") {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
const blockId = focusedNode?.data?.blockId;
|
|
if (blockId != null && shouldDispatchToBlock(waveEvent)) {
|
|
const bcm = getBlockComponentModel(blockId);
|
|
const viewModel = bcm?.viewModel;
|
|
if (viewModel?.keyDownHandler) {
|
|
const handledByBlock = viewModel.keyDownHandler(waveEvent);
|
|
if (handledByBlock) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function registerControlShiftStateUpdateHandler() {
|
|
getApi().onControlShiftStateUpdate((state: boolean) => {
|
|
if (state) {
|
|
setControlShift();
|
|
} else {
|
|
unsetControlShift();
|
|
}
|
|
});
|
|
}
|
|
|
|
function registerElectronReinjectKeyHandler() {
|
|
getApi().onReinjectKey((event: WaveKeyboardEvent) => {
|
|
appHandleKeyDown(event);
|
|
});
|
|
}
|
|
|
|
function tryReinjectKey(event: WaveKeyboardEvent): boolean {
|
|
return appHandleKeyDown(event);
|
|
}
|
|
|
|
function countTermBlocks(): number {
|
|
const allBCMs = getAllBlockComponentModels();
|
|
let count = 0;
|
|
let gsGetBound = globalStore.get.bind(globalStore);
|
|
for (const bcm of allBCMs) {
|
|
const viewModel = bcm.viewModel;
|
|
if (viewModel.viewType == "term" && viewModel.isBasicTerm?.(gsGetBound)) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function registerGlobalKeys() {
|
|
globalKeyMap.set("Cmd:]", () => {
|
|
switchTab(1);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Shift:Cmd:]", () => {
|
|
switchTab(1);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:[", () => {
|
|
switchTab(-1);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Shift:Cmd:[", () => {
|
|
switchTab(-1);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:n", () => {
|
|
handleCmdN();
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:d", () => {
|
|
handleSplitHorizontal("after");
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Shift:Cmd:d", () => {
|
|
handleSplitVertical("after");
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:i", () => {
|
|
handleCmdI();
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:t", () => {
|
|
createTab();
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:w", () => {
|
|
genericClose();
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:Shift:w", () => {
|
|
if (isStaticTabPinned()) {
|
|
TabBarModel.getInstance().jiggleActivePinnedTab();
|
|
return true;
|
|
}
|
|
simpleCloseStaticTab();
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:m", () => {
|
|
const layoutModel = getLayoutModelForStaticTab();
|
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
|
if (focusedNode != null) {
|
|
layoutModel.magnifyNodeToggle(focusedNode.id);
|
|
}
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:ArrowUp", () => {
|
|
switchBlockInDirection(NavigateDirection.Up);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:ArrowDown", () => {
|
|
switchBlockInDirection(NavigateDirection.Down);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:ArrowLeft", () => {
|
|
switchBlockInDirection(NavigateDirection.Left);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:ArrowRight", () => {
|
|
switchBlockInDirection(NavigateDirection.Right);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:k", () => {
|
|
const blockId = getFocusedBlockId();
|
|
if (blockId == null) {
|
|
return true;
|
|
}
|
|
replaceBlock(
|
|
blockId,
|
|
{
|
|
meta: {
|
|
view: "launcher",
|
|
},
|
|
},
|
|
true
|
|
);
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Cmd:g", () => {
|
|
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
|
|
if (bcm.openSwitchConnection != null) {
|
|
bcm.openSwitchConnection();
|
|
return true;
|
|
}
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:i", () => {
|
|
const curMI = globalStore.get(atoms.isTermMultiInput);
|
|
if (!curMI && countTermBlocks() <= 1) {
|
|
// don't turn on multi-input unless there are 2 or more basic term blocks
|
|
return true;
|
|
}
|
|
globalStore.set(atoms.isTermMultiInput, !curMI);
|
|
return true;
|
|
});
|
|
for (let idx = 1; idx <= 9; idx++) {
|
|
globalKeyMap.set(`Cmd:${idx}`, () => {
|
|
switchTabAbs(idx);
|
|
return true;
|
|
});
|
|
globalKeyMap.set(`Ctrl:Shift:c{Digit${idx}}`, () => {
|
|
switchBlockByBlockNum(idx);
|
|
return true;
|
|
});
|
|
globalKeyMap.set(`Ctrl:Shift:c{Numpad${idx}}`, () => {
|
|
switchBlockByBlockNum(idx);
|
|
return true;
|
|
});
|
|
}
|
|
globalKeyMap.set("Ctrl:Shift:c{Digit0}", () => {
|
|
WaveAIModel.getInstance().focusInput();
|
|
return true;
|
|
});
|
|
globalKeyMap.set("Ctrl:Shift:c{Numpad0}", () => {
|
|
WaveAIModel.getInstance().focusInput();
|
|
return true;
|
|
});
|
|
function activateSearch(event: WaveKeyboardEvent): boolean {
|
|
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
|
|
// Ctrl+f is reserved in most shells
|
|
if (event.control && bcm.viewModel.viewType == "term") {
|
|
return false;
|
|
}
|
|
if (bcm.viewModel.searchAtoms) {
|
|
globalStore.set(bcm.viewModel.searchAtoms.isOpen, true);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function deactivateSearch(): boolean {
|
|
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
|
|
if (bcm.viewModel.searchAtoms && globalStore.get(bcm.viewModel.searchAtoms.isOpen)) {
|
|
globalStore.set(bcm.viewModel.searchAtoms.isOpen, false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
globalKeyMap.set("Cmd:f", activateSearch);
|
|
globalKeyMap.set("Escape", () => {
|
|
if (modalsModel.hasOpenModals()) {
|
|
modalsModel.popModal();
|
|
return true;
|
|
}
|
|
if (deactivateSearch()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
globalKeyMap.set("Cmd:Shift:a", () => {
|
|
const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible();
|
|
WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible);
|
|
return true;
|
|
});
|
|
const allKeys = Array.from(globalKeyMap.keys());
|
|
// special case keys, handled by web view
|
|
allKeys.push("Cmd:l", "Cmd:r", "Cmd:ArrowRight", "Cmd:ArrowLeft", "Cmd:o");
|
|
getApi().registerGlobalWebviewKeys(allKeys);
|
|
|
|
const splitBlockKeys = new Map<string, KeyHandler>();
|
|
splitBlockKeys.set("ArrowUp", () => {
|
|
handleSplitVertical("before");
|
|
return true;
|
|
});
|
|
splitBlockKeys.set("ArrowDown", () => {
|
|
handleSplitVertical("after");
|
|
return true;
|
|
});
|
|
splitBlockKeys.set("ArrowLeft", () => {
|
|
handleSplitHorizontal("before");
|
|
return true;
|
|
});
|
|
splitBlockKeys.set("ArrowRight", () => {
|
|
handleSplitHorizontal("after");
|
|
return true;
|
|
});
|
|
globalChordMap.set("Ctrl:Shift:s", splitBlockKeys);
|
|
}
|
|
|
|
function registerBuilderGlobalKeys() {
|
|
globalKeyMap.set("Cmd:w", () => {
|
|
getApi().closeBuilderWindow();
|
|
return true;
|
|
});
|
|
const allKeys = Array.from(globalKeyMap.keys());
|
|
getApi().registerGlobalWebviewKeys(allKeys);
|
|
}
|
|
|
|
function getAllGlobalKeyBindings(): string[] {
|
|
const allKeys = Array.from(globalKeyMap.keys());
|
|
return allKeys;
|
|
}
|
|
|
|
export {
|
|
appHandleKeyDown,
|
|
disableGlobalKeybindings,
|
|
enableGlobalKeybindings,
|
|
getSimpleControlShiftAtom,
|
|
globalRefocus,
|
|
globalRefocusWithTimeout,
|
|
registerBuilderGlobalKeys,
|
|
registerControlShiftStateUpdateHandler,
|
|
registerElectronReinjectKeyHandler,
|
|
registerGlobalKeys,
|
|
tryReinjectKey,
|
|
unsetControlShift,
|
|
uxCloseBlock,
|
|
};
|