[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay bomb.sh/docs/tty
Something went wrong. Try again.
3.3 kB · 143 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144import { Buffer } from "node:buffer";import process from "node:process";import { each, ensure, main, until } from "effection";import { close, createTerm, fixed, grow, type KeyEvent, type Op, open, rgba, text,} from "../../mod.ts";import { alternateBuffer, progressiveInput, settings } from "../../settings.ts";import { useInput } from "../use-input.ts";import { useStdin } from "../use-stdin.ts";
const bg = rgba(20, 20, 30);const inputBg = rgba(35, 35, 50);const label = rgba(180, 180, 200);const hint = rgba(80, 80, 100);
await main(function* () { let { columns, rows } = terminalSize();
setRawMode(true);
let stdin = yield* useStdin(); let input = useInput(stdin);
let term = yield* until(createTerm({ width: columns, height: rows }));
let tty = settings(alternateBuffer(), progressiveInput(1)); writeStdout(tty.apply);
let value = ""; let caret = 0;
yield* ensure(() => { setRawMode(false); writeStdout(tty.revert); });
let { output } = term.render(frame(value, caret)); writeStdout(output);
for (let event of yield* each(input)) { if (event.type === "keydown") { let key = event as KeyEvent;
if (key.ctrl && key.key === "c") { break; }
if (key.key === "Escape") { break; }
if (key.key === "ArrowLeft") { if (caret > 0) { caret--; } } else if (key.key === "ArrowRight") { if (caret < [...value].length) { caret++; } } else if (key.key === "Backspace") { if (caret > 0) { let chars = [...value]; chars.splice(caret - 1, 1); value = chars.join(""); caret--; } } else if ( key.key.length === 1 && !key.ctrl && !key.alt ) { let chars = [...value]; chars.splice(caret, 0, key.key); value = chars.join(""); caret++; }
({ output } = term.render(frame(value, caret))); writeStdout(output); }
yield* each.next(); }});
function frame(value: string, caret: number): Op[] { return [ open("root", { layout: { width: grow(), height: grow(), direction: "ttb", padding: { left: 2, right: 2, top: 1, bottom: 1 }, gap: 1, }, bg, }), open("label", { layout: { height: fixed(1) } }), text("Name:", { color: label }), close(), open("input-box", { layout: { width: fixed(40), height: fixed(1), padding: { left: 1, right: 1 }, }, bg: inputBg, }), text(value, { color: label, caret }), close(), open("hint", { layout: { height: fixed(1) } }), text("← → move Backspace delete Esc or Ctrl+C exit", { color: hint }), close(), close(), ];}
function terminalSize(): { columns: number; rows: number } { return process.stdout.isTTY ? { columns: process.stdout.columns ?? 80, rows: process.stdout.rows ?? 24, } : { columns: 80, rows: 24 };}
function setRawMode(enabled: boolean): void { if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { process.stdin.setRawMode(enabled); }}
function writeStdout(bytes: Uint8Array): void { process.stdout.write(Buffer.from(bytes));}