aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libui/README1
-rw-r--r--libui/layout/layout.ha42
-rw-r--r--libui/libui.ha6
-rw-r--r--libui/widget/README2
-rw-r--r--libui/widget/list/README1
-rw-r--r--libui/widget/list/list.ha (renamed from libui/list/list.ha)45
-rw-r--r--libui/widget/widget.ha63
-rw-r--r--main.ha34
-rw-r--r--set/set.ha7
9 files changed, 180 insertions, 21 deletions
diff --git a/libui/README b/libui/README
new file mode 100644
index 0000000..df8992f
--- /dev/null
+++ b/libui/README
@@ -0,0 +1 @@
+The libui module provides widgets for creating a tui (terminal user interface).
diff --git a/libui/layout/layout.ha b/libui/layout/layout.ha
new file mode 100644
index 0000000..ef6bf71
--- /dev/null
+++ b/libui/layout/layout.ha
@@ -0,0 +1,42 @@
+use libui::widget;
+use io;
+use unix::tty;
+use fmt;
+use os;
+
+export type layout = struct {
+ widgets: []*widget::widget,
+};
+
+// Create and return a new layout from a list of widgets. [[finishall]] must be
+// called to properly free the widget's nad layout's resources.
+export fn newlayout(widgets: *widget::widget...) layout = {
+ return layout {
+ widgets = widgets,
+ };
+};
+
+// Display all the widgets contained in the given layout.
+export fn print(layout: layout) (void | widget::error) = {
+ for (let i = 0z; i < len(layout.widgets); i += 1) {
+ match (layout.widgets[i].print) {
+ case null =>
+ return;
+ case let f: *widget::print =>
+ f(layout.widgets[i])?;
+ };
+ };
+};
+
+// Finish and free the widgets in the given layout.
+export fn finishall(layout: *layout) void = {
+ for (let i = 0z; i < len(layout.widgets); i += 1) {
+ match (layout.widgets[i].finish) {
+ case null =>
+ return;
+ case let f: *widget::finish =>
+ f(layout.widgets[i]);
+ };
+ };
+ free(layout.widgets);
+};
diff --git a/libui/libui.ha b/libui/libui.ha
index 6ad33d1..7e40a9d 100644
--- a/libui/libui.ha
+++ b/libui/libui.ha
@@ -26,7 +26,7 @@ export fn init() ttyui = {
fmt::fatal(tty::strerror(e));
};
if (!tty::isatty(f)) {
- fmt::fatal("/dev/tty is not a tty");
+ fmt::fatal("stream is not a tty");
};
let term = match (tty::termios_query(f)) {
case let t: tty::termios =>
@@ -74,6 +74,7 @@ export fn scan(ui: ttyui) (rune | utf8::invalid | io::EOF | io::error) = {
};
// Notify (call) the ttyui's listeners with the ttyui and r as a parameter.
+// Returns true if a listener returned true, false otherwise.
export fn notify(ui: *ttyui, r: rune) bool = {
for (let i = 0z; i < len(ui.listeners); i += 1) {
if (ui.listeners[i](ui, r)) {
@@ -110,7 +111,8 @@ export fn addlistener(ui: *ttyui, l: listener) void = {
// Print a string or rune to the ttyui.
export fn print(ui: ttyui, arg: (str | rune)) void = {
- fmt::fprintf(ui.f, "{}\r", arg)!;
+ fmt::fprint(ui.f, arg)!;
+ //fmt::fprintf(ui.f, "{}\r", arg)!;
};
// Clear the ttyui.
diff --git a/libui/widget/README b/libui/widget/README
new file mode 100644
index 0000000..ae62628
--- /dev/null
+++ b/libui/widget/README
@@ -0,0 +1,2 @@
+This module contains functions common to all widgets and provides a base for
+implementing custom widget types (print, finish, ...).
diff --git a/libui/widget/list/README b/libui/widget/list/README
new file mode 100644
index 0000000..c8f71db
--- /dev/null
+++ b/libui/widget/list/README
@@ -0,0 +1 @@
+libui::list provides a list widget that supports line truncating, scrolling, item marking (contains, fnmatch and regex) and searching. Multiple convenient navigation functions are provided such as [[up]], [[down]], [[top]] and [[bottom]].
diff --git a/libui/list/list.ha b/libui/widget/list/list.ha
index 7b181c4..57239f3 100644
--- a/libui/list/list.ha
+++ b/libui/widget/list/list.ha
@@ -1,4 +1,5 @@
use libui;
+use libui::widget;
use fmt;
use os;
use strings;
@@ -11,11 +12,12 @@ use wcwidth;
use set;
export type listwidget = struct {
+ widget: widget::widget,
ui: libui::ttyui,
items: []str,
marked: set::set,
cursor: size,
- listeners: []listener,
+ //listeners: []listener,
frame: frame,
sz: ttysize,
};
@@ -31,6 +33,10 @@ export type ttysize = struct {
cols: u16,
};
+// An input listener on a list widget. The returning value is intended to be
+// used as a signal that will be returned by [[notify]] in order to trigger
+// certain more global ui events (terminate the program, change widget focus,
+// etc.). To register a listener with a listwidget, use [[addlistener]].
export type listener = *fn(l: *listwidget, r: rune) bool;
// Create a new list with the given items.
@@ -42,11 +48,16 @@ export fn newlist(ui: libui::ttyui, items: str...) listwidget = {
yield len(items);
};
let w = listwidget {
+ widget = widget::widget {
+ print = &print,
+ finish = &finish,
+ ...
+ },
ui = ui,
items = items,
marked = set::set {...},
cursor = 0z,
- listeners = [],
+ //listeners = [],
frame = frame {
start = 0u16,
end = rows: u16,
@@ -59,14 +70,24 @@ export fn newlist(ui: libui::ttyui, items: str...) listwidget = {
return w;
};
-// Add a listener to the given list.
-export fn addlistener(list: *listwidget, l: listener) void = {
- append(list.listeners, l);
+// Free the list's items, marked items and call the common widget finish
+// function [[widget::finishcommon]].
+export fn finish(list: *widget::widget) void = {
+ const list = list: *listwidget;
+ free(list.items);
+ set::finish(&list.marked);
+ widget::finishcommon(list);
};
+// Add a listener to the given list.
+//export fn addlistener(list: *listwidget, l: listener) void = {
+ //append(list.listeners, l);
+//};
+
// Print the list's items while truncating the items to not be wider than the
// list.sz.cols.
-export fn print(list: *listwidget) (void | io::error | tty::error) = {
+export fn print(list: *widget::widget) (void | widget::error) = {
+ const list = list: *listwidget;
//let sz = libui::getwinsize(list.ui)?;
//let rows: (u16 | size) = if (sz.rows - 2 < len(list.items)) {
//yield sz.rows - 2;
@@ -98,16 +119,18 @@ export fn print(list: *listwidget) (void | io::error | tty::error) = {
};
strio::concat(&st, "\r\n")?;
};
+ // unsupported?
+ //io::copy(list.ui.f, &st)?;
let s = strio::string(&st);
- defer free(s);
libui::print(list.ui, s);
+ io::close(&st)?;
};
// Notify (call) the listwidget's listeners with the listwidget and r as a
-// parameter.
+// parameter. Returns true if a listener returned true, false otherwise.
export fn notify(l: *listwidget, r: rune) bool = {
- for (let i = 0z; i < len(l.listeners); i += 1) {
- if (l.listeners[i](l, r)) {
+ for (let i = 0z; i < len(l.widget.listeners); i += 1) {
+ if (l.widget.listeners[i](l, r)) {
return true;
};
};
@@ -213,7 +236,7 @@ export fn containsmark(l: *listwidget, s: str) void = {
};
};
-// Marks items based on fnmatch (globbing).
+// Marks items based on fnmatch (globbing syntax).
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])) {
diff --git a/libui/widget/widget.ha b/libui/widget/widget.ha
new file mode 100644
index 0000000..1f23aac
--- /dev/null
+++ b/libui/widget/widget.ha
@@ -0,0 +1,63 @@
+use io;
+use unix::tty;
+
+export type error = !(io::error | tty::error);
+
+// A function that displays the widget.
+export type print = fn(w: *widget) (void | error);
+
+// A function that frees the resources associated to the widget.
+export type finish = fn(w: *widget) void;
+
+// An input listener on a widget. The returning value is intended to be used as
+// a signal that will be returned by [[notify]] in order to trigger certain more
+// global ui events (terminate the program, change widget focus, etc.). To
+// register a listener with a widget, use [[addlistener]].
+export type listener = *fn(w: *widget, r: rune) bool;
+
+// A widget is an abstraction around a user-defined UI component. Custom widgets
+// can be created through sub-typing:
+//
+// export type my_widget = struct {
+// widget: widget::widget,
+// lines: []str,
+// };
+//
+// fn print(w: *widget::widget) (void | widget::error) = {
+// const w = w: *my_widget;
+// for (let i = 0z; i < len(w.lines); i += 1) {
+// fmt::println(w.lines[i])!;
+// };
+// };
+//
+// fn finish(w: *widget::widget) void = {
+// const w = w: *my_widget;
+// free(w.lines);
+// widget::finishcommon(w);
+// };
+//
+// let list = my_widget {
+// widget = widget::widget {
+// print = &print,
+// finish = &finish,
+// ...
+// };
+// lines = strings::split("one,two,three", ","),
+// };
+// let l = layout::newlayout(list);
+// layout::print(l)!;
+export type widget = struct {
+ print: nullable *print,
+ finish: nullable *finish,
+ listeners: []listener,
+};
+
+// Add a listener to the given widget.
+export fn addlistener(w: *widget, listener: listener) void = {
+ append(w.listeners, listener);
+};
+
+// Free the widget's listeners.
+export fn finishcommon(w: *widget) void = {
+ free(w.listeners);
+};
diff --git a/main.ha b/main.ha
index 2f96e56..0aeda39 100644
--- a/main.ha
+++ b/main.ha
@@ -1,5 +1,7 @@
use libui;
-use libui::list;
+use libui::widget;
+use libui::widget::list;
+use libui::layout;
use encoding::utf8;
use io;
use fmt;
@@ -26,7 +28,8 @@ fn globalrunehandler(ui: *libui::ttyui, r: rune) bool = {
return false;
};
-fn runehandler(l: *list::listwidget, r: rune) bool = {
+fn runehandler(l: *widget::widget, r: rune) bool = {
+ const l = l: *list::listwidget;
switch (r) {
case 'j' =>
list::down(l);
@@ -40,6 +43,7 @@ fn runehandler(l: *list::listwidget, r: rune) bool = {
case let s: str =>
fmt::println(s)!;
case let s: []str =>
+ defer free(s);
const out = strings::join("\n", s...);
defer free(out);
fmt::println(out)!;
@@ -192,7 +196,6 @@ fn runehandler(l: *list::listwidget, r: rune) bool = {
fn sighandler(sig: int, info: *signal::siginfo, ucontext: *void) void = {
switch (sig) {
case signal::SIGWINCH =>
- fmt::fprintln(os::stderr, "winch")!;
let sz = libui::getwinsize(u.list.ui)!;
let rows: (u16 | size) = if (sz.rows - 2 < len(u.list.items)) {
yield sz.rows - 2;
@@ -215,16 +218,21 @@ export fn main() void = {
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);
+ widget::addlistener(&l, &runehandler);
+
+ let layout = layout::newlayout(&l);
+
+ defer layout::finishall(&layout);
+
//defer free(searchterm);
libui::clear(l.ui);
- match (list::print(&l)) {
+
+ match (layout::print(layout)) {
case void =>
yield;
case let e: io::error =>
@@ -234,10 +242,22 @@ export fn main() void = {
fmt::fprintln(os::stderr, tty::strerror(e))!;
return;
};
+
+ //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;
+ //};
u = mainUI {
list = &l,
};
- signal::handle(signal::SIGWINCH, &sighandler, signal::flag::RESTART);
+ const sigs = signal::handle(signal::SIGWINCH, &sighandler, signal::flag::RESTART);
+ defer signal::restore(signal::SIGWINCH, &sigs);
for (true) {
let r = match (libui::scan(ui)) {
case let r: rune =>
diff --git a/set/set.ha b/set/set.ha
index 2c56efe..504d716 100644
--- a/set/set.ha
+++ b/set/set.ha
@@ -33,11 +33,16 @@ export fn contains(s: set, item: size) (size | nosuchitem) = {
return nosuchitem;
};
-// Clears all items
+// Clears all items.
export fn clear(s: *set) void = {
delete(s.items[..]);
};
+// Free the underlying slice.
+export fn finish(s: *set) void = {
+ free(s.items);
+};
+
@test fn add() void = {
let s = set {...};
assert(add(&s, 1z));