se tui; use fmt; use unix::tty; use io; use strings; use memio; use os; use strconv; export type coords = (u16, u16); export type printfn = fn(w: *widget) void; export type resizefn = fn(w: *widget, ttysize: tty::ttysize) void; export type finishfn = fn(w: *widget) void; export type widgetsize = (tty::ttysize | void); export type damagenone = void; export type damageall = void; export type damagerow = uint; export type damagecol = uint; //export type damageitem = (row | col | coords); export type damageitem = damagerow; export type damage = (damagenone | damageall | []damageitem); export type stylesfn = fn(w: *widget, txt: str, idx: size) str; //export type line = struct { // txt: str, //}; export type linesbuf = struct { lines: []str, styles: nullable *stylesfn, }; export type widget = struct { state: *tui::tui, print: *printfn, resize: *resizefn, finish: *finishfn, buf: linesbuf, pos: coords, sz: widgetsize, style: (*style | void), damage: damage, }; export type color = enum uint { BLACKFG = 30, REDFG = 31, GREENFG = 32, BROWNFG = 33, BLUEFG = 34, MAGENTAFG = 35, CYANFG = 36, WHITEFG = 37, DEFAULTFG = 39, BLACKBG = 40, REDBG = 41, GREENBG = 42, BROWNBG = 43, BLUEBG = 44, MAGENTABG = 45, CYANBG = 46, WHITEBG = 47, DEFAULTBG = 49, }; export type style = struct { border: bool, }; export def DEFAULT_STYLE: style = style { border = false, }; export def NEWLINE: str = "\r\n"; def gotoroot: str = "\x1B[1;1H"; def UNDERLINE: str = "\x1B[4m"; def NOUNDERLINE: str = "\x1B[0m"; def OVERLINE: rune = '\u0305'; def NOOVERLINE: str = "\x1B[0m"; def CLEAR: str = "\x1B[2J"; def CLEARROW: str = "\x1B[2K"; // Must free return value fn underline(s: str) str = { return strings::concat(UNDERLINE, s, NOUNDERLINE); }; // Must free return value fn overline(s: str) str = { const st = memio::dynamic(); defer io::close(&st)!; let iter = strings::iter(s); let iscolorcode = false; for (let r: rune => strings::next(&iter)) { if (r == '\x1B') { iscolorcode = true; }; const char = if (!iscolorcode) { yield strings::fromrunes([r, OVERLINE]); } else { yield strings::fromrunes([r]); }; defer free(char); memio::concat(&st, char)!; if (iscolorcode && r == 'm') iscolorcode = false; }; return strings::dup(memio::string(&st)!); }; fn minrows(out: io::file, x: u16, y: widgetsize) (u16 | tty::error) = { const y = match (y) { case let y: tty::ttysize => yield y; case void => yield tty::winsize(out)?; }; return if (x < y.rows) x else y.rows; }; // Must free return string export fn color_to_str(color: color) str = fmt::asprintf("\x1B[{}m", strconv::utos(color)); //{ // const cu = match (color) { // case let c: colorfg => yield c: uint; // case let c: colorbg => yield c: uint; // }; // return //}; fn clearrow(row: uint) str = fmt::asprintf("\x1B[{}d{}", row, CLEARROW); export fn print(w: *widget) void = { //tui::unraw(w.state); //defer tui::raw(w.state)!; const clear = match (w.damage) { case damageall => yield strings::dup(CLEAR); case damagenone => yield strings::dup(""); case let dam: []damageitem => let st = memio::dynamic(); defer io::close(&st)!; for (let item: damageitem .. dam) { memio::concat(&st, clearrow(item))!; //match (item) { //case let r: row => // yield clearrow(r); ////case let c: col => ////case let co: coords => //}; }; yield strings::dup(memio::string(&st)!); }; defer free(clear); let s = truncate_to_size(w); defer strings::freeall(s); let sstyle = applystyles(w.style, s); defer strings::freeall(sstyle); //const clear = if (w.state.clear) "\x1B[2J" else ""; let st = memio::dynamic(); defer io::close(&st)!; for(let i = 0z; i < len(sstyle); i += 1) { const line = match (w.buf.styles) { case let f: *stylesfn => yield f(w, sstyle[i], i); case null => yield strings::dup(sstyle[i]); }; defer free(line); memio::concat(&st, line)!; if (i < len(sstyle) - 1) { memio::concat(&st, NEWLINE)!; }; }; const joined = memio::string(&st)!; let seekpos = fmt::asprintf("{}\x1B[{};{}H", clear, w.pos.0, w.pos.1); defer free(seekpos); const sout = strings::concat(seekpos, joined); defer free(sout); fmt::fprint(w.state.out, sout)!; w.state.clear = false; }; // Applies styling (style) to the given string slice. fn applystyles(st: (*style | void), s: []str) []str = { return match (st) { case let st: *style => let sborder = if (st.border) { yield border(s); } else { yield strings::dupall(s); }; //defer strings::freeall(sborder); //const scolor = color_to_str(st.colorfg); //defer free(scolor); //const defcolor = color_to_str(colorfg::DEFAULTFG); //defer free(defcolor); //const sb = strings::concat(scolor, sborder[0]); //free(sborder[0]); //sborder[0] = sb; //const endidx = len(sborder) - 1; //const sb = strings::concat(sborder[endidx], defcolor); //free(sborder[endidx]); //sborder[endidx] = sb; yield sborder; case void => yield strings::dupall(s); }; }; // Truncates the text of the widget to the widget's size (rows and columns) and // returns the resulting lines. Must call [[strings::freeall]] on the result. fn truncate_to_size(w: *widget) []str = { let lines: []str = []; const nbrows = minrows(w.state.out, len(w.buf.lines): u16, w.sz)!; for (let i = 0z; i < nbrows; i += 1) { const line = w.buf.lines[i]; let item = match (w.sz) { case let sz: tty::ttysize => const s = if (tui::strwidth(line) > sz.columns) tui::subwidth(line, sz.columns) else line; yield strings::rpad(s, ' ', sz.columns); case void => const wsz = tty::winsize(w.state.out)!; const s = if (tui::strwidth(line) > wsz.columns) tui::subwidth(line, wsz.columns) else line; yield strings::dup(s); }; append(lines, item)!; //fmt::println(item)!; }; return lines; }; // Add a border around a slice of strings. Must call [[strings::freeall]] on the // result. fn border(s: []str) []str = { let lines: []str = []; for (let i = 0z; i < len(s); i += 1) { const si = strings::concat("|", s[i], "|"); si = if (i == 0) { defer free(si); yield overline(si); } else { yield si; }; si = if (i == len(s) - 1) { defer free(si); yield underline(si); } else { yield si; }; append(lines, si)!; }; return lines; }; export fn prints(out: io::file, s: str, pos: coords) void = { fmt::fprintf(out, "\x1B[{};{}H", pos.0, pos.1)!; fmt::fprint(out, s)!; }; export fn finish(w: *widget) void = { match (w.damage) { case let dam: []damageitem => free(dam); case damageall => return; }; }; export fn cleardamage(w: *widget) void = { let dam = if (w.damage is damageall) { return; } else { yield w.damage as []damageitem; }; for (let i = 0z; i < len(dam); i += 1) { delete(dam[i]); }; }; fn splitstr(s: str, delim: str) []str = { let spl = strings::split(s, delim); for (let i = 0z; i < len(spl); i += 1) { if (spl[i] == "") { delete(spl[i]); }; }; return spl; };