Compare commits

..

5 Commits

Author SHA1 Message Date
sawka f1a6dfb249 testing frame component 2024-04-15 22:59:13 -07:00
sawka 6319b26924 basic waveapp json -> react/html functionality sorta working 2024-04-12 16:25:52 -07:00
sawka 41fe49a54c implement ijson commands 2024-04-12 13:16:26 -07:00
sawka e044487489 tested ts ijson 2024-04-12 12:58:53 -07:00
sawka 0fdcd27fee go ijson implementation (tested), ts ijson implemented (untested) 2024-04-12 01:18:05 -07:00
65 changed files with 1432 additions and 2690 deletions
+1 -2
View File
@@ -24,7 +24,6 @@
"@table-nav/react": "^0.0.7",
"@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/react-table": "^8.10.3",
"@withfig/autocomplete": "^2.652.3",
"autobind-decorator": "^2.4.0",
"base64-js": "^1.5.1",
"classnames": "^2.3.1",
@@ -44,6 +43,7 @@
"papaparse": "^5.4.1",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-frame-component": "^5.2.6",
"react-markdown": "^9.0.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
@@ -80,7 +80,6 @@
"@types/throttle-debounce": "^5.0.1",
"@types/uuid": "^9.0.7",
"@types/webpack-env": "^1.18.3",
"@withfig/autocomplete-types": "^1.30.0",
"babel-loader": "^9.1.3",
"babel-plugin-jsx-control-statements": "^4.1.2",
"copy-webpack-plugin": "^12.0.0",
+2 -2
View File
@@ -9,8 +9,8 @@
--app-accent-color: rgb(75, 166, 57);
--app-accent-bg-color: rgba(75, 166, 57, 0.2);
--app-text-color: rgb(0, 0, 0);
--app-text-primary-color: rgb(23, 23, 23);
--app-text-secondary-color: rgb(76, 76, 76);
--app-text-primary-color: rgb(0, 0, 0, 0.9);
--app-text-secondary-color: rgb(0, 0, 0, 0.7);
--app-border-color: rgb(139 145 138);
--app-panel-bg-color: rgb(224, 224, 224);
--app-panel-bg-color-dev: rgb(224, 224, 224);
-1
View File
@@ -68,4 +68,3 @@ export const ErrorCode_InvalidCwd = "ERRCWD";
export const InputAuxView_History = "history";
export const InputAuxView_Info = "info";
export const InputAuxView_AIChat = "aichat";
export const InputAuxView_Suggestions = "suggestions";
+1 -16
View File
@@ -11,9 +11,9 @@ import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/commo
import { commandRtnHandler, isBlank } from "@/util/util";
import { getTermThemes } from "@/util/themeutil";
import * as appconst from "@/app/appconst";
import { MainView } from "@/common/elements/mainview";
import "./clientsettings.less";
import { MainView } from "../common/elements/mainview";
class ClientSettingsKeybindings extends React.Component<{}, {}> {
componentDidMount() {
@@ -110,12 +110,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
GlobalModel.getElectronApi().changeAutoUpdate(val);
}
@boundMethod
handleChangeAutocompleteEnabled(val: boolean): void {
const prtn: Promise<CommandRtnType> = GlobalCommandRunner.setAutocompleteEnabled(val);
commandRtnHandler(prtn, this.errorMessage);
}
getFontSizes(): DropdownItem[] {
const availableFontSizes: DropdownItem[] = [];
for (let s = appconst.MinFontSize; s <= appconst.MaxFontSize; s++) {
@@ -360,15 +354,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Command Autocomplete</div>
<div className="settings-input">
<Toggle
checked={cdata.clientopts.autocompleteenabled ?? false}
onChange={this.handleChangeAutocompleteEnabled}
/>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
</MainView>
-4
View File
@@ -58,8 +58,4 @@
.term-prompt-python {
color: var(--term-magenta);
}
.term-prompt-k8s {
color: var(--term-magenta);
}
}
+1 -12
View File
@@ -146,7 +146,6 @@ class Prompt extends React.Component<
let branchElem = null;
let pythonElem = null;
let condaElem = null;
let k8sElem = null;
if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) {
const branchName = festate["PROMPTVAR_GITBRANCH"];
branchElem = (
@@ -172,19 +171,9 @@ class Prompt extends React.Component<
</span>
);
}
if (!isBlank(festate["K8SCONTEXT"])) {
const k8sContext = festate["K8SCONTEXT"];
const k8sNs = festate["K8SNAMESPACE"];
k8sElem = (
<span title="k8s context:namespace" className="term-prompt-k8s">
k8s:({k8sContext}
{isBlank(k8sNs) ? "" : ":" + k8sNs}){" "}
</span>
);
}
return (
<span className={termClassNames}>
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem} {k8sElem}
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem}
</span>
);
}
+3 -4
View File
@@ -226,13 +226,12 @@
}
.ts,
.termopts,
.renderer {
.termopts {
display: flex;
}
.renderer .renderer-icon {
margin-right: 0.5em;
.renderer-icon {
margin-right: 0.2em;
}
.metapart-mono {
+2 -2
View File
@@ -291,10 +291,10 @@ class LineHeader extends React.Component<{ screen: LineContainerType; line: Line
</div>
<If condition={!isBlank(renderer) && renderer != "terminal"}>
<div className="meta-divider">|</div>
<div className="renderer">
<div className="renderer-icon">
<i className="fa-sharp fa-solid fa-fill renderer-icon" />
{renderer}
</div>
<div className="renderer-name">{renderer}</div>
</If>
</div>
);
+1 -1
View File
@@ -92,7 +92,7 @@ class AIChat extends React.Component<{}, {}> {
}
submitChatMessage(messageStr: string) {
const curLine = GlobalModel.inputModel.curLine;
const curLine = GlobalModel.inputModel.getCurLine();
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
prtn.then((rtn) => {
if (!rtn.success) {
+35 -33
View File
@@ -4,45 +4,47 @@
import * as React from "react";
import cn from "classnames";
import { If } from "tsx-control-statements/components";
import { observer } from "mobx-react";
import "./auxview.less";
interface AuxiliaryCmdViewProps {
title: string;
className?: string;
iconClass?: string;
titleBarContents?: React.ReactElement[];
children?: React.ReactNode;
onClose?: React.MouseEventHandler<HTMLDivElement>;
}
export class AuxiliaryCmdView extends React.Component<
{
title: string;
className?: string;
iconClass?: string;
titleBarContents?: React.ReactElement[];
children?: React.ReactNode;
onClose?: React.MouseEventHandler<HTMLDivElement>;
},
{}
> {
render() {
const { title, className, iconClass, titleBarContents, children, onClose } = this.props;
export const AuxiliaryCmdView: React.FC<AuxiliaryCmdViewProps> = observer((props) => {
const { title, className, iconClass, titleBarContents, children, onClose } = 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>
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={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 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={children != null}>
<div className="auxview-content">{children}</div>
</If>
</div>
<If condition={children != null}>
<div className="auxview-content">{children}</div>
</If>
</div>
);
});
);
}
}
+4 -18
View File
@@ -14,8 +14,7 @@
// TODO: use a generic selector for this
&.has-aichat,
&.has-history,
&.has-info,
&.has-suggestions {
&.has-info {
.base-cmdinput {
border-top: 1px solid var(--app-border-color);
}
@@ -110,11 +109,9 @@
}
}
.textarea,
.textarea-ghost {
position: absolute;
top: 0;
left: 0;
textarea {
color: var(--app-text-primary-color);
background-color: var(--app-bg-color);
padding: var(--termpad) 0;
resize: none;
overflow: auto;
@@ -122,21 +119,10 @@
font-family: var(--termfontfamily);
line-height: var(--termlineheight);
font-size: var(--termfontsize);
background-color: transparent;
border: none;
box-shadow: none;
}
.textarea {
color: var(--app-text-primary-color);
z-index: 2;
}
.textarea-ghost {
color: var(--app-text-secondary-color);
z-index: 1;
}
input.history-input {
border: 0;
padding: 0;
-5
View File
@@ -19,7 +19,6 @@ 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 "./cmdinput.less";
@@ -182,7 +181,6 @@ class CmdInput extends React.Component<{}, {}> {
hidePrompt = true;
}
}
return (
<div ref={this.cmdInputRef} className={cn("cmd-input", hasOpenView, { active: focusVal })}>
<Choose>
@@ -197,9 +195,6 @@ class CmdInput extends React.Component<{}, {}> {
<When condition={openView === appconst.InputAuxView_Info}>
<InfoMsg key="infomsg" />
</When>
<When condition={openView === appconst.InputAuxView_Suggestions}>
<SuggestionView />
</When>
</Choose>
<If condition={remote && remote.status != "connected"}>
<div className="remote-status-warning">
@@ -11,6 +11,7 @@
.history-clickable-opt {
cursor: pointer;
white-space: nowrap;
cursor: pointer;
&:hover {
color: var(--app-text-primary-color);
+1 -1
View File
@@ -225,7 +225,7 @@ class HistoryInfo extends React.Component<{}, {}> {
render() {
const inputModel = GlobalModel.inputModel;
const selItem = inputModel.getHistorySelectedItem();
const hitems = inputModel.filteredHistoryItems.slice().reverse();
const hitems = inputModel.getFilteredHistoryItems().slice().reverse();
const opts = inputModel.historyQueryOpts.get();
let hitem: HistoryItem = null;
let snames: Record<string, string> = {};
@@ -1,25 +0,0 @@
.suggestions-view .auxview-content {
display: flex;
flex-direction: column;
min-height: 1em;
.suggestion-item {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
cursor: pointer;
border-radius: 5px;
&:hover {
background-color: var(--table-tr-hover-bg-color);
}
&.is-selected {
font-weight: bold;
color: var(--app-text-primary-color);
background-color: var(--table-tr-selected-bg-color);
&:hover {
background-color: var(--table-tr-selected-hover-bg-color);
}
}
}
}
@@ -1,75 +0,0 @@
import { SuggestionBlob } from "@/autocomplete/runtime/model";
import { AuxiliaryCmdView } from "./auxview";
import { GlobalModel } from "@/models";
import cn from "classnames";
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { If } from "tsx-control-statements/components";
import "./suggestionview.less";
export const SuggestionView: React.FC = observer(() => {
const [selectedSuggestion, setSelectedSuggestion] = React.useState<number>(0);
useEffect(() => {
const keybindManager = GlobalModel.keybindManager;
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
setSuggestion(selectedSuggestion);
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
closeView();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
setSelectedSuggestion(Math.max(0, selectedSuggestion - 1));
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
setSelectedSuggestion(Math.min(suggestions?.suggestions.length - 1, selectedSuggestion + 1));
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:tab", (waveEvent) => {
setSelectedSuggestion(Math.min(suggestions?.suggestions.length - 1, selectedSuggestion + 1));
return true;
});
return () => {
GlobalModel.keybindManager.unregisterDomain("aichat");
};
});
const inputModel = GlobalModel.inputModel;
const autocompleteModel = GlobalModel.autocompleteModel;
const suggestions: SuggestionBlob = autocompleteModel.getSuggestions();
const closeView = () => {
inputModel.closeAuxView();
};
const setSuggestion = (idx: number) => {
autocompleteModel.applySuggestion(idx);
autocompleteModel.loadSuggestions();
closeView();
};
return (
<AuxiliaryCmdView title="Suggestions" className="suggestions-view" onClose={closeView}>
<If condition={!suggestions}>
<div className="no-suggestions">No suggestions</div>
</If>
{suggestions?.suggestions.map((suggestion, idx) => (
<div
key={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>
))}
</AuxiliaryCmdView>
);
});
+26 -47
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>;
@@ -40,7 +38,7 @@ function scrollDiv(div: any, amt: number) {
div.scrollTo({ top: newScrollTop, behavior: "smooth" });
}
class HistoryKeybindings extends React.Component<{}, {}> {
class HistoryKeybindings extends React.Component<{ inputObject: TextAreaInput }, {}> {
componentDidMount(): void {
if (GlobalModel.activeMainView != "session") {
return;
@@ -102,39 +100,39 @@ 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;
curPress: string;
componentDidMount() {
if (GlobalModel.activeMainView != "session") {
return;
}
const inputObject = this.props.inputObject;
this.lastTab = false;
const keybindManager = GlobalModel.keybindManager;
const inputModel = GlobalModel.inputModel;
keybindManager.registerKeybinding("pane", "cmdinput", "cmdinput:autocomplete", (waveEvent) => {
const lastTab = this.lastTab;
this.lastTab = true;
this.curPress = "tab";
if (this.lastTab) {
const curLine = inputModel.curLine;
if (curLine != "") {
inputModel.setActiveAuxView(appconst.InputAuxView_Suggestions);
}
const curLine = inputModel.getCurLine();
if (lastTab) {
GlobalModel.submitCommand(
"_compgen",
null,
[curLine],
{ comppos: String(curLine.length), compshow: "1", nohist: "1" },
true
);
} else {
this.lastTab = true;
GlobalModel.submitCommand(
"_compgen",
null,
[curLine],
{ comppos: String(curLine.length), nohist: "1" },
true
);
}
return true;
});
@@ -205,10 +203,6 @@ class CmdInputKeybindings extends React.Component<{ inputObject: TextAreaInput }
const rtn = inputObject.arrowDownPressed();
return rtn;
});
keybindManager.registerKeybinding("pane", "cmdinput", "generic:selectRight", (waveEvent) => {
inputObject.arrowRightPressed();
return true;
});
keybindManager.registerKeybinding("pane", "cmdinput", "generic:selectPageAbove", (waveEvent) => {
this.curPress = "historyupdown";
inputObject.scrollPage(true);
@@ -246,7 +240,9 @@ class CmdInputKeybindings extends React.Component<{ inputObject: TextAreaInput }
@mobxReact.observer
class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: () => void }, {}> {
lastTab: boolean = false;
lastHistoryUpDown: boolean = false;
lastTabCurLine: OV<string> = mobx.observable.box(null);
lastFocusType: string = null;
mainInputRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
historyInputRef: React.RefObject<HTMLInputElement> = React.createRef();
@@ -403,11 +399,6 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
return true;
}
@boundMethod
arrowRightPressed() {
GlobalModel.autocompleteModel.applyPrimarySuggestion();
}
scrollPage(up: boolean) {
const inputModel = GlobalModel.inputModel;
const infoScroll = inputModel.hasScrollingInfoMsg();
@@ -571,7 +562,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
render() {
const model = GlobalModel;
const inputModel = model.inputModel;
const curLine = inputModel.curLine;
const curLine = inputModel.getCurLine();
let displayLines = 1;
const numLines = curLine.split("\n").length;
const maxCols = this.getTextAreaMaxCols();
@@ -614,10 +605,6 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
}
}
const isHistoryFocused = auxViewFocused && inputModel.getActiveAuxView() == appconst.InputAuxView_History;
// Will be null if the feature is disabled
const primaryAutocompleteSuggestion = GlobalModel.autocompleteModel.getPrimarySuggestionCompletion();
return (
<div
className="textareainput-div control is-expanded"
@@ -625,23 +612,15 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
style={{ height: computedOuterHeight }}
>
<If condition={!auxViewFocused}>
<CmdInputKeybindings inputObject={this} />
<CmdInputKeybindings inputObject={this}></CmdInputKeybindings>
</If>
<If condition={isHistoryFocused}>
<HistoryKeybindings />
<HistoryKeybindings inputObject={this}></HistoryKeybindings>
</If>
<If condition={!util.isBlank(shellType)}>
<div className="shelltag">{shellType}</div>
</If>
<If condition={primaryAutocompleteSuggestion}>
<div
className="textarea-ghost"
style={{ height: computedInnerHeight, minHeight: computedInnerHeight, fontSize: termFontSize }}
>
{`${"\xa0".repeat(curLine.length)}${primaryAutocompleteSuggestion}`}
</div>
</If>
<textarea
key="main"
ref={this.mainInputRef}
+1 -55
View File
@@ -12,7 +12,6 @@ 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<
@@ -63,59 +62,6 @@ 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() ? (
@@ -137,7 +83,7 @@ class ScreenTab extends React.Component<
"color-" + screen.getTabColor()
)}
onPointerDown={() => onSwitchScreen(screen.screenId)}
onContextMenu={this.onContextMenu}
onContextMenu={(event) => this.openScreenSettings(event, screen)}
onDragEnd={this.handleDragEnd}
>
<div className="background"></div>
+1 -2
View File
@@ -121,8 +121,7 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> {
if (screen == null) {
return;
}
let numLines = screen.getScreenLines().lines.length;
if (numLines < 10) {
if (screen.getScreenLines().lines.length == 0) {
GlobalCommandRunner.screenDelete(screen.screenId, false);
GlobalModel.modalsModel.popModal();
return;
-3
View File
@@ -1,3 +0,0 @@
# 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`.

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