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; };