// 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 { 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(6); // 6 bytes is enough for all the keys while (true) { const n = await Deno.stdin.read(buf); if (n === null) break; const [a, b, c] = buf; if (a === 3) { // Ctrl+C block?.clear(); block?.["layout"]?.clearAll(); Deno.stdin.setRaw(false); Deno.exit(130); } if (a === 13) { // Enter break; } else if (a === 127 || a === 8) { // Backspace if (cursorPos > 0) { input.splice(cursorPos - 1, 1); cursorPos--; } } else if (a === 46) { // Delete if (cursorPos < input.length) { input.splice(cursorPos, 1); } } else if (a === 27 && b === 91) { // Arrow keys if (c === 51 && cursorPos < input.length) { // delete input.splice(cursorPos, 1); } if (c === 68 && cursorPos > 0) cursorPos--; // Left if (c === 67 && cursorPos < input.length) cursorPos++; // Right } else if (a >= 32 && a <= 126) { // Printable ASCII input.splice(cursorPos, 0, String.fromCharCode(a)); cursorPos++; } 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, 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); }