// License: MPL-2.0 // (c) 2022 Julian Hurst use fmt; use os; use io; use unix::tty; use errors; use bufio; use encoding::utf8; use strings; export type key = (rune | specialkey); // A listener on a rune input that returns if the ui needs to terminate or not export type listener = *fn(ui: *ttyui, r: key) bool; export type ttyui = struct { term: tty::termios, f: io::file, listeners: []listener, doclear: bool, }; const CLEARESC: str = "\x1B[2J\x1B[1;1H\r"; // Initializes the UI and returns a ttyui. export fn init() ttyui = { let f = match (tty::open()) { case let f: io::file => yield f; case let e: tty::error => fmt::fatal(tty::strerror(e)); }; if (!tty::isatty(f)) { fmt::fatal("stream is not a tty"); }; let term = match (tty::termios_query(f)) { case let t: tty::termios => yield t; case let e: errors::error => fmt::fatal(errors::strerror(e)); }; tty::makeraw(&term)!; //tty::noecho(&term)!; let ui = ttyui { term = term, f = f, listeners = [], doclear = false, }; hidecursor(&ui); return ui; }; export fn hidecursor(ui: *ttyui) void = { print(ui, "\x1B[?25l"); }; export fn showcursor(ui: *ttyui) void = { print(ui, "\x1B[?25h"); }; // Returns the window size for the given ttyui. export fn getwinsize(ui: ttyui) (tty::ttysize | tty::error) = { return tty::winsize(ui.f); }; // Suspend the UI. To restore it, use [[resume]]. export fn suspend(ui: *ttyui) void = { showcursor(ui); tty::termios_restore(&ui.term); }; // Resumes the UI after a [[suspend]]. export fn resume(ui: *ttyui) void = { tty::makeraw(&ui.term)!; tty::noecho(&ui.term)!; hidecursor(ui); }; // Restores the UI state and closes and frees the resources associated with the // given ttyui. export fn finish(ui: *ttyui) void = { showcursor(ui); tty::termios_restore(&ui.term); io::close(ui.f)!; free(ui.listeners); }; // Scans a rune. A convenience function for [[bufio::read_rune]]. export fn scan(ui: ttyui) (key | utf8::invalid | io::EOF | io::error) = { //const r = bufio::read_rune(ui.f)?; const r = match (bufio::read_rune(ui.f)?) { case let r: rune => yield r; case => return io::EOF; }; return getkey(r); }; // Notify (call) the ttyui's listeners with the ttyui and r as a parameter. // Returns true if a listener returned true, false otherwise. export fn notify(ui: *ttyui, r: (rune | specialkey)) bool = { for (let i = 0z; i < len(ui.listeners); i += 1) { if (ui.listeners[i](ui, r)) { return true; }; }; return false; }; fn loop(ui: *ttyui) void = { for (true) { let r = match (bufio::read_rune(ui.f)) { case let r: rune => yield r; case utf8::invalid => fmt::fatal("Invalid utf8 sequence found"); case io::EOF => fmt::fatal("EOF"); case let e: io::error => fmt::fatal(io::strerror(e)); }; for (let i = 0z; i < len(ui.listeners); i += 1) { if (ui.listeners[i](ui, r)) { return; }; }; }; }; // Add a listener to the given ttyui. export fn addlistener(ui: *ttyui, l: listener) void = { append(ui.listeners, l)!; }; // Print a string or rune to the ttyui. export fn print(ui: *ttyui, arg: str) void = { if (ui.doclear) { let out = strings::concat(CLEARESC, arg)!; defer free(out); fmt::fprint(ui.f, out)!; ui.doclear = false; } else { fmt::fprint(ui.f, arg)!; }; //fmt::fprintf(ui.f, "{}\r", arg)!; }; // Clear the ttyui. export fn clear(ui: ttyui) void = { fmt::fprintf(ui.f, CLEARESC)!; }; // Schedule a clear on the next print to the ttyui. Used to optimize printing // and avoid intermittent blanking/flickering by grouping the clear and print in // the same write syscall. export fn doclear(ui: *ttyui) void = { ui.doclear = true; };