Compare commits

..

12 Commits

Author SHA1 Message Date
Sylvia Crowe 01c9a3444a fix: integrate skeema knownhosts fix
This fix makes it possible to differentiate between keys when multiple
are provided by the remote server. It does not solve the case of
multiple keys of the same type being shared, but it handles multiple
keys of different types being shared, which is much more common.
2024-08-28 07:14:35 -07:00
Cole Lashley 69b9ce33b3 User input keybindings fix (#494)
* added user input fix and focusVal optoinal in footer

* small fixes and removed focusVal for now
2024-03-26 15:05:18 -07:00
Evan Simkowitz a121bd4bb5 Move bookmarks, history, playbook, and telemetry code out of sstore (#493)
* break out telemetry and playbook

* break out bookmarks

* add license disclaimers
2024-03-25 20:20:52 -07:00
dependabot[bot] dcc7b2943e Bump webpack-dev-middleware from 5.3.3 to 5.3.4 (#490)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 09:55:15 -07:00
Cole Lashley bfa549ef22 Term keybindings (#482)
* added term keybindings

* removed logs and added a dump logs flag in keyutil

* un capture keys that we aren't using

* moved keybindings to inside terminal
2024-03-22 17:59:29 -07:00
Cole Lashley 376e339dfe Keybind UI utils (#487)
* added utils to get all of the keybinds and pretty print them, for display purposes

* added info section

* clarifying meta and alt behavior

* changed keybinduidescription to keybind config
2024-03-22 17:58:06 -07:00
Cole Lashley 646e260488 Code edit keybindings (#483)
* added codeedit keybindings

* removed logs

* added computed property to codeedit
2024-03-22 17:55:45 -07:00
Cole Lashley d3c48e3a3e Control keybindings (#489)
* fixed inline settings textedit and added datepicker keybindings

* added dropdown keybindings

* added observable to make sure that keybindings aren't double registered

* added enter and escape keybindings for datepicker

* dropdown closure fix
2024-03-22 17:53:27 -07:00
Cole Lashley c0c53edb84 Modal keybindings (#484)
* first draft at modal keybindings

* added modal keybindings and inline settings text edit keybindings

* added switch for keybindings in modal footer

* removed logs

* remove another console.log

* fix userinput keybindings -- should be generic:cancel for 2nd one
2024-03-21 23:10:02 -07:00
Cole Lashley 923cf71e0a Ai chat keybindings (#481)
* added aichat keybindings

* remove console.log
2024-03-21 18:30:05 -07:00
Cole Lashley f705a4df0a Textarea keybindings (#470)
* first commit for textarea keybindings

* added empty onkeydown to get around default behavior for now

* added history keybindings

* removed tab special case

* fix two small issues with keybindings
2024-03-21 18:26:21 -07:00
Cole Lashley 75a82de5bf Adding engineered query to chat command (#488)
* added engineered query to chat command

* let ai know responses are rendered in markdown
2024-03-21 18:00:55 -07:00
40 changed files with 2034 additions and 1461 deletions
+64 -2
View File
@@ -1,7 +1,8 @@
[
{
"command": "system:toggleDeveloperTools",
"keys": ["Cmd:Option:i"]
"keys": ["Cmd:Option:i"],
"info": "Opens the chrome developer tool menu"
},
{
"command": "system:hideWindow",
@@ -15,10 +16,62 @@
"command": "generic:confirm",
"keys": ["Enter"]
},
{
"command": "generic:expandTextInput",
"keys": ["Shift:Enter", "Ctrl:Enter"]
},
{
"command": "generic:deleteItem",
"keys": ["Backspace", "Delete"]
},
{
"command": "generic:space",
"keys": ["Space"]
},
{
"command": "generic:tab",
"keys": ["Tab"]
},
{
"command": "generic:numpad-0",
"keys": ["0"]
},
{
"command": "generic:numpad-1",
"keys": ["1"]
},
{
"command": "generic:numpad-2",
"keys": ["2"]
},
{
"command": "generic:numpad-3",
"keys": ["3"]
},
{
"command": "generic:numpad-4",
"keys": ["4"]
},
{
"command": "generic:numpad-5",
"keys": ["5"]
},
{
"command": "generic:numpad-6",
"keys": ["6"]
},
{
"command": "generic:numpad-7",
"keys": ["7"]
},
{
"command": "generic:numpad-8",
"keys": ["8"]
},
{
"command": "generic:numpad-9",
"keys": ["9"]
},
{
"command": "generic:selectAbove",
"keys": ["ArrowUp"]
@@ -27,6 +80,14 @@
"command": "generic:selectBelow",
"keys": ["ArrowDown"]
},
{
"command": "generic:selectLeft",
"keys": ["ArrowLeft"]
},
{
"command": "generic:selectRight",
"keys": ["ArrowRight"]
},
{
"command": "generic:selectPageAbove",
"keys": ["PageUp"]
@@ -67,7 +128,8 @@
},
{
"command": "app:restartCommand",
"keys": ["Cmd:r"]
"keys": ["Cmd:r"],
"info": "Restarts the command running in the current selected line"
},
{
"command": "app:restartLastCommand",
+14
View File
@@ -134,14 +134,28 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+6 -6
View File
@@ -5,14 +5,14 @@
<base href="../" />
<script charset="UTF-8" src="dist-dev/waveterm.js"></script>
<link rel="stylesheet" href="public/bulma-0.9.4.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css">
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css">
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css">
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css">
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css">
<link rel="stylesheet" href="dist-dev/waveterm.css" />
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/default.css" />
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/default.css">
</head>
<body>
<div id="measure"></div>
+89 -36
View File
@@ -1,9 +1,12 @@
import React, { useState, useEffect, useRef, createRef } from "react";
import * as mobx from "mobx";
import ReactDOM from "react-dom";
import dayjs from "dayjs";
import cs from "classnames";
import { Button } from "@/elements";
import { If } from "tsx-control-statements/components";
import { GlobalModel } from "@/models";
import { v4 as uuidv4 } from "uuid";
import "./datepicker.less";
@@ -36,6 +39,10 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
MM: selDate.format("MM"),
DD: selDate.format("DD"),
});
let curUuid = uuidv4();
let keybindsRegistered = mobx.observable.box(false, {
name: "datepicker-keybinds-registered",
});
useEffect(() => {
inputRefs.current = {
@@ -273,6 +280,11 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
setShowYearAccordion(false);
};
const closeModal = () => {
setIsOpen(false);
setShowYearAccordion(false);
};
const dayPickerModal = isOpen
? ReactDOM.createPortal(
<div ref={modalRef} className="day-picker-modal" style={calculatePosition()}>
@@ -300,13 +312,13 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
}
};
const handleArrowNavigation = (event, currentPart) => {
const handleArrowNavigation = (key, currentPart) => {
const currentIndex = formatParts.indexOf(currentPart);
let targetInput;
if (event.key === "ArrowLeft" && currentIndex > 0) {
if (key == "ArrowLeft" && currentIndex > 0) {
targetInput = inputRefs.current[formatParts[currentIndex - 1]].current;
} else if (event.key === "ArrowRight" && currentIndex < formatParts.length - 1) {
} else if (key == "ArrowRight" && currentIndex < formatParts.length - 1) {
targetInput = inputRefs.current[formatParts[currentIndex + 1]].current;
}
@@ -315,51 +327,91 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
}
};
const handleKeyDown = (event, currentPart) => {
const key = event.key;
const handleKeyDown = (event, currentPart) => {};
if (key === "ArrowLeft" || key === "ArrowRight") {
// Handle arrow navigation without selecting text
handleArrowNavigation(event, currentPart);
const handleFocus = (event, part) => {
event.target.select();
registerKeybindings(event, part);
};
const registerKeybindings = (event: any, part: string) => {
if (keybindsRegistered.get() == true) {
return;
}
if (key === " ") {
// Handle spacebar press to toggle the modal
mobx.action(() => {
keybindsRegistered.set(true);
})();
let keybindManager = GlobalModel.keybindManager;
let domain = "datepicker-" + curUuid + "-" + part;
keybindManager.registerKeybinding("control", domain, "generic:selectLeft", (waveEvent) => {
handleArrowNavigation("ArrowLeft", part);
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:selectRight", (waveEvent) => {
handleArrowNavigation("ArrowRight", part);
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:space", (waveEvent) => {
toggleModal();
return;
}
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:confirm", (waveEvent) => {
toggleModal();
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:cancel", (waveEvent) => {
closeModal();
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:tab", (waveEvent) => {
handleArrowNavigation("ArrowRight", part);
return true;
});
for (let numpadKey = 0; numpadKey <= 9; numpadKey++) {
keybindManager.registerKeybinding(
"control",
domain,
"generic:numpad-" + numpadKey.toString(),
(waveEvent) => {
let currentPart = part;
const maxLength = currentPart === "YYYY" ? 4 : 2;
const newValue = event.target.value.length < maxLength ? event.target.value + numpadKey : numpadKey;
let selectionTimeoutId = null;
handleDatePartChange(currentPart, newValue);
if (key.match(/[0-9]/)) {
// Handle numeric keys
event.preventDefault();
const maxLength = currentPart === "YYYY" ? 4 : 2;
const newValue = event.target.value.length < maxLength ? event.target.value + key : key;
let selectionTimeoutId = null;
handleDatePartChange(currentPart, newValue);
// Clear any existing timeout
if (selectionTimeoutId !== null) {
clearTimeout(selectionTimeoutId);
}
// Clear any existing timeout
if (selectionTimeoutId !== null) {
clearTimeout(selectionTimeoutId);
}
// Re-focus and select the input after state update
selectionTimeoutId = setTimeout(() => {
event.target.focus();
event.target.select();
}, 0);
// Re-focus and select the input after state update
selectionTimeoutId = setTimeout(() => {
event.target.focus();
event.target.select();
}, 0);
return true;
}
);
}
};
const handleFocus = (event) => {
event.target.select();
const handleBlur = (event, part) => {
unregisterKeybindings(part);
};
const unregisterKeybindings = (part) => {
mobx.action(() => {
keybindsRegistered.set(false);
})();
let domain = "datepicker-" + curUuid + "-" + part;
GlobalModel.keybindManager.unregisterDomain(domain);
};
// Prevent use from selecting text in the input
const handleMouseDown = (event) => {
const handleMouseDown = (event, part) => {
event.preventDefault();
handleFocus(event);
handleFocus(event, part);
};
const handleIconKeyDown = (event) => {
@@ -413,8 +465,9 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
value={dateParts[part]}
onChange={(e) => handleDatePartChange(part, e.target.value)}
onKeyDown={(e) => handleKeyDown(e, part)}
onMouseDown={handleMouseDown}
onFocus={handleFocus}
onMouseDown={(e) => handleMouseDown(e, part)}
onFocus={(e) => handleFocus(e, part)}
onBlur={(e) => handleBlur(e, part)}
maxLength={part === "YYYY" ? 4 : 2}
className="date-input"
placeholder={part}
+73 -41
View File
@@ -7,6 +7,8 @@ import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import { If } from "tsx-control-statements/components";
import ReactDOM from "react-dom";
import { v4 as uuidv4 } from "uuid";
import { GlobalModel } from "@/models";
import "./dropdown.less";
@@ -39,6 +41,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
wrapperRef: React.RefObject<HTMLDivElement>;
menuRef: React.RefObject<HTMLDivElement>;
timeoutId: any;
curUuid: string;
constructor(props: DropdownProps) {
super(props);
@@ -50,6 +53,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
};
this.wrapperRef = React.createRef();
this.menuRef = React.createRef();
this.curUuid == uuidv4();
}
componentDidMount() {
@@ -102,49 +106,76 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
@boundMethod
handleFocus() {
this.setState({ isTouched: true });
this.registerKeybindings();
}
registerKeybindings() {
let keybindManager = GlobalModel.keybindManager;
let domain = "dropdown-" + this.curUuid;
keybindManager.registerKeybinding("control", domain, "generic:confirm", (waveEvent) => {
this.handleConfirm();
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:space", (waveEvent) => {
this.handleConfirm();
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:cancel", (waveEvent) => {
this.setState({ isOpen: false });
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:selectAbove", (waveEvent) => {
const { isOpen } = this.state;
const { options } = this.props;
if (isOpen) {
this.setState((prevState) => ({
highlightedIndex:
prevState.highlightedIndex > 0 ? prevState.highlightedIndex - 1 : options.length - 1,
}));
}
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:selectBelow", (waveEvent) => {
const { isOpen } = this.state;
const { options } = this.props;
if (isOpen) {
this.setState((prevState) => ({
highlightedIndex:
prevState.highlightedIndex < options.length - 1 ? prevState.highlightedIndex + 1 : 0,
}));
}
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:tab", (waveEvent) => {
this.setState({ isOpen: false });
return true;
});
}
handleConfirm() {
const { options } = this.props;
const { isOpen, highlightedIndex } = this.state;
if (isOpen) {
const option = options[highlightedIndex];
if (option) {
this.handleSelect(option.value, undefined);
}
} else {
this.toggleDropdown();
}
}
handleBlur() {
this.unregisterKeybindings();
}
unregisterKeybindings() {
let domain = "dropdown-" + this.curUuid;
GlobalModel.keybindManager.unregisterDomain(domain);
}
@boundMethod
handleKeyDown(event: React.KeyboardEvent) {
const { options } = this.props;
const { isOpen, highlightedIndex } = this.state;
switch (event.key) {
case "Enter":
case " ":
if (isOpen) {
const option = options[highlightedIndex];
if (option) {
this.handleSelect(option.value, undefined);
}
} else {
this.toggleDropdown();
}
break;
case "Escape":
this.setState({ isOpen: false });
break;
case "ArrowUp":
if (isOpen) {
this.setState((prevState) => ({
highlightedIndex:
prevState.highlightedIndex > 0 ? prevState.highlightedIndex - 1 : options.length - 1,
}));
}
break;
case "ArrowDown":
if (isOpen) {
this.setState((prevState) => ({
highlightedIndex:
prevState.highlightedIndex < options.length - 1 ? prevState.highlightedIndex + 1 : 0,
}));
}
break;
case "Tab":
this.setState({ isOpen: false });
break;
}
}
handleKeyDown(event: React.KeyboardEvent) {}
@boundMethod
handleSelect(value: string, event?: React.MouseEvent | React.KeyboardEvent) {
@@ -228,7 +259,8 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
tabIndex={0}
onKeyDown={this.handleKeyDown}
onClick={this.handleClick}
onFocus={this.handleFocus}
onFocus={this.handleFocus.bind(this)}
onBlur={this.handleBlur.bind(this)}
>
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
<If condition={label}>
@@ -8,6 +8,8 @@ import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import { If } from "tsx-control-statements/components";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import { GlobalModel } from "@/models";
import { v4 as uuidv4 } from "uuid";
import "./inlinesettingstextedit.less";
@@ -27,6 +29,11 @@ class InlineSettingsTextEdit extends React.Component<
tempText: OV<string>;
shouldFocus: boolean = false;
inputRef: React.RefObject<any> = React.createRef();
curId: string;
componentDidMount(): void {
this.curId = uuidv4();
}
componentDidUpdate(): void {
if (this.shouldFocus) {
@@ -52,6 +59,7 @@ class InlineSettingsTextEdit extends React.Component<
this.tempText = null;
this.props.onChange(newText);
})();
this.unregisterKeybindings();
}
@boundMethod
@@ -60,24 +68,38 @@ class InlineSettingsTextEdit extends React.Component<
this.isEditing.set(false);
this.tempText = null;
})();
this.unregisterKeybindings();
}
@boundMethod
handleKeyDown(e: any): void {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
e.stopPropagation();
handleFocus() {
this.registerKeybindings();
}
registerKeybindings() {
let keybindManager = GlobalModel.keybindManager;
let domain = "inline-settings" + this.curId;
keybindManager.registerKeybinding("control", domain, "generic:confirm", (waveEvent) => {
this.confirmChange();
return;
}
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
return true;
});
keybindManager.registerKeybinding("control", domain, "generic:cancel", (waveEvent) => {
this.cancelChange();
return;
}
return;
return true;
});
}
unregisterKeybindings() {
let domain = "inline-settings" + this.curId;
GlobalModel.keybindManager.unregisterDomain(domain);
}
handleBlur() {
this.unregisterKeybindings();
this.cancelChange();
}
componentWillUnmount(): void {
this.unregisterKeybindings();
}
@boundMethod
@@ -99,7 +121,8 @@ class InlineSettingsTextEdit extends React.Component<
ref={this.inputRef}
className="input"
type="text"
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus.bind(this)}
onBlur={this.handleBlur.bind(this)}
placeholder={this.props.placeholder}
onChange={this.handleChangeText}
value={this.tempText.get()}
+47 -2
View File
@@ -6,8 +6,11 @@ import * as mobx from "mobx";
import { If } from "tsx-control-statements/components";
import ReactDOM from "react-dom";
import { Button } from "./button";
import { v4 as uuidv4 } from "uuid";
import { GlobalModel } from "@/models";
import "./modal.less";
import { boundMethod } from "autobind-decorator";
interface ModalHeaderProps {
onClose?: () => void;
@@ -30,10 +33,52 @@ interface ModalFooterProps {
onOk?: () => void;
cancelLabel?: string;
okLabel?: string;
keybindings?: boolean;
}
const ModalFooter: React.FC<ModalFooterProps> = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => (
class ModalKeybindings extends React.Component<{ onOk; onCancel }, {}> {
curId: string;
@boundMethod
componentDidMount(): void {
this.curId = uuidv4();
let domain = "modal-" + this.curId;
let keybindManager = GlobalModel.keybindManager;
if (this.props.onOk) {
keybindManager.registerKeybinding("modal", domain, "generic:confirm", (waveEvent) => {
this.props.onOk();
return true;
});
}
if (this.props.onCancel) {
keybindManager.registerKeybinding("modal", domain, "generic:cancel", (waveEvent) => {
this.props.onCancel();
return true;
});
}
}
@boundMethod
componentWillUnmount(): void {
GlobalModel.keybindManager.unregisterDomain("modal-" + this.curId);
}
render(): React.ReactNode {
return null;
}
}
const ModalFooter: React.FC<ModalFooterProps> = ({
onCancel,
onOk,
cancelLabel = "Cancel",
okLabel = "Ok",
keybindings = true,
}) => (
<div className="wave-modal-footer">
<If condition={keybindings}>
<ModalKeybindings onOk={onOk} onCancel={onCancel}></ModalKeybindings>
</If>
{onCancel && (
<Button className="secondary" onClick={onCancel}>
{cancelLabel}
@@ -79,4 +124,4 @@ class Modal extends React.Component<ModalProps> {
}
}
export { Modal };
export { Modal, ModalKeybindings };
+8
View File
@@ -18,6 +18,8 @@ interface TextFieldProps {
className?: string;
onChange?: (value: string) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
onFocus?: () => void;
onBlur?: () => void;
placeholder?: string;
defaultValue?: string;
decoration?: TextFieldDecorationProps;
@@ -78,6 +80,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
@boundMethod
handleFocus() {
this.setState({ focused: true });
if (this.props.onFocus) {
this.props.onFocus();
}
}
@boundMethod
@@ -91,6 +96,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
this.setState({ error: false, focused: false });
}
}
if (this.props.onBlur) {
this.props.onBlur();
}
}
@boundMethod
+3
View File
@@ -9,6 +9,7 @@ import { Markdown, Modal, Button, Checkbox } from "@/elements";
import { GlobalModel, GlobalCommandRunner } from "@/models";
import "./alert.less";
import { ModalKeybindings } from "../elements/modal";
@mobxReact.observer
class AlertModal extends React.Component<{}, {}> {
@@ -54,6 +55,7 @@ class AlertModal extends React.Component<{}, {}> {
</div>
<div className="wave-modal-footer">
<If condition={isConfirm}>
<ModalKeybindings onOk={this.handleOK} onCancel={this.closeModal}></ModalKeybindings>
<Button className="secondary" onClick={this.closeModal}>
Cancel
</Button>
@@ -62,6 +64,7 @@ class AlertModal extends React.Component<{}, {}> {
</Button>
</If>
<If condition={!isConfirm}>
<ModalKeybindings onOk={this.handleOK} onCancel={null}></ModalKeybindings>
<Button autoFocus={true} onClick={this.handleOK}>
Ok
</Button>
-16
View File
@@ -2,7 +2,6 @@ import * as React from "react";
import { GlobalModel } from "@/models";
import { Choose, When, If } from "tsx-control-statements/components";
import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import "./userinput.less";
@@ -44,19 +43,6 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
[userInputRequest]
);
function handleTextKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
e.stopPropagation();
handleSendText();
} else if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
handleSendCancel();
}
}
React.useEffect(() => {
let timeout: ReturnType<typeof setTimeout>;
if (countdown == 0) {
@@ -89,7 +75,6 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
value={responseText}
maxLength={400}
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</If>
<If condition={!userInputRequest.publictext}>
@@ -98,7 +83,6 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
value={responseText}
maxLength={400}
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</If>
</If>
@@ -14,6 +14,7 @@ import * as textmeasure from "@/util/textmeasure";
import * as appconst from "@/app/appconst";
import "./viewremoteconndetail.less";
import { ModalKeybindings } from "../elements/modal";
@mobxReact.observer
class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
@@ -382,6 +383,20 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
</div>
</div>
<div className="wave-modal-footer">
<ModalKeybindings
onOk={() => {
if (selectedRemoteStatus == "connecting") {
return;
}
this.handleClose();
}}
onCancel={() => {
if (selectedRemoteStatus == "connecting") {
return;
}
this.handleClose();
}}
></ModalKeybindings>
<Button
className="secondary"
disabled={selectedRemoteStatus == "connecting"}
+123 -58
View File
@@ -8,21 +8,64 @@ import { GlobalModel } from "@/models";
import { isBlank } from "@/util/util";
import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import { For } from "tsx-control-statements/components";
import { If, For } from "tsx-control-statements/components";
import { Markdown } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
componentDidMount(): void {
let AIChatObject = this.props.AIChatObject;
let keybindManager = GlobalModel.keybindManager;
let inputModel = GlobalModel.inputModel;
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
AIChatObject.onEnterKeyPressed();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:expandTextInput", (waveEvent) => {
AIChatObject.onExpandInputPressed();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
inputModel.closeAIAssistantChat(true);
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "aichat:clearHistory", (waveEvent) => {
inputModel.clearAIAssistantChat();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
return AIChatObject.onArrowUpPressed();
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
return AIChatObject.onArrowDownPressed();
});
}
componentWillUnmount(): void {
GlobalModel.keybindManager.unregisterDomain("aichat");
}
render() {
return null;
}
}
@mobxReact.observer
class AIChat extends React.Component<{}, {}> {
chatListKeyCount: number = 0;
textAreaNumLines: mobx.IObservableValue<number> = mobx.observable.box(1, { name: "textAreaNumLines" });
chatWindowScrollRef: React.RefObject<HTMLDivElement>;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
isFocused: OV<boolean>;
constructor(props: any) {
super(props);
this.chatWindowScrollRef = React.createRef();
this.textAreaRef = React.createRef();
this.isFocused = mobx.observable.box(false, {
name: "aichat-isfocused",
});
}
componentDidMount() {
@@ -66,67 +109,82 @@ class AIChat extends React.Component<{}, {}> {
return { numLines, linePos };
}
@mobx.action
@boundMethod
onKeyDown(e: any) {
onTextAreaFocused(e: any) {
mobx.action(() => {
let model = GlobalModel;
let inputModel = model.inputModel;
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
let resetCodeSelect = !ctrlMod;
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
if (!ctrlMod) {
if (inputModel.getCodeSelectSelectedIndex() == -1) {
let messageStr = e.target.value;
this.submitChatMessage(messageStr);
e.target.value = "";
} else {
inputModel.grabCodeSelectSelection();
}
} else {
e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end");
}
}
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
inputModel.closeAIAssistantChat(true);
}
if (checkKeyPressed(waveEvent, "Ctrl:l")) {
e.preventDefault();
e.stopPropagation();
inputModel.clearAIAssistantChat();
}
if (checkKeyPressed(waveEvent, "ArrowUp")) {
if (this.getLinePos(e.target).linePos > 1) {
// normal up arrow
return;
}
e.preventDefault();
inputModel.codeSelectSelectNextOldestCodeBlock();
resetCodeSelect = false;
}
if (checkKeyPressed(waveEvent, "ArrowDown")) {
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
return;
}
e.preventDefault();
inputModel.codeSelectSelectNextNewestCodeBlock();
resetCodeSelect = false;
}
if (resetCodeSelect) {
inputModel.codeSelectDeselectAll();
}
// set height of textarea based on number of newlines
this.textAreaNumLines.set(e.target.value.split(/\n/).length);
this.isFocused.set(true);
})();
}
onTextAreaBlur(e: any) {
mobx.action(() => {
this.isFocused.set(false);
})();
}
onTextAreaChange(e: any) {
// set height of textarea based on number of newlines
mobx.action(() => {
this.textAreaNumLines.set(e.target.value.split(/\n/).length);
GlobalModel.inputModel.codeSelectDeselectAll();
})();
}
onEnterKeyPressed() {
let inputModel = GlobalModel.inputModel;
let currentRef = this.textAreaRef.current;
if (currentRef == null) {
return;
}
if (inputModel.getCodeSelectSelectedIndex() == -1) {
let messageStr = currentRef.value;
this.submitChatMessage(messageStr);
currentRef.value = "";
} else {
inputModel.grabCodeSelectSelection();
}
}
onExpandInputPressed() {
let currentRef = this.textAreaRef.current;
if (currentRef == null) {
return;
}
currentRef.setRangeText("\n", currentRef.selectionStart, currentRef.selectionEnd, "end");
GlobalModel.inputModel.codeSelectDeselectAll();
}
onArrowUpPressed(): boolean {
let currentRef = this.textAreaRef.current;
if (currentRef == null) {
return false;
}
if (this.getLinePos(currentRef).linePos > 1) {
// normal up arrow
GlobalModel.inputModel.codeSelectDeselectAll();
return false;
}
GlobalModel.inputModel.codeSelectSelectNextOldestCodeBlock();
return true;
}
onArrowDownPressed(): boolean {
let currentRef = this.textAreaRef.current;
let inputModel = GlobalModel.inputModel;
if (currentRef == null) {
return false;
}
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
GlobalModel.inputModel.codeSelectDeselectAll();
return false;
}
inputModel.codeSelectSelectNextNewestCodeBlock();
return true;
}
@mobx.action
@boundMethod
onKeyDown(e: any) {}
renderError(err: string): any {
return <div className="chat-msg-error">{err}</div>;
}
@@ -192,9 +250,13 @@ class AIChat extends React.Component<{}, {}> {
const textAreaPadding = 2 * 0.5 * termFontSize;
let textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines + textAreaPadding;
let textAreaInnerHeight = this.textAreaNumLines.get() * textAreaLineHeight + textAreaPadding;
let isFocused = this.isFocused.get();
return (
<div className="cmd-aichat">
<If condition={isFocused}>
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
</If>
<div className="cmdinput-titlebar">
<div className="title-icon">
<i className="fa-sharp fa-solid fa-sparkles" />
@@ -217,6 +279,9 @@ class AIChat extends React.Component<{}, {}> {
autoComplete="off"
autoCorrect="off"
id="chat-cmd-input"
onFocus={this.onTextAreaFocused.bind(this)}
onBlur={this.onTextAreaBlur.bind(this)}
onChange={this.onTextAreaChange.bind(this)}
onKeyDown={this.onKeyDown}
style={{ height: textAreaInnerHeight, maxHeight: textAreaMaxHeight, fontSize: termFontSize }}
className={cn("chat-textarea")}
File diff suppressed because it is too large Load Diff
+2 -14
View File
@@ -352,6 +352,7 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
const indexHtml = isDev ? "index-dev.html" : "index.html";
win.loadFile(path.join(getElectronAppBasePath(), "public", indexHtml));
win.webContents.on("before-input-event", (e, input) => {
const waveEvent = adaptFromElectronKeyEvent(input);
if (win.isFocused()) {
wasActive = true;
}
@@ -379,7 +380,7 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
win.webContents.on("zoom-changed", (e) => {
win.webContents.send("zoom-changed");
});
win.webContents.setWindowOpenHandler(({ url }) => {
win.webContents.setWindowOpenHandler(({ url, frameName }) => {
if (url.startsWith("https://docs.waveterm.dev/")) {
console.log("openExternal docs", url);
electron.shell.openExternal(url);
@@ -400,19 +401,6 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
return { action: "deny" };
});
electron.session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
`default-src 'file://${getElectronAppBasePath()}/dist${isDev ? "-dev" : ""}/'`,
`script-src 'file://${getElectronAppBasePath()}/dist${isDev ? "-dev" : ""}/'`,
`style-src 'file://${getElectronAppBasePath()}/dist${isDev ? "-dev" : ""}/'`,
],
},
});
});
return win;
}
+3
View File
@@ -630,6 +630,9 @@ class InputModel {
}
codeSelectDeselectAll(direction: number = this.codeSelectBottom) {
if (this.codeSelectSelectedIndex.get() == direction) {
return;
}
mobx.action(() => {
this.codeSelectSelectedIndex.set(direction);
this.codeSelectBlockRefArray = [];
-17
View File
@@ -476,23 +476,6 @@ class Model {
if (isModKeyPress(e)) {
return;
}
if (this.alertMessage.get() != null) {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.modalsModel.popModal(() => this.cancelAlert());
return;
}
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
this.confirmAlert();
return;
}
return;
}
if (checkKeyPressed(waveEvent, "Escape") && this.modalsModel.store.length > 0) {
this.modalsModel.popModal();
return;
}
if (this.keybindManager.processKeyEvent(e, waveEvent)) {
return;
}
+4 -67
View File
@@ -12,7 +12,7 @@ import { MagicLayout } from "@/app/magiclayout";
import * as appconst from "@/app/appconst";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import { Model } from "./model";
import { GlobalCommandRunner } from "./global";
import { GlobalCommandRunner, GlobalModel } from "./global";
import { Cmd } from "./cmd";
import { ScreenLines } from "./screenlines";
import { getTermPtyData } from "@/util/modelutil";
@@ -469,7 +469,7 @@ class Screen {
this.renderers[lineId] = renderer;
}
setLineFocus(lineNum: number, focus: boolean): void {
setLineFocus(lineNum: number, lineid: string, focus: boolean): void {
mobx.action(() => this.termLineNumFocus.set(focus ? lineNum : 0))();
if (focus && this.selectedLine.get() != lineNum) {
GlobalCommandRunner.screenSelectLine(String(lineNum), "cmd");
@@ -498,71 +498,8 @@ class Screen {
})();
}
termCustomKeyHandlerInternal(e: any, termWrap: TermWrap): void {
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "ArrowUp")) {
termWrap.terminal.scrollLines(-1);
return;
}
if (checkKeyPressed(waveEvent, "ArrowDown")) {
termWrap.terminal.scrollLines(1);
return;
}
if (checkKeyPressed(waveEvent, "PageUp")) {
termWrap.terminal.scrollPages(-1);
return;
}
if (checkKeyPressed(waveEvent, "PageDown")) {
termWrap.terminal.scrollPages(1);
return;
}
}
isTermCapturedKey(e: any): boolean {
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 {
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" && checkKeyPressed(waveEvent, "Ctrl:Shift:v")) {
e.stopPropagation();
e.preventDefault();
let p = navigator.clipboard.readText();
p.then((text) => {
termWrap.dataHandler?.(text, termWrap);
});
return false;
}
if (termWrap.isRunning) {
return true;
}
let isCaptured = this.isTermCapturedKey(e);
if (!isCaptured) {
return true;
}
if (e.type != "keydown" || isModKeyPress(e)) {
return false;
}
e.stopPropagation();
e.preventDefault();
this.termCustomKeyHandlerInternal(e, termWrap);
return false;
return true;
}
loadTerminalRenderer(elem: Element, line: LineType, cmd: Cmd, width: number) {
@@ -588,7 +525,7 @@ class Screen {
termOpts: cmd.getTermOpts(),
winSize: { height: 0, width: width },
dataHandler: cmd.handleData.bind(cmd),
focusHandler: (focus: boolean) => this.setLineFocus(line.linenum, focus),
focusHandler: (focus: boolean) => this.setLineFocus(line.linenum, line.lineid, focus),
isRunning: cmd.isRunning(),
customKeyHandler: this.termCustomKeyHandler.bind(this),
fontSize: this.globalModel.getTermFontSize(),
+68 -15
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as mobx from "mobx";
import Editor, { Monaco } from "@monaco-editor/react";
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import cn from "classnames";
@@ -10,7 +11,12 @@ import { Markdown } from "@/elements";
import { GlobalModel, GlobalCommandRunner } from "@/models";
import Split from "react-split-it";
import loader from "@monaco-editor/loader";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import {
checkKeyPressed,
adaptFromReactOrNativeKeyEvent,
KeybindManager,
adaptFromElectronKeyEvent,
} from "@/util/keyutil";
import { Button, Dropdown } from "@/elements";
import "./code.less";
@@ -46,6 +52,18 @@ function renderCmdText(text: string): any {
// there is a global monaco variable (TODO get the correct TS type)
declare var monaco: any;
class CodeKeybindings extends React.Component<{ codeObject: SourceCodeRenderer }, {}> {
componentDidMount(): void {
this.props.codeObject.registerKeybindings();
}
componentWillUnmount(): void {
this.props.codeObject.unregisterKeybindings();
}
render() {
return null;
}
}
class SourceCodeRenderer extends React.Component<
{
data: ExtBlob;
@@ -131,6 +149,10 @@ class SourceCodeRenderer extends React.Component<
}
}
componentWillUnmount() {
this.unregisterKeybindings();
}
componentDidUpdate(prevProps: any): void {
if (!prevProps.shouldFocus && this.props.shouldFocus) {
if (this.monacoEditor) {
@@ -174,6 +196,30 @@ class SourceCodeRenderer extends React.Component<
}
};
registerKeybindings() {
const { lineId } = this.props.context;
let domain = "code-" + lineId;
let keybindManager = GlobalModel.keybindManager;
keybindManager.registerKeybinding("plugin", domain, "codeedit:save", (waveEvent) => {
this.doSave();
return true;
});
keybindManager.registerKeybinding("plugin", domain, "codeedit:close", (waveEvent) => {
this.doClose();
return true;
});
keybindManager.registerKeybinding("plugin", domain, "codeedit:togglePreview", (waveEvent) => {
this.togglePreview();
return true;
});
}
unregisterKeybindings() {
const { lineId } = this.props.context;
let domain = "code-" + lineId;
GlobalModel.keybindManager.unregisterDomain(domain);
}
handleEditorDidMount = (editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => {
this.monacoEditor = editor;
this.setInitialLanguage(editor);
@@ -184,20 +230,15 @@ class SourceCodeRenderer extends React.Component<
}, 2000);
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
let waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
if (checkKeyPressed(waveEvent, "Cmd:s") && this.state.isSave) {
e.preventDefault();
e.stopPropagation();
this.doSave();
}
if (checkKeyPressed(waveEvent, "Cmd:d")) {
e.preventDefault();
e.stopPropagation();
this.doClose();
}
if (checkKeyPressed(waveEvent, "Cmd:p")) {
e.preventDefault();
e.stopPropagation();
this.togglePreview();
console.log("keydown?", waveEvent);
if (
GlobalModel.keybindManager.checkKeysPressed(waveEvent, [
"codeedit:save",
"codeedit:close",
"codeedit:togglePreview",
])
) {
GlobalModel.keybindManager.processKeyEvent(e.browserEvent, waveEvent);
}
});
editor.onDidScrollChange((e) => {
@@ -503,8 +544,20 @@ class SourceCodeRenderer extends React.Component<
</div>
);
}
let { lineNum } = this.props.context;
let screen = GlobalModel.getActiveScreen();
let lineIsSelected = mobx.computed(
() => screen.getSelectedLine() == lineNum && screen.getFocusType() == "cmd",
{
name: "code-lineisselected",
}
);
console.log("lineis selected:", lineIsSelected.get());
return (
<div className="code-renderer">
<If condition={lineIsSelected.get()}>
<CodeKeybindings codeObject={this}></CodeKeybindings>
</If>
<Split sizes={[editorFraction, 1 - editorFraction]} onSetSizes={this.setSizes}>
{this.getCodeEditor()}
{isPreviewerAvailable && showPreview && this.getPreviewer()}
+1 -1
View File
@@ -148,7 +148,7 @@ class TermWrap {
});
this.terminal.open(elem);
if (opts.keyHandler != null) {
this.terminal.onKey((e) => opts.keyHandler(e, this));
//this.terminal.onKey((e) => opts.keyHandler(e, this));
}
if (opts.dataHandler != null) {
this.dataHandler = opts.dataHandler;
+59
View File
@@ -17,6 +17,60 @@ import "./terminal.less";
dayjs.extend(localizedFormat);
class TerminalKeybindings extends React.Component<{ termWrap: any; lineid: string }, {}> {
componentDidMount(): void {
this.registerKeybindings();
}
registerKeybindings() {
let keybindManager = GlobalModel.keybindManager;
let domain = "line-" + this.props.lineid;
let termWrap = this.props.termWrap;
keybindManager.registerKeybinding("plugin", domain, "terminal:copy", (waveEvent) => {
let termWrap = this.props.termWrap;
let sel = termWrap.terminal.getSelection();
navigator.clipboard.writeText(sel);
return true;
});
keybindManager.registerKeybinding("plugin", domain, "terminal:paste", (waveEvent) => {
let p = navigator.clipboard.readText();
p.then((text) => {
termWrap.dataHandler?.(text, termWrap);
});
return true;
});
keybindManager.registerKeybinding("plugin", domain, "generic:selectAbove", (waveEvent) => {
termWrap.terminal.scrollLines(-1);
return true;
});
keybindManager.registerKeybinding("plugin", domain, "generic:selectBelow", (waveEvent) => {
termWrap.terminal.scrollLines(1);
return true;
});
keybindManager.registerKeybinding("plugin", domain, "generic:selectPageAbove", (waveEvent) => {
termWrap.terminal.scrollLines(-10);
return true;
});
keybindManager.registerKeybinding("plugin", domain, "generic:selectPageBelow", (waveEvent) => {
termWrap.terminal.scrollLines(10);
return true;
});
}
unregisterKeybindings() {
let domain = "line-" + this.props.lineid;
GlobalModel.keybindManager.unregisterDomain(domain);
}
componentWillUnmount(): void {
this.unregisterKeybindings();
}
render(): React.ReactNode {
return null;
}
}
@mobxReact.observer
class TerminalRenderer extends React.Component<
{
@@ -153,6 +207,8 @@ class TerminalRenderer extends React.Component<
termHeight = 0;
}
let termLoaded = this.termLoaded.get();
let lineid = line.lineid;
let termWrap = screen.getTermWrap(lineid);
return (
<div
ref={this.elemRef}
@@ -169,6 +225,9 @@ class TerminalRenderer extends React.Component<
<If condition={!isFocused}>
<div key="term-block" className="term-block" onClick={this.clickTermBlock}></div>
</If>
<If condition={isFocused}>
<TerminalKeybindings termWrap={termWrap} lineid={lineid}></TerminalKeybindings>
</If>
<div
key="term-connectelem"
className="terminal-connectelem"

Some files were not shown because too many files have changed in this diff Show More