mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
feat: implement command execution
Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
+24
-2
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user