pdf-tools/cli/prompts.ts

187 lines
4.7 KiB
TypeScript

// deno-lint-disable-must-await-calls
import { log } from "util/logfile.ts";
import { Cursor } from "./cursor.ts";
import { colorize } from "./style.ts";
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
export async function cliPrompt(
message: string,
block?: TerminalBlock,
): Promise<string> {
const encoder = new TextEncoder();
const input: string[] = [];
let cursorPos = 0;
await Deno.stdin.setRaw(true);
const cursorVisible = Cursor["visible"];
Cursor.show();
let range: [number, number] = [0, 1];
if (block) {
range = block.setLines([message + " "]);
} else {
Deno.stdout.writeSync(encoder.encode(message + " "));
}
const render = () => {
const line = message + " " + input.join("");
const moveTo = `\x1b[${message.length + 2 + cursorPos}G`;
if (block) {
block.setPostRenderAction(function () {
Deno.stdout.writeSync(
encoder.encode(`\x1b[${this["lastRenderRow"]};1H`),
);
Deno.stdout.writeSync(encoder.encode(moveTo));
});
range = block.setLines([line], range);
} else {
Deno.stdout.writeSync(encoder.encode("\x1b[K" + line + moveTo));
}
};
render();
const buf = new Uint8Array(64); // large enough for most pastes
while (true) {
const n = await Deno.stdin.read(buf);
if (n === null) break;
for (let i = 0; i < n; i++) {
const byte = buf[i];
// Ctrl+C
if (byte === 3) {
block?.clear();
block?.["layout"]?.clearAll();
await Deno.stdin.setRaw(false);
Deno.exit(130);
}
if (byte === 13) { // Enter
break;
}
// Escape sequence?
if (byte === 27 && i + 1 < n && buf[i + 1] === 91) {
const third = buf[i + 2];
if (third === 68 && cursorPos > 0) cursorPos--; // Left
else if (third === 67 && cursorPos < input.length) cursorPos++; // Right
else if (third === 51 && i + 3 < n && buf[i + 3] === 126) { // Delete
if (cursorPos < input.length) input.splice(cursorPos, 1);
i += 1; // consume tilde
}
i += 2; // consume ESC [ X
continue;
}
// Backspace
if (byte === 127 || byte === 8) {
if (cursorPos > 0) {
input.splice(cursorPos - 1, 1);
cursorPos--;
}
continue;
}
// Delete (ASCII 46)
if (byte === 46 && cursorPos < input.length) {
input.splice(cursorPos, 1);
continue;
}
// Printable
if (byte >= 32 && byte <= 126) {
input.splice(cursorPos, 0, String.fromCharCode(byte));
cursorPos++;
}
// Other cases: ignore
}
render();
}
await Deno.stdin.setRaw(false);
if (!cursorVisible) {
Cursor.hide();
}
Deno.stdout.writeSync(encoder.encode("\n"));
return input.join("");
}
export function cliConfirm(message: string, block?: TerminalBlock) {
return cliPrompt(message + " (y/n)", block).then((v) =>
v.toLowerCase() === "y"
);
}
export function cliAlert(message: string, block?: TerminalBlock) {
return cliPrompt(
message + colorize(" Press Enter to continue", "gray"),
block,
).then((v) => {
return v;
});
}
export function cliLog(
message: string | object | Array<unknown>,
block?: TerminalBlock,
) {
if (!block) {
console.log(message);
} else {
if (typeof message === "object") message = Deno.inspect(message);
block.setLines(message.split("\n"));
}
}
if (import.meta.main) {
Cursor.hide();
const layout = new TerminalLayout();
const title = new TerminalBlock();
const block = new TerminalBlock();
const footer = new TerminalBlock();
block.setPreserveHistory(true);
// ScrollManager.enable(block);
title.setLines(["Hello, World!"]);
title.setFixedHeight(1);
footer.setLines(["Press Ctrl+C to exit"]);
footer.setFixedHeight(1);
layout.register("title", title);
layout.register("block", block);
layout.register("footer", footer);
Deno.addSignalListener("SIGINT", () => {
layout.clearAll();
// console.clear();
Deno.exit(0);
});
const name = await cliPrompt("Enter your name:", block);
cliLog(`Hello, ${name}!`, block);
const single = await cliConfirm("Are you single?", block);
cliLog(single ? "Do you want to go out with me?" : "Okay", block);
// ScrollManager.enable(block);
const loopingConvo = [
"No response?",
"I guess that's okay",
"Maybe I'll see you next week?",
"Wow, really not going to say anything to me?",
"Well, if that's how you feel",
];
let convo = 0;
setInterval(() => {
cliLog(loopingConvo[convo % loopingConvo.length], block);
convo++;
}, 2000);
// setTimeout(async () => {
// await cliAlert("Well, if that's that...", block);
// Deno.exit(0);
// }, 10000);
}