diff options
| author | Julian Hurst <ark@mansus.space> | 2022-05-05 17:53:53 +0200 |
|---|---|---|
| committer | Julian Hurst <ark@mansus.space> | 2022-05-05 17:53:53 +0200 |
| commit | c21fc5f59b93631b85d7f1d089c24e24360a9bb2 (patch) | |
| tree | 1e7623e5832a391826ce9d755ef448dee12a1905 | |
| download | ilhare-c21fc5f59b93631b85d7f1d089c24e24360a9bb2.tar.gz | |
Initial commit
| -rw-r--r-- | libui/libui.ha | 110 | ||||
| -rw-r--r-- | libui/list/list.ha | 155 | ||||
| -rw-r--r-- | main.ha | 114 |
3 files changed, 379 insertions, 0 deletions
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; +}; @@ -0,0 +1,114 @@ +use libui; +use libui::list; +use encoding::utf8; +use io; +use fmt; +use os; +use strings; +use unix::tty; + +type mainUI = struct { + list: list::listwidget, +}; + +fn globalrunehandler(ui: *libui::ttyui, r: rune) bool = { + if (r == 'q') { + return true; + }; + return false; +}; + +fn runehandler(l: *list::listwidget, r: rune) bool = { + switch (r) { + case 'j' => + list::down(l); + case 'k' => + list::up(l); + case 'l' => + // to print properly suspend the ui, print, then resume + libui::suspend(&l.ui); + fmt::println(l.items[l.cursor])!; + libui::resume(&l.ui); + return true; + case 'g' => + list::top(l); + //l.cursor = 0; + case 'G' => + list::bottom(l); + //l.cursor = len(l.items) - 1; + case '/' => + // TODO add commandline support + fmt::fprintln(os::stderr, "searching")!; + let c = l.cursor; + if (list::search(l, "workspace") != c) { + fmt::fprintln(os::stderr, "changed")!; + }; + case '\n' => + // Enter seems to crash for now (compiler bug) + yield; + }; + libui::clear(l.ui); + match (list::print(l)) { + case void => + yield; + case let e: io::error => + fmt::fprintln(os::stderr, io::strerror(e))!; + return true; + case let e: tty::error => + fmt::fprintln(os::stderr, tty::strerror(e))!; + return true; + }; + return false; +}; + +export fn main() void = { + let in = match (io::drain(os::stdin)) { + case let in: []u8 => + yield in; + case let e: io::error => + fmt::fatal(io::strerror(e)); + }; + defer free(in); + let sin = strings::fromutf8(in); + sin = strings::trim(sin, '\n'); + let items = strings::split(sin, "\n"); + defer free(items); + + let ui = libui::init(); + defer libui::finish(&ui); + let l = list::newlist(ui, items...); + libui::addlistener(&ui, &globalrunehandler); + list::addlistener(&l, &runehandler); + libui::clear(l.ui); + match (list::print(&l)) { + case void => + yield; + case let e: io::error => + fmt::fprintln(os::stderr, io::strerror(e))!; + return; + case let e: tty::error => + fmt::fprintln(os::stderr, tty::strerror(e))!; + return; + }; + for (true) { + let r = match (libui::scan(ui)) { + case let r: rune => + yield r; + case utf8::invalid => + fmt::fprintln(os::stderr, "Invalid utf8 sequence")!; + break; + case io::EOF => + fmt::fprintln(os::stderr, "EOF")!; + break; + case let e: io::error => + fmt::fprintln(os::stderr, io::strerror(e))!; + break; + }; + if (libui::notify(&ui, r)) { + break; + }; + if (list::notify(&l, r)) { + break; + }; + }; +}; |
