#if OS == .WINDOWS { #load "windows.jai"; } else #if (OS == .LINUX) || (OS == .MACOS) { #load "unix.jai"; } else { #assert(false, "Unsupported OS."); } #import "Basic"; #import "String"; #import "Thread"; Drawings :: struct { /* TODO Using unicode allows to just-print instead of jumping back and forth between drawing/text modes. But it seems that not all terminals support unicode?! Do we even bother with those terminals?! */ // test_drawing_without_mode :: () { // top := "┌───┐"; // btm := "└───┘"; // print(">%<\n", top); // print(">%<\n", btm); // } CornerBR :: "\x6A"; CornerTR :: "\x6B"; CornerTL :: "\x6C"; CornerBL :: "\x6D"; Cross :: "\x6E"; LineH :: "\x71"; TeeL :: "\x74"; TeeR :: "\x75"; TeeB :: "\x76"; TeeT :: "\x77"; LineV :: "\x78"; Blank :: "\x5F"; Diamond :: "\x60"; Checkerboard :: "\x61"; PlusMinus :: "\x67"; LessThanOrEqual :: "\x79"; GreaterThanOrEqual :: "\x7A"; Pi :: "\x7B"; NotEqual :: "\x7C"; CenteredDot :: "\x7E"; } Commands :: struct { AlternateScreenBuffer :: "\e[?1049h"; MainScreenBuffer :: "\e[?1049l"; DrawingMode :: "\e(0"; TextMode :: "\e(B"; ClearScreen :: "\e[2J"; ClearLine :: "\e[2K"; ClearScrollBack :: "\e[3J"; Bell :: "\x07"; SetWindowTitle :: "\e]0;%\e\\"; RefreshWindow :: "\e[7t"; // TODO Not yet tested. SetUTF8 :: "\e%G"; // TODO TEST ME PLEASE SetGraphicsRendition :: "\e[%m"; // Cursor Position SetCursorPosition :: "\e[%;%H"; // Cursor Visibility ShowCursor :: "\e[?25h"; HideCursor :: "\e[?25l"; StartBlinking :: "\e[?25h"; StopBlinking :: "\e[?25l"; SaveCursorPosition :: "\e7"; RestoreCursorPosition :: "\e8"; // Cursor Shape DefaultShape :: "\e[0 q"; BlinkingBlockShape :: "\e[1 q"; SteadyBlockShape :: "\e[2 q"; BlinkingUnderlineShape :: "\e[3 q"; SteadyUnderlineShape :: "\e[4 q"; BlinkingBarShape :: "\e[5 q"; SteadyBarShape :: "\e[6 q"; // Input Mode KeypadAppMode :: "\e="; KeypadNumMode :: "\e>"; CursorAppMode :: "\e[?1h"; CursorNormalMode :: "\e[?1l"; // Query State QueryCursorPosition :: "\e[6n"; // Emits the cursor position as: "ESC [ ; R" Where = row and = column. QueryDeviceAttributes :: "\e[0c"; QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); } Colors8b :: struct { Black :: 0; Maroon :: 1; Green :: 2; Olive :: 3; Navy :: 4; Purple :: 5; Teal :: 6; Silver :: 7; Gray :: 8; Red :: 9; Lime :: 10; Yellow :: 11; Blue :: 12; Magenta :: 13; Cyan :: 14; White :: 15; } // TODO Maybe rename. set_style_colors :: (foreground: u8, background: u8) { print( #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), foreground, background); } // set_colors_24b :: (foreground_r: u255) // TODO https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit set_style :: (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { print( #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), ifx bold then 1 else 22, ifx underline then 4 else 24, ifx strike_through then 9 else 29, ifx negative then 7 else 27); } // TODO Maybe make the OS_* procedures as inline?! /* We wanted the Key type to represent either UTF-8 encoded characters and also keyboard keys. The UTF-8 only requires up to 4 bytes, but some keyboard keys return up to 6 bytes. Therefore, we rounded it up to 8 bytes to support all this and more (if needed). This has to be compatible with: (#char "a" == key) ... so "a" must be stored in the LSB of key |-|-|-|-|-| string |a|b|c|0|0| key/u64 |0|0|c|b|a| -> that in memory lays as (BE:|0|0|c|b|a|) and (LE:|a|b|c|0|0|) */ Key :: u64; KEY_SIZE :: #run type_info(Key).runtime_size; Keys :: struct #type_info_none { // Terminal key-codes have 1 to 6 bytes, so we can signal special cases setting the edge-bytes. None : Key : 0xF0000000_0000000F; Resize : Key : 0xF0000000_0000001F; Space : Key : #char " "; Enter : Key : #char "\r"; Tab : Key : #char "\t"; Up : Key : #run to_key("\e[A"); Down : Key : #run to_key("\e[B"); Right : Key : #run to_key("\e[C"); Left : Key : #run to_key("\e[D"); MetaUp : Key : #run to_key("\e[1;1A"); MetaDown : Key : #run to_key("\e[1;1B"); MetaRight : Key : #run to_key("\e[1;1C"); MetaLeft : Key : #run to_key("\e[1;1D"); ShiftUp : Key : #run to_key("\e[1;2A"); ShiftDown : Key : #run to_key("\e[1;2B"); ShiftRight : Key : #run to_key("\e[1;2C"); ShiftLeft : Key : #run to_key("\e[1;2D"); AltUp : Key : #run to_key("\e[1;3A"); AltDown : Key : #run to_key("\e[1;3B"); AltRight : Key : #run to_key("\e[1;3C"); AltLeft : Key : #run to_key("\e[1;3D"); AltShiftUp : Key : #run to_key("\e[1;4A"); AltShiftDown : Key : #run to_key("\e[1;4B"); AltShiftRight : Key : #run to_key("\e[1;4C"); AltShiftLeft : Key : #run to_key("\e[1;4D"); CtrlUp : Key : #run to_key("\e[1;5A"); CtrlDown : Key : #run to_key("\e[1;5B"); CtrlRight : Key : #run to_key("\e[1;5C"); CtrlLeft : Key : #run to_key("\e[1;5D"); CtrlShiftUp : Key : #run to_key("\e[1;6A"); CtrlShiftDown : Key : #run to_key("\e[1;6B"); CtrlShiftRight : Key : #run to_key("\e[1;6C"); CtrlShiftLeft : Key : #run to_key("\e[1;6D"); CtrlAltUp : Key : #run to_key("\e[1;7A"); CtrlAltDown : Key : #run to_key("\e[1;7B"); CtrlAltRight : Key : #run to_key("\e[1;7C"); CtrlAltLeft : Key : #run to_key("\e[1;7D"); CtrlAltShiftUp : Key : #run to_key("\e[1;7A"); CtrlAltShiftDown : Key : #run to_key("\e[1;7B"); CtrlAltShiftRight : Key : #run to_key("\e[1;7C"); CtrlAltShiftLeft : Key : #run to_key("\e[1;7D"); Home : Key : #run to_key("\e[H"); End : Key : #run to_key("\e[F"); Escape : Key : 0x00000000_0000001B; Backspace : Key : 0x00000000_0000007F; Pause : Key : 0x00000000_0000001A; Insert : Key : #run to_key("\e[2~"); Delete : Key : #run to_key("\e[3~"); PgUp : Key : #run to_key("\e[5~"); PgDown : Key : #run to_key("\e[6~"); F1 : Key : #run to_key("\eOP"); F2 : Key : #run to_key("\eOQ"); F3 : Key : #run to_key("\eOR"); F4 : Key : #run to_key("\eOS"); F5 : Key : #run to_key("\e[15~"); F6 : Key : #run to_key("\e[17~"); F7 : Key : #run to_key("\e[18~"); F8 : Key : #run to_key("\e[19~"); F9 : Key : #run to_key("\e[20~"); F10 : Key : #run to_key("\e[21~"); F11 : Key : #run to_key("\e[23~"); F12 : Key : #run to_key("\e[24~"); MetaF1 : Key : #run to_key("\eO1P"); MetaF2 : Key : #run to_key("\eO1Q"); MetaF3 : Key : #run to_key("\eO1R"); MetaF4 : Key : #run to_key("\eO1S"); MetaF5 : Key : #run to_key("\e[15;1~"); MetaF6 : Key : #run to_key("\e[17;1~"); MetaF7 : Key : #run to_key("\e[18;1~"); MetaF8 : Key : #run to_key("\e[19;1~"); MetaF9 : Key : #run to_key("\e[20;1~"); MetaF10 : Key : #run to_key("\e[21;1~"); MetaF11 : Key : #run to_key("\e[23;1~"); MetaF12 : Key : #run to_key("\e[24;1~"); ShiftF1 : Key : #run to_key("\eO2P"); ShiftF2 : Key : #run to_key("\eO2Q"); ShiftF3 : Key : #run to_key("\eO2R"); ShiftF4 : Key : #run to_key("\eO2S"); ShiftF5 : Key : #run to_key("\e[15;2~"); ShiftF6 : Key : #run to_key("\e[17;2~"); ShiftF7 : Key : #run to_key("\e[18;2~"); ShiftF8 : Key : #run to_key("\e[19;2~"); ShiftF9 : Key : #run to_key("\e[20;2~"); ShiftF10 : Key : #run to_key("\e[21;2~"); ShiftF11 : Key : #run to_key("\e[23;2~"); ShiftF12 : Key : #run to_key("\e[24;2~"); AltF1 : Key : #run to_key("\eO3P"); AltF2 : Key : #run to_key("\eO3Q"); AltF3 : Key : #run to_key("\eO3R"); AltF4 : Key : #run to_key("\eO3S"); AltF5 : Key : #run to_key("\e[15;3~"); AltF6 : Key : #run to_key("\e[17;3~"); AltF7 : Key : #run to_key("\e[18;3~"); AltF8 : Key : #run to_key("\e[19;3~"); AltF9 : Key : #run to_key("\e[20;3~"); AltF10 : Key : #run to_key("\e[21;3~"); AltF11 : Key : #run to_key("\e[23;3~"); AltF12 : Key : #run to_key("\e[24;3~"); AltShiftF1 : Key : #run to_key("\eO4P"); AltShiftF2 : Key : #run to_key("\eO4Q"); AltShiftF3 : Key : #run to_key("\eO4R"); AltShiftF4 : Key : #run to_key("\eO4S"); AltShiftF5 : Key : #run to_key("\e[15;4~"); AltShiftF6 : Key : #run to_key("\e[17;4~"); AltShiftF7 : Key : #run to_key("\e[18;4~"); AltShiftF8 : Key : #run to_key("\e[19;4~"); AltShiftF9 : Key : #run to_key("\e[20;4~"); AltShiftF10 : Key : #run to_key("\e[21;4~"); AltShiftF11 : Key : #run to_key("\e[23;4~"); AltShiftF12 : Key : #run to_key("\e[24;4~"); CtrlF1 : Key : #run to_key("\eO5P"); CtrlF2 : Key : #run to_key("\eO5Q"); CtrlF3 : Key : #run to_key("\eO5R"); CtrlF4 : Key : #run to_key("\eO5S"); CtrlF5 : Key : #run to_key("\e[15;5~"); CtrlF6 : Key : #run to_key("\e[17;5~"); CtrlF7 : Key : #run to_key("\e[18;5~"); CtrlF8 : Key : #run to_key("\e[19;5~"); CtrlF9 : Key : #run to_key("\e[20;5~"); CtrlF10 : Key : #run to_key("\e[21;5~"); CtrlF11 : Key : #run to_key("\e[23;5~"); CtrlF12 : Key : #run to_key("\e[24;5~"); CtrlShiftF1 : Key : #run to_key("\eO6P"); CtrlShiftF2 : Key : #run to_key("\eO6Q"); CtrlShiftF3 : Key : #run to_key("\eO6R"); CtrlShiftF4 : Key : #run to_key("\eO6S"); CtrlShiftF5 : Key : #run to_key("\e[15;6~"); CtrlShiftF6 : Key : #run to_key("\e[17;6~"); CtrlShiftF7 : Key : #run to_key("\e[18;6~"); CtrlShiftF8 : Key : #run to_key("\e[19;6~"); CtrlShiftF9 : Key : #run to_key("\e[20;6~"); CtrlShiftF10 : Key : #run to_key("\e[21;6~"); CtrlShiftF11 : Key : #run to_key("\e[23;6~"); CtrlShiftF12 : Key : #run to_key("\e[24;6~"); CtrlAltF1 : Key : #run to_key("\eO7P"); CtrlAltF2 : Key : #run to_key("\eO7Q"); CtrlAltF3 : Key : #run to_key("\eO7R"); CtrlAltF4 : Key : #run to_key("\eO7S"); CtrlAltF5 : Key : #run to_key("\e[15;7~"); CtrlAltF6 : Key : #run to_key("\e[17;7~"); CtrlAltF7 : Key : #run to_key("\e[18;7~"); CtrlAltF8 : Key : #run to_key("\e[19;7~"); CtrlAltF9 : Key : #run to_key("\e[20;7~"); CtrlAltF10 : Key : #run to_key("\e[21;7~"); CtrlAltF11 : Key : #run to_key("\e[23;7~"); CtrlAltF12 : Key : #run to_key("\e[24;7~"); CtrlAltShiftF1 : Key : #run to_key("\eO8P"); CtrlAltShiftF2 : Key : #run to_key("\eO8Q"); CtrlAltShiftF3 : Key : #run to_key("\eO8R"); CtrlAltShiftF4 : Key : #run to_key("\eO8S"); CtrlAltShiftF5 : Key : #run to_key("\e[15;8~"); CtrlAltShiftF6 : Key : #run to_key("\e[17;8~"); CtrlAltShiftF7 : Key : #run to_key("\e[18;8~"); CtrlAltShiftF8 : Key : #run to_key("\e[19;8~"); CtrlAltShiftF9 : Key : #run to_key("\e[20;8~"); CtrlAltShiftF10 : Key : #run to_key("\e[21;8~"); CtrlAltShiftF11 : Key : #run to_key("\e[23;8~"); CtrlAltShiftF12 : Key : #run to_key("\e[24;8~"); } to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { k: Key; // #if DEBUG { // assert(str.count <= 8); // TODO Add DEBUG to module parameters. // } for 0..str.count-1 #no_abc { k |= ((cast(u64)str[it]) << (it*8)); } return k; } to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY str := talloc_string(KEY_SIZE); str.count = 0; while key != 0 #no_abc { str[str.count] = xx key & 0xFF; key >>= 8; str.count += 1; } return str; } is_escape_code :: inline (key: Key) -> bool { result := false; while key != 0 { key >>= 8; result |= ((key ^ Keys.Escape) == 0); } return result; } // TODO FIXME DEBUG HACK test_union :: () { // ti := type_info(K); print("\n---\n%\n---\n", type_info(Key).*); print("\n---\n%\n---\n", type_info(Key).runtime_size); a: Key; b: u8; print(">%\n", a == b); print(">%\n", a != Keys.None); c: string = ""; print(">%\n", a == to_key(c)); d: []u8 = xx ""; print(">%\n", a == to_key(d)); top := "┌───┐"; btm := "└───┘"; print(">%<\n", top); print(">%<\n", btm); str := "abcd"; tok := to_key(str); tos := to_string(tok); print("1:%:\n", str); print("2:"); for 0..7 { val := tok & 0xFF; tok >>=8 ; print("% ", FormatInt.{value=val, base=16, minimum_digits=2}); } print(":\n"); print("3:%:\n", tos); } #run test_union(); // Terminal action codes are encoded with values incompatible with UTF-8 to avoid collisions. initialized := false; // input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! input_buffer : [8] u8; // TODO FIXME Input buffer is too small!!! input_string : string; input_override : Key; was_resized : bool; #run { // TODO FIXME DEBUG HACK or maybe... let it be?! // Some tests. assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire terminal (6 bytes) or UTF8 (4 bytes) code."); } assert_is_initialized :: inline () { assert(initialized, "TUI is not ready."); } set_next_key :: (key: Key) { assert_is_initialized(); input_override = key; } get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_initialized(); // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte is_utf8_continuation_byte :: inline (byte: u8) -> bool { return (byte & 0xC0) == 0x80; } // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte count_utf8_bytes :: inline (byte: u8) -> int { if (byte & 0xE0) == 0xC0 return 1+1; if (byte & 0xF0) == 0xE0 return 1+2; if (byte & 0xF8) == 0xF0 return 1+3; return 1; } if input_override != xx Keys.None { defer input_override = xx Keys.None; return input_override; } if OS_was_terminal_resized() return xx Keys.Resize; // FIXME Old version... // if input_string.count > 0 { // defer advance(*input_string, 1); // return input_string[0]; // } // FIXME New version... if input_string.count > 0 #no_abc { // TODO Some trickery requires no_abc... which is not that nice in this case... utf8_bytes := count_utf8_bytes(input_string[0]); // TODO We're assuming the terminal buffer contains the entirety of what we need to read for the UTF8 symbols. if utf8_bytes > input_string.count { diff := utf8_bytes - input_string.count; // TODO Test this and make sure it's working...drop the following lines using Ctrl+V to fill the terminal buffer at once: // d€€€a // 1234567890123456789012345678901234567890 print(""); // Copy buffered bytes to the start, and read the remaining ones from input. for 0..input_string.count-1 { input_buffer[it] = input_string[it]; } aaa := OS_read_input(input_buffer.data + input_string.count, utf8_bytes-input_string.count); // TODO Does not check for read errors. assert(aaa == diff, "READ MORE THAN EXPECTED"); input_string.data = input_buffer.data; input_string.count = utf8_bytes; } to_parse := input_string; to_parse.count = utf8_bytes; key := to_key(to_parse); advance(*input_string, utf8_bytes); return key; } is_input_available := OS_wait_for_input(timeout_milliseconds); if OS_was_terminal_resized() return xx Keys.Resize; // FIXME Old version... // if is_input_available { // bytes_read := OS_read_input(input_buffer.data, input_buffer.count); // TODO Does not check for read errors. // if bytes_read > 0 { // input_string.data = input_buffer.data; // input_string.count = bytes_read; // defer advance(*input_string, 1); // return input_string[0]; // } // } // FIXME New version... if is_input_available { bytes_read := OS_read_input(input_buffer.data, input_buffer.count); // TODO Does not check for read errors. if bytes_read > 0 { input_string.data = input_buffer.data; input_string.count = bytes_read; utf8_bytes := count_utf8_bytes(input_string[0]); assert(utf8_bytes <= input_string.count, "The input buffer is too small."); // TODO Improve error message. // TODO This is only being done after the OS_wait_for_input... for now! to_parse := input_string; to_parse.count = utf8_bytes; // Must be a terminal escape sequence. if utf8_bytes == 1 && input_string[0] == #char "\e" { assert(input_string.count <= KEY_SIZE, "Received oversized terminal sequence."); // TODO to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; // TODO We should look into the input_string and search for the following escape sequence or somehting!? } key := to_key(to_parse); advance(*input_string, to_parse.count); return key; /// /// /// /// /// /// /// /// /// // DEBUG // br, bc := get_cursor_position(); // column := 3; // row += 1; // nr, rc := get_terminal_size(); // // if row >= (rc - 5) then row = 5; // // set_cursor_position(row, column); // for 0..input_string.count-1 { // print("%:% | ", // FormatInt.{base= 2, minimum_digits = 8, value = input_string[it]}, // FormatInt.{base= 16, minimum_digits = 2, value = input_string[it]}, // ); // TODO DEBUG // } // set_cursor_position(br, bc); /// /// /// /// /// /// /// /// /// } } 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 { assert_is_initialized(); if count_limit < 0 { builder: String_Builder; 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 < buffer.allocated break; expand(*builder); } return builder_to_string(*builder); } else { buffer := alloc_string(count_limit); buffer.count = OS_read_input(buffer.data, count_limit); 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 { assert(count_limit >= 0, "Invalid value on count_limit parameter."); str := alloc_string(count_limit); // TODO Show blinking cursor write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); row, col := get_cursor_position(); idx := 0; key := Keys.None; // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line while key != Keys.Resize && key != Keys.Escape { // TODO How to alloc/release temporary memory here? key = get_key(); if key == { case Keys.Enter; break; case Keys.Left; if idx == 0 continue; idx -= 1; case Keys.Right; if idx == str.count-1 continue; idx += 1; case Keys.Delete; if idx == str.count-1 continue; for idx..str.count-2 { str.data[it] = str.data[it+1]; } case Keys.Backspace; if idx == 0 continue; idx -= 1; for idx..str.count-2 { str.data[it] = str.data[it+1]; } case; if is_escape_code(key) continue; // TODO NOT WORKING FIX THIS key_str := to_string(key); str.data[idx] = key_str.data[0]; idx += 1; } // append(*builder, str); // set_cursor_position(row, col); // write_builder(*builder, false); set_cursor_position(row, col+idx); for idx..count_limit print_character(#char " "); set_cursor_position(row, col); write_string(str); // print(">%<", builder_to_string(*builder,, temporary_allocator)); set_cursor_position(row, col+idx); } write_strings(Commands.StopBlinking, Commands.DefaultShape); /* Use the get_key to read user input and show it on screen... should allow to move the cursor left and right and to delete/backspace. Enter should end the input, returning the input string and the Enter key. Escape should discard the input returning an empty string and a None key. Resize should discard the input returning an empty string and a Resize key. */ // result := ifx key == Keys.Enter then builder_to_string(*builder) else ""; result := ifx key == Keys.Enter then str else ""; return result, key; } start :: () { if initialized == true return; input_string.data = input_buffer.data; input_string.count = 0; input_override = xx Keys.None; write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.AlternateScreenBuffer, Commands.SetUTF8); OS_prepare_terminal(); initialized = true; } stop :: () { if initialized == false return; initialized = false; OS_reset_terminal(); write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); } flush_input :: () { OS_flush_input(); } // TODO move style related procedures here! // set_style :: () { // print("", Commands.) -- // } draw_box :: (x: int, y: int, width: int, height: int) { assert_is_initialized(); // TODO Check if using a String_Builder improves performance (measure it)! // TODO Validate input parameters against the terminal size. assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments."); auto_release_temp(); tmp_string: string; tmp_string = tprint(Commands.SetCursorPosition, y, x); write_strings( Commands.DrawingMode, tmp_string, Drawings.CornerTL ); for 1..width-2 { write_string(Drawings.LineH); } write_string(Drawings.CornerTR); for idx: y+1..y+height-2 { tmpL := tprint(Commands.SetCursorPosition, idx, x); tmpR := tprint(Commands.SetCursorPosition, idx, x+width-1); write_strings( tmpL, Drawings.LineV, tmpR, Drawings.LineV); } tmpBL := tprint(Commands.SetCursorPosition, y+height-1, x); write_strings( tmpBL, Drawings.CornerBL); for 1..width-2 { write_string(Drawings.LineH); } write_string(Drawings.CornerBR); write_string(Commands.TextMode); } // TODO Maybe rename to "clear()" clear_terminal :: inline () { assert_is_initialized(); write_string(Commands.ClearScreen); } // TODO Maybe rename to "get_size()" get_terminal_size :: () -> rows: int, columns: int { assert_is_initialized(); rows, columns: int = ---; #if OS == .WINDOWS { rows, columns = OS_get_terminal_size(); } else { auto_release_temp(); 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); } // 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]); } return rows, columns; } set_cursor_position :: (row: int, column: int) { assert_is_initialized(); auto_release_temp(); tmp_string := tprint(Commands.SetCursorPosition, row, column); write_string(tmp_string); } get_cursor_position :: () -> row: int, column: int { assert_is_initialized(); auto_release_temp(); 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"; // Discard head noise. while input.count >= 2 && (input[0] != FORMAT[0] || input[1] != FORMAT[1]) { advance(*input); } // Discard tail noise. while input.count >= 2 && input[input.count-1] != FORMAT[FORMAT.count-1] { input.count -= 1; } assert(input.count >= 2 && 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]); column := parse_int(*parts[1]); return row, column; } set_terminal_title :: (title: string) { assert_is_initialized(); print(Commands.SetWindowTitle, title); } #if OS == .WINDOWS { // Prototyping zone... keep clear! } else #if OS == .LINUX || OS == .MACOS { // Prototyping zone... keep clear! }