feat: implement command execution

Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
Chapman Pendery
2023-10-07 18:49:33 -07:00
parent 5df3dd9d5a
commit 9b2851e3db
5 changed files with 85 additions and 19 deletions
+24 -2
View File
@@ -1,5 +1,27 @@
import { render } from "../ui/ui.js";
import { executeShellCommandTTY } from "../runtime/utils.js";
export const action = () => {
render();
const shells = ["bash", "powershell", "pwsh"];
export const supportedShells = shells.join(", ");
type RootCommandOptions = {
shell: string | undefined;
command: string | undefined;
};
export const action = async (options: RootCommandOptions) => {
const shell = options.shell ?? "";
if (!shells.includes(shell)) {
console.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`);
process.exit(1);
}
const commandToExecute = await render(options.command);
const result = await executeShellCommandTTY(shell, commandToExecute);
if (result.code) {
process.exit(result.code);
} else {
process.exit(0);
}
// TODO: cache executed command to add to history
};
+9 -7
View File
@@ -1,16 +1,18 @@
import { Command } from "commander";
import bind from "./commands/bind.js";
import { action } from "./commands/root.js";
import { action, supportedShells } from "./commands/root.js";
const program = new Command();
program.name("clac")
.description('IDE style command line auto complete')
.version("0.0.0", "-v, --version", "output the current version")
.action(action)
program
.name("clac")
.description("IDE style command line auto complete")
.version("0.0.0", "-v, --version", "output the current version")
.option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
.option("-c, --command <commmand>", "command to use as initial input")
.action(action);
program.addCommand(bind);
program.parse();
program.parse();
-1
View File
@@ -174,7 +174,6 @@ const removeDuplicateSuggestions = (suggestions: Suggestion[], acceptedTokens: C
return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
};
// TODO: implement re-ranking globally
export const getSubcommandDrivenRecommendation = async (
subcommand: Fig.Subcommand,
persistentOptions: Fig.Option[],
+16 -1
View File
@@ -1,4 +1,8 @@
import { exec } from "node:child_process";
import { exec, spawn } from "node:child_process";
type ExecuteShellCommandTTYResult = {
code: number | null;
};
export const buildExecuteShellCommand =
(timeout: number) =>
@@ -9,3 +13,14 @@ export const buildExecuteShellCommand =
});
});
};
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,
});
});
});
};
+36 -8
View File
@@ -1,16 +1,20 @@
import React, { useCallback, useEffect, useState } from "react";
import { Text, Box, render as inkRender, measureElement, DOMElement, useApp } from "ink";
import { Text, Box, render as inkRender, measureElement, DOMElement, useInput, useApp } from "ink";
import wrapAnsi from "wrap-ansi";
import { getSuggestions } from "../runtime/runtime.js";
import { Suggestion } from "../runtime/model.js";
import Suggestions from "./suggestions.js";
import Input from "./input.js";
const Prompt = "> ";
import { executeShellCommandTTY } from "../runtime/utils.js";
// TODO: support tab completion
function UI() {
const [command, setCommand] = useState("");
const Prompt = "> ";
let uiResult = "";
function UI({ startingCommand }: { startingCommand: string }) {
const { exit } = useApp();
const [isExiting, setIsExiting] = useState(false);
const [command, setCommand] = useState(startingCommand);
const [activeSuggestion, setActiveSuggestion] = useState<Suggestion>();
const [tabCompletionDropSize, setTabCompletionDropSize] = useState(0);
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
@@ -24,6 +28,19 @@ function UI() {
}
}, []);
useInput((_, key) => {
if (key.return) {
setIsExiting(true);
}
});
useEffect(() => {
if (isExiting) {
uiResult = command;
exit();
}
}, [isExiting]);
useEffect(() => {
getSuggestions(command).then((suggestions) => {
setSuggestions(suggestions?.suggestions ?? []);
@@ -31,6 +48,15 @@ function UI() {
});
}, [command]);
if (isExiting) {
return (
<Text>
{Prompt}
{command}
</Text>
);
}
return (
<Box flexDirection="column" ref={measureRef}>
<Box>
@@ -43,9 +69,11 @@ function UI() {
);
}
export const render = () => {
const { waitUntilExit } = inkRender(<UI />);
return waitUntilExit();
export const render = async (command: string | undefined) => {
const { waitUntilExit } = inkRender(<UI startingCommand={command ?? ""} />);
await waitUntilExit();
return uiResult;
};
function getLeftPadding(windowWidth: number, command: string) {