mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f81e0c3583 | |||
| e5a168f34c | |||
| cc547e526b | |||
| 1b1e6edaee | |||
| f4aeb92d32 | |||
| b780d5b244 | |||
| d81a6ed480 | |||
| d8393ae50b | |||
| 79deb01ea4 | |||
| 12f9a50686 | |||
| 239933be3e | |||
| 4692cf698b | |||
| 907e87c217 | |||
| f97bb699a1 | |||
| 077545ed93 | |||
| 60d0f0e976 | |||
| cd4671e5bb | |||
| d8a714dd4a | |||
| e0fa1b2560 | |||
| 2dffcc9237 | |||
| 70090b8684 | |||
| 3f8952caf4 | |||
| 15a2486e94 | |||
| 38aa7016b8 | |||
| cff353ca94 | |||
| 5b440ace65 | |||
| 87de0dbc44 | |||
| 74839b8e1b | |||
| 8a24953a88 | |||
| a95854514a | |||
| 229fcf8cd4 | |||
| 83542ddab8 | |||
| b520242f04 | |||
| bc73ed1b6c | |||
| c4647945a4 | |||
| 3db40b1c63 | |||
| f6d7c37915 | |||
| a41e866b1f | |||
| a772715ae4 | |||
| d32e2aae05 | |||
| 3f9d52a7e4 | |||
| a239a8d70e | |||
| a1bddd971f | |||
| 722318f57d | |||
| 3b633328a1 |
+3
-1
@@ -23,6 +23,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",
|
||||
@@ -77,6 +78,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",
|
||||
@@ -105,4 +107,4 @@
|
||||
"scripts": {
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel, getApi } from "@/models";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/common/elements";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown, Button } from "@/common/elements";
|
||||
import { commandRtnHandler, isBlank } from "@/util/util";
|
||||
import { getTermThemes } from "@/util/themeutil";
|
||||
import * as appconst from "@/app/appconst";
|
||||
|
||||
import "./clientsettings.less";
|
||||
import { MainView } from "../common/elements/mainview";
|
||||
import { MainView } from "@/common/elements/mainview";
|
||||
|
||||
class ClientSettingsKeybindings extends React.Component<{}, {}> {
|
||||
componentDidMount() {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { SuggestionBlob } from "@/autocomplete/runtime/model";
|
||||
import { GlobalModel } from "@/models";
|
||||
import cn from "classnames";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const AutocompleteSuggestions = async (props: { curLine: string }) => {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const suggestions = await useMemo(async () => await inputModel.getSuggestions(), [props.curLine]);
|
||||
|
||||
if (!suggestions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = suggestions.suggestions.map((s) => `${s.icon} ${s.name}`);
|
||||
|
||||
return (
|
||||
<div className="autocomplete-suggestions">
|
||||
{items.map((item, idx) => (
|
||||
<div key={idx} className={cn("autocomplete-item")}>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -92,6 +92,8 @@
|
||||
|
||||
.textareainput-div {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.control {
|
||||
padding: var(--termpad) 0;
|
||||
@@ -121,6 +123,11 @@
|
||||
font-size: var(--termfontsize);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
input.history-input {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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`.
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./runtime/runtime";
|
||||
export * from "./utils/shell";
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import log from "../utils/log";
|
||||
import { runTemplates } from "./template";
|
||||
import { buildExecuteShellCommand } from "./utils";
|
||||
|
||||
const getGeneratorContext = (cwd: string): Fig.GeneratorContext => {
|
||||
return {
|
||||
environmentVariables: Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] != null)
|
||||
),
|
||||
currentWorkingDirectory: cwd,
|
||||
currentProcess: "", // TODO: define current process
|
||||
sshPrefix: "", // deprecated, should be empty
|
||||
isDangerous: false,
|
||||
searchTerm: "", // TODO: define search term
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: add support for caching, trigger, & getQueryTerm
|
||||
export const runGenerator = async (
|
||||
generator: Fig.Generator,
|
||||
tokens: string[],
|
||||
cwd: string
|
||||
): Promise<Fig.Suggestion[]> => {
|
||||
// TODO: support trigger
|
||||
const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
|
||||
|
||||
const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
|
||||
const suggestions = [];
|
||||
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();
|
||||
if (postProcess) {
|
||||
suggestions.push(...postProcess(scriptStdout, tokens));
|
||||
} else if (splitOn) {
|
||||
suggestions.push(...scriptStdout.split(splitOn).map((s) => ({ name: s })));
|
||||
}
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext(cwd))));
|
||||
}
|
||||
|
||||
if (template != null) {
|
||||
const templateSuggestions = await runTemplates(template, cwd);
|
||||
if (filterTemplateSuggestions) {
|
||||
suggestions.push(...filterTemplateSuggestions(templateSuggestions));
|
||||
} else {
|
||||
suggestions.push(...templateSuggestions);
|
||||
}
|
||||
}
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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: Suggestion[];
|
||||
argumentDescription?: string;
|
||||
charactersToDrop?: number;
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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;
|
||||
};
|
||||
@@ -0,0 +1,393 @@
|
||||
// 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 { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion";
|
||||
import { SuggestionBlob } from "./model";
|
||||
import { buildExecuteShellCommand, resolveCwd } from "./utils";
|
||||
import { Shell } from "../utils/shell";
|
||||
import { getApi } from "@/models";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
|
||||
const specSet: any = {};
|
||||
(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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadedSpecs[rootToken.token]) {
|
||||
return loadedSpecs[rootToken.token];
|
||||
}
|
||||
if (specSet[rootToken.token]) {
|
||||
const spec = (await import(`@withfig/autocomplete/build/${specSet[rootToken.token]}`)).default;
|
||||
loadedSpecs[rootToken.token] = spec;
|
||||
return spec;
|
||||
}
|
||||
};
|
||||
|
||||
// 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 rootToken = activeCmd.at(0);
|
||||
if (activeCmd.length === 0 || !rootToken?.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spec = await loadSpec(activeCmd);
|
||||
if (spec == null) return;
|
||||
const subcommand = getSubcommand(spec);
|
||||
if (subcommand == null) return;
|
||||
|
||||
const lastCommand = activeCmd.at(-1);
|
||||
const { cwd: resolvedCwd, pathy, complete: pathyComplete } = await resolveCwd(lastCommand, cwd, shell);
|
||||
if (pathy && lastCommand) {
|
||||
lastCommand.isPath = true;
|
||||
lastCommand.isPathComplete = pathyComplete;
|
||||
}
|
||||
const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd);
|
||||
if (result == null) return;
|
||||
|
||||
let charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
|
||||
if (pathy) {
|
||||
charactersToDrop = pathyComplete ? 0 : getApi().pathBaseName(lastCommand?.token ?? "").length;
|
||||
}
|
||||
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.prototype.hasOwnProperty.call(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> => {
|
||||
if (tokens.length === 0) {
|
||||
return getSubcommandDrivenRecommendation(
|
||||
subcommand,
|
||||
persistentOptions,
|
||||
undefined,
|
||||
argsDepleted,
|
||||
argsUsed,
|
||||
acceptedTokens,
|
||||
cwd
|
||||
);
|
||||
} else if (!tokens.at(0)?.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;
|
||||
}
|
||||
|
||||
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
||||
if (nextSubcommand != null) {
|
||||
return runSubcommand(
|
||||
tokens.slice(1),
|
||||
nextSubcommand,
|
||||
cwd,
|
||||
getPersistentOptions(persistentOptions, subcommand.options),
|
||||
getPersistentTokens(acceptedTokens.concat(activeToken))
|
||||
);
|
||||
}
|
||||
|
||||
if (activeArgsLength <= 0) {
|
||||
return; // not subcommand or option & no args exist
|
||||
}
|
||||
|
||||
const args = getArgs(subcommand.args);
|
||||
if (args.length != 0) {
|
||||
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));
|
||||
};
|
||||
@@ -0,0 +1,311 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { CommandToken } from "./parser";
|
||||
import { runGenerator } from "./generator";
|
||||
import { runTemplates } from "./template";
|
||||
import { Suggestion, SuggestionBlob } from "./model";
|
||||
import { getApi } from "@/models";
|
||||
|
||||
enum SuggestionIcons {
|
||||
File = "📄",
|
||||
Folder = "📁",
|
||||
Subcommand = "📦",
|
||||
Option = "🔗",
|
||||
Argument = "💲",
|
||||
Mixin = "🏝️",
|
||||
Shortcut = "🔥",
|
||||
Special = "⭐",
|
||||
Default = "📀",
|
||||
}
|
||||
|
||||
const getIcon = (icon: string | undefined, suggestionType: Fig.SuggestionType | undefined): string => {
|
||||
// TODO: enable fig icons once spacing is better
|
||||
// if (icon && /[^\u0000-\u00ff]/.test(icon)) {
|
||||
// return icon;
|
||||
// }
|
||||
switch (suggestionType) {
|
||||
case "arg":
|
||||
return SuggestionIcons.Argument;
|
||||
case "file":
|
||||
return SuggestionIcons.File;
|
||||
case "folder":
|
||||
return SuggestionIcons.Folder;
|
||||
case "option":
|
||||
return SuggestionIcons.Option;
|
||||
case "subcommand":
|
||||
return SuggestionIcons.Subcommand;
|
||||
case "mixin":
|
||||
return SuggestionIcons.Mixin;
|
||||
case "shortcut":
|
||||
return SuggestionIcons.Shortcut;
|
||||
case "special":
|
||||
return SuggestionIcons.Special;
|
||||
}
|
||||
return SuggestionIcons.Default;
|
||||
};
|
||||
|
||||
const getLong = (suggestion: Fig.SingleOrArray<string>): string => {
|
||||
return suggestion instanceof Array ? suggestion.reduce((p, c) => (p.length > c.length ? p : c)) : suggestion;
|
||||
};
|
||||
|
||||
const toSuggestion = (suggestion: Fig.Suggestion, name?: string, type?: Fig.SuggestionType): Suggestion | undefined => {
|
||||
if (suggestion.name == null) return;
|
||||
return {
|
||||
name: name ?? getLong(suggestion.name),
|
||||
description: suggestion.description,
|
||||
icon: getIcon(suggestion.icon, type ?? suggestion.type),
|
||||
allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
|
||||
priority: suggestion.priority ?? 50,
|
||||
insertValue: suggestion.insertValue,
|
||||
};
|
||||
};
|
||||
|
||||
function filter<
|
||||
T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string>; type?: Fig.SuggestionType | undefined }
|
||||
>(
|
||||
suggestions: T[],
|
||||
filterStrategy: FilterStrategy | undefined,
|
||||
partialCmd: string | undefined,
|
||||
suggestionType: Fig.SuggestionType | undefined
|
||||
): Suggestion[] {
|
||||
if (!partialCmd)
|
||||
return suggestions
|
||||
.map((s) => toSuggestion(s, undefined, suggestionType))
|
||||
.filter((s) => s != null) as Suggestion[];
|
||||
|
||||
switch (filterStrategy) {
|
||||
case "fuzzy":
|
||||
return suggestions
|
||||
.map((s) => {
|
||||
if (s.name == null) return;
|
||||
if (s.name instanceof Array) {
|
||||
const matchedName = s.name.find((n) => n.toLowerCase().includes(partialCmd.toLowerCase()));
|
||||
return matchedName != null
|
||||
? {
|
||||
name: matchedName,
|
||||
description: s.description,
|
||||
icon: getIcon(s.icon, s.type ?? suggestionType),
|
||||
allNames: s.name,
|
||||
priority: s.priority ?? 50,
|
||||
insertValue: s.insertValue,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
return s.name.toLowerCase().includes(partialCmd.toLowerCase())
|
||||
? {
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
icon: getIcon(s.icon, s.type ?? suggestionType),
|
||||
allNames: [s.name],
|
||||
priority: s.priority ?? 50,
|
||||
insertValue: s.insertValue,
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((s) => s != null) as Suggestion[];
|
||||
default:
|
||||
return suggestions
|
||||
.map((s) => {
|
||||
if (s.name == null) return;
|
||||
if (s.name instanceof Array) {
|
||||
const matchedName = s.name.find((n) => n.toLowerCase().startsWith(partialCmd.toLowerCase()));
|
||||
return matchedName != null
|
||||
? {
|
||||
name: matchedName,
|
||||
description: s.description,
|
||||
icon: getIcon(s.icon, s.type ?? suggestionType),
|
||||
allNames: s.name,
|
||||
insertValue: s.insertValue,
|
||||
priority: s.priority ?? 50,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
return s.name.toLowerCase().startsWith(partialCmd.toLowerCase())
|
||||
? {
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
icon: getIcon(s.icon, s.type ?? suggestionType),
|
||||
allNames: [s.name],
|
||||
insertValue: s.insertValue,
|
||||
priority: s.priority ?? 50,
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((s) => s != null) as Suggestion[];
|
||||
}
|
||||
}
|
||||
|
||||
type FilterStrategy = "fuzzy" | "prefix" | "default";
|
||||
|
||||
const generatorSuggestions = async (
|
||||
generator: Fig.SingleOrArray<Fig.Generator> | undefined,
|
||||
acceptedTokens: CommandToken[],
|
||||
filterStrategy: FilterStrategy | undefined,
|
||||
partialCmd: string | undefined,
|
||||
cwd: string
|
||||
): Promise<Suggestion[]> => {
|
||||
const generators = generator instanceof Array ? generator : generator ? [generator] : [];
|
||||
const tokens = acceptedTokens.map((t) => t.token);
|
||||
if (partialCmd) tokens.push(partialCmd);
|
||||
const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens, cwd)))).flat();
|
||||
return filter<Fig.Suggestion>(
|
||||
suggestions.map((suggestion) => ({ ...suggestion, priority: suggestion.priority ?? 60 })),
|
||||
filterStrategy,
|
||||
partialCmd,
|
||||
undefined
|
||||
);
|
||||
};
|
||||
|
||||
const templateSuggestions = async (
|
||||
templates: Fig.Template | undefined,
|
||||
filterStrategy: FilterStrategy | undefined,
|
||||
partialCmd: string | undefined,
|
||||
cwd: string
|
||||
): Promise<Suggestion[]> => {
|
||||
return filter<Fig.Suggestion>(await runTemplates(templates ?? [], cwd), filterStrategy, partialCmd, undefined);
|
||||
};
|
||||
|
||||
const suggestionSuggestions = (
|
||||
suggestions: (string | Fig.Suggestion)[] | undefined,
|
||||
filterStrategy: FilterStrategy | undefined,
|
||||
partialCmd: string | undefined
|
||||
): Suggestion[] => {
|
||||
const cleanedSuggestions = suggestions?.map((s) => (typeof s === "string" ? { name: s } : s)) ?? [];
|
||||
return filter<Fig.Suggestion>(cleanedSuggestions ?? [], filterStrategy, partialCmd, undefined);
|
||||
};
|
||||
|
||||
const subcommandSuggestions = (
|
||||
subcommands: Fig.Subcommand[] | undefined,
|
||||
filterStrategy: FilterStrategy | undefined,
|
||||
partialCmd: string | undefined
|
||||
): Suggestion[] => {
|
||||
return filter<Fig.Subcommand>(subcommands ?? [], filterStrategy, partialCmd, "subcommand");
|
||||
};
|
||||
|
||||
const optionSuggestions = (
|
||||
options: Fig.Option[] | undefined,
|
||||
acceptedTokens: CommandToken[],
|
||||
filterStrategy: FilterStrategy | undefined,
|
||||
partialCmd: string | undefined
|
||||
): Suggestion[] => {
|
||||
const usedOptions = new Set(acceptedTokens.filter((t) => t.isOption).map((t) => t.token));
|
||||
const validOptions = options?.filter(
|
||||
(o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true
|
||||
);
|
||||
return filter<Fig.Option>(validOptions ?? [], filterStrategy, partialCmd, "option");
|
||||
};
|
||||
|
||||
const removeAcceptedSuggestions = (suggestions: Suggestion[], acceptedTokens: CommandToken[]): Suggestion[] => {
|
||||
const seen = new Set<string>(acceptedTokens.map((t) => t.token));
|
||||
return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
|
||||
};
|
||||
|
||||
const removeDuplicateSuggestion = (suggestions: Suggestion[]): Suggestion[] => {
|
||||
const seen = new Set<string>();
|
||||
return suggestions
|
||||
.map((s) => {
|
||||
if (seen.has(s.name)) return null;
|
||||
seen.add(s.name);
|
||||
return s;
|
||||
})
|
||||
.filter((s): s is Suggestion => s != null);
|
||||
};
|
||||
|
||||
const removeEmptySuggestion = (suggestions: Suggestion[]): Suggestion[] => {
|
||||
return suggestions.filter((s) => s.name.length > 0);
|
||||
};
|
||||
|
||||
export const getSubcommandDrivenRecommendation = async (
|
||||
subcommand: Fig.Subcommand,
|
||||
persistentOptions: Fig.Option[],
|
||||
partialToken: CommandToken | undefined,
|
||||
argsDepleted: boolean,
|
||||
argsFromSubcommand: boolean,
|
||||
acceptedTokens: CommandToken[],
|
||||
cwd: string
|
||||
): Promise<SuggestionBlob | undefined> => {
|
||||
if (argsDepleted && argsFromSubcommand) {
|
||||
return;
|
||||
}
|
||||
let partialCmd = partialToken?.token;
|
||||
if (partialToken?.isPath) {
|
||||
partialCmd = partialToken.isPathComplete ? "" : getApi().pathBaseName(partialCmd ?? "");
|
||||
}
|
||||
|
||||
const suggestions: Suggestion[] = [];
|
||||
const argLength = subcommand.args instanceof Array ? subcommand.args.length : subcommand.args ? 1 : 0;
|
||||
const allOptions = persistentOptions.concat(subcommand.options ?? []);
|
||||
|
||||
if (!argsFromSubcommand) {
|
||||
suggestions.push(...subcommandSuggestions(subcommand.subcommands, subcommand.filterStrategy, partialCmd));
|
||||
suggestions.push(...optionSuggestions(allOptions, acceptedTokens, subcommand.filterStrategy, partialCmd));
|
||||
}
|
||||
if (argLength != 0) {
|
||||
const activeArg = subcommand.args instanceof Array ? subcommand.args[0] : subcommand.args;
|
||||
suggestions.push(
|
||||
...(await generatorSuggestions(
|
||||
activeArg?.generators,
|
||||
acceptedTokens,
|
||||
activeArg?.filterStrategy,
|
||||
partialCmd,
|
||||
cwd
|
||||
))
|
||||
);
|
||||
suggestions.push(...suggestionSuggestions(activeArg?.suggestions, activeArg?.filterStrategy, partialCmd));
|
||||
suggestions.push(
|
||||
...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: removeDuplicateSuggestion(
|
||||
removeEmptySuggestion(
|
||||
removeAcceptedSuggestions(
|
||||
suggestions.sort((a, b) => b.priority - a.priority),
|
||||
acceptedTokens
|
||||
)
|
||||
)
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const getArgDrivenRecommendation = async (
|
||||
args: Fig.Arg[],
|
||||
subcommand: Fig.Subcommand,
|
||||
persistentOptions: Fig.Option[],
|
||||
partialToken: CommandToken | undefined,
|
||||
acceptedTokens: CommandToken[],
|
||||
variadicArgBound: boolean,
|
||||
cwd: string
|
||||
): Promise<SuggestionBlob | undefined> => {
|
||||
let partialCmd = partialToken?.token;
|
||||
if (partialToken?.isPath) {
|
||||
partialCmd = partialToken.isPathComplete ? "" : getApi().pathBaseName(partialCmd ?? "");
|
||||
}
|
||||
|
||||
const activeArg = args[0];
|
||||
const allOptions = persistentOptions.concat(subcommand.options ?? []);
|
||||
const suggestions = [
|
||||
...(await generatorSuggestions(args[0].generators, acceptedTokens, activeArg?.filterStrategy, partialCmd, cwd)),
|
||||
...suggestionSuggestions(args[0].suggestions, activeArg?.filterStrategy, partialCmd),
|
||||
...(await templateSuggestions(args[0].template, activeArg?.filterStrategy, partialCmd, cwd)),
|
||||
];
|
||||
|
||||
if (activeArg.isOptional || (activeArg.isVariadic && variadicArgBound)) {
|
||||
suggestions.push(...subcommandSuggestions(subcommand.subcommands, activeArg?.filterStrategy, partialCmd));
|
||||
suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: removeDuplicateSuggestion(
|
||||
removeEmptySuggestion(
|
||||
removeAcceptedSuggestions(
|
||||
suggestions.sort((a, b) => b.priority - a.priority),
|
||||
acceptedTokens
|
||||
)
|
||||
)
|
||||
),
|
||||
argumentDescription: activeArg.description ?? activeArg.name,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
// const fsAsync = window.require("fs/promises");
|
||||
import log from "../utils/log";
|
||||
|
||||
// TODO: implement filepaths template
|
||||
const filepathsTemplate = async (cwd: string): Promise<Fig.TemplateSuggestion[]> => {
|
||||
// const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
||||
// return files
|
||||
// .filter((f) => f.isFile() || f.isDirectory())
|
||||
// .map((f) => ({ name: f.name, priority: 55, context: { templateType: "filepaths" } }));
|
||||
return [];
|
||||
};
|
||||
|
||||
// TODO: implement folders template
|
||||
const foldersTemplate = async (cwd: string): Promise<Fig.TemplateSuggestion[]> => {
|
||||
// const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
||||
// return files
|
||||
// .filter((f) => f.isDirectory())
|
||||
// .map((f) => ({ name: f.name, priority: 55, context: { templateType: "folders" } }));
|
||||
return [];
|
||||
};
|
||||
|
||||
// TODO: implement history template
|
||||
const historyTemplate = (): Fig.TemplateSuggestion[] => {
|
||||
return [];
|
||||
};
|
||||
|
||||
// TODO: implement help template
|
||||
const helpTemplate = (): Fig.TemplateSuggestion[] => {
|
||||
return [];
|
||||
};
|
||||
|
||||
export const runTemplates = async (
|
||||
template: Fig.TemplateStrings[] | Fig.Template,
|
||||
cwd: string
|
||||
): Promise<Fig.TemplateSuggestion[]> => {
|
||||
const templates = template instanceof Array ? template : [template];
|
||||
return (
|
||||
await Promise.all(
|
||||
templates.map(async (t) => {
|
||||
try {
|
||||
switch (t) {
|
||||
case "filepaths":
|
||||
return await filepathsTemplate(cwd);
|
||||
case "folders":
|
||||
return await foldersTemplate(cwd);
|
||||
case "history":
|
||||
return historyTemplate();
|
||||
case "help":
|
||||
return helpTemplate();
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug({ msg: "template failed", e, template: t, cwd });
|
||||
return [];
|
||||
}
|
||||
})
|
||||
)
|
||||
).flat();
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { CommandToken } from "./parser";
|
||||
import { Shell } from "../utils/shell";
|
||||
import { GlobalModel, getApi } from "@/models";
|
||||
|
||||
export type ExecuteShellCommandTTYResult = {
|
||||
code: number | null;
|
||||
};
|
||||
|
||||
export const buildExecuteShellCommand =
|
||||
(timeout: number): Fig.ExecuteCommandFunction =>
|
||||
async ({ command, env, args, cwd }: Fig.ExecuteCommandInput): Promise<Fig.ExecuteCommandOutput> => {
|
||||
const resp = await GlobalModel.submitEphemeralCommand("eval", null, [command, ...args], null, false, {
|
||||
expectsresponse: true,
|
||||
overridecwd: cwd,
|
||||
env: env,
|
||||
timeoutms: timeout,
|
||||
});
|
||||
console.log("resp", resp);
|
||||
|
||||
const { stdout, stderr } = await GlobalModel.getEphemeralCommandOutput(resp);
|
||||
console.log("stdout", stdout);
|
||||
console.log("stderr", stderr);
|
||||
return { stdout, stderr, status: stderr ? 1 : 0 };
|
||||
};
|
||||
|
||||
export const resolveCwd = async (
|
||||
cmdToken: CommandToken | undefined,
|
||||
cwd: string,
|
||||
shell: Shell
|
||||
): Promise<{ cwd: string; pathy: boolean; complete: boolean }> => {
|
||||
if (cmdToken == null) return { cwd, pathy: false, complete: false };
|
||||
const { token } = cmdToken;
|
||||
const sep = shell == Shell.Bash ? "/" : getApi()?.pathSep();
|
||||
return { cwd: cwd, pathy: true, complete: token.endsWith(sep) };
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// import os from "node:os";
|
||||
// import path from "node:path";
|
||||
// import fs from "node:fs";
|
||||
// import fsAsync from "node:fs/promises";
|
||||
|
||||
// const logFolder = path.join(os.homedir(), ".inshellisense");
|
||||
// const logTarget = path.join(logFolder, "inshellisense.log");
|
||||
let logEnabled = false;
|
||||
|
||||
const reset = async () => {
|
||||
// if (!fs.existsSync(logTarget)) {
|
||||
// await fsAsync.mkdir(logFolder, { recursive: true });
|
||||
// }
|
||||
// await fsAsync.writeFile(logTarget, "");
|
||||
// noop
|
||||
};
|
||||
|
||||
const debug = (content: object) => {
|
||||
if (!logEnabled) {
|
||||
return;
|
||||
}
|
||||
// fs.appendFile(logTarget, JSON.stringify(content) + "\n", (err) => {
|
||||
// if (err != null) {
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
console.log("[autocomplete]", content);
|
||||
};
|
||||
|
||||
export const enable = async () => {
|
||||
await reset();
|
||||
logEnabled = true;
|
||||
};
|
||||
|
||||
export default { reset, debug, enable };
|
||||
@@ -0,0 +1,9 @@
|
||||
export enum Shell {
|
||||
Bash = "bash",
|
||||
Powershell = "powershell",
|
||||
Pwsh = "pwsh",
|
||||
Zsh = "zsh",
|
||||
Fish = "fish",
|
||||
Cmd = "cmd",
|
||||
Xonsh = "xonsh",
|
||||
}
|
||||
@@ -558,6 +558,14 @@ electron.nativeTheme.on("updated", () => {
|
||||
}
|
||||
});
|
||||
|
||||
electron.ipcMain.on("path-basename", (event, p) => {
|
||||
event.returnValue = path.basename(p);
|
||||
});
|
||||
|
||||
electron.ipcMain.on("path-sep", (event) => {
|
||||
event.returnValue = path.sep;
|
||||
});
|
||||
|
||||
function readLastLinesOfFile(filePath: string, lineCount: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
child_process.exec(`tail -n ${lineCount} "${filePath}"`, (err, stdout, stderr) => {
|
||||
|
||||
@@ -31,4 +31,6 @@ contextBridge.exposeInMainWorld("api", {
|
||||
contextEditMenu: (position, opts) => ipcRenderer.send("context-editmenu", position, opts),
|
||||
onWaveSrvStatusChange: (callback) => ipcRenderer.on("wavesrv-status-change", callback),
|
||||
onToggleDevUI: (callback) => ipcRenderer.on("toggle-devui", callback),
|
||||
pathBaseName: (path) => ipcRenderer.sendSync("path-basename", path),
|
||||
pathSep: () => ipcRenderer.sendSync("path-sep"),
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ import { App } from "@/app/app";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import { loadFonts } from "@/util/fontutil";
|
||||
import * as textmeasure from "@/util/textmeasure";
|
||||
import { getApi } from "@/models";
|
||||
|
||||
// @ts-ignore
|
||||
let VERSION = __WAVETERM_VERSION__;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user