mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
fix: generators by syncing types/generators & providing path field support (#129)
Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
Generated
+23
-20
@@ -849,9 +849,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@fig/autocomplete-generators": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fig/autocomplete-generators/-/autocomplete-generators-2.2.5.tgz",
|
||||
"integrity": "sha512-FBwrK3U32fMTc0Zij0ixdbqFc4Yt0hkEqhTzsjUGlA0WXkEw+/r8jswmZkwjaj2+W+1yqMWyuk4BaY6FQAdfuA=="
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fig/autocomplete-generators/-/autocomplete-generators-2.4.0.tgz",
|
||||
"integrity": "sha512-fiaaCGmsgnbUJbVbNAcVDmrnCGj/SmfarK6WKt/lfQP9k1hLHkkmZQ836VSMJvPP1vAKFAiXpdJziG6EGyjAYg=="
|
||||
},
|
||||
"node_modules/@fig/autocomplete-helpers": {
|
||||
"version": "1.0.7",
|
||||
@@ -2089,11 +2089,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@withfig/autocomplete": {
|
||||
"version": "2.633.0",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete/-/autocomplete-2.633.0.tgz",
|
||||
"integrity": "sha512-vNjwa88e6x0TSjUyWWqmQ+q7LlpV46RqnJqJX10tHz5WmO0mDADrlSNG5AneaXWLsGqznPQscsmA9PLWWrvxCA==",
|
||||
"version": "2.648.2",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete/-/autocomplete-2.648.2.tgz",
|
||||
"integrity": "sha512-Dw6Irj1YoWEq92zCPRLNDMA4voKjna7jr10DuHHvnrFRbt4xOiYVbBZM+mPCkRMTerUOjrSOcPGQM4j3sA/fyA==",
|
||||
"dependencies": {
|
||||
"@fig/autocomplete-generators": "^2.2.5",
|
||||
"@fig/autocomplete-generators": "^2.4.0",
|
||||
"@fig/autocomplete-helpers": "^1.0.7",
|
||||
"@fig/autocomplete-hooks": "^1.0.2",
|
||||
"@withfig/api-bindings": "^0.30.3",
|
||||
@@ -2102,12 +2102,15 @@
|
||||
"semver": "^7.5.4",
|
||||
"strip-json-comments": "^5.0.1",
|
||||
"yaml": "^2.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@withfig/autocomplete-types": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete-types/-/autocomplete-types-1.28.0.tgz",
|
||||
"integrity": "sha512-DB5qNZ9r5/giXD/6WY7dPQEWKh6/KEgnwSnRDaNusTfseKoEXUczVM9KLJRzuzNri1EBhzqJWPFqjYwBkr63jA==",
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete-types/-/autocomplete-types-1.29.0.tgz",
|
||||
"integrity": "sha512-lg7OR48K408nNPiciZ0HXveJuEgEAaylwk9VEnfPGgoWgNFnEPI32dZdZVTHTEtoE0taLByF8O8uosiYsl2+kQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@@ -8557,9 +8560,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@fig/autocomplete-generators": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fig/autocomplete-generators/-/autocomplete-generators-2.2.5.tgz",
|
||||
"integrity": "sha512-FBwrK3U32fMTc0Zij0ixdbqFc4Yt0hkEqhTzsjUGlA0WXkEw+/r8jswmZkwjaj2+W+1yqMWyuk4BaY6FQAdfuA=="
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fig/autocomplete-generators/-/autocomplete-generators-2.4.0.tgz",
|
||||
"integrity": "sha512-fiaaCGmsgnbUJbVbNAcVDmrnCGj/SmfarK6WKt/lfQP9k1hLHkkmZQ836VSMJvPP1vAKFAiXpdJziG6EGyjAYg=="
|
||||
},
|
||||
"@fig/autocomplete-helpers": {
|
||||
"version": "1.0.7",
|
||||
@@ -9518,11 +9521,11 @@
|
||||
}
|
||||
},
|
||||
"@withfig/autocomplete": {
|
||||
"version": "2.633.0",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete/-/autocomplete-2.633.0.tgz",
|
||||
"integrity": "sha512-vNjwa88e6x0TSjUyWWqmQ+q7LlpV46RqnJqJX10tHz5WmO0mDADrlSNG5AneaXWLsGqznPQscsmA9PLWWrvxCA==",
|
||||
"version": "2.648.2",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete/-/autocomplete-2.648.2.tgz",
|
||||
"integrity": "sha512-Dw6Irj1YoWEq92zCPRLNDMA4voKjna7jr10DuHHvnrFRbt4xOiYVbBZM+mPCkRMTerUOjrSOcPGQM4j3sA/fyA==",
|
||||
"requires": {
|
||||
"@fig/autocomplete-generators": "^2.2.5",
|
||||
"@fig/autocomplete-generators": "^2.4.0",
|
||||
"@fig/autocomplete-helpers": "^1.0.7",
|
||||
"@fig/autocomplete-hooks": "^1.0.2",
|
||||
"@withfig/api-bindings": "^0.30.3",
|
||||
@@ -9534,9 +9537,9 @@
|
||||
}
|
||||
},
|
||||
"@withfig/autocomplete-types": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete-types/-/autocomplete-types-1.28.0.tgz",
|
||||
"integrity": "sha512-DB5qNZ9r5/giXD/6WY7dPQEWKh6/KEgnwSnRDaNusTfseKoEXUczVM9KLJRzuzNri1EBhzqJWPFqjYwBkr63jA==",
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@withfig/autocomplete-types/-/autocomplete-types-1.29.0.tgz",
|
||||
"integrity": "sha512-lg7OR48K408nNPiciZ0HXveJuEgEAaylwk9VEnfPGgoWgNFnEPI32dZdZVTHTEtoE0taLByF8O8uosiYsl2+kQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import os from "node:os";
|
||||
import { Command } from "commander";
|
||||
import { getSuggestions } from "../runtime/runtime.js";
|
||||
import { Shell } from "../utils/shell.js";
|
||||
|
||||
const action = async (input: string) => {
|
||||
const suggestions = await getSuggestions(input, process.cwd());
|
||||
const suggestions = await getSuggestions(input, process.cwd(), os.platform() === "win32" ? Shell.Cmd : Shell.Bash);
|
||||
process.stdout.write(JSON.stringify(suggestions));
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ export class ISTerm implements IPty {
|
||||
case IstermOscPt.CurrentWorkingDirectory: {
|
||||
const cwd = data.split(";").at(1);
|
||||
if (cwd != null) {
|
||||
this.cwd = this._sanitizedCwd(this._deserializeIsMessage(cwd));
|
||||
this.cwd = path.resolve(this._sanitizedCwd(this._deserializeIsMessage(cwd)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+15
-12
@@ -18,24 +18,22 @@ const getGeneratorContext = (cwd: string): Fig.GeneratorContext => {
|
||||
|
||||
// TODO: add support for caching, trigger, & getQueryTerm
|
||||
export const runGenerator = async (generator: Fig.Generator, tokens: string[], cwd: string): Promise<Fig.Suggestion[]> => {
|
||||
const { script, postProcess, scriptTimeout, splitOn, custom, template } = generator;
|
||||
// TODO: support trigger
|
||||
const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
|
||||
|
||||
const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
|
||||
const suggestions = [];
|
||||
try {
|
||||
if (script) {
|
||||
const scriptOutput =
|
||||
typeof script === "function"
|
||||
? script(tokens)
|
||||
: typeof script === "string"
|
||||
? await executeShellCommand(script)
|
||||
: script != null
|
||||
? await executeShellCommand((script as string[]).join(" "))
|
||||
: "";
|
||||
const shellInput = typeof script === "function" ? script(tokens) : script;
|
||||
const scriptOutput = Array.isArray(shellInput)
|
||||
? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1) })
|
||||
: await executeShellCommand(shellInput);
|
||||
|
||||
if (postProcess) {
|
||||
suggestions.push(...postProcess(scriptOutput, tokens));
|
||||
suggestions.push(...postProcess(scriptOutput.stdout, tokens));
|
||||
} else if (splitOn) {
|
||||
suggestions.push(...scriptOutput.split(splitOn).map((s) => ({ name: s })));
|
||||
suggestions.push(...scriptOutput.stdout.split(splitOn).map((s) => ({ name: s })));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +42,12 @@ export const runGenerator = async (generator: Fig.Generator, tokens: string[], c
|
||||
}
|
||||
|
||||
if (template != null) {
|
||||
suggestions.push(...(await runTemplates(template, cwd)));
|
||||
const templateSuggestions = await runTemplates(template, cwd);
|
||||
if (filterTemplateSuggestions) {
|
||||
suggestions.push(...filterTemplateSuggestions(templateSuggestions));
|
||||
} else {
|
||||
suggestions.push(...templateSuggestions);
|
||||
}
|
||||
}
|
||||
return suggestions;
|
||||
} catch (e) {
|
||||
|
||||
@@ -6,6 +6,8 @@ export type CommandToken = {
|
||||
complete: boolean;
|
||||
isOption: boolean;
|
||||
isPersistent?: boolean;
|
||||
isPath?: boolean;
|
||||
isPathComplete?: boolean;
|
||||
};
|
||||
|
||||
const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
|
||||
|
||||
+18
-7
@@ -6,10 +6,12 @@ import speclist, {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
} from "@withfig/autocomplete/build/index.js";
|
||||
import path from "node:path";
|
||||
import { parseCommand, CommandToken } from "./parser.js";
|
||||
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
|
||||
import { SuggestionBlob } from "./model.js";
|
||||
import { buildExecuteShellCommand } from "./utils.js";
|
||||
import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
|
||||
import { Shell } from "../utils/shell.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
|
||||
const specSet: any = {};
|
||||
@@ -55,7 +57,7 @@ const lazyLoadSpecLocation = async (location: Fig.SpecLocation): Promise<Fig.Spe
|
||||
return; //TODO: implement spec location loading
|
||||
};
|
||||
|
||||
export const getSuggestions = async (cmd: string, cwd: string): Promise<SuggestionBlob | undefined> => {
|
||||
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) {
|
||||
@@ -67,10 +69,19 @@ export const getSuggestions = async (cmd: string, cwd: string): Promise<Suggesti
|
||||
const subcommand = getSubcommand(spec);
|
||||
if (subcommand == null) return;
|
||||
|
||||
const result = await runSubcommand(activeCmd.slice(1), subcommand, cwd);
|
||||
if (result == null) return;
|
||||
const lastCommand = activeCmd.at(-1);
|
||||
const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
|
||||
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 : path.basename(lastCommand?.token ?? "").length;
|
||||
}
|
||||
return { ...result, charactersToDrop };
|
||||
};
|
||||
|
||||
@@ -193,7 +204,7 @@ const runArg = async (
|
||||
} 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].token, acceptedTokens, fromVariadic, cwd);
|
||||
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd);
|
||||
}
|
||||
|
||||
const activeToken = tokens[0];
|
||||
@@ -240,7 +251,7 @@ const runSubcommand = async (
|
||||
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].token, argsDepleted, argsUsed, acceptedTokens, cwd);
|
||||
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd);
|
||||
}
|
||||
|
||||
const activeToken = tokens[0];
|
||||
|
||||
+38
-11
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import path from "node:path";
|
||||
|
||||
import { CommandToken } from "./parser.js";
|
||||
import { runGenerator } from "./generator.js";
|
||||
import { runTemplates } from "./template.js";
|
||||
@@ -178,11 +180,22 @@ const optionSuggestions = (
|
||||
return filter<Fig.Option>(validOptions ?? [], filterStrategy, partialCmd, "option");
|
||||
};
|
||||
|
||||
const removeDuplicateSuggestions = (suggestions: Suggestion[], acceptedTokens: CommandToken[]): Suggestion[] => {
|
||||
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);
|
||||
};
|
||||
@@ -190,7 +203,7 @@ const removeEmptySuggestion = (suggestions: Suggestion[]): Suggestion[] => {
|
||||
export const getSubcommandDrivenRecommendation = async (
|
||||
subcommand: Fig.Subcommand,
|
||||
persistentOptions: Fig.Option[],
|
||||
partialCmd: string | undefined,
|
||||
partialToken: CommandToken | undefined,
|
||||
argsDepleted: boolean,
|
||||
argsFromSubcommand: boolean,
|
||||
acceptedTokens: CommandToken[],
|
||||
@@ -199,6 +212,11 @@ export const getSubcommandDrivenRecommendation = async (
|
||||
if (argsDepleted && argsFromSubcommand) {
|
||||
return;
|
||||
}
|
||||
let partialCmd = partialToken?.token;
|
||||
if (partialToken?.isPath) {
|
||||
partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
|
||||
}
|
||||
|
||||
const suggestions: Suggestion[] = [];
|
||||
const argLength = subcommand.args instanceof Array ? subcommand.args.length : subcommand.args ? 1 : 0;
|
||||
const allOptions = persistentOptions.concat(subcommand.options ?? []);
|
||||
@@ -215,10 +233,12 @@ export const getSubcommandDrivenRecommendation = async (
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: removeEmptySuggestion(
|
||||
removeDuplicateSuggestions(
|
||||
suggestions.sort((a, b) => b.priority - a.priority),
|
||||
acceptedTokens,
|
||||
suggestions: removeDuplicateSuggestion(
|
||||
removeEmptySuggestion(
|
||||
removeAcceptedSuggestions(
|
||||
suggestions.sort((a, b) => b.priority - a.priority),
|
||||
acceptedTokens,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
@@ -228,11 +248,16 @@ export const getArgDrivenRecommendation = async (
|
||||
args: Fig.Arg[],
|
||||
subcommand: Fig.Subcommand,
|
||||
persistentOptions: Fig.Option[],
|
||||
partialCmd: string | undefined,
|
||||
partialToken: CommandToken | undefined,
|
||||
acceptedTokens: CommandToken[],
|
||||
variadicArgBound: boolean,
|
||||
cwd: string,
|
||||
): Promise<SuggestionBlob | undefined> => {
|
||||
let partialCmd = partialToken?.token;
|
||||
if (partialToken?.isPath) {
|
||||
partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
|
||||
}
|
||||
|
||||
const activeArg = args[0];
|
||||
const allOptions = persistentOptions.concat(subcommand.options ?? []);
|
||||
const suggestions = [
|
||||
@@ -247,10 +272,12 @@ export const getArgDrivenRecommendation = async (
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: removeEmptySuggestion(
|
||||
removeDuplicateSuggestions(
|
||||
suggestions.sort((a, b) => b.priority - a.priority),
|
||||
acceptedTokens,
|
||||
suggestions: removeDuplicateSuggestion(
|
||||
removeEmptySuggestion(
|
||||
removeAcceptedSuggestions(
|
||||
suggestions.sort((a, b) => b.priority - a.priority),
|
||||
acceptedTokens,
|
||||
),
|
||||
),
|
||||
),
|
||||
argumentDescription: activeArg.description ?? activeArg.name,
|
||||
|
||||
@@ -4,27 +4,27 @@
|
||||
import fsAsync from "node:fs/promises";
|
||||
import log from "../utils/log.js";
|
||||
|
||||
const filepathsTemplate = async (cwd: string): Promise<Fig.Suggestion[]> => {
|
||||
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: 90 }));
|
||||
return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 90, context: { templateType: "filepaths" } }));
|
||||
};
|
||||
|
||||
const foldersTemplate = async (cwd: string): Promise<Fig.Suggestion[]> => {
|
||||
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: 90 }));
|
||||
return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 90, context: { templateType: "folders" } }));
|
||||
};
|
||||
|
||||
// TODO: implement history template
|
||||
const historyTemplate = (): Fig.Suggestion[] => {
|
||||
const historyTemplate = (): Fig.TemplateSuggestion[] => {
|
||||
return [];
|
||||
};
|
||||
|
||||
// TODO: implement help template
|
||||
const helpTemplate = (): Fig.Suggestion[] => {
|
||||
const helpTemplate = (): Fig.TemplateSuggestion[] => {
|
||||
return [];
|
||||
};
|
||||
|
||||
export const runTemplates = async (template: Fig.TemplateStrings[] | Fig.Template, cwd: string): Promise<Fig.Suggestion[]> => {
|
||||
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(
|
||||
|
||||
+44
-15
@@ -1,30 +1,59 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { exec, spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import fsAsync from "node:fs/promises";
|
||||
|
||||
import { CommandToken } from "./parser.js";
|
||||
import { Shell } from "../utils/shell.js";
|
||||
|
||||
export type ExecuteShellCommandTTYResult = {
|
||||
code: number | null;
|
||||
};
|
||||
|
||||
export const buildExecuteShellCommand =
|
||||
(timeout: number) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use cwd in the future
|
||||
async (command: string, cwd?: string): Promise<string> => {
|
||||
(timeout: number): Fig.ExecuteCommandFunction =>
|
||||
async ({ command, env, args, cwd }: Fig.ExecuteCommandInput): Promise<Fig.ExecuteCommandOutput> => {
|
||||
const child = spawn(command, args, { cwd, env });
|
||||
setTimeout(() => child.kill("SIGKILL"), timeout);
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (data) => (stdout += data));
|
||||
child.stderr.on("data", (data) => (stderr += data));
|
||||
return new Promise((resolve) => {
|
||||
exec(command, { timeout, cwd }, (_, stdout, stderr) => {
|
||||
resolve(stdout || stderr);
|
||||
child.on("close", (code) => {
|
||||
resolve({
|
||||
status: code ?? 0,
|
||||
stderr,
|
||||
stdout,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const executeShellCommandTTY = async (shell: string, command: string): Promise<ExecuteShellCommandTTYResult> => {
|
||||
const child = spawn(shell, ["-c", command.trim()], { stdio: "inherit" });
|
||||
return new Promise((resolve) => {
|
||||
child.on("close", (code) => {
|
||||
resolve({
|
||||
code,
|
||||
});
|
||||
});
|
||||
});
|
||||
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 ? "/" : path.sep;
|
||||
if (!token.includes(sep)) return { cwd, pathy: false, complete: false };
|
||||
const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
|
||||
try {
|
||||
await fsAsync.access(resolvedCwd, fsAsync.constants.R_OK);
|
||||
return { cwd: resolvedCwd, pathy: true, complete: token.endsWith(sep) };
|
||||
} catch {
|
||||
// fallback to the parent folder if possible
|
||||
const baselessCwd = resolvedCwd.substring(0, resolvedCwd.length - path.basename(resolvedCwd).length);
|
||||
try {
|
||||
await fsAsync.access(baselessCwd, fsAsync.constants.R_OK);
|
||||
return { cwd: baselessCwd, pathy: true, complete: token.endsWith(sep) };
|
||||
} catch {
|
||||
/*empty*/
|
||||
}
|
||||
return { cwd, pathy: false, complete: false };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -482,6 +482,16 @@ exports[`parseCommand optionsSuggestedAfterVariadicArg 1`] = `
|
||||
"argumentDescription": undefined,
|
||||
"charactersToDrop": 1,
|
||||
"suggestions": [
|
||||
{
|
||||
"allNames": [
|
||||
"package-lock.json",
|
||||
],
|
||||
"description": undefined,
|
||||
"icon": "📀",
|
||||
"insertValue": undefined,
|
||||
"name": "package-lock.json",
|
||||
"priority": 90,
|
||||
},
|
||||
{
|
||||
"allNames": [
|
||||
"-@",
|
||||
@@ -502,16 +512,6 @@ exports[`parseCommand optionsSuggestedAfterVariadicArg 1`] = `
|
||||
"name": "-1",
|
||||
"priority": 50,
|
||||
},
|
||||
{
|
||||
"allNames": [
|
||||
"-A",
|
||||
],
|
||||
"description": "List all entries except for . and ... Always set for the super-user",
|
||||
"icon": "🔗",
|
||||
"insertValue": undefined,
|
||||
"name": "-A",
|
||||
"priority": 50,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { getSuggestions } from "../../runtime/runtime.js";
|
||||
import { Shell } from "../../utils/shell.js";
|
||||
|
||||
const testData = [
|
||||
{ name: "partialPrefixFilter", command: "git sta" },
|
||||
@@ -26,7 +27,7 @@ describe(`parseCommand`, () => {
|
||||
testData.forEach(({ command, name, skip, maxSuggestions }) => {
|
||||
if (skip) return;
|
||||
test(name, async () => {
|
||||
const suggestions = await getSuggestions(command, process.cwd());
|
||||
const suggestions = await getSuggestions(command, process.cwd(), Shell.Cmd);
|
||||
if (suggestions != null && suggestions.suggestions != null) {
|
||||
suggestions.suggestions = suggestions?.suggestions.slice(0, maxSuggestions);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { renderBox, truncateText, truncateMultilineText } from "./utils.js";
|
||||
import ansi from "ansi-escapes";
|
||||
import chalk from "chalk";
|
||||
import { parseKeystroke } from "../utils/ansi.js";
|
||||
import { Shell } from "../utils/shell.js";
|
||||
|
||||
const maxSuggestions = 5;
|
||||
const suggestionWidth = 40;
|
||||
@@ -26,12 +27,14 @@ export class SuggestionManager {
|
||||
#command: string;
|
||||
#activeSuggestionIdx: number;
|
||||
#suggestBlob?: SuggestionBlob;
|
||||
#shell: Shell;
|
||||
|
||||
constructor(terminal: ISTerm) {
|
||||
constructor(terminal: ISTerm, shell: Shell) {
|
||||
this.#term = terminal;
|
||||
this.#suggestBlob = { suggestions: [] };
|
||||
this.#command = "";
|
||||
this.#activeSuggestionIdx = 0;
|
||||
this.#shell = shell;
|
||||
}
|
||||
|
||||
private async _loadSuggestions(): Promise<void> {
|
||||
@@ -44,7 +47,7 @@ export class SuggestionManager {
|
||||
return;
|
||||
}
|
||||
this.#command = commandText;
|
||||
const suggestionBlob = await getSuggestions(commandText, this.#term.cwd);
|
||||
const suggestionBlob = await getSuggestions(commandText, this.#term.cwd, this.#shell);
|
||||
this.#suggestBlob = suggestionBlob;
|
||||
}
|
||||
|
||||
@@ -148,7 +151,8 @@ export class SuggestionManager {
|
||||
this.#activeSuggestionIdx = Math.min(this.#activeSuggestionIdx + 1, (this.#suggestBlob?.suggestions.length ?? 1) - 1);
|
||||
} else if (keyStroke == "tab") {
|
||||
const removals = "\u007F".repeat(this.#suggestBlob?.charactersToDrop ?? 0);
|
||||
const chars = this.#suggestBlob?.suggestions.at(this.#activeSuggestionIdx)?.name + " ";
|
||||
const suggestion = this.#suggestBlob?.suggestions.at(this.#activeSuggestionIdx);
|
||||
const chars = suggestion?.insertValue ?? suggestion?.name + " ";
|
||||
if (this.#suggestBlob == null || !chars.trim() || this.#suggestBlob?.suggestions.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ export const renderConfirmation = (live: boolean): string => {
|
||||
|
||||
export const render = async (shell: Shell) => {
|
||||
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns });
|
||||
const suggestionManager = new SuggestionManager(term);
|
||||
const suggestionManager = new SuggestionManager(term, shell);
|
||||
let hasActiveSuggestions = false;
|
||||
let previousSuggestionsRows = 0;
|
||||
process.stdin.setRawMode(true);
|
||||
|
||||
Reference in New Issue
Block a user