Intenational key binding fixes (#234)

* first pass of copy file

* first pass fixing up function

* first pass fixing key press

* fixed up key press parsing

* reverted cmdrunner.go

* fixed cmdrunner.go again :p

* fixed cmdrunner again lol

* Add job status indicators to tabs within a workspace (#232)

Adds job status indicators that will show any updates to running commands while you are focused away from a tab. These will show up as status icons in the tab view.

These indicators will reset for a given tab when you focus back to it.

I've updated the inner formatting of the tab to use flexboxes, allowing the title to display more text when there are no icons to display.

Also includes some miscellaneous for-loop pattern improvements in model.ts and removing of unused variables, etc.

* first pass fixing key press

* added key util check file

* addressed rebase artifacts

* fixed more rebase artifacts

* fixed keybindings

* removed log

* fixed a ton of rebase artifacts

* fixed a ton of rebase artifacts

* added cmd maps to altKey if not on macos

* fixed platform check code and fixed some small bugs
This commit is contained in:
Cole Lashley 2024-01-26 10:50:11 -08:00 committed by GitHub
parent b97423268c
commit 0648d48ba1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 316 additions and 111 deletions

View File

@ -13,6 +13,7 @@ import { RemoteType, StatusIndicatorLevel } from "../../types/types";
import ReactDOM from "react-dom";
import { GlobalModel } from "../../model/model";
import * as appconst from "../appconst";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
@ -714,13 +715,14 @@ class InlineSettingsTextEdit extends React.Component<
@boundMethod
handleKeyDown(e: any): void {
if (e.code == "Enter") {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
e.stopPropagation();
this.confirmChange();
return;
}
if (e.code == "Escape") {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
this.cancelChange();

View File

@ -15,6 +15,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
import customParseFormat from "dayjs/plugin/customParseFormat";
import { Line } from "../line/linecomps";
import { CmdStrCode } from "../common/common";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg";
import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
@ -95,14 +96,14 @@ function formatSessionName(snames: Record<string, string>, sessionId: string): s
}
@mobxReact.observer
class HistoryCheckbox extends React.Component<{ checked: boolean, partialCheck?: boolean, onClick?: () => void }, {}> {
class HistoryCheckbox extends React.Component<{ checked: boolean; partialCheck?: boolean; onClick?: () => void }, {}> {
@boundMethod
clickHandler(): void {
if (this.props.onClick) {
this.props.onClick();
}
}
render() {
if (this.props.checked) {
return <CheckedCheckbox onClick={this.clickHandler} className="history-checkbox checkbox-icon" />;
@ -110,14 +111,18 @@ class HistoryCheckbox extends React.Component<{ checked: boolean, partialCheck?:
if (this.props.partialCheck) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" fill="#D5FEAF" fill-opacity="0.026"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C4 6.89543 4.89543 6 6 6H10C11.1046 6 12 6.89543 12 8C12 9.10457 11.1046 10 10 10H6C4.89543 10 4 9.10457 4 8Z" fill="#58C142"/>
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" stroke="#3B3F3A"/>
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" fill="#D5FEAF" fill-opacity="0.026" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4 8C4 6.89543 4.89543 6 6 6H10C11.1046 6 12 6.89543 12 8C12 9.10457 11.1046 10 10 10H6C4.89543 10 4 9.10457 4 8Z"
fill="#58C142"
/>
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" stroke="#3B3F3A" />
</svg>
);
}
else {
return <div onClick={this.clickHandler} className="history-checkbox state-unchecked"/>
);
} else {
return <div onClick={this.clickHandler} className="history-checkbox state-unchecked" />;
}
}
}
@ -207,7 +212,8 @@ class HistoryView extends React.Component<{}, {}> {
@boundMethod
searchKeyDown(e: any) {
if (e.code == "Enter") {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
GlobalModel.historyViewModel.submitSearch();
return;
@ -452,8 +458,8 @@ class HistoryView extends React.Component<{}, {}> {
<div onClick={this.toggleSessionDropdown}>
<span className="label">
{hvm.searchSessionId.get() == null
? "Limit Workspace"
: formatSessionName(snames, hvm.searchSessionId.get())}
? "Limit Workspace"
: formatSessionName(snames, hvm.searchSessionId.get())}
</span>
<AngleDownIcon className="icon" />
</div>
@ -486,8 +492,8 @@ class HistoryView extends React.Component<{}, {}> {
<div onClick={this.toggleRemoteDropdown}>
<span className="label">
{hvm.searchRemoteId.get() == null
? "Limit Remote"
: formatRemoteName(rnames, { remoteid: hvm.searchRemoteId.get() })}
? "Limit Remote"
: formatRemoteName(rnames, { remoteid: hvm.searchRemoteId.get() })}
</span>
<AngleDownIcon className="icon" />
</div>
@ -513,9 +519,7 @@ class HistoryView extends React.Component<{}, {}> {
</div>
</div>
<div className="fromts">
<div className="fromts-text">
From:&nbsp;
</div>
<div className="fromts-text">From:&nbsp;</div>
<div className="hoverEffect">
<input
type="date"
@ -550,7 +554,10 @@ class HistoryView extends React.Component<{}, {}> {
</div>
<div className={cn("control-bar", "is-top", { "is-hidden": items.length == 0 })}>
<div className="control-checkbox" onClick={this.handleControlCheckbox} title="Toggle Selection">
<HistoryCheckbox checked={numSelected > 0 && numSelected == items.length} partialCheck={numSelected > 0}/>
<HistoryCheckbox
checked={numSelected > 0 && numSelected == items.length}
partialCheck={numSelected > 0}
/>
</div>
<div
className={cn(
@ -562,7 +569,7 @@ class HistoryView extends React.Component<{}, {}> {
>
<span>
<TrashIcon className="trash-icon" title="Purge Selected Items" />
&nbsp;Delete Items
&nbsp;Delete Items
</span>
</div>
<div className="spacer" />
@ -591,7 +598,7 @@ class HistoryView extends React.Component<{}, {}> {
className={cn("history-item", { "is-selected": hvm.selectedItems.get(item.historyid) })}
>
<td className="selectbox" onClick={() => this.handleSelect(item.historyid)}>
<HistoryCheckbox checked={hvm.selectedItems.get(item.historyid)}/>
<HistoryCheckbox checked={hvm.selectedItems.get(item.historyid)} />
</td>
<td className="cmdstr">
<HistoryCmdStr
@ -608,13 +615,31 @@ class HistoryView extends React.Component<{}, {}> {
<td className="ts text-standard">{getHistoryViewTs(nowDate, item.ts)}</td>
<td className="downarrow" onClick={() => this.activateItem(item.historyid)}>
<If condition={activeItemId != item.historyid}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M12.1297 6.62492C12.3999 6.93881 12.3645 7.41237 12.0506 7.68263L8.48447 10.7531C8.20296 10.9955 7.78645 10.9952 7.50519 10.7526L3.94636 7.68213C3.63274 7.41155 3.59785 6.93796 3.86843 6.62434C4.13901 6.31072 4.6126 6.27583 4.92622 6.54641L7.99562 9.19459L11.0719 6.54591C11.3858 6.27565 11.8594 6.31102 12.1297 6.62492Z" fill="#C3C8C2"/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M12.1297 6.62492C12.3999 6.93881 12.3645 7.41237 12.0506 7.68263L8.48447 10.7531C8.20296 10.9955 7.78645 10.9952 7.50519 10.7526L3.94636 7.68213C3.63274 7.41155 3.59785 6.93796 3.86843 6.62434C4.13901 6.31072 4.6126 6.27583 4.92622 6.54641L7.99562 9.19459L11.0719 6.54591C11.3858 6.27565 11.8594 6.31102 12.1297 6.62492Z"
fill="#C3C8C2"
/>
</svg>
</If>
<If condition={activeItemId == item.historyid}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3.87035 9.37508C3.60009 9.06119 3.63546 8.58763 3.94936 8.31737L7.51553 5.24692C7.79704 5.00455 8.21355 5.00476 8.49481 5.24742L12.0536 8.31787C12.3673 8.58845 12.4022 9.06204 12.1316 9.37566C11.861 9.68928 11.3874 9.72417 11.0738 9.45359L8.00438 6.80541L4.92806 9.45409C4.61416 9.72435 4.14061 9.68898 3.87035 9.37508Z" fill="#C3C8C2"/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M3.87035 9.37508C3.60009 9.06119 3.63546 8.58763 3.94936 8.31737L7.51553 5.24692C7.79704 5.00455 8.21355 5.00476 8.49481 5.24742L12.0536 8.31787C12.3673 8.58845 12.4022 9.06204 12.1316 9.37566C11.861 9.68928 11.3874 9.72417 11.0738 9.45359L8.00438 6.80541L4.92806 9.45409C4.61416 9.72435 4.14061 9.68898 3.87035 9.37508Z"
fill="#C3C8C2"
/>
</svg>
</If>
</td>

View File

@ -13,6 +13,7 @@ import { TextAreaInput } from "./textareainput";
import { If, For } from "tsx-control-statements/components";
import type { OpenAICmdInfoChatMessageType } from "../../../types/types";
import { Markdown } from "../../common/common";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../../util/keyutil";
@mobxReact.observer
class AIChat extends React.Component<{}, {}> {
@ -76,8 +77,8 @@ class AIChat extends React.Component<{}, {}> {
let inputModel = model.inputModel;
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
let resetCodeSelect = !ctrlMod;
if (e.code == "Enter") {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
if (!ctrlMod) {
if (inputModel.getCodeSelectSelectedIndex() == -1) {
@ -91,17 +92,18 @@ class AIChat extends React.Component<{}, {}> {
e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end");
}
}
if (e.code == "Escape") {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
inputModel.closeAIAssistantChat();
}
if (e.code == "KeyL" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:l")) {
e.preventDefault();
e.stopPropagation();
inputModel.clearAIAssistantChat();
}
if (e.code == "ArrowUp") {
if (checkKeyPressed(waveEvent, "ArrowUp")) {
if (this.getLinePos(e.target).linePos > 1) {
// normal up arrow
return;
@ -110,7 +112,7 @@ class AIChat extends React.Component<{}, {}> {
inputModel.codeSelectSelectNextOldestCodeBlock();
resetCodeSelect = false;
}
if (e.code == "ArrowDown") {
if (checkKeyPressed(waveEvent, "ArrowDown")) {
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
return;
}

View File

@ -13,6 +13,7 @@ import { GlobalModel, GlobalCommandRunner, Screen } from "../../../model/model";
import { getMonoFontSize } from "../../../util/textmeasure";
import { isModKeyPress, hasNoModifiers } from "../../../util/util";
import * as appconst from "../../appconst";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../../util/keyutil";
type OV<T> = mobx.IObservableValue<T>;
@ -177,12 +178,13 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
let curLine = inputModel.getCurLine();
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
let lastTab = this.lastTab;
this.lastTab = e.code == "Tab";
this.lastTab = checkKeyPressed(waveEvent, "Tab");
let lastHist = this.lastHistoryUpDown;
this.lastHistoryUpDown = false;
if (e.code == "Tab") {
if (checkKeyPressed(waveEvent, "Tab")) {
e.preventDefault();
if (lastTab) {
GlobalModel.submitCommand(
@ -204,7 +206,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
return;
}
}
if (e.code == "Enter") {
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
if (!ctrlMod) {
if (GlobalModel.inputModel.isEmpty()) {
@ -224,7 +226,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
GlobalModel.inputModel.setCurLine(e.target.value);
return;
}
if (e.code == "Escape") {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
let inputModel = GlobalModel.inputModel;
@ -235,50 +237,50 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
inputModel.closeAIAssistantChat();
return;
}
if (e.code == "KeyE" && e.getModifierState("Meta")) {
if (checkKeyPressed(waveEvent, "Cmd:e")) {
e.preventDefault();
e.stopPropagation();
let inputModel = GlobalModel.inputModel;
inputModel.toggleExpandInput();
}
if (e.code == "KeyC" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:c")) {
e.preventDefault();
inputModel.resetInput();
return;
}
if (e.code == "KeyU" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:u")) {
e.preventDefault();
this.controlU();
return;
}
if (e.code == "KeyP" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:p")) {
e.preventDefault();
this.controlP();
return;
}
if (e.code == "KeyN" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:n")) {
e.preventDefault();
this.controlN();
return;
}
if (e.code == "KeyW" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:w")) {
e.preventDefault();
this.controlW();
return;
}
if (e.code == "KeyY" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:y")) {
e.preventDefault();
this.controlY();
return;
}
if (e.code == "KeyR" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:r")) {
e.preventDefault();
inputModel.openHistory();
return;
}
if ((e.code == "ArrowUp" || e.code == "ArrowDown") && hasNoModifiers(e)) {
if (checkKeyPressed(waveEvent, "ArrowUp") || checkKeyPressed(waveEvent, "ArrowDown")) {
if (!inputModel.isHistoryLoaded()) {
if (e.code == "ArrowUp") {
if (checkKeyPressed(waveEvent, "ArrowUp")) {
this.lastHistoryUpDown = true;
inputModel.loadHistory(false, 1, "screen");
}
@ -286,7 +288,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
}
// invisible history movement
let linePos = this.getLinePos(e.target);
if (e.code == "ArrowUp") {
if (checkKeyPressed(waveEvent, "ArrowUp")) {
if (!lastHist && linePos.linePos > 1) {
// regular arrow
return;
@ -296,7 +298,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
this.lastHistoryUpDown = true;
return;
}
if (e.code == "ArrowDown") {
if (checkKeyPressed(waveEvent, "ArrowDown")) {
if (!lastHist && linePos.linePos < linePos.numLines) {
// regular arrow
return;
@ -307,16 +309,16 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
return;
}
}
if (e.code == "PageUp" || e.code == "PageDown") {
if (checkKeyPressed(waveEvent, "PageUp") || checkKeyPressed(waveEvent, "PageDown")) {
e.preventDefault();
let infoScroll = inputModel.hasScrollingInfoMsg();
if (infoScroll) {
let div = document.querySelector(".cmd-input-info");
let amt = pageSize(div);
scrollDiv(div, e.code == "PageUp" ? -amt : amt);
scrollDiv(div, checkKeyPressed(waveEvent, "PageUp") ? -amt : amt);
}
}
if (e.code == "Space" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:Space")) {
e.preventDefault();
inputModel.openAIAssistantChat();
}
@ -338,32 +340,29 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
@boundMethod
onHistoryKeyDown(e: any) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
let inputModel = GlobalModel.inputModel;
if (e.code == "Escape") {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
inputModel.resetHistory();
return;
}
if (e.code == "Enter") {
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
inputModel.grabSelectedHistoryItem();
return;
}
if (e.code == "KeyG" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:g")) {
e.preventDefault();
inputModel.resetInput();
return;
}
if (e.code == "KeyC" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:c")) {
e.preventDefault();
inputModel.resetInput();
return;
}
if (
e.code == "KeyR" &&
(e.getModifierState("Meta") || e.getModifierState("Control")) &&
!e.getModifierState("Shift")
) {
if (checkKeyPressed(waveEvent, "Cmd:r") || checkKeyPressed(waveEvent, "Ctrl:r")) {
e.preventDefault();
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
if (opts.limitRemote) {
@ -376,7 +375,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
inputModel.setHistoryQueryOpts(opts);
return;
}
if (e.code == "KeyS" && (e.getModifierState("Meta") || e.getModifierState("Control"))) {
if (checkKeyPressed(waveEvent, "Cmd:s") || checkKeyPressed(waveEvent, "Ctrl:s")) {
e.preventDefault();
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
let htype = opts.queryType;
@ -390,26 +389,26 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
inputModel.setHistoryType(htype);
return;
}
if (e.code == "Tab") {
if (checkKeyPressed(waveEvent, "Tab")) {
e.preventDefault();
return;
}
if (e.code == "ArrowUp" || e.code == "ArrowDown") {
if (checkKeyPressed(waveEvent, "ArrowUp") || checkKeyPressed(waveEvent, "ArrowDown")) {
e.preventDefault();
inputModel.moveHistorySelection(e.code == "ArrowUp" ? 1 : -1);
inputModel.moveHistorySelection(checkKeyPressed(waveEvent, "ArrowUp") ? 1 : -1);
return;
}
if (e.code == "PageUp" || e.code == "PageDown") {
if (checkKeyPressed(waveEvent, "PageUp") || checkKeyPressed(waveEvent, "PageDown")) {
e.preventDefault();
inputModel.moveHistorySelection(e.code == "PageUp" ? 10 : -10);
inputModel.moveHistorySelection(checkKeyPressed(waveEvent, "PageUp") ? 10 : -10);
return;
}
if (e.code == "KeyP" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:p")) {
e.preventDefault();
inputModel.moveHistorySelection(1);
return;
}
if (e.code == "KeyN" && e.getModifierState("Control")) {
if (checkKeyPressed(waveEvent, "Ctrl:n")) {
e.preventDefault();
inputModel.moveHistorySelection(-1);
return;

View File

@ -12,6 +12,8 @@ import * as winston from "winston";
import * as util from "util";
import { sprintf } from "sprintf-js";
import { v4 as uuidv4 } from "uuid";
import { checkKeyPressed, adaptFromElectronKeyEvent, setKeyUtilPlatform } from "../util/keyutil";
import { platform } from "os";
const WaveAppPathVarName = "WAVETERM_APP_PATH";
const WaveDevVarName = "WAVETERM_DEV";
@ -189,7 +191,7 @@ let menuTemplate = [
},
{
label: "File",
submenu: [{ role: "close" },],
submenu: [{ role: "close" }],
},
{
role: "editMenu",
@ -244,6 +246,7 @@ function shFrameNavHandler(event: any, url: any) {
function createMainWindow(clientData) {
let bounds = calcBounds(clientData);
setKeyUtilPlatform(platform());
let win = new electron.BrowserWindow({
x: bounds.x,
y: bounds.y,
@ -261,6 +264,7 @@ function createMainWindow(clientData) {
let indexHtml = isDev ? "index-dev.html" : "index.html";
win.loadFile(path.join(getAppBasePath(), "public", indexHtml));
win.webContents.on("before-input-event", (e, input) => {
let waveEvent = adaptFromElectronKeyEvent(input);
if (win.isFocused()) {
wasActive = true;
}
@ -268,12 +272,12 @@ function createMainWindow(clientData) {
return;
}
let mods = getMods(input);
if (input.code == "KeyT" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:t")) {
win.webContents.send("t-cmd", mods);
e.preventDefault();
return;
}
if (input.code == "KeyI" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:i")) {
e.preventDefault();
if (!input.alt) {
win.webContents.send("i-cmd", mods);
@ -283,35 +287,35 @@ function createMainWindow(clientData) {
return;
}
if (input.code == "KeyR" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:r")) {
if (input.shift) {
e.preventDefault();
win.reload();
}
return;
}
if (input.code == "KeyL" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:l")) {
win.webContents.send("l-cmd", mods);
e.preventDefault();
return;
}
if (input.code == "KeyW" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:w")) {
e.preventDefault();
win.webContents.send("w-cmd", mods);
return;
}
if (input.code == "KeyH" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:h")) {
win.webContents.send("h-cmd", mods);
e.preventDefault();
return;
}
if (input.code == "KeyP" && input.meta) {
if (checkKeyPressed(waveEvent, "Cmd:p")) {
win.webContents.send("p-cmd", mods);
e.preventDefault();
return;
}
if (input.meta && (input.code == "ArrowUp" || input.code == "ArrowDown")) {
if (input.code == "ArrowUp") {
if (checkKeyPressed(waveEvent, "Cmd:ArrowUp") || checkKeyPressed(waveEvent, "Cmd:ArrowDown")) {
if (checkKeyPressed(waveEvent, "Cmd:ArrowUp")) {
win.webContents.send("meta-arrowup");
} else {
win.webContents.send("meta-arrowdown");
@ -319,8 +323,8 @@ function createMainWindow(clientData) {
e.preventDefault();
return;
}
if (input.meta && (input.code == "PageUp" || input.code == "PageDown")) {
if (input.code == "PageUp") {
if (checkKeyPressed(waveEvent, "Cmd:PageUp") || checkKeyPressed(waveEvent, "Cmd:PageDown")) {
if (checkKeyPressed(waveEvent, "Cmd:PageUp")) {
win.webContents.send("meta-pageup");
} else {
win.webContents.send("meta-pagedown");
@ -336,8 +340,8 @@ function createMainWindow(clientData) {
e.preventDefault();
win.webContents.send("digit-cmd", { digit: digitNum }, mods);
}
if ((input.code == "BracketRight" || input.code == "BracketLeft") && input.meta) {
let rel = input.code == "BracketRight" ? 1 : -1;
if (checkKeyPressed(waveEvent, "Cmd:[") || checkKeyPressed(waveEvent, "Cmd:]")) {
let rel = checkKeyPressed(waveEvent, "Cmd:]") ? 1 : -1;
win.webContents.send("bracket-cmd", { relative: rel }, mods);
e.preventDefault();
return;

View File

@ -81,6 +81,7 @@ import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil";
import { MagicLayout } from "../app/magiclayout";
import { modalsRegistry } from "../app/common/modals/registry";
import * as appconst from "../app/appconst";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } from "../util/keyutil";
dayjs.extend(customParseFormat);
dayjs.extend(localizedFormat);
@ -806,41 +807,48 @@ class Screen {
}
termCustomKeyHandlerInternal(e: any, termWrap: TermWrap): void {
if (e.code == "ArrowUp") {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "ArrowUp")) {
termWrap.terminal.scrollLines(-1);
return;
}
if (e.code == "ArrowDown") {
if (checkKeyPressed(waveEvent, "ArrowDown")) {
termWrap.terminal.scrollLines(1);
return;
}
if (e.code == "PageUp") {
if (checkKeyPressed(waveEvent, "PageUp")) {
termWrap.terminal.scrollPages(-1);
return;
}
if (e.code == "PageDown") {
if (checkKeyPressed(waveEvent, "PageDown")) {
termWrap.terminal.scrollPages(1);
return;
}
}
isTermCapturedKey(e: any): boolean {
let keys = ["ArrowUp", "ArrowDown", "PageUp", "PageDown"];
if (keys.includes(e.code) && keyHasNoMods(e)) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (
checkKeyPressed(waveEvent, "ArrowUp") ||
checkKeyPressed(waveEvent, "ArrowDown") ||
checkKeyPressed(waveEvent, "PageUp") ||
checkKeyPressed(waveEvent, "PageDown")
) {
return true;
}
return false;
}
termCustomKeyHandler(e: any, termWrap: TermWrap): boolean {
if (e.type == "keypress" && e.code == "KeyC" && e.shiftKey && e.ctrlKey) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (e.type == "keypress" && checkKeyPressed(waveEvent, "Ctrl:Shift:c")) {
e.stopPropagation();
e.preventDefault();
let sel = termWrap.terminal.getSelection();
navigator.clipboard.writeText(sel);
return false;
}
if (e.type == "keypress" && e.code == "KeyV" && e.shiftKey && e.ctrlKey) {
if (e.type == "keypress" && checkKeyPressed(waveEvent, "Ctrl:Shift:v")) {
e.stopPropagation();
e.preventDefault();
let p = navigator.clipboard.readText();
@ -2526,7 +2534,8 @@ class HistoryViewModel {
}
handleDocKeyDown(e: any): void {
if (e.code == "Escape") {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.closeView();
return;
@ -2766,7 +2775,8 @@ class BookmarksModel {
}
handleDocKeyDown(e: any): void {
if (e.code == "Escape") {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
if (this.editingBookmark.get() != null) {
this.cancelEdit();
@ -2778,7 +2788,7 @@ class BookmarksModel {
if (this.editingBookmark.get() != null) {
return;
}
if (e.code == "Backspace" || e.code == "Delete") {
if (checkKeyPressed(waveEvent, "Backspace") || checkKeyPressed(waveEvent, "Delete")) {
if (this.activeBookmark.get() == null) {
return;
}
@ -2786,7 +2796,13 @@ class BookmarksModel {
this.handleDeleteBookmark(this.activeBookmark.get());
return;
}
if (e.code == "ArrowUp" || e.code == "ArrowDown" || e.code == "PageUp" || e.code == "PageDown") {
if (
checkKeyPressed(waveEvent, "ArrowUp") ||
checkKeyPressed(waveEvent, "ArrowDown") ||
checkKeyPressed(waveEvent, "PageUp") ||
checkKeyPressed(waveEvent, "PageDown")
) {
e.preventDefault();
if (this.bookmarks.length == 0) {
return;
@ -2810,14 +2826,14 @@ class BookmarksModel {
})();
return;
}
if (e.code == "Enter") {
if (checkKeyPressed(waveEvent, "Enter")) {
if (this.activeBookmark.get() == null) {
return;
}
this.useBookmark(this.activeBookmark.get());
return;
}
if (e.code == "KeyE") {
if (checkKeyPressed(waveEvent, "e")) {
if (this.activeBookmark.get() == null) {
return;
}
@ -2825,7 +2841,7 @@ class BookmarksModel {
this.handleEditBookmark(this.activeBookmark.get());
return;
}
if (e.code == "KeyC") {
if (checkKeyPressed(waveEvent, "c")) {
if (this.activeBookmark.get() == null) {
return;
}
@ -3366,6 +3382,7 @@ class Model {
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
name: "model-wavesrv-running",
});
this.platform = this.getPlatform();
this.termFontSize = mobx.computed(() => {
let cdata = this.clientData.get();
if (cdata == null || cdata.feopts == null || cdata.feopts.termfontsize == null) {
@ -3409,9 +3426,14 @@ class Model {
return this.platform;
}
this.platform = getApi().getPlatform();
setKeyUtilPlatform(this.platform);
return this.platform;
}
testGlobalModel() {
return "";
}
needsTos(): boolean {
let cdata = this.clientData.get();
if (cdata == null) {
@ -3553,16 +3575,17 @@ class Model {
}
docKeyDownHandler(e: KeyboardEvent) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (isModKeyPress(e)) {
return;
}
if (this.alertMessage.get() != null) {
if (e.code == "Escape") {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.cancelAlert();
return;
}
if (e.code == "Enter") {
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
this.confirmAlert();
return;
@ -3585,7 +3608,7 @@ class Model {
this.historyViewModel.handleDocKeyDown(e);
return;
}
if (e.code == "Escape") {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
if (this.activeMainView.get() == "webshare") {
this.showSessionView();
@ -3601,16 +3624,11 @@ class Model {
}
return;
}
if (e.code == "KeyB" && e.getModifierState("Meta")) {
if (checkKeyPressed(waveEvent, "Cmd:b")) {
e.preventDefault();
GlobalCommandRunner.bookmarksView();
}
if (
this.activeMainView.get() == "session" &&
e.code == "KeyS" &&
e.getModifierState("Meta") &&
e.getModifierState("Control")
) {
if (this.activeMainView.get() == "session" && checkKeyPressed(waveEvent, "Cmd:Ctrl:s")) {
e.preventDefault();
let activeScreen = this.getActiveScreen();
if (activeScreen != null) {
@ -3622,7 +3640,7 @@ class Model {
}
}
}
if (e.code == "KeyD" && e.getModifierState("Meta")) {
if (checkKeyPressed(waveEvent, "Cmd:d")) {
let ranDelete = this.deleteActiveLine();
if (ranDelete) {
e.preventDefault();

View File

@ -10,6 +10,7 @@ import { GlobalModel, GlobalCommandRunner } from "../../model/model";
import Split from "react-split-it";
import loader from "@monaco-editor/loader";
loader.config({ paths: { vs: "./node_modules/monaco-editor/min/vs" } });
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
import "./code.less";
@ -152,17 +153,18 @@ class SourceCodeRenderer extends React.Component<
this.setInitialLanguage(editor);
this.setEditorHeight();
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
if (e.code === "KeyS" && e.metaKey && this.state.isSave) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
if (checkKeyPressed(waveEvent, "Cmd:s") && this.state.isSave) {
e.preventDefault();
e.stopPropagation();
this.doSave();
}
if (e.code === "KeyD" && e.metaKey) {
if (checkKeyPressed(waveEvent, "Cmd:d")) {
e.preventDefault();
e.stopPropagation();
this.doClose();
}
if (e.code === "KeyP" && e.metaKey) {
if (checkKeyPressed(waveEvent, "Cmd:p")) {
e.preventDefault();
e.stopPropagation();
this.togglePreview();

153
src/util/keyutil.ts Normal file
View File

@ -0,0 +1,153 @@
import * as React from "react";
import * as electron from "electron";
type KeyPressDecl = {
mods: {
Cmd?: boolean;
Option?: boolean;
Shift?: boolean;
Ctrl?: boolean;
};
key: string;
};
var PLATFORM: string;
const PlatformMacOS: string = "darwin";
function setKeyUtilPlatform(platform: string) {
PLATFORM = platform;
}
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 {
rtn.key = key;
if (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
rtn.mods.Shift = true;
} else if (key == " ") {
rtn.key = "Space";
// we allow " " and "Space" to be mapped to Space key
}
}
}
}
return rtn;
}
function checkKeyPressed(event: WaveKeyboardEvent, description: string): boolean {
let keyPress = parseKeyDescription(description);
if (keyPress.mods.Option && !event.alt) {
return false;
}
if (keyPress.mods.Cmd && !event.cmd) {
return false;
}
if (keyPress.mods.Shift && !event.shift) {
return false;
}
if (keyPress.mods.Ctrl && !event.control) {
return false;
}
let eventKey = event.key;
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 (descKey != eventKey) {
return false;
}
return true;
}
type ModKeyStrs = "Cmd" | "Option" | "Shift" | "Ctrl";
interface WaveKeyboardEvent {
type: string;
/**
* Equivalent to KeyboardEvent.key.
*/
key: string;
/**
* Equivalent to KeyboardEvent.code.
*/
code: string;
/**
* Equivalent to KeyboardEvent.shiftKey.
*/
shift: boolean;
/**
* Equivalent to KeyboardEvent.controlKey.
*/
control: boolean;
/**
* Equivalent to KeyboardEvent.altKey.
*/
alt: boolean;
/**
* Equivalent to KeyboardEvent.metaKey.
*/
cmd: boolean;
repeat: boolean;
/**
* Equivalent to KeyboardEvent.location.
*/
location: number;
}
function adaptFromReactOrNativeKeyEvent(event: React.KeyboardEvent | KeyboardEvent): WaveKeyboardEvent {
let rtn: WaveKeyboardEvent = {} as WaveKeyboardEvent;
rtn.control = event.ctrlKey;
rtn.shift = event.shiftKey;
if (PLATFORM == PlatformMacOS) {
rtn.cmd = event.metaKey;
rtn.alt = event.altKey;
} else {
rtn.cmd = 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;
if (PLATFORM == PlatformMacOS) {
rtn.cmd = event.meta;
rtn.alt = event.alt;
} else {
rtn.cmd = 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, setKeyUtilPlatform };
export type { WaveKeyboardEvent };