aboutsummaryrefslogtreecommitdiff
path: root/modules/TUI
diff options
context:
space:
mode:
authordam <dam@gudinoff>2024-05-04 01:43:34 +0100
committerdam <dam@gudinoff>2024-05-04 01:43:34 +0100
commit675b0a5fea60dc33a97b0dc1871bb9a0b61818cc (patch)
tree0d663361d843a878114d496bc4e9817c2e9da7f4 /modules/TUI
parent6b90a43d69142ff684b792ba2da951e5bbddc8a6 (diff)
downloadtask-time-tracker-675b0a5fea60dc33a97b0dc1871bb9a0b61818cc.tar.zst
task-time-tracker-675b0a5fea60dc33a97b0dc1871bb9a0b61818cc.zip
WIP : Cleanup TUI module. Finally decided to go with hard-asserts (vs soft-errors/logs).
Diffstat (limited to 'modules/TUI')
-rw-r--r--modules/TUI/module.jai70
-rw-r--r--modules/TUI/tests.jai247
-rw-r--r--modules/TUI/unix.jai2
3 files changed, 279 insertions, 40 deletions
diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai
index 464b0d9..a6a3d11 100644
--- a/modules/TUI/module.jai
+++ b/modules/TUI/module.jai
@@ -120,6 +120,9 @@ Commands :: struct #type_info_none {
CursorNormalMode :: "\e[?1l";
}
+// TODO Check which procedures need the assert_is_active call.
+// TODO Review the error messages on the asserts.
+
#if COLOR_MODE_BITS == 4 {
#load "palette_4b.jai";
@@ -189,15 +192,15 @@ set_font_style :: inline (bold: bool, underline: bool = false, strike_through: b
set_style :: (style: Style) {
set_font_style(style.bold, style.underline, style.strike_through, style.negative);
set_colors(style.foreground, style.background);
- context.tui_style = style;
+ context.terminal_style = style;
}
-clear_style :: () {
+clear_style :: inline () {
write_string(#run sprint(Commands.SetGraphicsRendition, "0"));
}
using_style :: (style: Style) #expand {
- __style := context.tui_style;
+ __style := context.terminal_style;
set_style(style);
`defer set_style(__style);
}
@@ -215,24 +218,23 @@ using_style :: (style: Style) #expand {
Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes.
-to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } {
+to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } {
+ assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count);
+
k: Key;
- // #if DEBUG {
- // assert(str.count <= 8); // TODO Add DEBUG to module parameters.
- // }
- for 0..str.count-1 #no_abc {
+ for 0..str.count-1 {
k |= ((cast(u64)str[it]) << (it*8));
}
return k;
}
-to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY
- str := talloc_string(KEY_SIZE);
+to_string :: (key: Key) -> string {
+ str := alloc_string(KEY_SIZE);
str.count = 0;
- while key != 0 #no_abc {
- str[str.count] = xx key & 0xFF;
- key >>= 8;
+ while key != 0 {
str.count += 1;
+ str[str.count-1] = xx key & 0xFF;
+ key >>= 8;
}
return str;
}
@@ -280,7 +282,7 @@ Keys :: struct #type_info_none {
F12 : Key : #run to_key("#f12");
}
-#add_context tui_style: Style;
+#add_context terminal_style: Style;
active := false;
@@ -297,7 +299,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) {
}
assert_is_active :: inline () {
- assert(active, "Please call TUI.setup_terminal() before using this procedure."); // TODO Improve error message... and maybe use the logger and return success=false flag
+ assert(active, "Please call setup_terminal() before using the module procedures.");
}
////////////////////////////////////////////////////////////////////////////////
@@ -309,6 +311,7 @@ set_next_key :: inline (key: Key) {
input_override = key;
}
+// TODO Provide some documentation comments.
get_key :: (timeout_milliseconds: s32 = -1) -> Key {
assert_is_active();
@@ -377,15 +380,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key {
return to_key(to_parse);
}
-// TODO Review me and add some comments.
-read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := true {
+// If count_limit has a non-negative value it will be used as the limit to the number of bytes on the returned string.
+// If any characters are provided in the terminators list, they will be used to scan and interrupt the input, including
+// the terminator as the last character.
+// At least one of the arguments must be properly setup to avoid an infinite-loop reading the input.
+read_input :: (count_limit: int = -1, terminators: .. u8) -> string {
assert_is_active();
-
- if count_limit < 0 && terminators.count <= 0 {
- log_error("Invalid arguments passed to read_input(): (%).\n", count_limit); // TODO Improve error message.
- return "", false;
- }
-
+ assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop.");
+
+ // TODO Provide some documentation comments.
if count_limit < 0 {
builder: String_Builder;
init_string_builder(*builder);
@@ -439,13 +442,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success :=
// Resize discards the input returning an empty string and a Resize key.
read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
assert_is_active();
-
- // TODO If we pass success... then, does it make sense to return success=true on resize?
- assert(count_limit >= 0, "Invalid value passed to count_limit.");
- // if count_limit < 0 {
- // log_error("Invalid arguments passed to read_input_line(): (%, %).\n", count_limit, is_visible);
- // return "", Keys.None, false;
- // }
+ assert(count_limit >= 0, "Invalid value passed to count_limit(): %.", count_limit);
builder := String_Builder.{ allocator = temporary_allocator };
@@ -510,7 +507,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
if is_escape_code(key) continue;
buff_idx := map_character_to_buffer_idx(str, idx);
- key_str := to_string(key);
+ key_str := to_string(key,, allocator = temporary_allocator);
// Make sure we have space to append the new character at the end (in case we're trying to do it).
if buff_idx > count_limit - key_str.count then continue;
@@ -538,7 +535,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
setup_terminal :: () -> success := true #must {
if active == true return;
-
+
input_string.data = input_buffer.data;
input_string.count = 0;
input_override = xx Keys.None;
@@ -591,14 +588,9 @@ flush_input :: () {
input_string.count = 0;
}
-// TODO What if we return success and fail when input arguments are invalid?
draw_box :: (x: int, y: int, width: int, height: int) {
- assert_is_active(); //TODO NOT NEEDED
-
- if x <= 0 || y <= 0 || width <= 1 || height <= 1 {
- log_error("Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); // TODO Improve error message.
- return;
- }
+ assert_is_active();
+ assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height);
auto_release_temp();
diff --git a/modules/TUI/tests.jai b/modules/TUI/tests.jai
new file mode 100644
index 0000000..fe213cb
--- /dev/null
+++ b/modules/TUI/tests.jai
@@ -0,0 +1,247 @@
+// build: jai -import_dir ../ tests.jai
+#import "Basic";
+TUI :: #import "TUI";
+
+main :: () {
+
+ assert_result :: (result: bool, error_message: string) {
+ if result == true {
+ print("- success\n", to_standard_error = true);
+ }
+ else {
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ print("- ERROR: %", error_message, to_standard_error = true);
+ exit(1);
+ }
+ }
+
+ next_line :: inline () {
+ x, y := TUI.get_cursor_position();
+ TUI.set_cursor_position(1, y+1);
+ }
+
+ if 1 {
+ print("TEST : set and get cursor position\n", to_standard_error = true);
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ X :: 2;
+ Y :: 3;
+ TUI.set_cursor_position(X, Y);
+ x, y := TUI.get_cursor_position();
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ assert_result(x == X && y == Y, "Failed set/get cursor position.\n");
+ }
+
+ if 1 {
+ print("TEST : module logger\n", to_standard_error = true);
+ log("- log: before module start.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.set_cursor_position(3, 3);
+ print("wait");
+ sleep_milliseconds(500);
+ log("- log: while module is active.");
+ sleep_milliseconds(500);
+ print(" a bit");
+ sleep_milliseconds(1000);
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ log("- log: after module stop.");
+ }
+
+ if 1 {
+ print("TEST : test key input\n", to_standard_error = true);
+ auto_release_temp();
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.clear_terminal();
+ TUI.set_cursor_position(1, 1);
+ write_string("Press q to exit, other key to print it to screen, wait 1s to see animation.");
+ next_line();
+ key: TUI.Key;
+ while(key != #char "q") {
+ key = TUI.get_key(1000);
+ if key == TUI.Keys.None {
+ write_string("-");
+ }
+ else if key == TUI.Keys.Resize {
+ write_string("#");
+ }
+ else {
+ // else if key >= 32 && key <= 128 then print_character(cast,force(u8)key)
+ write_string(TUI.to_string(key));
+ }
+ }
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ print("- success\n", to_standard_error = true);
+ }
+
+ if 1 {
+ print("TEST : draw box\n", to_standard_error = true);
+ auto_release_temp();
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.flush_input();
+ TUI.clear_terminal();
+ TUI.draw_box(1, 2, 5, 3);
+ TUI.set_cursor_position(1, 1);
+ print("Can you see the box below? (y/n)");
+ key := TUI.get_key();
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ assert_result(key == #char "y", "Failed to draw box.\n");
+ }
+
+ if 1 {
+ print("TEST : get terminal size\n", to_standard_error = true);
+ auto_release_temp();
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.clear_terminal();
+ width, height := TUI.get_terminal_size();
+ TUI.set_cursor_position(1, 1);
+ print("Is terminal size %x%? (y/n)", width, height);
+ key: TUI.Key = xx TUI.Keys.None;
+ while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) {
+ key = TUI.get_key();
+ }
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ assert_result(key == #char "y", "Failed to get terminal size.\n");
+ }
+
+ if 1 {
+ print("TEST : set terminal title\n", to_standard_error = true);
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ title := "BAZINGA";
+ TUI.set_terminal_title(title);
+ TUI.set_cursor_position(1, 1);
+ print("Is terminal title '%'? (y/n)", title);
+ key: TUI.Key = xx TUI.Keys.None;
+ while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) {
+ key = TUI.get_key();
+ }
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ assert_result(key == #char "y", "Failed to set terminal title.\n");
+ }
+
+ if 1 {
+ print("TEST : print keys\n", to_standard_error = true);
+ auto_release_temp();
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ key: TUI.Key = #char "d";
+ last_none_char := "X";
+
+ width, height := TUI.get_terminal_size();
+ TUI.clear_terminal();
+ TUI.draw_box(1, 1, width, height);
+ drop_down := 0;
+
+ while(key != #char "q") {
+
+ if key == {
+ case TUI.Keys.None; {
+ TUI.set_cursor_position(2, 2);
+ last_none_char = ifx last_none_char == "X" then "+" else "X";
+ write_strings(last_none_char, " (press q to exit)");
+ }
+
+ case TUI.Keys.Resize; #through;
+ case #char "c"; {
+ width, height = TUI.get_terminal_size();
+ TUI.clear_terminal();
+ TUI.draw_box(1, 1, width, height);
+ drop_down = 0;
+ }
+
+ case; {
+ TUI.set_cursor_position(2, 3+drop_down);
+ str := TUI.to_string(key);
+ array_to_print: [..] string;
+ write_string(": ");
+ for 0..str.count-1 {
+ print("% ", FormatInt.{value = cast(u8)str[it], base=16});
+ }
+ write_string(": ");
+ for 0..str.count-1 {
+ if str[it] == #char "\e" {
+ str[it] = #char "#";
+ }
+ }
+ write_string(str);
+ write_string(" :");
+ drop_down += 1;
+ }
+ }
+
+ x := ifx width > 24 then width-24 else 1;
+ y := ifx height > 1 then height-1 else 1;
+
+ TUI.set_cursor_position(x, y);
+ print("size = %x%\n", width, height);
+ key = TUI.get_key(1000);
+
+ // __mark := get_temporary_storage_mark();
+ // set_temporary_storage_mark(__mark);
+ }
+ print("- success");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ }
+
+ if 1 {
+ print("TEST : user input\n", to_standard_error = true);
+ auto_release_temp();
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.clear_terminal();
+ TUI.set_cursor_position(1, 1);
+ print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):");
+ next_line();
+ str, key := TUI.read_input_line(15);
+ TUI.set_cursor_position(1, 3);
+ error_message: string;
+ if key == {
+ case TUI.Keys.Escape; {
+ print("Have you pressed Esc? (y/n)");
+ error_message = "Failed to read line on Esc.";
+ }
+
+ case TUI.Keys.Resize; {
+ print("Have you resized the terminal? (y/n)");
+ error_message = "Failed to read line on resize.";
+
+ }
+ case; {
+ print("Have you entered '%'? (y/n)", str);
+ error_message = "Failed to read line.";
+ }
+ }
+ answer := TUI.get_key();
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ assert_result(answer == #char "y", error_message);
+ }
+
+ if 1 {
+ print("TEST : hidden user input\n", to_standard_error = true);
+ auto_release_temp();
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.clear_terminal();
+ TUI.set_cursor_position(1, 1);
+ print("Enter some secret (use Enter to finish, Esc to cancel, or resize to abort):");
+ next_line();
+ str, key := TUI.read_input_line(15, false);
+ TUI.set_cursor_position(1, 3);
+ error_message: string;
+ if key == {
+ case TUI.Keys.Escape; {
+ print("Have you pressed Esc? (y/n)");
+ error_message = "Failed to read line on Esc.";
+ }
+
+ case TUI.Keys.Resize; {
+ print("Have you resized the terminal? (y/n)");
+ error_message = "Failed to read line on resize.";
+ }
+ case; {
+ print("Have you entered '%'? (y/n)", str);
+ error_message = "Failed to read line.";
+ }
+ }
+ answer := TUI.get_key();
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+ assert_result(answer == #char "y", error_message);
+ }
+
+ // -- -- -- Testing TUI -- STOP
+}
diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai
index a8e0edf..940ac80 100644
--- a/modules/TUI/unix.jai
+++ b/modules/TUI/unix.jai
@@ -195,7 +195,7 @@
tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc;
// https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html
- tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc;
+ tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc;
////////////////////////////////////////////////////////////////////////////////