From c21fc5f59b93631b85d7f1d089c24e24360a9bb2 Mon Sep 17 00:00:00 2001 From: Julian Hurst Date: Thu, 5 May 2022 17:53:53 +0200 Subject: Initial commit --- libui/libui.ha | 110 +++++++++++++++++++++++++++++++++++++ libui/list/list.ha | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 libui/libui.ha create mode 100644 libui/list/list.ha (limited to 'libui') diff --git a/libui/libui.ha b/libui/libui.ha new file mode 100644 index 0000000..0546d24 --- /dev/null +++ b/libui/libui.ha @@ -0,0 +1,110 @@ +use fmt; +use os; +use io; +use unix::tty; +use errors; +use bufio; +use encoding::utf8; +use strings; + + +// A listener on a rune input that returns if the ui needs to terminate or not +export type listener = *fn(ui: *ttyui, r: rune) bool; + +export type ttyui = struct { + term: tty::termios, + f: io::file, + listeners: []listener, +}; + +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("/dev/tty 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)!; + + return ttyui { + term = term, + f = f, + listeners = [], + }; +}; + +export fn getwinsize(ui: ttyui) (tty::ttysize | tty::error) = { + return tty::winsize(ui.f); +}; + +export fn suspend(ui: *ttyui) void = { + tty::termios_restore(&ui.term); +}; + +export fn resume(ui: *ttyui) void = { + tty::makeraw(&ui.term)!; +}; + +export fn finish(ui: *ttyui) void = { + tty::termios_restore(&ui.term); + io::close(ui.f)!; + free(ui.listeners); +}; + +export fn scan(ui: ttyui) (rune | utf8::invalid | io::EOF | io::error) = { + return bufio::scanrune(ui.f); +}; + +export fn notify(ui: *ttyui, r: rune) 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::scanrune(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; + }; + }; + }; +}; + +export fn addlistener(ui: *ttyui, l: listener) void = { + append(ui.listeners, l); +}; + + +export fn print(ui: ttyui, arg: (str | rune)) void = { + fmt::fprintf(ui.f, "{}\r\n", arg)!; +}; + +export fn clear(ui: ttyui) void = { + fmt::fprintf(ui.f, "\x1B[2J\x1B[1;1H\r")!; +}; + +//export @symbol("wcwidth") fn wcwidth(r: rune) int; diff --git a/libui/list/list.ha b/libui/list/list.ha new file mode 100644 index 0000000..6b11b75 --- /dev/null +++ b/libui/list/list.ha @@ -0,0 +1,155 @@ +use libui; +use fmt; +use os; +use strings; +use io; +use strio; +use unix::tty; +use wcwidth; + +export type listwidget = struct { + ui: libui::ttyui, + items: []str, + cursor: size, + listeners: []listener, + frame: frame, + sz: ttysize, +}; + +export type frame = struct { + start: u16, + // largest value is nb of items + end: u16, +}; + +export type ttysize = struct { + rows: u16, + cols: u16, +}; + +export type listener = *fn(l: *listwidget, r: rune) bool; + +export fn newlist(ui: libui::ttyui, items: str...) listwidget = { + let sz = libui::getwinsize(ui)!; + let rows: (u16 | size) = if (sz.rows - 2 < len(items)) { + yield sz.rows - 2; + } else { + yield len(items); + }; + let w = listwidget { + ui = ui, + items = items, + cursor = 0z, + listeners = [], + frame = frame { + start = 0u16, + end = rows: u16, + }, + sz = ttysize { + rows = rows: u16, + cols = sz.columns, + }, + }; + return w; +}; + +export fn addlistener(list: *listwidget, l: listener) void = { + append(list.listeners, l); +}; + +export fn print(list: *listwidget) (void | io::error | tty::error) = { + let sz = libui::getwinsize(list.ui)?; + let rows: (u16 | size) = if (sz.rows - 2 < len(list.items)) { + yield sz.rows - 2; + } else { + yield len(list.items); + }; + + //fmt::fprintln(os::stderr, rows)!; + + list.frame.end = list.frame.start + rows: u16; + + let st = strio::dynamic(); + strio::concat(&st, "\r")?; + for (let i = list.frame.start; i < list.frame.end: u16; i += 1) { + let item = list.items[i]; + let truncitem = wcwidth::truncate(item, sz.columns); + if (list.cursor == i) { + strio::concat(&st, "\x1B[104;1m\x1B[30m")?; + strio::concat(&st, truncitem)?; + strio::concat(&st, "\x1B[0m")?; + //libui::print(list.ui, strings::concat("\x1B[31;1m> ", list.items[i], "\x1B[0m")); + } else { + strio::concat(&st, truncitem)?; + //libui::print(list.ui, list.items[i]); + }; + strio::concat(&st, "\r\n")?; + }; + let s = strio::string(&st); + defer free(s); + libui::print(list.ui, s); +}; + +export fn notify(l: *listwidget, r: rune) bool = { + for (let i = 0z; i < len(l.listeners); i += 1) { + if (l.listeners[i](l, r)) { + return true; + }; + }; + return false; +}; + +export fn down(l: *listwidget) size = { + if (l.cursor < len(l.items) - 1) { + l.cursor += 1; + }; + reframe(l); + return l.cursor; +}; + +export fn up(l: *listwidget) size = { + if (l.cursor > 0) { + l.cursor -= 1; + }; + reframe(l); + return l.cursor; +}; + +fn reframe(l: *listwidget) bool = { + let reframed: bool = false; + if (l.cursor < l.frame.start) { + l.frame.end -= l.frame.start - l.cursor: u16; + l.frame.start = l.cursor: u16; + reframed = true; + }; + if (l.cursor >= l.frame.end) { + l.frame.start += l.cursor: u16 - l.frame.end + 1; + l.frame.end = l.cursor: u16; + reframed = true; + }; + return reframed; +}; + +export fn top(l: *listwidget) size = { + l.cursor = 0; + l.frame.start = 0; + l.frame.end = l.frame.start + l.sz.rows; + return l.cursor; +}; + +export fn bottom(l: *listwidget) size = { + l.cursor = len(l.items) - 1; + l.frame.end = len(l.items): u16; + l.frame.start = l.frame.end - l.sz.rows; + return l.cursor; +}; + +export fn search(l: *listwidget, s: str) size = { + for (let i = 0z; i < len(l.items); i += 1) { + if (strings::contains(l.items[i], s)) { + l.cursor = i; + reframe(l); + }; + }; + return l.cursor; +}; -- cgit v1.2.3