fix: generators by syncing types/generators & providing path field support (#129)

Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
Chapman Pendery
2023-12-18 11:56:40 -08:00
committed by GitHub
parent 6928eecb8f
commit 724304cb85
13 changed files with 171 additions and 89 deletions
+23 -20
View File
@@ -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": {
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+2
View File
@@ -6,6 +6,8 @@ export type CommandToken = {
complete: boolean;
isOption: boolean;
isPersistent?: boolean;
isPath?: boolean;
isPathComplete?: boolean;
};
const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
+18 -7
View File
@@ -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
View File
@@ -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,
+7 -7
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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);
}
+7 -3
View File
@@ -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
View File
@@ -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);