aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TUI/module.jai170
-rw-r--r--TUI/unix.jai141
-rw-r--r--TUI/windows.jai57
-rw-r--r--ttt.jai6
4 files changed, 195 insertions, 179 deletions
diff --git a/TUI/module.jai b/TUI/module.jai
index 3298e59..ec319d7 100644
--- a/TUI/module.jai
+++ b/TUI/module.jai
@@ -9,13 +9,6 @@
#import "Basic";
#import "String";
-// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
-// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#designate-character-set
-// https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md
-
-isTUIActive := false; // TODO Rename this variable.
-
-
Drawings :: struct {
CornerBR :: "\x6A";
CornerTR :: "\x6B";
@@ -86,16 +79,20 @@ Commands :: struct {
}
-// TODO Maybe rename to "setup()"
+
+initialized := false;
+
+
start :: () {
+ if initialized return;
write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.EnterAlternateBuffer, Commands.SetUTF8);
OS_prepare_terminal();
- isTUIActive = true;
+ initialized = true;
}
-// TODO Maybe rename to "reset()"
stop :: () {
- isTUIActive = false;
+ if initialized == false return;
+ initialized = false;
OS_reset_terminal();
write_strings(Commands.EnterMainBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor);
}
@@ -146,19 +143,17 @@ draw_box :: (x: int, y: int, width: int, height: int) {
// TODO Maybe rename to "clear()"
clear_terminal :: inline () {
+ assert(initialized, "TUI is not ready.");
write_string(Commands.ClearScreen);
}
// TODO Maybe rename to "get_size()"
get_terminal_size :: () -> rows: int, columns: int {
+ assert(initialized, "TUI is not ready.");
rows, columns := OS_get_terminal_size();
return rows, columns;
}
-// read_input: () -> string {
- // return OS_read_input();
-// }
-
set_cursor_position :: (row: int, column: int) {
auto_release_temp();
tmp_string := tprint(Commands.SetCursorPosition, row, column);
@@ -166,21 +161,24 @@ set_cursor_position :: (row: int, column: int) {
}
get_cursor_position :: () -> row: int, column: int {
- assert(isTUIActive, "TUI is not active."); // TODO
+ assert(initialized, "TUI is not ready."); // TODO Should I use this inside each and every procedure?
+
+ auto_release_temp();
+
+ write_string(Commands.QueryCursorPosition);
- // TODO Hide input echo.
+ input := talloc_string(64);
+ input.count = OS_read_input(input.data, input.count); // TODO Does not check for read errors.
- write_string(Commands.QueryCursorPosition); // Returned
- input := read_input(.MACHINE);
- // Result: \e[21;1R
+ // Expected message format: \e[<r>;<c>R
+ // where <r> is the number of rows and <c> of columns.
assert(
- input.data[0] == #char "\e" &&
- input.data[1] == #char "[" &&
- input.data[input.count-1] == #char "R",
+ input[0] == #char "\e" &&
+ input[1] == #char "[" &&
+ input[input.count-1] == #char "R",
"Query cursor position returned invalid response.");
- input.data += 2;
- print(">%<\n", input);
+ advance(*input, 2);
parts := split(input, ";");
row := parse_int(*parts[0]);
column := parse_int(*parts[1]);
@@ -192,12 +190,33 @@ Input_Mode :: enum u8 {
MACHINE; // Hides cursor, hides input, and reads right away once the first input is available.
}
-read_input :: (mode: Input_Mode = .HUMAN) -> string {
- if mode == .HUMAN write_string(Commands.ShowCursor);
- defer if mode == .HUMAN write_string(Commands.HideCursor);
- return OS_read_input(mode);
-}
+read_input :: (allocator: Allocator = temp, $mode: Input_Mode = .HUMAN) -> string {
+ #if mode == .HUMAN {
+ write_string(Commands.ShowCursor);
+ defer write_string(Commands.HideCursor);
+
+ OS_set_input_mode(.HUMAN);
+ defer OS_set_input_mode(.MACHINE);
+ }
+ assert(allocator.proc != null, "Argument 'allocator.proc' has invalid null value.");
+
+ #assert(mode != .MACHINE); // TODO Keep an eye if I try to use read_input for machine read. Eventually, remove mode from the procedure arguments.
+
+ builder: String_Builder();
+ builder.allocator = allocator;
+ init_string_builder(*builder);
+
+ while(1) {
+ buffer := get_current_buffer(*builder);
+ buffer_data := get_buffer_data(buffer);
+ buffer.count = OS_read_input(buffer_data, buffer.allocated); // TODO Does not check for read errors.
+ if buffer.count == 0 || buffer_data[buffer.count-1] == #char "\n" break;
+ assert(buffer.count == buffer.allocated); // TODO If newline wasn't detected, it's because the buffer got full.
+ expand(*builder);
+ }
+ return builder_to_string(*builder, allocator);
+}
#if OS == .WINDOWS {
@@ -205,95 +224,4 @@ read_input :: (mode: Input_Mode = .HUMAN) -> string {
}
else #if OS == .LINUX || OS == .MACOS {
// Prototyping zone... keep clear!
- OS_read_input :: (mode: Input_Mode) -> string {
-
- term : My_Termios;
- tcgetattr(STDIN_FILENO, *term);
-
- // Input modes.
- Input_Modes :: enum_flags u32 {
- IGNBRK; // Ignore break condition.
- BRKINT; // Signal interrupt on break.
- IGNPAR; // Ignore characters with parity errors.
- PARMRK; // Mark parity and framing errors.
- INPCK; // Enable input parity check.
- ISTRIP; // Strip 8th bit off characters.
- INLCR; // Map NL to CR on input.
- IGNCR; // Ignore CR.
- ICRNL; // Map CR to NL on input.
- IXON; // Enable start/stop output control.
- IXOFF; // Enable start/stop input control.
- IXANY; // Any character will restart after stop.
- __NOT_USED__;
- IMAXBEL; // Ring bell when input queue is full.
- IUCLC; // Translate upper case input to lower case.
- }
-
- // Local modes.
- Local_Modes :: enum_flags u32 {
- ECHOKE; // Visual erase for KILL.
- ECHOE; // Visual erase for ERASE.
- ECHOK; // Echo NL after KILL.
- ECHO; // Enable echo.
- ECHONL; // Echo NL even if ECHO is off.
- ECHOPRT; // Hardcopy visual erase.
- ECHOCTL; // Echo control characters as ^X.
- ISIG; // Enable signals.
- ICANON; // Do erase and kill processing.
- ALTWERASE; // Alternate WERASE algorithm.
- IEXTEN; // Enable DISCARD and LNEXT.
- EXTPROC; // External processing.
- TOSTOP; // Send SIGTTOU for background output.
- FLUSHO; // Output being flushed (state).
- XCASE; // Canonical upper/lower case.
- NOKERNINFO; // Disable VSTATUS.
- PENDIN; // Retype pending input (state).
- NOFLSH; // Disable flush after interrupt.
-
- }
-
- backup: My_Termios;
- tcgetattr(STDIN_FILENO, *backup);
-
- if mode == {
- case .HUMAN;
- term.c_iflag |= xx cast(Input_Modes)(.IXOFF | .ICRNL);
- term.c_lflag |= xx cast(Local_Modes)(.NOKERNINFO | .ECHO | .ECHOE | .ECHOKE);
-
- case .MACHINE;
- iflags: Input_Modes = (.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON);
- term.c_iflag &= xx ~(iflags);
- lflags: Local_Modes = (.ECHO | .ECHONL | .ICANON | .IEXTEN);
- term.c_lflag &= xx ~lflags;
- }
-
- // term.c_iflag &= 0xFFFFFA14;// ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
- // term.c_oflag &= 0xFFFFFFFE;// ~OPOST;
- // term.c_lflag &= 0xFFFF7FB4;// ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
- // term.c_cflag &= 0xFFFFFECF;// ~(CSIZE | PARENB);
- // term.c_cflag |= 0x00000030;
-
- tcsetattr(STDIN_FILENO, 0, *term);
-
- result: string = ---;
- buffer: [8192] u8;
- write_string(Commands.ShowCursor);
- bytes_read := read(STDIN_FILENO, buffer.data, buffer.count-1);
- write_string(Commands.HideCursor);
-
- // TODO WIP WIP WIP
- result.data = alloc(bytes_read, temp);
- memcpy(result.data, buffer.data, bytes_read);
- result.count = bytes_read;
- // result = "jat";
- // result.count = bytes_read;
- // result.data = buffer.data;
- // result = copy_temporary_string(buffer);
- // result = tprint("%", xx buffer.data);
- // result = to_string(buffer.data, bytes_read); // TODO WIP WIP WIP This is still using the stack allocated buffer and WILL FAIL!
-
- tcsetattr(STDIN_FILENO, 0, *backup);
-
- return result;
- };
}
diff --git a/TUI/unix.jai b/TUI/unix.jai
index e5c81c3..6bcf13a 100644
--- a/TUI/unix.jai
+++ b/TUI/unix.jai
@@ -1,9 +1,61 @@
+#scope_file
+
#import "POSIX";
+#import "System";
+
+ // Required to do unlocking input.
+ libc :: #system_library "libc";
+ // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html
+ tcsetattr :: (fd : s32, optional_actions : s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc;
- __term : My_Termios;
+ // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html
+ tcgetattr :: (fd : s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc;
- My_Termios :: struct {
+
+ // Input modes.
+ Input_Modes :: enum_flags u32 {
+ IGNBRK; // Ignore break condition.
+ BRKINT; // Signal interrupt on break.
+ IGNPAR; // Ignore characters with parity errors.
+ PARMRK; // Mark parity and framing errors.
+ INPCK; // Enable input parity check.
+ ISTRIP; // Strip 8th bit off characters.
+ INLCR; // Map NL to CR on input.
+ IGNCR; // Ignore CR.
+ ICRNL; // Map CR to NL on input.
+ IXON; // Enable start/stop output control.
+ IXOFF; // Enable start/stop input control.
+ IXANY; // Any character will restart after stop.
+ __NOT_USED__;
+ IMAXBEL; // Ring bell when input queue is full.
+ IUCLC; // Translate upper case input to lower case.
+ }
+
+ // Local modes.
+ Local_Modes :: enum_flags u32 {
+ ECHOKE; // Visual erase for KILL.
+ ECHOE; // Visual erase for ERASE.
+ ECHOK; // Echo NL after KILL.
+ ECHO; // Enable echo.
+ ECHONL; // Echo NL even if ECHO is off.
+ ECHOPRT; // Hardcopy visual erase.
+ ECHOCTL; // Echo control characters as ^X.
+ ISIG; // Enable signals.
+ ICANON; // Do erase and kill processing.
+ ALTWERASE; // Alternate WERASE algorithm.
+ IEXTEN; // Enable DISCARD and LNEXT.
+ EXTPROC; // External processing.
+ TOSTOP; // Send SIGTTOU for background output.
+ FLUSHO; // Output being flushed (state).
+ XCASE; // Canonical upper/lower case.
+ NOKERNINFO; // Disable VSTATUS.
+ PENDIN; // Retype pending input (state).
+ NOFLSH; // Disable flush after interrupt.
+
+ }
+
+ Terminal_IO_Mode :: struct {
c_iflag : u32; // Input mode flags.
c_oflag : u32; // Output mode flags.
c_cflag : u32; // Control modes flags.
@@ -14,50 +66,85 @@
c_ospeed : u32; // Output speed (baud rates).
}
- // Required to do unlocking input.
- libc :: #system_library "libc";
- // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html
- tcsetattr :: (fd : s32, optional_actions : s32, termios_p : *My_Termios) -> s32 #foreign libc;
+ initial_tio_mode : Terminal_IO_Mode;
+ default_tio_mode: Terminal_IO_Mode;
+ blocking_tio_mode: Terminal_IO_Mode;
+ unblocking_tio_mode: Terminal_IO_Mode;
- // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html
- tcgetattr :: (fd : s32, termios_p : *My_Termios) -> s32 #foreign libc;
-
+
+#scope_export
OS_prepare_terminal :: () {
// TODO Required to do unlocking input.
- tcgetattr(STDIN_FILENO, *__term);
- term_new := __term;
+ tcgetattr(STDIN_FILENO, *initial_tio_mode);
+ default_tio_mode := initial_tio_mode;
+
+ default_tio_mode.c_iflag &= 0xFFFFFA14;// ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+ default_tio_mode.c_oflag &= 0xFFFFFFFE;// ~OPOST;
+ default_tio_mode.c_lflag &= 0xFFFF7FB4;// ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ default_tio_mode.c_cflag &= 0xFFFFFECF;// ~(CSIZE | PARENB);
+ default_tio_mode.c_cflag |= 0x00000030; // TODO WHAT IS THIS?
- term_new.c_iflag &= 0xFFFFFA14;// ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
- term_new.c_oflag &= 0xFFFFFFFE;// ~OPOST;
- term_new.c_lflag &= 0xFFFF7FB4;// ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
- term_new.c_cflag &= 0xFFFFFECF;// ~(CSIZE | PARENB);
- term_new.c_cflag |= 0x00000030; // TODO WHAT IS THIS?
+ blocking_tio_mode = default_tio_mode;
+ blocking_tio_mode.c_iflag |= xx cast(Input_Modes)(.IXOFF | .ICRNL);
+ blocking_tio_mode.c_lflag |= xx cast(Local_Modes)(.NOKERNINFO | .ECHO | .ECHOE | .ECHOKE);
- tcsetattr(STDIN_FILENO, 0, *term_new);
+ unblocking_tio_mode = default_tio_mode;
+ iflags: Input_Modes = (.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON);
+ unblocking_tio_mode.c_iflag &= xx ~(iflags);
+ lflags: Local_Modes = (.ECHO | .ECHONL | .ICANON | .IEXTEN);
+ unblocking_tio_mode.c_lflag &= xx ~lflags;
+
+ tcsetattr(STDIN_FILENO, 0, *default_tio_mode);
}
OS_reset_terminal :: () {
- tcsetattr(STDIN_FILENO, 0, *__term); // return echo
+ tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // return echo
}
OS_get_terminal_size :: () -> rows: int, columns: int {
- buffer: [512] u8;
- write_string(Commands.QueryWindowSizeInChars);
- bytes_read := read(STDIN_FILENO, buffer.data, buffer.count-1);
- str := to_string(buffer.data, bytes_read);
+ auto_release_temp();
+
+ input := talloc_string(64);
+ write_string(Commands.QueryWindowSizeInChars);
+ input.count = OS_read_input(input.data, input.count);
- // Result: [8;79;156t
+ // Expected message format: [8;<r>;<c>t
+ // where <r> is the number of rows and <c> of columns.
assert(
- buffer.data[0] == #char "\e" &&
- buffer.data[1] == #char "[" &&
- buffer.data[2] == #char "8",
+ input[0] == #char "\e" &&
+ input[1] == #char "[" &&
+ input[2] == #char "8",
"Query windows size in chars returned invalid response.");
- parts := split(str, ";");
+ parts := split(input, ";");
rows := parse_int(*parts[1]);
columns := parse_int(*parts[2]);
return rows, columns;
}
+
+// TODO Maybe we should use a NON-BLOCKING state by default... and only change to blocking when performing a HUMAN read...?
+
+OS_set_input_mode :: (mode: Input_Mode) {
+ if mode == {
+ case .HUMAN;
+ tcsetattr(STDIN_FILENO, 0, *blocking_tio_mode);
+ // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string)
+ case .MACHINE;
+ tcsetattr(STDIN_FILENO, 0, *unblocking_tio_mode);
+ // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string)
+ case;
+ // TODO ERROR
+ }
+}
+
+OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" {
+ bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read);
+ if bytes_read < 0 {
+ error_code, error_message := get_error_value_and_string();
+ return -1, true, error_message;
+ }
+ return bytes_read;
+}
diff --git a/TUI/windows.jai b/TUI/windows.jai
index 84dd271..85dab0b 100644
--- a/TUI/windows.jai
+++ b/TUI/windows.jai
@@ -1,4 +1,12 @@
+#scope_file
+
#import "Windows";
+#import "System";
+
+// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
+// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#designate-character-set
+// https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md
+
kernel32 :: #system_library "kernel32";
@@ -84,7 +92,9 @@
initial_stdout_mode: u32;
default_stdout_mode: Console_Mode;
-
+
+#scope_export
+
OS_prepare_terminal :: () {
print("TODO TUI\n", to_standard_error = true);
@@ -147,38 +157,25 @@ OS_get_terminal_size :: () -> rows: int, columns: int {
return rows, columns;
}
-OS_read_input :: (mode: Input_Mode) -> string {
- result: string = ---;
-
+OS_set_input_mode :: (mode: Input_Mode) {
if mode == {
case .HUMAN;
- assert(SetConsoleMode(stdin, xx blocking_stdin_mode), "Failed to set input mode to blocking mode."); // @speed TODO Could check if was already set before applying.
-
+ assert(SetConsoleMode(stdin, xx blocking_stdin_mode), "Failed to set input mode to blocking mode.");
+ // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string)
case .MACHINE;
- // assert(SetConsoleMode(stdin, xx unblocking_stdin_mode), "Failed to set input mode to unblocking mode."); // @speed TODO Could check if was already set before applying.
- if SetConsoleMode(stdin, xx unblocking_stdin_mode) == false {
- print("Failed to set input mode: %.", GetLastError(), to_standard_error = true);
- exit(0);
- }
+ assert(SetConsoleMode(stdin, xx unblocking_stdin_mode), "Failed to set input mode to unblocking mode.");
+ // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string)
+ case;
+ // TODO ERROR
}
+}
- MAX_BYTES_TO_READ :: 10;
- temp : [MAX_BYTES_TO_READ] u8;
- bytes_read : s32;
-
- if ReadConsoleA(stdin, temp.data, xx temp.count, *bytes_read) {
-
- // TODO If the number of bytes_read is equal to the buffer size... we may have some more data to read?!
- // ---> To fix this, we should check the last read byte and see if it's a newline (at least for HUMAN mode)!
-
- // TODO Maybe pass the input result in the temporary memory (unless specified otherwise)?!
-
- result.data = alloc(bytes_read);
- result.count = bytes_read;
- memcpy(result.data, temp.data, bytes_read);
+OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" {
+ bytes_read: s32;
+ error := ReadConsoleA(stdin, buffer, bytes_to_read, *bytes_read);
+ if error {
+ _, error_message := get_error_value_and_string();
+ return -1, true, error_message;
}
-
- assert(SetConsoleMode(stdin, xx default_stdin_mode), "Failed to set default input mode."); // @speed TODO Maybe compare current mode with default one before applying?!
-
- return result;
-};
+ return bytes_read;
+}
diff --git a/ttt.jai b/ttt.jai
index 9154a60..a78cf30 100644
--- a/ttt.jai
+++ b/ttt.jai
@@ -1191,7 +1191,7 @@ main :: () {
TUI.set_cursor_position(3, 3);
input := TUI.read_input();
- // sleep_milliseconds(1500);
+ // sleep_milliseconds(5000);
TUI.stop();
print("- - -\n");
print("window r:c = %:%\n", rows, columns);
@@ -1251,6 +1251,10 @@ main :: () {
app_directory = join(home_path, "/", APP_FOLDER_NAME);
db_file_path = join(app_directory, "/", DB_FILE_NAME);
ar_file_path = join(app_directory, "/", AR_FILE_NAME);
+
+ // TODO app data should be stored under:
+ // Windows: APPDATA (~/AppData/Roaming)
+ // Unix: XDG_DATA_HOME (~/.local/share)
make_directory_if_it_does_not_exist(app_directory, recursive = true);
}