Skip to content

Commit

Permalink
Merge pull request #101 from tsirysndr/feat/fzf
Browse files Browse the repository at this point in the history
feat: use `fzf` for autocompletion
  • Loading branch information
tsirysndr authored Oct 19, 2023
2 parents 5af32b1 + 09bd112 commit 4aa6a5a
Showing 1 changed file with 140 additions and 17 deletions.
157 changes: 140 additions & 17 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { Input, green, cyan, magenta } from "./deps.ts";
import {
KeyCode,
parse,
} from "https://deno.land/x/[email protected]/keycode/mod.ts";
import { green, cyan, magenta } from "./deps.ts";
import { plugins } from "./plugins/mod.ts";
import { availableCommands, evaluateSystemCommand } from "./src/helpers.ts";
import {
availableCommands,
evaluateSystemCommand,
spawn,
} from "./src/helpers.ts";
import Brew from "./plugins/brew.ts";

const history: string[] = [];
const useSuggestions = [
Expand All @@ -9,7 +18,7 @@ const useSuggestions = [
];

async function repl(
message = "",
message = "> ",
suggestions = [
"use",
"help",
Expand All @@ -21,21 +30,17 @@ async function repl(
],
evaluate: (command: string) => Promise<void> = evaluateSystemCommand
) {
const command = await Input.prompt({
message,
suggestions,
});

const command = await readline(message, suggestions);
if (command === "exit") {
if (message === "") {
if (message === "> ") {
console.log("Bye!");
return;
}
repl();
return;
}

if (command === "list" && message === "") {
if (command === "list" && message === "> ") {
console.log("Available plugins:");
plugins.forEach((plugin) => console.log(green(plugin.name)));
console.log(`type ${cyan("use <plugin>")} to use a plugin`);
Expand All @@ -44,7 +49,7 @@ async function repl(
return;
}

if (command === "help" && message === "") {
if (command === "help" && message === "> ") {
console.log(`Common Commands:
use Use a plugin
help Show this message
Expand All @@ -55,18 +60,20 @@ async function repl(
return;
}

if (command.startsWith("use ")) {
const pluginName = command.split(" ")[1];
if (command!.startsWith("use ")) {
const pluginName = command!.split(" ")[1];
const selectedPlugin = plugins.find((p) => p.name === pluginName);
if (selectedPlugin) {
history.push(`use ${selectedPlugin.name}`);
await selectedPlugin.install();
repl(
selectedPlugin.name,
`${selectedPlugin.name} > `,
[
...Object.keys(selectedPlugin.commands),
...history,
...useSuggestions,
"exit",
"use",
],
(command: string) => selectedPlugin.evaluate(command)
);
Expand All @@ -89,8 +96,8 @@ async function repl(
return;
}

history.push(command);
await evaluate(command);
history.push(command!);
await evaluate(command!);
repl(message, [...suggestions, ...history], evaluate);
}

Expand All @@ -112,7 +119,123 @@ if (import.meta.main) {
./((((((((((((((((((((((((((( .
.`)
);
console.log("Repl v0.6.2 🚀 ✨");
console.log("Repl v0.7.0 🚀 ✨");
console.log("exit using ctrl+c, or exit, type help for more info");
repl();
}

async function readline(message: string, suggestions: string[]) {
Deno.stdout.writeSync(new TextEncoder().encode(message));
let input: string[] = [];
let cursor = 0;
while (true) {
const data = new Uint8Array(8);

Deno.stdin.setRaw(true);
const nread = await Deno.stdin.read(data);
Deno.stdin.setRaw(false);

if (nread === null) {
break;
}

const keys: Array<KeyCode> = parse(data.subarray(0, nread));

for (const key of keys) {
if (key.ctrl && key.name === "c") {
console.log("\nexit");
Deno.exit();
}
if (key.name === "up") {
continue;
}
if (key.name === "down") {
continue;
}
if (key.name === "left") {
if (cursor > 0) {
cursor = cursor - 1;
} else {
continue;
}
}
if (key.name === "right") {
if (cursor < input.length) {
cursor = cursor + 1;
} else {
continue;
}
}
if (key.name === "return") {
console.log();
return input.join("");
}
if (key.sequence === "\x7f") {
if (cursor > 0) {
cursor = cursor - 1;
input.splice(cursor, 1);
}
Deno.stdout.writeSync(new TextEncoder().encode("\x1b[2K\r"));
Deno.stdout.writeSync(new TextEncoder().encode(message));
Deno.stdout.writeSync(new TextEncoder().encode(`${input.join("")}`));
if (cursor < input.length) {
Deno.stdout.writeSync(
new TextEncoder().encode(`\x1b[${input.length - cursor}D`)
);
continue;
}
Deno.stdout.writeSync(
new TextEncoder().encode(`\x1b[${input.length - cursor - 3}C`)
);
}
if (
key.sequence?.match(/^[0-9a-zA-Z!@#$%^&*()_+=\[\]{};:'",<.>/?\\| -]+$/u)
) {
cursor++;
input.splice(cursor, 0, key.sequence);
if (input.join("").split(" ").length > 1) {
Deno.stdout.writeSync(new TextEncoder().encode(key.sequence));
continue;
}
const selected = await fzf(suggestions, input.join(""));
Deno.stdout.writeSync(new TextEncoder().encode(selected));
if (selected) {
Deno.stdout.writeSync(new TextEncoder().encode("\x1b[2K\r"));
Deno.stdout.writeSync(new TextEncoder().encode(message));

input = selected.split("");
cursor = input.length;
Deno.stdout.writeSync(new TextEncoder().encode(`${input.join("")}`));
continue;
}
}

Deno.stdout.writeSync(new TextEncoder().encode(key.sequence));
}
}
}

async function fzf(suggestions: string[], message: string) {
await setupFzf();
const command = new Deno.Command("sh", {
args: [
"-c",
'echo "' + suggestions.join("\n") + '" | fzf' + " -q " + message,
],
stdin: "inherit",
stdout: "piped",
stderr: "inherit",
});
const child = command.spawn();
const { stdout } = await child.output();
const decoder = new TextDecoder();
const output = decoder.decode(stdout);
const lines = output.split("\n");
const selected = lines[lines.length - 2];
return selected;
}

async function setupFzf() {
await new Brew().install();
await spawn("sh", ["-c", "type fzf > /dev/null || brew install fzf"]);
}

0 comments on commit 4aa6a5a

Please sign in to comment.