aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libui/list/list.ha63
-rw-r--r--main.ha79
-rw-r--r--set/set.ha80
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];
+ };
+};
diff --git a/main.ha b/main.ha
index 4809a18..2f96e56 100644
--- a/main.ha
+++ b/main.ha
@@ -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);
+};