Compare commits

...

53 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 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
Evan Simkowitz 0d40d6fff3 looking good 2024-04-22 12:15:47 -07:00
Evan Simkowitz 86cbba1f79 Got the results up to parity, next step is to work on performance 2024-04-19 19:01:11 -07:00
Evan Simkowitz bdf17f0133 move Newton parser to its own file 2024-04-19 16:27:19 -07:00
Evan Simkowitz f5f0791b32 Merge branch 'evan/inshellisense' into evan/inshellisense-newton 2024-04-19 15:08:40 -07:00
Evan Simkowitz fbc5e1606b added suggestions for options 2024-04-19 15:06:26 -07:00
Evan Simkowitz fde2ce97d2 save work 2024-04-17 10:29:32 -07:00
Evan Simkowitz 93845c405d Got mostly to parity with suggestions in the parser! 2024-04-16 18:01:18 -07:00
Evan Simkowitz f2b322c7d7 working to merge my parser into the inshellisense model 2024-04-15 20:22:47 -07:00
49 changed files with 2862 additions and 1280 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(
+32 -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";
@@ -7,68 +6,79 @@ import { observer } from "mobx-react";
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
key={suggestion.name}
{suggestions?.map((suggestion, idx) => (
<option
key={getFirst(suggestion.name)}
title={suggestion.description}
className={cn("suggestion-item", { "is-selected": selectedSuggestion === idx })}
onClick={() => {
setSuggestion(idx);
}}
>
{suggestion.icon} {suggestion.name} {suggestion.description ? `- ${suggestion.description}` : ""}
</div>
{`${suggestion.icon} ${suggestion.displayName ?? getAll(suggestion.name).join(",")} ${
suggestion.description ? `- ${suggestion.description}` : ""
}`}
</option>
))}
</AuxiliaryCmdView>
);
+5 -18
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>;
@@ -102,19 +100,6 @@ class HistoryKeybindings extends React.Component<{}, {}> {
}
}
const SuggestionsInfoMsg = (props: { suggestions: SuggestionBlob }) => {
const suggestions = props.suggestions.suggestions;
return (
<div className="cmd-input-info">
{suggestions.map((s) => (
<div key={s.name} className="cmd-input-info-item">
{s.icon} {s.name}
</div>
))}
</div>
);
};
class CmdInputKeybindings extends React.Component<{ inputObject: TextAreaInput }, {}> {
curPress: string;
lastTab: boolean;
@@ -613,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();
@@ -624,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 };
+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;
}
}
+8 -5
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);
@@ -176,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}
@@ -298,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 />
+4 -2
View File
@@ -1,3 +1,5 @@
# Autocomplete parser
# Newton autocomplete parser
This is the autocomplete parser for Wave. Much of the runtime for this parser is forked from the [@microsoft/inshellisense project](https://github.com/microsoft/inshellisense). We've modified the exec code to proxy commands to the active `waveshell` instance. All suggestions, as with inshellisense, come from the [@withfig/autocomplete project](https://github.com/withfig/autocomplete). We will be supplementing these with some of our own autocomplete for our own `/slashcommands` and `metacommands`.
Newton is a Fig-compatible autocomplete parser. It builds on a lot of goodness from the [@microsoft/inshellisense project](https://github.com/microsoft/inshellisense), with heavy modifications to minimize recursion and allow for caching of intermediate states. All suggestions, as with inshellisense, come from the [@withfig/autocomplete project](https://github.com/withfig/autocomplete). We will be supplementing these with some of our own autocomplete for our own `/slashcommands` and `metacommands`.
Any exec commands that need to be run are proxied through the Wave backend to ensure no additional permissions are required.

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