aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Hurst <ark@mansus.space>2022-05-05 17:53:53 +0200
committerJulian Hurst <ark@mansus.space>2022-05-05 17:53:53 +0200
commitc21fc5f59b93631b85d7f1d089c24e24360a9bb2 (patch)
tree1e7623e5832a391826ce9d755ef448dee12a1905
downloadilhare-c21fc5f59b93631b85d7f1d089c24e24360a9bb2.tar.gz
Initial commit
-rw-r--r--libui/libui.ha110
-rw-r--r--libui/list/list.ha155
-rw-r--r--main.ha114
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;
+};
diff --git a/main.ha b/main.ha
new file mode 100644
index 0000000..9d2dfde
--- /dev/null
+++ b/main.ha
@@ -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;
+ };
+ };
+};