From e8ddaf3289a836c10f45b1b8c019922a99393488 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 9 Feb 2024 01:18:03 +0000 Subject: Reimplemented procedure to read input. --- TUI/module.jai | 129 +++++++++++++++++++++++++++++++++++++++++---------------- TUI/unix.jai | 3 +- ttt.jai | 7 +++- 3 files changed, 102 insertions(+), 37 deletions(-) diff --git a/TUI/module.jai b/TUI/module.jai index 21f1dd7..de56ee8 100644 --- a/TUI/module.jai +++ b/TUI/module.jai @@ -559,33 +559,55 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { return xx Keys.None; } -// TODO Maybe rename!? Or replace with read_string... or read_input... or something else?! -get_string :: (count_limit: int = -1) -> string { +// TODO Review me! +read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_initialized(); - + + assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? + // if count_limit < 0 && terminators.count == 0 { + // TODO Write error to log. + // return ""; + // } + if count_limit < 0 { builder: String_Builder; init_string_builder(*builder); - while(1) { + while read_loop := true { 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 < buffer.allocated break; - expand(*builder); + buffer.count += OS_read_input(buffer_data + buffer.count, buffer.allocated - buffer.count); + for 0..buffer.count-1 { + for t: terminators { + if buffer_data[it] == t then break read_loop; + } + } + if buffer.count == buffer.allocated then expand(*builder); + OS_wait_for_input(); } return builder_to_string(*builder); } else { buffer := alloc_string(count_limit); - buffer.count = OS_read_input(buffer.data, count_limit); + buffer.count = 0; + + while read_loop := true { + buffer.count += OS_read_input(buffer.data + buffer.count, count_limit - buffer.count); + if buffer.count == count_limit then break; + for 0..buffer.count-1 { + for t: terminators { + if buffer[it] == t then break read_loop; + } + } + OS_wait_for_input(); + } + return buffer; } } // TODO UNTESTED -// TODO Is count_limit the number of bytes of UTF8 symbols? -user_line_input :: (count_limit: int, is_visible: bool = true) -> string, Key { +read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert(count_limit >= 0, "Invalid value on count_limit parameter."); str := alloc_string(count_limit); @@ -689,6 +711,8 @@ stop :: () { flush_input :: () { OS_flush_input(); + input_string.data = input_buffer.data; + input_string.count = 0; } // TODO move style related procedures here! @@ -753,33 +777,46 @@ get_terminal_size :: () -> rows: int, columns: int { auto_release_temp(); - rows, columns: int = ---; - flush_input(); write_string(Commands.QueryWindowSizeInChars); - input := get_string(64,, temporary_allocator); - - // Expected response format: \e[8;;t - // where is the number of rows and of columns. - FORMAT :: "\e[8;;t"; - // Discard head noise. - while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { - advance(*input); - } + rows, columns: int = ---; + if OS_wait_for_input(0) { - // Discard tail noise. - while input.count >= 3 && input[input.count-1] != FORMAT[FORMAT.count-1] { - input.count -= 1; - } - - assert(input.count >= 3 && - input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], - "Query window size in chars returned invalid response."); + // Expected response format: \e[8;;t + // where is the number of rows and of columns. + FORMAT :: "\e[8;;t"; + input := read_input(64, #char "t",, temporary_allocator); - parts := split(input, ";",, temporary_allocator); - rows = parse_int(*parts[1]); - columns = parse_int(*parts[2]); + // Discard head noise. + while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { + advance(*input); + } + + // Discard tail noise. + while input.count >= 3 && input[input.count-1] != FORMAT[FORMAT.count-1] { + input.count -= 1; + } + + assert(input.count >= 3 && + input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], + "Query window size in chars returned invalid response."); + + parts := split(input, ";",, temporary_allocator); + rows = parse_int(*parts[1]); + columns = parse_int(*parts[2]); + } + // Some systems don't allow to query the terminal size directly. + // In such cases, measure it indirectly by the maximum possible cursor position. + else { + #import "Math"; // TODO Maybe use S16_MAX values directly. + flush_input(); + cursor_row, cursor_column := get_cursor_position(); + defer set_cursor_position(cursor_row, cursor_column); + + set_cursor_position(S16_MAX, S16_MAX); + rows, columns = get_cursor_position(); + } return rows, columns; } @@ -798,12 +835,12 @@ get_cursor_position :: () -> row: int, column: int { flush_input(); write_string(Commands.QueryCursorPosition); - input := get_string(64,, temporary_allocator); // Expected response format: \e[;R // where is the number of rows and of columns. FORMAT :: "\e[;R"; - + input := read_input(64, #char "R"); + // Discard head noise. while input.count >= 2 && (input[0] != FORMAT[0] || input[1] != FORMAT[1]) { advance(*input); @@ -818,7 +855,6 @@ get_cursor_position :: () -> row: int, column: int { input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[input.count-1] == FORMAT[FORMAT.count-1], "Query cursor position returned invalid response."); - advance(*input, 2); parts := split(input, ";",, temporary_allocator); row := parse_int(*parts[0]); @@ -832,6 +868,29 @@ set_terminal_title :: (title: string) { } +test :: () { + + // A) Testing stuff +#if true { + start(); + flush_input(); + set_cursor_position(S16_MAX, S16_MAX); + r_a, c_a := get_cursor_position(); + stop(); + print("\n\rA) size: %, %\n", r_a, c_a); +} + + // B) Built way +#if true { + start(); + r_b, c_b := get_terminal_size(); + stop(); + print("\n\rB) size: %, %\n", r_b, c_b); +} + +} + + #if OS == .WINDOWS { // Prototyping zone... keep clear! } diff --git a/TUI/unix.jai b/TUI/unix.jai index 7a25b6b..2da4437 100644 --- a/TUI/unix.jai +++ b/TUI/unix.jai @@ -257,6 +257,7 @@ OS_flush_input :: inline () { tcflush(STDIN_FILENO, TCIFLUSH); } +// TODO Nothing is checking for the errors returned by this... shame! 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 { @@ -269,7 +270,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bo // timeout_milliseconds // 0: do not wait // -1: wait indefinitely -OS_wait_for_input :: (timeout_milliseconds: s32) -> is_input_available: bool { +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { fds := pollfd.[ .{ fd = STDIN_FILENO, events = POLLIN, revents = 0 } ]; nfds := fds.count; poll_return := poll(fds.data, xx nfds, xx timeout_milliseconds); // TODO Wait for input using poll syscall. This breaks and throws '-1 | 4 | Interrupted system call' when we resize the window while polling. diff --git a/ttt.jai b/ttt.jai index 3e95802..d4a5320 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1239,6 +1239,11 @@ main :: () { // TODO Test input + { + TUI.test(); + exit(0); + } + if 1 { print("TEST : set and get cursor position --\n", to_standard_error = true); TUI.start(); @@ -1372,7 +1377,7 @@ main :: () { TUI.set_cursor_position(1, 1); print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):"); TUI.set_cursor_position(2, 1); - str, key := TUI.user_line_input(15); + str, key := TUI.read_input_line(15); TUI.set_cursor_position(3, 1); if key == { case TUI.Keys.Escape; { -- cgit v1.2.3