// 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(64); // large enough for most pastes inputLoop: 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 inputLoop; } // 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, 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); }