Compare commits

...

48 Commits

Author SHA1 Message Date
Evan Simkowitz e4d3bfd90a save work 2024-04-25 14:48:30 -07:00
Evan Simkowitz 9073b9b31e add utils to assist in fs lookups 2024-04-25 14:47:44 -07:00
Evan Simkowitz cf28bb53ca fix path recommendations 2024-04-25 14:47:18 -07:00
Evan Simkowitz e59f70d26b fix option suggestions not showing if there's a complete match for one option and a partial for others 2024-04-25 11:01:37 -07:00
Evan Simkowitz deb9e23d53 Add ability to enabling autocomplete logging in clientsettings 2024-04-24 17:17:50 -07:00
Evan Simkowitz 3a9aa5813f fix file completion display 2024-04-24 16:41:37 -07:00
Evan Simkowitz c3aa027cc8 fix cwd resolution 2024-04-24 15:41:19 -07:00
Evan Simkowitz 7bdf332ce0 fix insertValue calculation 2024-04-24 15:22:39 -07:00
Evan Simkowitz 9483b43afa add default icon case, though it should never be hit 2024-04-23 18:22:16 -07:00
Evan Simkowitz 3fc4eb40cc remove unused log utils 2024-04-23 18:19:48 -07:00
Evan Simkowitz b142940a4c update import 2024-04-23 18:14:36 -07:00
Evan Simkowitz 3e4a22e7b8 handle -- argument properly 2024-04-23 18:08:01 -07:00
Evan Simkowitz b4928780d2 Merge branch 'main' into evan/inshellisense-newton-merge 2024-04-23 17:50:04 -07:00
Evan Simkowitz a5e092e6c5 move pos to {cursor} if present 2024-04-23 17:45:25 -07:00
Mike Sawka 8f93b3e263 activity updates for v0.7.3 (#600)
* adding more activity updates, tabs, workspaces, startup/shutdown

* name change, allow activity updates from FE.  aichat + history opens

* activity updates for non-standard renderers
2024-04-23 17:40:14 -07:00
Evan Simkowitz 6a7ee12c10 Pull in loadSpec functions from evan/inshellisense branch 2024-04-23 17:32:45 -07:00
Evan Simkowitz f5af7ad283 Merge branch 'main' into evan/inshellisense-newton-merge 2024-04-23 17:05:45 -07:00
Evan Simkowitz eadfb92f94 Fix regression in the order of messages in the AI chat auxview (#597) 2024-04-23 16:35:33 -07:00
Evan Simkowitz a50db14b3e Fix improper formatting when AuxiliaryCmdView titlebar is missing (#596)
* Fix improper formatting when AuxiliaryCmdView titlebar is missing

* fix bad save
2024-04-23 16:16:41 -07:00
Evan Simkowitz 6308c6c4b0 Simplify scrollHistoryItemIntoView method (#595)
* Simplify scrollHistoryItemIntoView

* remove unnecessary block argument

* actually the block nearest is necessary, otherwise the scroll will jump even if the item is already in view
2024-04-23 15:38:02 -07:00
Red J Adaya cdfc446763 Improved tab settings pull down (#592)
* add label to tab settings terminal theme dropdown

* add connection label. add New Conection item

* fix wrong prop

* simplify logic

* fix conflicts
2024-04-23 13:14:56 -07:00
Cole Lashley b08aaa03fb Remove log for duplicate key register (#540)
* removed log for duplicate keybindings

* changed dumplogs to GlobalModel.isDev, and re added back log

* added back dumplogs

* one last dump logs
2024-04-23 12:30:03 -07:00
Cole Lashley 64244626a1 auxview focus + keybindings (#575)
* first draft of figuring out auxviews and focus

* added more focus logic

* fixed aichat focus issue

* fixed artifacts

* removed command

* addressed review comments
2024-04-23 11:47:55 -07:00
Cole Lashley c683d10008 Keybinding fixes (#591)
* keybinding fixes

* changed screen and session commands to functions rather than slash commands to avoid printing errors

* added a static wait for console commands

* added sleep hardcode

* add an isArray check for commandStr
2024-04-23 11:18:39 -07:00
Evan Simkowitz b115d04876 Merge branch 'main' into evan/inshellisense-newton-merge 2024-04-23 11:08:39 -07:00
Evan Simkowitz 265cc30347 Update electron-rebuild reference to @electron/rebuild as recommended by Yarn (#593) 2024-04-23 11:07:52 -07:00
Evan Simkowitz 5f755e6574 Merge branch 'main' into evan/inshellisense-newton-merge 2024-04-23 10:51:25 -07:00
Evan Simkowitz e04c5af41d Make the AuxiliaryCmdView title bar optional and more flexible (#588)
* Make the auxView tilebar optional and make sure it flows as part of the flexbox

* simplify condition

* fix ai chat scroll direction

* remove unnecessary change

* remove unused class
2024-04-23 10:50:56 -07:00
Yiheng Li 2b0b206614 fix: inline text edit button use onMouseDown (#579) 2024-04-23 10:44:15 -07:00
Mikael Eklund 7c63e24525 Issue #584 fix in package.json (#589) 2024-04-22 23:12:24 -07:00
Evan Simkowitz 0b74f4afcf bad merge 2024-04-22 19:48:02 -07:00
Evan Simkowitz 77706bf21e Merge branch 'evan/auxview-fix' into evan/inshellisense-newton-merge 2024-04-22 19:45:47 -07:00
Evan Simkowitz 6bd5931056 remove unused class 2024-04-22 19:40:48 -07:00
Evan Simkowitz e5ee4e4069 remove unnecessary change 2024-04-22 19:38:44 -07:00
Evan Simkowitz f05cb30dca fix ai chat scroll direction 2024-04-22 19:35:59 -07:00
Evan Simkowitz 260b29167d simplify condition 2024-04-22 19:32:44 -07:00
Evan Simkowitz e543538bae Make the auxView tilebar optional and make sure it flows as part of the flexbox 2024-04-22 19:30:30 -07:00
Evan Simkowitz bd7a83ba0e making auxview more flexible 2024-04-22 19:21:57 -07:00
Evan Simkowitz 5d1c92d643 prefix is pushing through well for option args with separators 2024-04-22 17:57:13 -07:00
Evan Simkowitz c6f187d65d support arg separators 2024-04-22 17:14:33 -07:00
Evan Simkowitz b476ab31c6 fixing more display issues 2024-04-22 15:46:40 -07:00
Evan Simkowitz 20b25a9272 Merge branch 'evan/inshellisense' into evan/inshellisense-newton-merge 2024-04-22 13:54:53 -07:00
Evan Simkowitz 35cf4b6a3b Merge branch 'main' into evan/inshellisense 2024-04-22 13:54:42 -07:00
Evan Simkowitz ff50e89156 save 2024-04-22 13:53:07 -07:00
Evan Simkowitz 024f46bf7b save work 2024-04-22 13:44:40 -07:00
Evan Simkowitz 73164e0152 almost there, just need to fix icon displays and reimplement the cached imports 2024-04-22 12:58:42 -07:00
Sylvie Crowe 8fdcf10cae fix: ignore computer sleep for telemetry clock (#577)
* fix: ignore computer sleep for telemetry clock

This uses unix time with regular integer comparisons to prevent the
telemetry clock from needing to wait for 8 hours of the program running.
Now, it only needs to wait 8 real-time hours instead.

* drop telemetry time to 4 hours, tick every 10 minutes
2024-04-19 18:26:31 -07:00
Mike Sawka 6336f87cf2 tab context menu (w/ close tab option) (#583)
* create tab context menu (for close tab)

* small update to ctx menu, also make all tab deletes (including keybinding) not popup confirm modal if less than 10 blocks
2024-04-19 18:25:26 -07:00
49 changed files with 1998 additions and 1587 deletions
+22 -40
View File
@@ -107,12 +107,12 @@
{
"command": "app:openConnectionsView",
"keys": [],
"commandStr": "/mainview connections"
"commandStr": ["/mainview connections"]
},
{
"command": "app:openSettingsView",
"keys": [],
"commandStr": "/mainview clientsettings"
"commandStr": ["/mainview clientsettings"]
},
{
"command": "app:newTab",
@@ -149,48 +149,39 @@
},
{
"command": "app:selectTab-1",
"keys": ["Cmd:1"],
"commandStr":"/screen 1"
"keys": ["Cmd:1"]
},
{
"command": "app:selectTab-2",
"keys": ["Cmd:2"],
"commandStr":"/screen 2"
"keys": ["Cmd:2"]
},
{
"command": "app:selectTab-3",
"keys": ["Cmd:3"],
"commandStr":"/screen 3"
"keys": ["Cmd:3"]
},
{
"command": "app:selectTab-4",
"keys": ["Cmd:4"],
"commandStr":"/screen 4"
"keys": ["Cmd:4"]
},
{
"command": "app:selectTab-5",
"keys": ["Cmd:5"],
"commandStr":"/screen 5"
"keys": ["Cmd:5"]
},
{
"command": "app:selectTab-6",
"keys": ["Cmd:6"],
"commandStr":"/screen 6"
"keys": ["Cmd:6"]
},
{
"command": "app:selectTab-7",
"keys": ["Cmd:7"],
"commandStr":"/screen 7"
"keys": ["Cmd:7"]
},
{
"command": "app:selectTab-8",
"keys": ["Cmd:8"],
"commandStr":"/screen 8"
"keys": ["Cmd:8"]
},
{
"command": "app:selectTab-9",
"keys": ["Cmd:9"],
"commandStr":"/screen 9"
"keys": ["Cmd:9"]
},
{
"command": "app:selectTabLeft",
@@ -202,48 +193,39 @@
},
{
"command": "app:selectWorkspace-1",
"keys": ["Cmd:Ctrl:1"],
"commandStr": "/session 1"
"keys": ["Cmd:Ctrl:1"]
},
{
"command": "app:selectWorkspace-2",
"keys": ["Cmd:Ctrl:2"],
"commandStr": "/session 2"
"keys": ["Cmd:Ctrl:2"]
},
{
"command": "app:selectWorkspace-3",
"keys": ["Cmd:Ctrl:3"],
"commandStr": "/session 3"
"keys": ["Cmd:Ctrl:3"]
},
{
"command": "app:selectWorkspace-4",
"keys": ["Cmd:Ctrl:4"],
"commandStr": "/session 4"
"keys": ["Cmd:Ctrl:4"]
},
{
"command": "app:selectWorkspace-5",
"keys": ["Cmd:Ctrl:5"],
"commandStr": "/session 5"
"keys": ["Cmd:Ctrl:5"]
},
{
"command": "app:selectWorkspace-6",
"keys": ["Cmd:Ctrl:6"],
"commandStr": "/session 6"
"keys": ["Cmd:Ctrl:6"]
},
{
"command": "app:selectWorkspace-7",
"keys": ["Cmd:Ctrl:7"],
"commandStr": "/session 7"
"keys": ["Cmd:Ctrl:7"]
},
{
"command": "app:selectWorkspace-8",
"keys": ["Cmd:Ctrl:8"],
"commandStr": "/session 8"
"keys": ["Cmd:Ctrl:8"]
},
{
"command": "app:selectWorkspace-9",
"keys": ["Cmd:Ctrl:9"],
"commandStr": "/session 9"
"keys": ["Cmd:Ctrl:9"]
},
{
"command": "app:toggleSidebar",
@@ -256,7 +238,7 @@
{
"command": "app:openBookmarksView",
"keys": ["Cmd:b"],
"commandStr": "/bookmarks:show"
"commandStr": ["/bookmarks:show"]
},
{
"command": "bookmarks:edit",
@@ -301,7 +283,7 @@
{
"command": "cmdinput:openHistory",
"keys": ["Ctrl:r"],
"commandStr": "/history"
"commandStr": ["/history"]
},
{
"command": "cmdinput:openAIChat",
+4 -1
View File
@@ -47,6 +47,7 @@
"react-markdown": "^9.0.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"shell-quote": "^1.8.1",
"sprintf-js": "^1.1.2",
"throttle-debounce": "^5.0.0",
"tsx-control-statements": "^5.1.1",
@@ -69,6 +70,7 @@
"@babel/preset-env": "^7.18.2",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.17.12",
"@electron/rebuild": "^3.6.0",
"@svgr/webpack": "^8.1.0",
"@types/classnames": "^2.3.1",
"@types/electron": "^1.6.10",
@@ -76,6 +78,7 @@
"@types/papaparse": "^5.3.10",
"@types/react": "^18.0.12",
"@types/semver": "^7.5.6",
"@types/shell-quote": "^1.7.5",
"@types/sprintf-js": "^1.1.3",
"@types/throttle-debounce": "^5.0.1",
"@types/uuid": "^9.0.7",
@@ -109,4 +112,4 @@
"scripts": {
"postinstall": "electron-builder install-app-deps"
}
}
}
+16
View File
@@ -116,6 +116,13 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeAutocompleteDebuggingEnabled(val: boolean): void {
mobx.action(() => {
GlobalModel.autocompleteModel.loggingEnabled = val;
})();
}
getFontSizes(): DropdownItem[] {
const availableFontSizes: DropdownItem[] = [];
for (let s = appconst.MinFontSize; s <= appconst.MaxFontSize; s++) {
@@ -369,6 +376,15 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Command Autocomplete Debugging</div>
<div className="settings-input">
<Toggle
checked={GlobalModel.autocompleteModel.loggingEnabled}
onChange={this.handleChangeAutocompleteDebuggingEnabled}
/>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
</MainView>
+5 -2
View File
@@ -5,7 +5,7 @@
width: 100%;
border: 2px solid var(--form-element-border-color);
border-radius: 6px;
line-height: 22px;
line-height: 20px;
background: var(--form-element-bg-color);
&.no-label {
@@ -63,11 +63,14 @@
display: flex;
min-width: 120px;
padding: 5px 8px;
justify-content: space-between;
align-items: center;
align-self: stretch;
border-radius: 6px;
i {
margin-right: 8px;
}
&-highlighted,
&:hover {
background: rgba(241, 246, 243, 0.08);
+8 -6
View File
@@ -163,7 +163,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
if (isOpen) {
const option = options[highlightedIndex];
if (option) {
this.handleSelect(option.value, undefined);
this.handleSelect(option, undefined);
}
} else {
this.toggleDropdown();
@@ -185,18 +185,19 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
handleKeyDown(event: React.KeyboardEvent) {}
@boundMethod
handleSelect(value: string, event?: React.MouseEvent | React.KeyboardEvent) {
handleSelect({ value, noop }: DropdownItem, event?: React.MouseEvent | React.KeyboardEvent) {
const { onChange } = this.props;
if (event) {
event.stopPropagation(); // This stops the event from bubbling up to the wrapper
}
if (!("value" in this.props)) {
this.setState({ internalValue: value });
}
onChange(value);
this.setState({ isOpen: false, isTouched: true });
this.unregisterKeybindings();
if (!("value" in this.props) && !noop) {
this.setState({ internalValue: value });
}
}
@boundMethod
@@ -245,10 +246,11 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
className={cn("wave-dropdown-item unselectable", {
"wave-dropdown-item-highlighted": index === highlightedIndex,
})}
onClick={(e) => this.handleSelect(option.value, e)}
onClick={(e) => this.handleSelect(option, e)}
onMouseEnter={() => this.setState({ highlightedIndex: index })}
onMouseLeave={() => this.setState({ highlightedIndex: -1 })}
>
{option.icon && <span className="wave-dropdown-item-icon">{option.icon}</span>}
{option.label}
</div>
))}
@@ -131,7 +131,7 @@ class InlineSettingsTextEdit extends React.Component<
</div>
<div className="control">
<div
onClick={this.cancelChange}
onMouseDown={this.cancelChange}
title="Cancel (Esc)"
className="button is-prompt-danger is-outlined is-small"
>
@@ -142,7 +142,7 @@ class InlineSettingsTextEdit extends React.Component<
</div>
<div className="control">
<div
onClick={this.confirmChange}
onMouseDown={this.confirmChange}
title="Confirm (Enter)"
className="button is-wave-green is-outlined is-small"
>
+64 -62
View File
@@ -1,70 +1,72 @@
.cmd-aichat {
padding-bottom: 0 !important;
.auxview-content {
flex-flow: column nowrap;
.chat-window {
display: flex;
overflow-y: auto;
margin-bottom: 5px;
flex-direction: column;
height: 100%;
.chat-window {
overflow-y: auto;
margin-bottom: 5px;
// This is the filler that will push the chat messages to the bottom until the chat window is full
.filler {
flex: 1 1 auto;
flex-direction: column-reverse;
}
.chat-input {
padding: 0.5em 0.5em 0.5em 0.5em;
flex: 1 0 auto;
.chat-textarea {
color: var(--app-text-primary-color);
background-color: var(--cmdinput-textarea-bg);
resize: none;
width: 100%;
border: transparent;
outline: none;
overflow: auto;
overflow-wrap: anywhere;
font-family: var(--termfontfamily);
font-weight: normal;
line-height: var(--termlineheight);
}
}
.chat-msg {
margin-top: calc(var(--termpad) * 2);
margin-bottom: calc(var(--termpad) * 2);
.chat-msg-header {
display: flex;
margin-bottom: 2px;
i {
margin-right: 0.5em;
}
.chat-username {
font-weight: bold;
margin-right: 5px;
}
}
}
.chat-msg-assistant {
color: var(--app-text-color);
}
.chat-msg-user {
.msg-text {
font-family: var(--markdown-font);
font-size: 14px;
white-space: pre-wrap;
}
}
.chat-msg-error {
color: var(--cmdinput-text-error);
font-family: var(--markdown-font);
font-size: 14px;
}
}
.chat-input {
padding: 0.5em 0.5em 0.5em 0.5em;
flex: 0 0 auto;
.chat-textarea {
color: var(--app-text-primary-color);
background-color: var(--cmdinput-textarea-bg);
resize: none;
width: 100%;
border: transparent;
outline: none;
overflow: auto;
overflow-wrap: anywhere;
font-family: var(--termfontfamily);
font-weight: normal;
line-height: var(--termlineheight);
}
}
.chat-msg {
margin-top: calc(var(--termpad) * 2);
margin-bottom: calc(var(--termpad) * 2);
.chat-msg-header {
display: flex;
margin-bottom: 2px;
i {
margin-right: 0.5em;
}
.chat-username {
font-weight: bold;
margin-right: 5px;
}
}
}
.chat-msg-assistant {
color: var(--app-text-color);
}
.chat-msg-user {
.msg-text {
font-family: var(--markdown-font);
font-size: 14px;
white-space: pre-wrap;
}
}
.chat-msg-error {
color: var(--cmdinput-text-error);
font-family: var(--markdown-font);
font-size: 14px;
}
}
+6 -18
View File
@@ -9,6 +9,7 @@ import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import { Markdown } from "@/elements";
import { AuxiliaryCmdView } from "./auxview";
import * as appconst from "@/app/appconst";
import "./aichat.less";
@@ -56,18 +57,13 @@ class AIChat extends React.Component<{}, {}> {
chatListKeyCount: number = 0;
chatWindowScrollRef: React.RefObject<HTMLDivElement>;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
isFocused: OV<boolean>;
termFontSize: number = 14;
constructor(props: any) {
super(props);
this.chatWindowScrollRef = React.createRef();
this.textAreaRef = React.createRef();
this.isFocused = mobx.observable.box(false, {
name: "aichat-isfocused",
});
}
componentDidMount() {
const inputModel = GlobalModel.inputModel;
if (this.chatWindowScrollRef?.current != null) {
@@ -109,13 +105,14 @@ class AIChat extends React.Component<{}, {}> {
onTextAreaFocused(e: any) {
mobx.action(() => {
this.isFocused.set(true);
GlobalModel.inputModel.setAuxViewFocus(true);
})();
this.onTextAreaChange(e);
}
onTextAreaBlur(e: any) {
mobx.action(() => {
this.isFocused.set(false);
GlobalModel.inputModel.setAuxViewFocus(false);
})();
}
@@ -237,17 +234,7 @@ class AIChat extends React.Component<{}, {}> {
render() {
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
const chitem: OpenAICmdInfoChatMessageType = null;
const renderKeybindings = mobx
.computed(() => {
return (
this.isFocused.get() ||
GlobalModel.inputModel.hasFocus() ||
(GlobalModel.getActiveScreen().getFocusType() == "input" &&
GlobalModel.activeMainView.get() == "session")
);
})
.get();
const renderKeybindings = GlobalModel.inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_AIChat);
return (
<AuxiliaryCmdView
title="Wave AI"
@@ -259,6 +246,7 @@ class AIChat extends React.Component<{}, {}> {
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
</If>
<div className="chat-window" ref={this.chatWindowScrollRef}>
<div className="filler"></div>
<For each="chitem" index="idx" of={chatMessageItems}>
{this.renderChatMessage(chitem)}
</For>
+10 -9
View File
@@ -1,21 +1,18 @@
// For the additonal views, we want less padding on the top and bottom than we want for the base-cmdinput div
.auxview {
padding: var(--termpad) calc(var(--termpad) * 2) var(--termpad) calc(var(--termpad) * 3 - 1px);
overflow: auto;
flex-shrink: 1;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
--auxview-titlebar-height: 18px;
.auxview-titlebar {
position: absolute;
z-index: 22;
top: 0;
left: 0;
background-color: var(--app-panel-bg-color);
color: var(--term-blue);
padding: 6px 10px 6px 10px;
display: flex;
flex: 0 0 auto;
flex-direction: row;
width: 100%;
border-bottom: 1px solid var(--app-border-color);
@@ -23,6 +20,7 @@
user-select: none;
cursor: default;
line-height: var(--auxview-titlebar-height);
overflow: hidden;
.title-string {
font-weight: bold;
@@ -46,6 +44,9 @@
.auxview-content {
display: flex;
padding-top: calc(var(--auxview-titlebar-height) + 6px);
flex-flow: column nowrap;
flex: 1 1 auto;
padding: var(--termpad) calc(var(--termpad) * 2) var(--termpad) calc(var(--termpad) * 3 - 1px);
overflow-y: auto;
}
}
+33 -17
View File
@@ -3,10 +3,11 @@
import * as React from "react";
import cn from "classnames";
import { If } from "tsx-control-statements/components";
import { Choose, If, Otherwise, When } from "tsx-control-statements/components";
import { observer } from "mobx-react";
import "./auxview.less";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
interface AuxiliaryCmdViewProps {
title: string;
@@ -15,6 +16,7 @@ interface AuxiliaryCmdViewProps {
titleBarContents?: React.ReactElement[];
children?: React.ReactNode;
onClose?: React.MouseEventHandler<HTMLDivElement>;
scrollable?: boolean;
}
export const AuxiliaryCmdView: React.FC<AuxiliaryCmdViewProps> = observer((props) => {
@@ -22,26 +24,40 @@ export const AuxiliaryCmdView: React.FC<AuxiliaryCmdViewProps> = observer((props
return (
<div className={cn("auxview", className)}>
<div className="auxview-titlebar">
<If condition={iconClass != null}>
<div className="title-icon">
<i className={iconClass} />
</div>
</If>
<div className="title-string">{title}</div>
<If condition={title || onClose || titleBarContents || iconClass}>
<div className="auxview-titlebar">
<If condition={iconClass != null}>
<div className="title-icon">
<i className={iconClass} />
</div>
</If>
<div className="title-string">{title}</div>
<If condition={titleBarContents != null}>{titleBarContents}</If>
<If condition={titleBarContents != null}>{titleBarContents}</If>
<div className="flex-spacer"></div>
<div className="flex-spacer"></div>
<If condition={onClose != null}>
<div className="close-button" title="Close (ESC)" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark-large" />
</div>
</If>
</div>
<If condition={onClose != null}>
<div className="close-button" title="Close (ESC)" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark-large" />
</div>
</If>
</div>
</If>
<If condition={children != null}>
<div className="auxview-content">{children}</div>
<Choose>
<When condition={props.scrollable}>
<OverlayScrollbarsComponent
className="auxview-content"
options={{ scrollbars: { autoHide: "leave" } }}
>
{children}
</OverlayScrollbarsComponent>
</When>
<Otherwise>
<div className="auxview-content">{children}</div>
</Otherwise>
</Choose>
</If>
</div>
);
-5
View File
@@ -21,13 +21,8 @@
}
}
&.has-info {
padding-top: var(--termpad);
}
&.has-history,
&.has-aichat {
padding-top: var(--termpad);
height: max(300px, 70%);
}
+2 -2
View File
@@ -19,7 +19,7 @@ import { CenteredIcon, RotateIcon } from "@/common/icons/icons";
import { AIChat } from "./aichat";
import * as util from "@/util/util";
import * as appconst from "@/app/appconst";
import { SuggestionView } from "./suggestionview";
import { AutocompleteSuggestionView } from "./suggestionview";
import "./cmdinput.less";
@@ -198,7 +198,7 @@ class CmdInput extends React.Component<{}, {}> {
<InfoMsg key="infomsg" />
</When>
<When condition={openView === appconst.InputAuxView_Suggestions}>
<SuggestionView />
<AutocompleteSuggestionView />
</When>
</Choose>
<If condition={remote && remote.status != "connected"}>
@@ -20,6 +20,7 @@
.history-items {
color: var(--app-text-color);
height: 100%;
width: 100%;
.history-item {
+3 -2
View File
@@ -174,7 +174,7 @@ class HistoryInfo extends React.Component<{}, {}> {
handleItemClick(hitem: HistoryItem) {
const inputModel = GlobalModel.inputModel;
const selItem = inputModel.getHistorySelectedItem();
inputModel.setAuxViewFocus(false);
inputModel.setAuxViewFocus(!inputModel.getAuxViewFocus());
if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) {
inputModel.grabSelectedHistoryItem();
return;
@@ -239,10 +239,11 @@ class HistoryInfo extends React.Component<{}, {}> {
return (
<AuxiliaryCmdView
title="History"
className="cmd-history hide-scrollbar"
className="cmd-history"
onClose={this.handleClose}
titleBarContents={this.getTitleBarContents()}
iconClass="fa-sharp fa-solid fa-clock-rotate-left"
scrollable={true}
>
<div
className={cn(
+30 -22
View File
@@ -1,4 +1,3 @@
import { SuggestionBlob } from "@/autocomplete/runtime/model";
import { AuxiliaryCmdView } from "./auxview";
import { GlobalModel } from "@/models";
import cn from "classnames";
@@ -9,58 +8,66 @@ import { If } from "tsx-control-statements/components";
import "./suggestionview.less";
import { getAll, getFirst } from "@/autocomplete/runtime/utils";
export const SuggestionView: React.FC = observer(() => {
export const AutocompleteSuggestionView: React.FC = observer(() => {
const inputModel = GlobalModel.inputModel;
const autocompleteModel = GlobalModel.autocompleteModel;
const [selectedSuggestion, setSelectedSuggestion] = React.useState<number>(0);
const updateScroll = (index: number) => {
setSelectedSuggestion(index);
autocompleteModel.setPrimarySuggestionIndex(index);
const element = document.getElementsByClassName("suggestion-item")[index] as HTMLElement;
if (element) {
element.scrollIntoView({ block: "nearest" });
}
};
useEffect(() => {
const keybindManager = GlobalModel.keybindManager;
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
keybindManager.registerKeybinding("pane", "autocomplete", "generic:confirm", (waveEvent) => {
setSuggestion(selectedSuggestion);
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
keybindManager.registerKeybinding("pane", "autocomplete", "generic:cancel", (waveEvent) => {
closeView();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
setSelectedSuggestion(Math.max(0, selectedSuggestion - 1));
keybindManager.registerKeybinding("pane", "autocomplete", "generic:selectAbove", (waveEvent) => {
updateScroll(Math.max(0, selectedSuggestion - 1));
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
setSelectedSuggestion(Math.min(suggestions?.suggestions.length - 1, selectedSuggestion + 1));
keybindManager.registerKeybinding("pane", "autocomplete", "generic:selectBelow", (waveEvent) => {
updateScroll(Math.min(suggestions?.length - 1, selectedSuggestion + 1));
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:tab", (waveEvent) => {
setSelectedSuggestion(Math.min(suggestions?.suggestions.length - 1, selectedSuggestion + 1));
keybindManager.registerKeybinding("pane", "autocomplete", "generic:tab", (waveEvent) => {
updateScroll(Math.min(suggestions?.length - 1, selectedSuggestion + 1));
return true;
});
return () => {
GlobalModel.keybindManager.unregisterDomain("aichat");
GlobalModel.keybindManager.unregisterDomain("autocomplete");
};
});
const inputModel = GlobalModel.inputModel;
const autocompleteModel = GlobalModel.autocompleteModel;
const suggestions: SuggestionBlob = autocompleteModel.getSuggestions();
const suggestions: Fig.Suggestion[] = autocompleteModel.getSuggestions();
const closeView = () => {
inputModel.closeAuxView();
};
const setSuggestion = (idx: number) => {
console.log("setSuggestion", idx);
autocompleteModel.applySuggestion(idx);
autocompleteModel.loadSuggestions();
closeView();
};
return (
<AuxiliaryCmdView title="Suggestions" className="suggestions-view" onClose={closeView}>
<If condition={!suggestions}>
<AuxiliaryCmdView title="Suggestions" className="suggestions-view" onClose={closeView} scrollable={true}>
<If condition={!suggestions || suggestions.length == 0}>
<div className="no-suggestions">No suggestions</div>
</If>
{suggestions?.suggestions.map((suggestion, idx) => (
<div
{suggestions?.map((suggestion, idx) => (
<option
key={getFirst(suggestion.name)}
title={suggestion.description}
className={cn("suggestion-item", { "is-selected": selectedSuggestion === idx })}
@@ -68,9 +75,10 @@ export const SuggestionView: React.FC = observer(() => {
setSuggestion(idx);
}}
>
{suggestion.icon} {getAll(suggestion.name).join(",")}{" "}
{suggestion.description ? `- ${suggestion.description}` : ""}
</div>
{`${suggestion.icon} ${suggestion.displayName ?? getAll(suggestion.name).join(",")} ${
suggestion.description ? `- ${suggestion.description}` : ""
}`}
</option>
))}
</AuxiliaryCmdView>
);
+5 -5
View File
@@ -11,8 +11,6 @@ import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
import { getMonoFontSize } from "@/util/textmeasure";
import * as appconst from "@/app/appconst";
import { Shell, getSuggestions } from "@/autocomplete";
import { SuggestionBlob } from "@/autocomplete/runtime/model";
type OV<T> = mobx.IObservableValue<T>;
@@ -600,7 +598,9 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
}
}
}
const isHistoryFocused = auxViewFocused && inputModel.getActiveAuxView() == appconst.InputAuxView_History;
const renderCmdInputKeybindings = inputModel.shouldRenderAuxViewKeybindings(null);
const renderHistoryKeybindings = inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_History);
// Will be null if the feature is disabled
const primaryAutocompleteSuggestion = GlobalModel.autocompleteModel.getPrimarySuggestionCompletion();
@@ -611,10 +611,10 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
ref={this.controlRef}
style={{ height: computedOuterHeight }}
>
<If condition={!auxViewFocused}>
<If condition={renderCmdInputKeybindings}>
<CmdInputKeybindings inputObject={this} />
</If>
<If condition={isHistoryFocused}>
<If condition={renderHistoryKeybindings}>
<HistoryKeybindings />
</If>
+16 -51
View File
@@ -5,12 +5,10 @@ import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalCommandRunner, GlobalModel, Screen } from "@/models";
import { Button, TextField, Dropdown } from "@/elements";
import { getRemoteStr, getRemoteStrWithAlias } from "@/common/prompt/prompt";
import { TextField, Dropdown } from "@/elements";
import { getRemoteStrWithAlias } from "@/common/prompt/prompt";
import * as util from "@/util/util";
import { TabIcon } from "@/elements/tabicon";
import { ReactComponent as EllipseIcon } from "@/assets/icons/ellipse.svg";
import { ReactComponent as Check12Icon } from "@/assets/icons/check12.svg";
import { ReactComponent as GlobeIcon } from "@/assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "@/assets/icons/statuscircle.svg";
import * as appconst from "@/app/appconst";
@@ -18,52 +16,6 @@ import * as appconst from "@/app/appconst";
import "./screenview.less";
import "./tabs.less";
@mobxReact.observer
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
errorMessage: OV<string | null> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
constructor(props) {
super(props);
}
@boundMethod
clickNewConnection(): void {
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
}
render() {
let { screen } = this.props;
let rptr = screen.curRemote.get();
return (
<div className="newtab-container">
<div className="newtab-section name-section">
<TabNameTextField screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="newtab-spacer" />
<div className="newtab-section conn-section">
<div className="unselectable">
You're connected to <b>[{getRemoteStrWithAlias(rptr)}]</b>. Do you want to change it?
</div>
<div>
<TabRemoteSelector screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="text-caption cr-help-text">
To change connection from the command line use `/connect [alias|user@host]`
</div>
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<TabIconSelector screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<TabColorSelector screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
);
}
}
@mobxReact.observer
class TabNameTextField extends React.Component<{ screen: Screen; errorMessage?: OV<string> }, {}> {
@boundMethod
@@ -163,6 +115,10 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
@boundMethod
selectRemote(cname: string): void {
if (cname == null) {
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
return;
}
mobx.action(() => {
this.selectedRemoteCN.set(cname);
})();
@@ -191,6 +147,14 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
}
return a.remoteidx - b.remoteidx;
});
options.push({
label: "New Connection",
value: null,
icon: <i className="fa-sharp fa-solid fa-plus"></i>,
noop: true,
});
return options;
}
@@ -210,6 +174,7 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
let curRemote = GlobalModel.getRemoteByName(selectedRemote);
return (
<Dropdown
label="Connection"
className="conn-dropdown"
options={this.getOptions()}
defaultValue={curRemote.remotecanonicalname}
@@ -227,4 +192,4 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
}
}
export { NewTabSettings, TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector };
export { TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector };
+55 -1
View File
@@ -12,6 +12,7 @@ import * as constants from "@/app/appconst";
import { Reorder } from "framer-motion";
import { MagicLayout } from "@/app/magiclayout";
import { TabIcon } from "@/elements/tabicon";
import * as appconst from "@/app/appconst";
@mobxReact.observer
class ScreenTab extends React.Component<
@@ -62,6 +63,59 @@ class ScreenTab extends React.Component<
})();
}
@boundMethod
onContextMenu(e: React.MouseEvent) {
e.preventDefault();
e.stopPropagation();
let { screen, activeScreenId } = this.props;
if (activeScreenId != screen.screenId) {
// only show context menu for active tab
GlobalCommandRunner.switchScreen(screen.screenId);
return;
}
let colorSubMenu: ContextMenuItem[] = [];
for (let color of appconst.TabColors) {
colorSubMenu.push({
label: color,
click: () => {
GlobalCommandRunner.screenSetSettings(screen.screenId, { tabcolor: color }, false);
},
});
}
let menu: ContextMenuItem[] = [
{
label: "New Tab",
click: () => {
GlobalCommandRunner.createNewScreen();
},
},
{
type: "separator",
},
{
label: "Set Tab Color",
submenu: colorSubMenu,
},
{
label: "All Tab Settings",
click: () => {
GlobalModel.tabSettingsOpen.set(true);
},
},
{
type: "separator",
},
{
label: "Close Tab",
click: () => {
GlobalModel.onCloseCurrentTab();
},
},
];
GlobalModel.contextMenuModel.showContextMenu(menu, { x: e.clientX, y: e.clientY });
return;
}
render() {
let { screen, activeScreenId, index, onSwitchScreen } = this.props;
let archived = screen.archived.get() ? (
@@ -83,7 +137,7 @@ class ScreenTab extends React.Component<
"color-" + screen.getTabColor()
)}
onPointerDown={() => onSwitchScreen(screen.screenId)}
onContextMenu={(event) => this.openScreenSettings(event, screen)}
onContextMenu={this.onContextMenu}
onDragEnd={this.handleDragEnd}
>
<div className="background"></div>
+5 -1
View File
@@ -40,7 +40,7 @@
border-bottom: none;
}
.close-icon {
.close-button {
position: absolute;
right: 10px;
top: 10px;
@@ -187,4 +187,8 @@
}
}
}
.terminal-theme-dropdown {
width: 412px;
}
}
+10 -6
View File
@@ -15,10 +15,9 @@ import { ScreenTabs } from "./screen/tabs";
import { ErrorBoundary } from "@/common/error/errorboundary";
import { boundMethod } from "autobind-decorator";
import type { Screen } from "@/models";
import { Button } from "@/elements";
import { Button, Dropdown } from "@/elements";
import { commandRtnHandler } from "@/util/util";
import { getTermThemes } from "@/util/themeutil";
import { Dropdown } from "@/elements/dropdown";
import { getRemoteStrWithAlias } from "@/common/prompt/prompt";
import { TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector } from "./screen/newtabsettings";
import * as util from "@/util/util";
@@ -47,7 +46,10 @@ class SessionKeybindings extends React.Component<{}, {}> {
return true;
});
for (let index = 1; index <= 9; index++) {
keybindManager.registerKeybinding("mainview", "session", "app:selectTab-" + index, null);
keybindManager.registerKeybinding("mainview", "session", "app:selectTab-" + index, (waveEvent) => {
GlobalModel.onSwitchScreenCmd(index);
return true;
});
}
keybindManager.registerKeybinding("mainview", "session", "app:selectTabLeft", (waveEvent) => {
GlobalModel.onBracketCmd(-1);
@@ -121,7 +123,8 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> {
if (screen == null) {
return;
}
if (screen.getScreenLines().lines.length == 0) {
let numLines = screen.getScreenLines().lines.length;
if (numLines < 10) {
GlobalCommandRunner.screenDelete(screen.screenId, false);
GlobalModel.modalsModel.popModal();
return;
@@ -175,6 +178,7 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> {
<If condition={termThemes.length > 0}>
<div className="newtab-section">
<Dropdown
label="Terminal Theme"
className="terminal-theme-dropdown"
options={termThemes}
defaultValue={currTermTheme}
@@ -297,9 +301,9 @@ class WorkspaceView extends React.Component<{}, {}> {
<ScreenTabs key={"tabs-" + sessionId} session={session} />
<If condition={activeScreen != null}>
<div key="pulldown" className={cn("tab-settings-pulldown", { closed: !showTabSettings })}>
<button className="close-icon" onClick={this.toggleTabSettings}>
<Button className="close-button secondary ghost" onClick={this.toggleTabSettings}>
<i className="fa-solid fa-sharp fa-xmark-large" />
</button>
</Button>
<TabSettings key={activeScreen.screenId} screen={activeScreen} />
<If condition={showTabSettings && !isHidden}>
<TabSettingsPulldownKeybindings />

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