Compare commits

...

93 Commits

Author SHA1 Message Date
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 899347e90f Merge branch 'main' into evan/inshellisense 2024-04-19 15:08:27 -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
Evan Simkowitz c1bd5f5765 fix command execution 2024-04-15 17:22:25 -07:00
Evan Simkowitz b97c7e186d remove unnecessary debugs 2024-04-12 16:07:49 -07:00
Evan Simkowitz 0442fc84d4 update debugs to be more helpful 2024-04-12 16:04:05 -07:00
Evan Simkowitz 27efae3ad5 make the logic less gross 2024-04-12 16:03:27 -07:00
Evan Simkowitz 4d8c2cd242 remove unused import 2024-04-12 15:57:17 -07:00
Evan Simkowitz a560499b58 return filepaths when in doubt 2024-04-12 15:53:54 -07:00
Evan Simkowitz e8b489bcd9 add history and root spec 2024-04-12 15:35:45 -07:00
Evan Simkowitz 31c12c4705 remove and modify debugs 2024-04-11 18:25:35 -07:00
Evan Simkowitz 787bcbc9d8 replace logs with debugs 2024-04-11 18:23:34 -07:00
Evan Simkowitz 985304c9aa demote dotfiles and dotdirs 2024-04-11 18:19:00 -07:00
Evan Simkowitz 13cbfdeb6e fix isEnabled 2024-04-11 18:16:28 -07:00
Evan Simkowitz 53290b47e1 fix bad merge 2024-04-11 18:14:05 -07:00
Evan Simkowitz 4bdd092d33 remove lastCurLine from textareainput 2024-04-11 18:09:28 -07:00
Evan Simkowitz fb2b3dfc6a move import 2024-04-11 18:07:56 -07:00
Evan Simkowitz fa27964d49 move import 2024-04-11 18:06:58 -07:00
Evan Simkowitz 95132011e4 remove import 2024-04-11 18:06:43 -07:00
Evan Simkowitz dae4210b72 rename stuff and add comments 2024-04-11 18:03:04 -07:00
Evan Simkowitz e394ba70a6 move keybinding 2024-04-11 17:55:28 -07:00
Evan Simkowitz 483b2eff6f add right arrow to apply suggestion 2024-04-11 17:54:54 -07:00
Evan Simkowitz 2f4a6fa6e2 Merge branch 'main' into evan/inshellisense 2024-04-11 17:45:24 -07:00
Evan Simkowitz 4af1b19ffd fix lastCurLine 2024-04-11 17:41:11 -07:00
Evan Simkowitz 8b1d930cd6 move import 2024-04-11 17:38:24 -07:00
Evan Simkowitz a4e8648968 add placeholder div if no suggestions 2024-04-11 17:37:50 -07:00
Evan Simkowitz 429ea37419 change onclose 2024-04-11 17:23:16 -07:00
Evan Simkowitz a8f3e356f6 add keybindings 2024-04-11 17:22:51 -07:00
Evan Simkowitz 85235aaad8 move autocomplete to its own model, add more styling to suggestion view 2024-04-11 17:16:22 -07:00
Evan Simkowitz 619cfe4931 rudimentary show all autocomplete 2024-04-11 16:00:52 -07:00
Evan Simkowitz 11f1c3bb70 remove finished todos 2024-04-11 15:50:46 -07:00
Evan Simkowitz 8d140c9920 Add file and folder completions 2024-04-11 15:49:46 -07:00
Evan Simkowitz 205a9d1e2e Merge branch 'main' into evan/inshellisense 2024-04-10 23:11:14 -07:00
Evan Simkowitz d3139d51cf add new license 2024-04-10 22:08:08 -07:00
Evan Simkowitz 5c931de40c fix caching 2024-04-10 21:48:23 -07:00
Evan Simkowitz 497a96bfc2 adds trigger and begins to add caching 2024-04-10 17:20:51 -07:00
Evan Simkowitz 427447c592 Merge branch 'main' into evan/inshellisense 2024-04-09 17:11:25 -07:00
Evan Simkowitz 1fe5d425da set non-transparent colors for light mode, fix fuzzy letters issue with ghost prompt 2024-04-09 17:07:15 -07:00
Evan Simkowitz a395043a1c enforce setting 2024-04-09 16:40:47 -07:00
Evan Simkowitz ff9f9b43b6 Add client setting for autocomplete 2024-04-09 16:29:59 -07:00
Evan Simkowitz 98ba0b46ac clear suggestions in more places 2024-04-09 12:33:16 -07:00
Evan Simkowitz fb3253d018 cache last command 2024-04-09 12:22:27 -07:00
Evan Simkowitz 8d91d156aa handle flicker when deleting chars 2024-04-09 12:09:42 -07:00
Evan Simkowitz b7eec15853 Adding ghost text 2024-04-09 11:57:07 -07:00
Evan Simkowitz 38ff6183a2 get suggestions on typing 2024-04-08 18:47:35 -07:00
Evan Simkowitz 1b1e6edaee Merge branch 'main' into evan/inshellisense 2024-04-08 13:47:17 -07:00
Evan Simkowitz f4aeb92d32 Merge branch 'main' into evan/inshellisense 2024-04-08 13:16:01 -07:00
Evan Simkowitz b780d5b244 Merge branch 'main' into evan/inshellisense 2024-04-08 11:19:25 -07:00
Evan Simkowitz d81a6ed480 Merge branch 'main' into evan/inshellisense 2024-04-05 10:49:22 -07:00
Evan Simkowitz d8393ae50b Merge branch 'main' into evan/inshellisense 2024-04-02 18:48:59 -07:00
Evan Simkowitz 79deb01ea4 save work 2024-04-02 18:48:38 -07:00
Evan Simkowitz 12f9a50686 beginning to add autocomplete to textareainput 2024-04-02 18:35:12 -07:00
Evan Simkowitz 239933be3e Got autocomplete working! 2024-04-02 18:14:49 -07:00
Evan Simkowitz 4692cf698b hook up runtime 2024-04-02 17:13:54 -07:00
Evan Simkowitz 907e87c217 expand cwd 2024-04-02 17:05:14 -07:00
Evan Simkowitz f97bb699a1 plumb timeout to waveshell 2024-04-02 16:57:34 -07:00
Evan Simkowitz 077545ed93 bad merrge 2024-04-02 15:50:46 -07:00
Evan Simkowitz 60d0f0e976 Merge branch 'main' into evan/inshellisense 2024-04-02 15:49:32 -07:00
Evan Simkowitz cd4671e5bb store pointer to syncs 2024-04-02 14:58:39 -07:00
Evan Simkowitz d8a714dd4a ensure only one downstream writer can read from the buffer 2024-04-02 14:28:15 -07:00
Evan Simkowitz e0fa1b2560 document UsePty 2024-04-02 14:19:46 -07:00
Evan Simkowitz 2dffcc9237 add UsePty to EphemeralRunOpts 2024-04-02 14:17:12 -07:00
Evan Simkowitz 70090b8684 add package comment 2024-04-02 14:08:10 -07:00
Evan Simkowitz 3f8952caf4 more naming changes 2024-04-02 14:06:51 -07:00
Evan Simkowitz 15a2486e94 delete unused package 2024-04-02 14:01:57 -07:00
Evan Simkowitz 38aa7016b8 comment 2024-04-02 14:01:03 -07:00
Evan Simkowitz cff353ca94 formatting 2024-04-02 13:59:59 -07:00
Evan Simkowitz 5b440ace65 Making EphemeralWriteCloser into a more generic BufferedPipe 2024-04-02 13:58:56 -07:00
Evan Simkowitz 87de0dbc44 revert waveshell changes 2024-04-01 22:17:44 -07:00
Evan Simkowitz 74839b8e1b env vars are now working 2024-04-01 22:12:58 -07:00
Evan Simkowitz 8a24953a88 fix stdout/stderr 2024-04-01 20:55:23 -07:00
Evan Simkowitz a95854514a Add separate paths for stdout and stderr writers 2024-04-01 20:51:26 -07:00
Evan Simkowitz 229fcf8cd4 got cwd override working 2024-04-01 18:27:18 -07:00
Evan Simkowitz 83542ddab8 clean up code 2024-04-01 17:42:03 -07:00
Evan Simkowitz b520242f04 fixing spaghetti 2024-04-01 17:12:51 -07:00
Evan Simkowitz bc73ed1b6c save debug statements 2024-04-01 16:04:42 -07:00
Evan Simkowitz c4647945a4 fix bad merge 2024-04-01 14:22:23 -07:00
Evan Simkowitz 3db40b1c63 Merge branch 'main' into evan/inshellisense 2024-04-01 14:22:17 -07:00
Evan Simkowitz f6d7c37915 save debugging 2024-03-29 16:54:29 -07:00
Evan Simkowitz a41e866b1f Command pipes thru, triggers infinite loop 2024-03-28 18:44:37 -07:00
Evan Simkowitz a772715ae4 Add EphemeralWriteCloser 2024-03-28 18:02:27 -07:00
Evan Simkowitz d32e2aae05 Merge branch 'main' into evan/inshellisense 2024-03-28 16:24:27 -07:00
Evan Simkowitz 3f9d52a7e4 save work 2024-03-26 16:04:37 -07:00
Evan Simkowitz a239a8d70e Merge branch 'main' into evan/inshellisense 2024-03-26 13:00:21 -07:00
Evan Simkowitz a1bddd971f save work 2024-03-25 17:43:02 -07:00
Evan Simkowitz 722318f57d save work, starting to add backend types 2024-03-25 10:54:27 -07:00
Evan Simkowitz 3b633328a1 initial 2024-03-22 16:01:08 -07:00
46 changed files with 3398 additions and 138 deletions
+2
View File
@@ -24,6 +24,7 @@
"@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",
@@ -79,6 +80,7 @@
"@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(0, 0, 0, 0.9);
--app-text-secondary-color: rgb(0, 0, 0, 0.7);
--app-text-primary-color: rgb(23, 23, 23);
--app-text-secondary-color: rgb(76, 76, 76);
--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,3 +68,4 @@ 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";
+16 -1
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,6 +110,12 @@ 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++) {
@@ -354,6 +360,15 @@ 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>
+1 -1
View File
@@ -92,7 +92,7 @@ class AIChat extends React.Component<{}, {}> {
}
submitChatMessage(messageStr: string) {
const curLine = GlobalModel.inputModel.getCurLine();
const curLine = GlobalModel.inputModel.curLine;
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
prtn.then((rtn) => {
if (!rtn.success) {
+33 -35
View File
@@ -4,47 +4,45 @@
import * as React from "react";
import cn from "classnames";
import { If } from "tsx-control-statements/components";
import { observer } from "mobx-react";
import "./auxview.less";
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;
interface AuxiliaryCmdViewProps {
title: string;
className?: string;
iconClass?: string;
titleBarContents?: React.ReactElement[];
children?: React.ReactNode;
onClose?: React.MouseEventHandler<HTMLDivElement>;
}
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>
export const AuxiliaryCmdView: React.FC<AuxiliaryCmdViewProps> = observer((props) => {
const { title, className, iconClass, titleBarContents, children, onClose } = props;
<If condition={titleBarContents != null}>{titleBarContents}</If>
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>
<div className="flex-spacer"></div>
<If condition={titleBarContents != null}>{titleBarContents}</If>
<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>
<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={children != null}>
<div className="auxview-content">{children}</div>
</If>
</div>
);
});
+18 -4
View File
@@ -14,7 +14,8 @@
// TODO: use a generic selector for this
&.has-aichat,
&.has-history,
&.has-info {
&.has-info,
&.has-suggestions {
.base-cmdinput {
border-top: 1px solid var(--app-border-color);
}
@@ -109,9 +110,11 @@
}
}
textarea {
color: var(--app-text-primary-color);
background-color: var(--app-bg-color);
.textarea,
.textarea-ghost {
position: absolute;
top: 0;
left: 0;
padding: var(--termpad) 0;
resize: none;
overflow: auto;
@@ -119,10 +122,21 @@
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,6 +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 "./cmdinput.less";
@@ -181,6 +182,7 @@ class CmdInput extends React.Component<{}, {}> {
hidePrompt = true;
}
}
return (
<div ref={this.cmdInputRef} className={cn("cmd-input", hasOpenView, { active: focusVal })}>
<Choose>
@@ -195,6 +197,9 @@ 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,7 +11,6 @@
.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.getFilteredHistoryItems().slice().reverse();
const hitems = inputModel.filteredHistoryItems.slice().reverse();
const opts = inputModel.historyQueryOpts.get();
let hitem: HistoryItem = null;
let snames: Record<string, string> = {};
@@ -0,0 +1,25 @@
.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);
}
}
}
}
@@ -0,0 +1,77 @@
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";
import { getAll, getFirst } from "@/autocomplete/runtime/utils";
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={getFirst(suggestion.name)}
title={suggestion.description}
className={cn("suggestion-item", { "is-selected": selectedSuggestion === idx })}
onClick={() => {
setSuggestion(idx);
}}
>
{suggestion.icon} {getAll(suggestion.name).join(",")}{" "}
{suggestion.description ? `- ${suggestion.description}` : ""}
</div>
))}
</AuxiliaryCmdView>
);
});
+34 -26
View File
@@ -11,6 +11,8 @@ 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>;
@@ -38,7 +40,7 @@ function scrollDiv(div: any, amt: number) {
div.scrollTo({ top: newScrollTop, behavior: "smooth" });
}
class HistoryKeybindings extends React.Component<{ inputObject: TextAreaInput }, {}> {
class HistoryKeybindings extends React.Component<{}, {}> {
componentDidMount(): void {
if (GlobalModel.activeMainView != "session") {
return;
@@ -101,38 +103,25 @@ class HistoryKeybindings extends React.Component<{ inputObject: TextAreaInput },
}
class CmdInputKeybindings extends React.Component<{ inputObject: TextAreaInput }, {}> {
lastTab: boolean;
curPress: string;
lastTab: boolean;
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";
const curLine = inputModel.getCurLine();
if (lastTab) {
GlobalModel.submitCommand(
"_compgen",
null,
[curLine],
{ comppos: String(curLine.length), compshow: "1", nohist: "1" },
true
);
if (this.lastTab) {
const curLine = inputModel.curLine;
if (curLine != "") {
inputModel.setActiveAuxView(appconst.InputAuxView_Suggestions);
}
} else {
GlobalModel.submitCommand(
"_compgen",
null,
[curLine],
{ comppos: String(curLine.length), nohist: "1" },
true
);
this.lastTab = true;
}
return true;
});
@@ -203,6 +192,10 @@ 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);
@@ -240,9 +233,7 @@ 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();
@@ -399,6 +390,11 @@ 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();
@@ -562,7 +558,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
render() {
const model = GlobalModel;
const inputModel = model.inputModel;
const curLine = inputModel.getCurLine();
const curLine = inputModel.curLine;
let displayLines = 1;
const numLines = curLine.split("\n").length;
const maxCols = this.getTextAreaMaxCols();
@@ -605,6 +601,10 @@ 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"
@@ -612,15 +612,23 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
style={{ height: computedOuterHeight }}
>
<If condition={!auxViewFocused}>
<CmdInputKeybindings inputObject={this}></CmdInputKeybindings>
<CmdInputKeybindings inputObject={this} />
</If>
<If condition={isHistoryFocused}>
<HistoryKeybindings inputObject={this}></HistoryKeybindings>
<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}
+3
View File
@@ -0,0 +1,3 @@
# 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`.
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
export * from "./runtime/runtime";
export * from "./utils/shell";
+164
View File
@@ -0,0 +1,164 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
// Modified from https://github.com/microsoft/inshellisense/blob/main/src/runtime/generator.ts
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { GlobalModel } from "@/models";
import log from "../utils/log";
import { runTemplates } from "./template";
import { buildExecuteShellCommand } from "./utils";
const getGeneratorContext = async (cwd: string): Promise<Fig.GeneratorContext> => {
const resp = await GlobalModel.submitEphemeralCommand("eval", null, ["env"], null, false, {
expectsresponse: true,
overridecwd: cwd,
env: null,
});
const { stdout, stderr } = await GlobalModel.getEphemeralCommandOutput(resp);
if (stderr) {
log.debug({ msg: "failed to get environment variables", stderr });
}
const env = {};
stdout
.split("\n")
.filter((s) => s.length > 0)
.forEach((line) => {
const [key, value] = line.split("=");
env[key] = value;
});
return {
environmentVariables: env,
currentWorkingDirectory: cwd,
currentProcess: "", // TODO: define current process
sshPrefix: "", // deprecated, should be empty
isDangerous: false,
searchTerm: "", // TODO: define search term
};
};
let lastFirstToken = "";
let lastFinalToken = "";
let cachedSuggestions: Fig.Suggestion[] = [];
// TODO: add support getQueryTerm
export const runGenerator = async (
generator: Fig.Generator,
tokens: string[],
cwd: string
): Promise<Fig.Suggestion[]> => {
const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions, trigger } =
generator;
const newToken = tokens.at(-1) ?? "";
if (lastFirstToken == tokens.at(0) && trigger && cachedSuggestions.length > 0) {
log.debug("trigger", trigger);
if (typeof trigger === "string") {
if (!newToken?.includes(trigger)) {
log.debug("trigger string", newToken, trigger);
return cachedSuggestions;
}
} else if (typeof trigger === "function") {
log.debug("trigger function", "newToken:", newToken, "lastToken: ", lastFinalToken);
if (!trigger(newToken, lastFinalToken ?? "")) {
log.debug("trigger function false");
return cachedSuggestions;
} else {
log.debug("trigger function true");
}
} else {
switch (trigger.on) {
case "change": {
log.debug("trigger change", newToken, lastFinalToken);
if (lastFinalToken && newToken && lastFinalToken === newToken) {
log.debug("trigger change false");
return cachedSuggestions;
} else {
log.debug("trigger change true");
}
break;
}
case "match": {
if (Array.isArray(trigger.string)) {
log.debug("trigger match array", newToken, trigger.string);
if (!trigger.string.some((t) => newToken === t)) {
log.debug("trigger match false");
return cachedSuggestions;
} else {
log.debug("trigger match true");
}
} else if (trigger.string !== newToken) {
log.debug("trigger match single true", newToken, trigger.string);
return cachedSuggestions;
} else {
log.debug("trigger match single false", newToken, trigger.string);
}
break;
}
case "threshold": {
log.debug("trigger threshold", newToken, lastFinalToken, trigger.length);
if (Math.abs(newToken.length - lastFinalToken.length) < trigger.length) {
log.debug("trigger threshold false");
return cachedSuggestions;
} else {
log.debug("trigger threshold true");
}
break;
}
}
}
} else if (lastFirstToken === tokens.at(0) && newToken && lastFinalToken === newToken) {
log.debug("lastToken === newToken", lastFinalToken, newToken);
return cachedSuggestions;
}
log.debug("lastToken !== newToken", lastFinalToken, newToken);
const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
const suggestions = [];
lastFinalToken = tokens[-1];
lastFirstToken = tokens[0];
try {
if (script) {
const shellInput = typeof script === "function" ? script(tokens) : script;
const scriptOutput = Array.isArray(shellInput)
? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1), cwd })
: await executeShellCommand({ ...shellInput, cwd });
const scriptStdout = scriptOutput.stdout.trim();
const scriptStderr = scriptOutput.stderr.trim();
if (scriptStderr) {
log.debug("script error, skipping processing", scriptStderr);
} else if (postProcess) {
suggestions.push(...postProcess(scriptStdout, tokens));
} else if (splitOn) {
suggestions.push(...scriptStdout.split(splitOn).map((s) => ({ name: s })));
}
}
if (custom) {
log.debug("custom", custom);
const customSuggestions = await custom(tokens, executeShellCommand, await getGeneratorContext(cwd));
log.debug("customSuggestions", customSuggestions);
suggestions.push(...customSuggestions);
}
if (template != null) {
const templateSuggestions = await runTemplates(template, cwd);
if (filterTemplateSuggestions) {
suggestions.push(...filterTemplateSuggestions(templateSuggestions));
} else {
suggestions.push(...templateSuggestions);
}
}
cachedSuggestions = suggestions;
return suggestions;
} catch (e) {
const err = typeof e === "string" ? e : e instanceof Error ? e.message : e;
log.debug({ msg: "generator failed", err, script, splitOn, template });
}
return suggestions;
};
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
// Modified from https://github.com/microsoft/inshellisense/blob/main/src/runtime/model.ts
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export type Suggestion = {
name: string;
allNames: string[];
description?: string;
icon: string;
priority: number;
insertValue?: string;
};
export type SuggestionBlob = {
suggestions: Fig.Suggestion[];
argumentDescription?: string;
charactersToDrop?: number;
};
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
// Modified from https://github.com/microsoft/inshellisense/blob/main/src/runtime/parser.ts
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export type CommandToken = {
token: string;
complete: boolean;
isOption: boolean;
isPersistent?: boolean;
isPath?: boolean;
isPathComplete?: boolean;
};
const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
const spaceRegex = /\s/;
export const parseCommand = (command: string): CommandToken[] => {
const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
return lastCommand ? lex(lastCommand) : [];
};
const lex = (command: string): CommandToken[] => {
const tokens: CommandToken[] = [];
let [readingQuotedString, readingFlag, readingCmd] = [false, false, false];
let readingIdx = 0;
let readingQuoteChar = "";
[...command].forEach((char, idx) => {
const reading = readingQuotedString || readingFlag || readingCmd;
if (!reading && (char === `'` || char === `"`)) {
[readingQuotedString, readingIdx, readingQuoteChar] = [true, idx, char];
return;
} else if (!reading && char === `-`) {
[readingFlag, readingIdx] = [true, idx];
return;
} else if (!reading && !spaceRegex.test(char)) {
[readingCmd, readingIdx] = [true, idx];
return;
}
if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== "\\") {
readingQuotedString = false;
const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
tokens.push({
token: command.slice(readingIdx, idx + 1),
complete,
isOption: false,
});
} else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
readingFlag = false;
tokens.push({
token: command.slice(readingIdx, idx),
complete: true,
isOption: true,
});
} else if (readingCmd && spaceRegex.test(char)) {
readingCmd = false;
tokens.push({
token: command.slice(readingIdx, idx),
complete: true,
isOption: false,
});
}
});
const reading = readingQuotedString || readingFlag || readingCmd;
if (reading) {
tokens.push({
token: command.slice(readingIdx),
complete: false,
isOption: readingFlag,
});
}
return tokens;
};
+460
View File
@@ -0,0 +1,460 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
// Modified from https://github.com/microsoft/inshellisense/blob/main/src/runtime/runtime.ts
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import speclist, {
diffVersionedCompletions as versionedSpeclist,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
} from "@withfig/autocomplete/build/index";
import { parseCommand, CommandToken } from "./parser";
import { Newton } from "./newton";
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion";
import { SuggestionBlob } from "./model";
import { buildExecuteShellCommand, resolveCwd } from "./utils";
import { Shell } from "../utils/shell";
import { getApi } from "@/models";
import log from "../utils/log";
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
const specSet: any = {};
const rootSpec: Fig.Spec = {
name: "root",
filterStrategy: "prefix",
subcommands: (speclist as string[])
.filter((s) => !s.includes("/") && !s.includes("@")) // filter out versioned commands and subcommands in subdirectories
.map((s) => {
return {
name: s,
loadSpec: s,
};
}),
};
const filepathSpec: Fig.Spec = {
name: "filepaths",
args: {
name: "filepaths",
isVariadic: true,
template: "filepaths",
},
};
(speclist as string[]).forEach((s) => {
let activeSet = specSet;
const specRoutes = s.split("/");
specRoutes.forEach((route, idx) => {
if (typeof activeSet !== "object") {
return;
}
if (idx === specRoutes.length - 1) {
const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
activeSet[route] = `${s}${prefix}`;
} else {
activeSet[route] = activeSet[route] || {};
activeSet = activeSet[route];
}
});
});
const loadedSpecs: { [key: string]: Fig.Spec } = {};
const loadSpec = async (cmd: CommandToken[]): Promise<Fig.Spec | undefined> => {
const rootToken = cmd.at(0);
if (!rootToken?.complete) {
log.debug("root token not complete");
return;
}
if (loadedSpecs[rootToken.token]) {
log.debug("loaded spec found");
return loadedSpecs[rootToken.token];
}
if (specSet[rootToken.token]) {
log.debug("loading spec");
const spec = (await import(`@withfig/autocomplete/build/${specSet[rootToken.token]}`)).default;
loadedSpecs[rootToken.token] = spec;
return spec;
} else {
log.debug("no spec found");
return;
}
};
// this load spec function should only be used for `loadSpec` on the fly as it is cacheless
const lazyLoadSpec = async (key: string): Promise<Fig.Spec | undefined> => {
return (await import(`@withfig/autocomplete/build/${key}.js`)).default;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- will be implemented in below TODO
const lazyLoadSpecLocation = async (location: Fig.SpecLocation): Promise<Fig.Spec | undefined> => {
return; //TODO: implement spec location loading
};
export const getSuggestions = async (cmd: string, cwd: string, shell: Shell): Promise<SuggestionBlob | undefined> => {
const activeCmd = parseCommand(cmd);
const parserCmd = activeCmd.map((c) => c.token);
if (cmd.endsWith(" ")) {
parserCmd.push(" ");
}
const parser = new Newton(undefined, parserCmd, cwd);
const sugg = await parser.generateSuggestions();
log.debug("newton done");
log.debug("activeCmd", activeCmd);
if (activeCmd.length === 0) {
return;
}
const spec = await loadSpec(activeCmd);
let result: SuggestionBlob | undefined = undefined;
const lastCommand = activeCmd.at(-1);
let charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
log.debug("charactersToDrop", charactersToDrop);
if (spec) {
log.debug("spec found", spec);
const subcommand = getSubcommand(spec);
if (subcommand == null) return;
const { cwd: resolvedCwd, pathy, complete: pathyComplete } = await resolveCwd(lastCommand, cwd, shell);
if (pathy && lastCommand) {
lastCommand.isPath = true;
lastCommand.isPathComplete = pathyComplete;
}
result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd);
if (pathy) {
log.debug("pathy", pathy);
charactersToDrop = pathyComplete ? 0 : getApi().pathBaseName(lastCommand?.token ?? "").length;
log.debug("new charactersToDrop", charactersToDrop);
}
} else if (cmd.endsWith(" ")) {
// if the first token is complete and we don't have a spec, just return filepaths
log.debug("no spec found, first token complete, returning filepaths");
result = await runSubcommand(activeCmd, filepathSpec, cwd);
} else {
// if the first token is not complete, return root spec
log.debug("no spec found, first token not complete, returning root spec");
result = await runSubcommand(activeCmd, rootSpec, cwd);
}
if (result == null) return;
log.debug("result og", result);
log.debug("result newton", sugg);
return { ...result, charactersToDrop };
};
const getPersistentOptions = (persistentOptions: Fig.Option[], options?: Fig.Option[]) => {
const persistentOptionNames = new Set(
persistentOptions.map((o) => (typeof o.name === "string" ? [o.name] : o.name)).flat()
);
return persistentOptions.concat(
(options ?? []).filter(
(o) =>
(typeof o.name == "string"
? !persistentOptionNames.has(o.name)
: o.name.some((n) => !persistentOptionNames.has(n))) && o.isPersistent === true
)
);
};
// TODO: handle subcommands that are versioned
const getSubcommand = (spec?: Fig.Spec): Fig.Subcommand | undefined => {
if (spec == null) return;
if (typeof spec === "function") {
const potentialSubcommand = spec();
if (Object.hasOwn(potentialSubcommand, "name")) {
return potentialSubcommand as Fig.Subcommand;
}
return;
}
return spec;
};
const executeShellCommand = buildExecuteShellCommand(5000);
const genSubcommand = async (command: string, parentCommand: Fig.Subcommand): Promise<Fig.Subcommand | undefined> => {
if (!parentCommand.subcommands || parentCommand.subcommands.length === 0) return;
const subcommandIdx = parentCommand.subcommands.findIndex((s) =>
Array.isArray(s.name) ? s.name.includes(command) : s.name === command
);
if (subcommandIdx === -1) return;
const subcommand = parentCommand.subcommands[subcommandIdx];
// this pulls in the spec from the load spec and overwrites the subcommand in the parent with the loaded spec.
// then it returns the subcommand and clears the loadSpec field so that it doesn't get called again
switch (typeof subcommand.loadSpec) {
case "function": {
const partSpec = await subcommand.loadSpec(command, executeShellCommand);
if (partSpec instanceof Array) {
const locationSpecs = (await Promise.all(partSpec.map((s) => lazyLoadSpecLocation(s)))).filter(
(s) => s != null
) as Fig.Spec[];
const subcommands = locationSpecs
.map((s) => getSubcommand(s))
.filter((s) => s != null) as Fig.Subcommand[];
(parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx] = {
...subcommand,
...(subcommands.find((s) => s?.name == command) ?? []),
loadSpec: undefined,
};
return (parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx];
} else if (Object.prototype.hasOwnProperty.call(partSpec, "type")) {
const locationSingleSpec = await lazyLoadSpecLocation(partSpec as Fig.SpecLocation);
(parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx] = {
...subcommand,
...(getSubcommand(locationSingleSpec) ?? []),
loadSpec: undefined,
};
return (parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx];
} else {
(parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx] = {
...subcommand,
...partSpec,
loadSpec: undefined,
};
return (parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx];
}
}
case "string": {
const spec = await lazyLoadSpec(subcommand.loadSpec as string);
(parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx] = {
...subcommand,
...(getSubcommand(spec) ?? []),
loadSpec: undefined,
};
return (parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx];
}
case "object": {
(parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx] = {
...subcommand,
...(subcommand.loadSpec ?? {}),
loadSpec: undefined,
};
return (parentCommand.subcommands as Fig.Subcommand[])[subcommandIdx];
}
case "undefined": {
return subcommand;
}
}
};
const getOption = (activeToken: CommandToken, options: Fig.Option[]): Fig.Option | undefined => {
return options.find((o) =>
typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)
);
};
const getPersistentTokens = (tokens: CommandToken[]): CommandToken[] => {
return tokens.filter((t) => t.isPersistent === true);
};
const getArgs = (args: Fig.SingleOrArray<Fig.Arg> | undefined): Fig.Arg[] => {
return args instanceof Array ? args : args != null ? [args] : [];
};
const runOption = async (
tokens: CommandToken[],
option: Fig.Option,
subcommand: Fig.Subcommand,
cwd: string,
persistentOptions: Fig.Option[],
acceptedTokens: CommandToken[]
): Promise<SuggestionBlob | undefined> => {
if (tokens.length === 0) {
throw new Error("invalid state reached, option expected but no tokens found");
}
const activeToken = tokens[0];
const isPersistent = persistentOptions.some((o) =>
typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)
);
if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
const args = option.args instanceof Array ? option.args : [option.args];
return runArg(
tokens.slice(1),
args,
subcommand,
cwd,
persistentOptions,
acceptedTokens.concat(activeToken),
true,
false
);
}
return runSubcommand(
tokens.slice(1),
subcommand,
cwd,
persistentOptions,
acceptedTokens.concat({ ...activeToken, isPersistent })
);
};
const runArg = async (
tokens: CommandToken[],
args: Fig.Arg[],
subcommand: Fig.Subcommand,
cwd: string,
persistentOptions: Fig.Option[],
acceptedTokens: CommandToken[],
fromOption: boolean,
fromVariadic: boolean
): Promise<SuggestionBlob | undefined> => {
if (args.length === 0) {
return runSubcommand(tokens, subcommand, cwd, persistentOptions, acceptedTokens, true, !fromOption);
} else if (tokens.length === 0) {
return await getArgDrivenRecommendation(
args,
subcommand,
persistentOptions,
undefined,
acceptedTokens,
fromVariadic,
cwd
);
} else if (!tokens.at(0)?.complete) {
return await getArgDrivenRecommendation(
args,
subcommand,
persistentOptions,
tokens[0],
acceptedTokens,
fromVariadic,
cwd
);
}
const activeToken = tokens[0];
if (args.every((a) => a.isOptional)) {
if (activeToken.isOption) {
const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
if (option != null) {
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
}
return;
}
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
if (nextSubcommand != null) {
return runSubcommand(
tokens.slice(1),
nextSubcommand,
cwd,
persistentOptions,
getPersistentTokens(acceptedTokens.concat(activeToken))
);
}
}
const activeArg = args[0];
if (activeArg.isVariadic) {
return runArg(
tokens.slice(1),
args,
subcommand,
cwd,
persistentOptions,
acceptedTokens.concat(activeToken),
fromOption,
true
);
} else if (activeArg.isCommand) {
if (tokens.length <= 0) {
return;
}
const spec = await loadSpec(tokens);
if (spec == null) return;
const subcommand = getSubcommand(spec);
if (subcommand == null) return;
return runSubcommand(tokens.slice(1), subcommand, cwd);
}
return runArg(
tokens.slice(1),
args.slice(1),
subcommand,
cwd,
persistentOptions,
acceptedTokens.concat(activeToken),
fromOption,
false
);
};
const runSubcommand = async (
tokens: CommandToken[],
subcommand: Fig.Subcommand,
cwd: string,
persistentOptions: Fig.Option[] = [],
acceptedTokens: CommandToken[] = [],
argsDepleted = false,
argsUsed = false
): Promise<SuggestionBlob | undefined> => {
log.debug("runSubcommand", tokens, subcommand, cwd, persistentOptions, acceptedTokens, argsDepleted, argsUsed);
if (tokens.length === 0) {
log.debug("tokens length 0");
return getSubcommandDrivenRecommendation(
subcommand,
persistentOptions,
undefined,
argsDepleted,
argsUsed,
acceptedTokens,
cwd
);
} else if (!tokens.at(0)?.complete) {
log.debug("tokens not complete");
return getSubcommandDrivenRecommendation(
subcommand,
persistentOptions,
tokens[0],
argsDepleted,
argsUsed,
acceptedTokens,
cwd
);
}
const activeToken = tokens[0];
const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
const allOptions = [...persistentOptions, ...(subcommand.options ?? [])];
if (activeToken.isOption) {
const option = getOption(activeToken, allOptions);
if (option != null) {
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
}
return;
}
log.debug("getting next subcommand", activeToken.token, subcommand);
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
log.debug("nextSubcommand", nextSubcommand);
if (nextSubcommand) {
log.debug("has next subcommand");
return runSubcommand(
tokens.slice(1),
nextSubcommand,
cwd,
getPersistentOptions(persistentOptions, subcommand.options),
getPersistentTokens(acceptedTokens.concat(activeToken))
);
}
if (activeArgsLength <= 0) {
log.debug("no args specified");
return; // not subcommand or option & no args exist
}
const args = getArgs(subcommand.args);
if (args.length != 0) {
log.debug("args specified");
return runArg(tokens, args, subcommand, cwd, allOptions, acceptedTokens, false, false);
}
// if the subcommand has no args specified, fallback to the subcommand and ignore this item
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken));
};

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