Skip to content

Commit 8dcc69b

Browse files
authored
feat: add command suggestions (#359)
* feat: add support for base level suggestions Signed-off-by: Chapman Pendery <[email protected]> * test: command suggestions Signed-off-by: Chapman Pendery <[email protected]> * fix: formatting Signed-off-by: Chapman Pendery <[email protected]> --------- Signed-off-by: Chapman Pendery <[email protected]>
1 parent a99e8d9 commit 8dcc69b

File tree

2 files changed

+64
-7
lines changed

2 files changed

+64
-7
lines changed

src/runtime/runtime.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import speclist, {
88
} from "@withfig/autocomplete/build/index.js";
99
import path from "node:path";
1010
import { parseCommand, CommandToken } from "./parser.js";
11-
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
12-
import { SuggestionBlob } from "./model.js";
11+
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation, SuggestionIcons } from "./suggestion.js";
12+
import { Suggestion, SuggestionBlob } from "./model.js";
1313
import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
1414
import { Shell } from "../utils/shell.js";
15-
import { aliasExpand } from "./alias.js";
15+
import { aliasExpand, getAliasNames } from "./alias.js";
1616
import { getConfig } from "../utils/config.js";
1717
import log from "../utils/log.js";
1818

@@ -87,9 +87,13 @@ export const loadLocalSpecsSet = async () => {
8787
export const getSuggestions = async (cmd: string, cwd: string, shell: Shell): Promise<SuggestionBlob | undefined> => {
8888
let activeCmd = parseCommand(cmd, shell);
8989
const rootToken = activeCmd.at(0);
90-
if (activeCmd.length === 0 || !rootToken?.complete) {
90+
if (activeCmd.length === 0) {
9191
return;
9292
}
93+
if (rootToken != null && !rootToken.complete) {
94+
return runCommand(rootToken);
95+
}
96+
9397
activeCmd = aliasExpand(activeCmd);
9498

9599
const spec = await loadSpec(activeCmd);
@@ -346,3 +350,37 @@ const runSubcommand = async (
346350
// if the subcommand has no args specified, fallback to the subcommand and ignore this item
347351
return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken));
348352
};
353+
354+
const runCommand = async (token: CommandToken): Promise<SuggestionBlob | undefined> => {
355+
const specs = Object.keys(specSet)
356+
.filter((spec) => spec.startsWith(token.token))
357+
.sort();
358+
const aliases = getAliasNames()
359+
.filter((spec) => spec.startsWith(token.token))
360+
.sort();
361+
return {
362+
suggestions: [
363+
...aliases.map(
364+
(alias) =>
365+
({
366+
name: alias,
367+
type: "shortcut",
368+
allNames: [alias],
369+
icon: SuggestionIcons.Shortcut,
370+
priority: 100,
371+
}) as Suggestion,
372+
),
373+
...specs.map(
374+
(spec) =>
375+
({
376+
name: spec,
377+
type: "subcommand",
378+
allNames: [spec],
379+
icon: SuggestionIcons.Subcommand,
380+
priority: 40,
381+
}) as Suggestion,
382+
),
383+
],
384+
charactersToDrop: token.tokenLength,
385+
};
386+
};

src/tests/runtime/runtime.test.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe(`parseCommand`, () => {
4646
});
4747
});
4848

49-
const commandSuggestionsData = [
49+
const subcommandSuggestionsData = [
5050
{
5151
name: "brewInstallMullvad",
5252
command: "brew install mullvad-brow",
@@ -64,10 +64,10 @@ const commandSuggestionsData = [
6464
{ name: "preCommitRun", command: "pre-commit run ", maxSuggestions: 1, expectedIcons: [SuggestionIcons.Option] }, // script + post-process generator w/ console logs
6565
];
6666

67-
describe(`getCommandSuggestions`, () => {
67+
describe(`getSubcommandSuggestions`, () => {
6868
beforeAll(async () => await getCommandSuggestionsSetup());
6969

70-
commandSuggestionsData.forEach(({ command, name, maxSuggestions, expectedNames, expectedIcons, platforms }) => {
70+
subcommandSuggestionsData.forEach(({ command, name, maxSuggestions, expectedNames, expectedIcons, platforms }) => {
7171
if (platforms != null && !platforms.includes(process.platform)) return;
7272
test(name, async () => {
7373
const suggestions = await getSuggestions(command, process.cwd(), Shell.Bash);
@@ -91,3 +91,22 @@ const getCommandSuggestionsSetup = async () => {
9191
const getCommandSuggestionsCleanup = async () => {
9292
fs.rmSync("demo.ts");
9393
};
94+
95+
const commandSuggestionsData = [
96+
{ name: "gi", command: "gi", maxSuggestions: 2, expectedNames: ["gibo", "git"], expectedIcons: [SuggestionIcons.Subcommand] }, // subcommand generator
97+
];
98+
99+
describe(`getCommandSuggestions`, () => {
100+
commandSuggestionsData.forEach(({ command, name, maxSuggestions, expectedNames, expectedIcons }) => {
101+
test(name, async () => {
102+
const suggestions = await getSuggestions(command, process.cwd(), Shell.Bash);
103+
if (suggestions != null && suggestions.suggestions != null) {
104+
suggestions.suggestions = suggestions?.suggestions.slice(0, maxSuggestions);
105+
}
106+
const names = suggestions?.suggestions.map((s) => s.allNames).flat() ?? [];
107+
const icons = suggestions?.suggestions.map((s) => s.icon) ?? [];
108+
expect(names).toEqual(expect.arrayContaining(expectedNames ?? []));
109+
expect(icons).toEqual(expect.arrayContaining(expectedIcons ?? []));
110+
});
111+
});
112+
});

0 commit comments

Comments
 (0)