diff options
| -rw-r--r-- | libui/list/list.ha | 63 | ||||
| -rw-r--r-- | main.ha | 79 | ||||
| -rw-r--r-- | set/set.ha | 80 |
3 files changed, 219 insertions, 3 deletions
diff --git a/libui/list/list.ha b/libui/list/list.ha index 16e2ecc..7b181c4 100644 --- a/libui/list/list.ha +++ b/libui/list/list.ha @@ -5,11 +5,15 @@ use strings; use io; use strio; use unix::tty; +use regex; +use fnmatch; use wcwidth; +use set; export type listwidget = struct { ui: libui::ttyui, items: []str, + marked: set::set, cursor: size, listeners: []listener, frame: frame, @@ -40,6 +44,7 @@ export fn newlist(ui: libui::ttyui, items: str...) listwidget = { let w = listwidget { ui = ui, items = items, + marked = set::set {...}, cursor = 0z, listeners = [], frame = frame { @@ -83,9 +88,13 @@ export fn print(list: *listwidget) (void | io::error | tty::error) = { strio::concat(&st, truncitem)?; strio::concat(&st, "\x1B[0m")?; //libui::print(list.ui, strings::concat("\x1B[31;1m> ", list.items[i], "\x1B[0m")); - } else { + } else if (set::contains(list.marked, i) is size){ + strio::concat(&st, "\x1B[46;1m\x1B[30m")?; strio::concat(&st, truncitem)?; + strio::concat(&st, "\x1B[0m")?; //libui::print(list.ui, list.items[i]); + } else { + strio::concat(&st, truncitem)?; }; strio::concat(&st, "\r\n")?; }; @@ -182,3 +191,55 @@ export fn rsearch(l: *listwidget, s: str) size = { }; return l.cursor; }; + +// Toggles marking the currently selected item. +export fn tmark(l: *listwidget) void = { + if (!set::add(&l.marked, l.cursor)) { + set::del(&l.marked, l.cursor); + }; +}; + +// Clears all marked items. +export fn clearmarked(l: *listwidget) void = { + set::clear(&l.marked); +}; + +// Marks items that contain s (case sensitive). +export fn containsmark(l: *listwidget, s: str) void = { + for (let i = 0z; i < len(l.items); i += 1) { + if (strings::contains(l.items[i], s)) { + set::add(&l.marked, i); + }; + }; +}; + +// Marks items based on fnmatch (globbing). +export fn fnmatchmark(l: *listwidget, s: str) void = { + for (let i = 0z; i < len(l.items); i += 1) { + if (fnmatch::fnmatch(s, l.items[i])) { + set::add(&l.marked, i); + }; + }; +}; + +// Marks items according to a regular expression (POSIX ERE). +export fn regexmark(l: *listwidget, re: *regex::regex) void = { + for (let i = 0z; i < len(l.items); i += 1) { + if (regex::test(re, l.items[i])) { + set::add(&l.marked, i); + }; + }; +}; + +// Returns the selected item or marked items if there are any. +export fn selected(l: listwidget) (str | []str) = { + if (len(l.marked.items) > 0) { + let result: []str = []; + for (let i = 0z; i < len(l.marked.items); i += 1) { + append(result, l.items[l.marked.items[i]]); + }; + return result; + } else { + return l.items[l.cursor]; + }; +}; @@ -8,6 +8,7 @@ use strings; use unix::tty; use unix::signal; use bufio; +use regex; let u: mainUI = mainUI {...}; @@ -34,7 +35,15 @@ fn runehandler(l: *list::listwidget, r: rune) bool = { case 'l' => // to print properly suspend the ui, print, then resume libui::suspend(&l.ui); - fmt::println(l.items[l.cursor])!; + //fmt::println(l.items[l.cursor])!; + match (list::selected(*l)) { + case let s: str => + fmt::println(s)!; + case let s: []str => + const out = strings::join("\n", s...); + defer free(out); + fmt::println(out)!; + }; libui::resume(&l.ui); return true; case 'g' => @@ -93,6 +102,68 @@ fn runehandler(l: *list::listwidget, r: rune) bool = { let c = l.cursor; list::search(l, searchterm); libui::resume(&l.ui); + case 's' => + // TODO add commandline support maybe + libui::suspend(&l.ui); + fmt::fprint(l.ui.f, "s: ")!; + let line = match (bufio::scanline(l.ui.f)) { + case let s: []u8 => + yield s; + case io::EOF => + fmt::fprintln(os::stderr, "EOF")!; + return true; + case let e: io::error => + fmt::fprintln(os::stderr, io::strerror(e))!; + return true; + }; + defer free(line); + list::containsmark(l, strings::fromutf8(line)); + libui::resume(&l.ui); + case 'S' => + // TODO add commandline support maybe + libui::suspend(&l.ui); + fmt::fprint(l.ui.f, "S: ")!; + let line = match (bufio::scanline(l.ui.f)) { + case let s: []u8 => + yield s; + case io::EOF => + fmt::fprintln(os::stderr, "EOF")!; + return true; + case let e: io::error => + fmt::fprintln(os::stderr, io::strerror(e))!; + return true; + }; + defer free(line); + list::fnmatchmark(l, strings::fromutf8(line)); + libui::resume(&l.ui); + case 'r' => + // TODO add commandline support maybe + libui::suspend(&l.ui); + fmt::fprint(l.ui.f, "r: ")!; + let line = match (bufio::scanline(l.ui.f)) { + case let s: []u8 => + yield s; + case io::EOF => + fmt::fprintln(os::stderr, "EOF")!; + return true; + case let e: io::error => + fmt::fprintln(os::stderr, io::strerror(e))!; + return true; + }; + defer free(line); + match (regex::compile(strings::fromutf8(line))) { + case let re: regex::regex => + list::regexmark(l, &re); + regex::finish(&re); + case let e: regex::error => + fmt::fprintln(os::stderr, regex::strerror(e))!; + }; + libui::resume(&l.ui); + case ' ' => + list::tmark(l); + list::down(l); + case 'c' => + list::clearmarked(l); case '\n' => // For some reason enter doesn't send r == '\n' fmt::fprintln(os::stderr, "This is not detected")!; @@ -151,7 +222,7 @@ export fn main() void = { let l = list::newlist(ui, items...); libui::addlistener(&ui, &globalrunehandler); list::addlistener(&l, &runehandler); - defer free(searchterm); + //defer free(searchterm); libui::clear(l.ui); match (list::print(&l)) { case void => @@ -189,3 +260,7 @@ export fn main() void = { }; }; }; + +@fini fn finish() void = { + free(searchterm); +}; diff --git a/set/set.ha b/set/set.ha new file mode 100644 index 0000000..2c56efe --- /dev/null +++ b/set/set.ha @@ -0,0 +1,80 @@ +export type set = struct { + items: []size, +}; + +export type nosuchitem = !void; + +export fn add(s: *set, item: size) bool = { + for (let i = 0z; i < len(s.items); i += 1) { + if (s.items[i] == item) { + return false; + }; + }; + append(s.items, item); + return true; +}; + +export fn del(s: *set, item: size) bool = { + match (contains(*s, item)) { + case let i: size => + delete(s.items[i]); + return true; + case nosuchitem => + return false; + }; +}; + +export fn contains(s: set, item: size) (size | nosuchitem) = { + for (let i = 0z; i < len(s.items); i += 1) { + if (s.items[i] == item) { + return i; + }; + }; + return nosuchitem; +}; + +// Clears all items +export fn clear(s: *set) void = { + delete(s.items[..]); +}; + +@test fn add() void = { + let s = set {...}; + assert(add(&s, 1z)); + assert(len(s.items) == 1); + assert(!add(&s, 1z)); + assert(len(s.items) == 1); +}; + +@test fn del() void = { + let s = set {...}; + assert(add(&s, 1z)); + assert(len(s.items) == 1); + assert(del(&s, 1z)); + assert(len(s.items) == 0); + assert(!del(&s, 1z)); + assert(len(s.items) == 0); +}; + +@test fn contains() void = { + let s = set {...}; + assert(add(&s, 1z)); + assert(!add(&s, 1z)); + const c = contains(s, 1z); + assert(c is size); + assert(c as size == 0); + assert(add(&s, 2z)); + const c = contains(s, 2z); + assert(c is size); + assert(c as size == 1); +}; + +@test fn clear() void = { + let s = set {...}; + assert(add(&s, 1z)); + assert(len(s.items) == 1); + assert(!add(&s, 1z)); + assert(len(s.items) == 1); + clear(&s); + assert(len(s.items) == 0); +}; |
