// TODO Move TUI into ./modules/TUI so we can stop calling --import_dir on compile. #if OS == { case .LINUX; #load "unix.jai"; case .MACOS; #load "unix.jai"; case .WINDOWS; #load "windows.jai"; case; #assert(false, "Unsupported OS."); } #import "Basic"; #import "String"; #import "Thread"; #import "Hash_Table"; // Special Graphics Characters Drawings :: struct { Blank :: "\x5F"; Diamond :: "\x60"; Checkerboard :: "\x61"; HorizontalTab :: "\x62"; FormFeed :: "\x63"; CarriageReturn :: "\x64"; LineFeed :: "\x65"; DegreeSymbol :: "\x66"; PlusMinus :: "\x67"; NewLine :: "\x68"; VerticalTab :: "\x69"; CornerBR :: "\x6A"; CornerTR :: "\x6B"; CornerTL :: "\x6C"; CornerBL :: "\x6D"; Cross :: "\x6E"; LineHT :: "\x6F"; LineHt :: "\x70"; LineH :: "\x71"; LineHb :: "\x72"; LineHB :: "\x73"; TeeL :: "\x74"; TeeR :: "\x75"; TeeB :: "\x76"; TeeT :: "\x77"; LineV :: "\x78"; LessThanOrEqual :: "\x79"; GreaterThanOrEqual :: "\x7A"; Pi :: "\x7B"; NotEqual :: "\x7C"; PoundSign :: "\x7D"; 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. SetIEC2022 :: "\e%@"; SetUTF8 :: "\e%G"; 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?! // TODO Terminal action codes are encoded with values incompatible with UTF-8 to avoid collisions. /* 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; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. KEY_SIZE :: #run type_info(Key).runtime_size; 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 { /* TODO Check if LSB is not # but there is a `#`, then it's a escape code... or NONE... or RESIZE :S Or...we could change the special codes and set the `#` at the end... then we could simply do: return (key && 0x00FF) ^ # == 0 && (key && 0xFF00) == 0 */ result := false; while key != 0 { key >>= 8; result |= ((key ^ #char "#") == 0); } return result; } Keys :: struct #type_info_none { None : Key : #run to_key("#none"); Resize : Key : #run to_key("#resize"); Space : Key : #char " "; Enter : Key : #char "\r"; Tab : Key : #char "\t"; Escape : Key : 0x00000000_0000001B; Backspace : Key : 0x00000000_0000007F; Pause : Key : 0x00000000_0000001A; Up : Key : #run to_key("#up"); Down : Key : #run to_key("#down"); Right : Key : #run to_key("right"); Left : Key : #run to_key("left"); Home : Key : #run to_key("#home"); End : Key : #run to_key("#end"); Insert : Key : #run to_key("#ins"); Delete : Key : #run to_key("#del"); PgUp : Key : #run to_key("#pup"); PgDown : Key : #run to_key("#pdown"); F1 : Key : #run to_key("#f1"); F2 : Key : #run to_key("#f2"); F3 : Key : #run to_key("#f3"); F4 : Key : #run to_key("#f4"); F5 : Key : #run to_key("#f5"); F6 : Key : #run to_key("#f6"); F7 : Key : #run to_key("#f7"); F8 : Key : #run to_key("#f8"); F9 : Key : #run to_key("#f9"); F10 : Key : #run to_key("#f10"); F11 : Key : #run to_key("#f11"); F12 : Key : #run to_key("#f12"); } key_map: Table(string, Key); setup_key_map :: () { /* This table was created/tested using the following terminals: - konsole - kitty - xterm - linux console */ // Up table_set(*key_map, "\e[A", to_key("#up")); table_set(*key_map, "\e[1;1A", to_key("#up+m")); table_set(*key_map, "\e[1;2A", to_key("#up+s")); table_set(*key_map, "\e[1;3A", to_key("#up+a")); table_set(*key_map, "\e[1;4A", to_key("#up+A")); table_set(*key_map, "\e[1;5A", to_key("#up+c")); table_set(*key_map, "\e[1;6A", to_key("#up+C")); table_set(*key_map, "\e[1;7A", to_key("#up+x")); table_set(*key_map, "\e[1;8A", to_key("#up+X")); // Up - kitty table_set(*key_map, "\e[1;9A", to_key("#up+m")); // Down table_set(*key_map, "\e[B", to_key("#down")); table_set(*key_map, "\e[1;1B", to_key("#down+m")); table_set(*key_map, "\e[1;2B", to_key("#down+s")); table_set(*key_map, "\e[1;3B", to_key("#down+a")); table_set(*key_map, "\e[1;4B", to_key("#down+A")); table_set(*key_map, "\e[1;5B", to_key("#down+c")); table_set(*key_map, "\e[1;6B", to_key("#down+C")); table_set(*key_map, "\e[1;7B", to_key("#down+x")); table_set(*key_map, "\e[1;8B", to_key("#down+X")); // Down - kitty table_set(*key_map, "\e[1;9B", to_key("#down+m")); // Right table_set(*key_map, "\e[C", to_key("#right")); table_set(*key_map, "\e[1;1C", to_key("#right+m")); table_set(*key_map, "\e[1;2C", to_key("#right+s")); table_set(*key_map, "\e[1;3C", to_key("#right+a")); table_set(*key_map, "\e[1;4C", to_key("#right+A")); table_set(*key_map, "\e[1;5C", to_key("#right+c")); table_set(*key_map, "\e[1;6C", to_key("#right+C")); table_set(*key_map, "\e[1;7C", to_key("#right+x")); table_set(*key_map, "\e[1;8C", to_key("#right+X")); // Right - kitty table_set(*key_map, "\e[1;9C", to_key("#right+m")); // Left table_set(*key_map, "\e[D", to_key("#left")); table_set(*key_map, "\e[1;1D", to_key("#left+m")); table_set(*key_map, "\e[1;2D", to_key("#left+s")); table_set(*key_map, "\e[1;3D", to_key("#left+a")); table_set(*key_map, "\e[1;4D", to_key("#left+A")); table_set(*key_map, "\e[1;5D", to_key("#left+c")); table_set(*key_map, "\e[1;6D", to_key("#left+C")); table_set(*key_map, "\e[1;7D", to_key("#left+x")); table_set(*key_map, "\e[1;8D", to_key("#left+X")); // Left - kitty table_set(*key_map, "\e[1;9D", to_key("#left+m")); // Home table_set(*key_map, "\e[H", to_key("#home")); table_set(*key_map, "\e[1~", to_key("#home")); table_set(*key_map, "\e[1;1H", to_key("#home+m")); table_set(*key_map, "\e[1;2H", to_key("#home+s")); table_set(*key_map, "\e[1;3H", to_key("#home+a")); table_set(*key_map, "\e[1;4H", to_key("#home+A")); table_set(*key_map, "\e[1;5H", to_key("#home+c")); table_set(*key_map, "\e[1;6H", to_key("#home+C")); table_set(*key_map, "\e[1;7H", to_key("#home+x")); table_set(*key_map, "\e[1;8H", to_key("#home+X")); // Home - kitty table_set(*key_map, "\e[1;9H", to_key("#home+m")); // End table_set(*key_map, "\e[F", to_key("#end")); table_set(*key_map, "\e[4~", to_key("#end")); table_set(*key_map, "\e[1;1F", to_key("#end+m")); table_set(*key_map, "\e[1;2F", to_key("#end+s")); table_set(*key_map, "\e[1;3F", to_key("#end+a")); table_set(*key_map, "\e[1;4F", to_key("#end+A")); table_set(*key_map, "\e[1;5F", to_key("#end+c")); table_set(*key_map, "\e[1;6F", to_key("#end+C")); table_set(*key_map, "\e[1;7F", to_key("#end+x")); table_set(*key_map, "\e[1;8F", to_key("#end+X")); // End - kitty table_set(*key_map, "\e[1;9F", to_key("#end+m")); // Insert table_set(*key_map, "\e[2~", to_key("#ins")); table_set(*key_map, "\e[2;1~", to_key("#ins+m")); table_set(*key_map, "\e[2;2~", to_key("#ins+s")); table_set(*key_map, "\e[2;3~", to_key("#ins+a")); table_set(*key_map, "\e[2;4~", to_key("#ins+A")); table_set(*key_map, "\e[2;5~", to_key("#ins+c")); table_set(*key_map, "\e[2;6~", to_key("#ins+C")); table_set(*key_map, "\e[2;7~", to_key("#ins+x")); table_set(*key_map, "\e[2;8~", to_key("#ins+X")); // Insert - kitty table_set(*key_map, "\e[2;9~", to_key("#ins+m")); // Delete table_set(*key_map, "\e[3~", to_key("#del")); table_set(*key_map, "\e[3;1~", to_key("#del+m")); table_set(*key_map, "\e[3;2~", to_key("#del+s")); table_set(*key_map, "\e[3;3~", to_key("#del+a")); table_set(*key_map, "\e[3;4~", to_key("#del+A")); table_set(*key_map, "\e[3;5~", to_key("#del+c")); table_set(*key_map, "\e[3;6~", to_key("#del+C")); table_set(*key_map, "\e[3;7~", to_key("#del+x")); table_set(*key_map, "\e[3;8~", to_key("#del+X")); // Delete - kitty table_set(*key_map, "\e[3;9~", to_key("#del+m")); // Page Up table_set(*key_map, "\e[5~", to_key("#pup")); table_set(*key_map, "\e[5;1~", to_key("#pup+m")); table_set(*key_map, "\e[5;2~", to_key("#pup+s")); table_set(*key_map, "\e[5;3~", to_key("#pup+a")); table_set(*key_map, "\e[5;4~", to_key("#pup+A")); table_set(*key_map, "\e[5;5~", to_key("#pup+c")); table_set(*key_map, "\e[5;6~", to_key("#pup+C")); table_set(*key_map, "\e[5;7~", to_key("#pup+x")); table_set(*key_map, "\e[5;8~", to_key("#pup+X")); // Page Up - kitty table_set(*key_map, "\e[5;9~", to_key("#pup+m")); // Page Down table_set(*key_map, "\e[6~", to_key("#pdown")); table_set(*key_map, "\e[6;1~", to_key("#pdown+m")); table_set(*key_map, "\e[6;2~", to_key("#pdown+s")); table_set(*key_map, "\e[6;3~", to_key("#pdown+a")); table_set(*key_map, "\e[6;4~", to_key("#pdown+A")); table_set(*key_map, "\e[6;5~", to_key("#pdown+c")); table_set(*key_map, "\e[6;6~", to_key("#pdown+C")); table_set(*key_map, "\e[6;7~", to_key("#pdown+x")); table_set(*key_map, "\e[6;8~", to_key("#pdown+X")); // Page Down - kitty table_set(*key_map, "\e[6;9~", to_key("#pdown+m")); // F1 table_set(*key_map, "\eOP", to_key("#f1")); table_set(*key_map, "\eO1P", to_key("#f1+m")); table_set(*key_map, "\eO2P", to_key("#f1+s")); table_set(*key_map, "\eO3P", to_key("#f1+a")); table_set(*key_map, "\eO4P", to_key("#f1+A")); table_set(*key_map, "\eO5P", to_key("#f1+c")); table_set(*key_map, "\eO6P", to_key("#f1+C")); table_set(*key_map, "\eO7P", to_key("#f1+x")); table_set(*key_map, "\eO8P", to_key("#f1+X")); // F1 - xterm table_set(*key_map, "\e[1;2P", to_key("#f1+s")); table_set(*key_map, "\e[1;3P", to_key("#f1+a")); table_set(*key_map, "\e[1;4P", to_key("#f1+A")); table_set(*key_map, "\e[1;5P", to_key("#f1+c")); table_set(*key_map, "\e[1;6P", to_key("#f1+C")); table_set(*key_map, "\e[1;7P", to_key("#f1+x")); table_set(*key_map, "\e[1;8P", to_key("#f1+X")); // F1 - kitty table_set(*key_map, "\e[1;9P", to_key("#f1+m")); // F1 - linux console table_set(*key_map, "\e[[A", to_key("#f1")); table_set(*key_map, "\e[25~", to_key("#f1+s")); // F2 table_set(*key_map, "\eOQ", to_key("#f2")); table_set(*key_map, "\eO1Q", to_key("#f2+m")); table_set(*key_map, "\eO2Q", to_key("#f2+s")); table_set(*key_map, "\eO3Q", to_key("#f2+a")); table_set(*key_map, "\eO4Q", to_key("#f2+A")); table_set(*key_map, "\eO5Q", to_key("#f2+c")); table_set(*key_map, "\eO6Q", to_key("#f2+C")); table_set(*key_map, "\eO7Q", to_key("#f2+x")); table_set(*key_map, "\eO8Q", to_key("#f2+X")); // F2 - xterm table_set(*key_map, "\e[1;2Q", to_key("#f2+s")); table_set(*key_map, "\e[1;3Q", to_key("#f2+a")); table_set(*key_map, "\e[1;4Q", to_key("#f2+A")); table_set(*key_map, "\e[1;5Q", to_key("#f2+c")); table_set(*key_map, "\e[1;6Q", to_key("#f2+C")); table_set(*key_map, "\e[1;7Q", to_key("#f2+x")); table_set(*key_map, "\e[1;8Q", to_key("#f2+X")); // F2 - kitty table_set(*key_map, "\e[1;9Q", to_key("#f2+m")); // F2 - linux console table_set(*key_map, "\e[[B", to_key("#f2")); table_set(*key_map, "\e[26~", to_key("#f2+s")); // F3 table_set(*key_map, "\eOR", to_key("#f3")); table_set(*key_map, "\eO1R", to_key("#f3+m")); table_set(*key_map, "\eO2R", to_key("#f3+s")); table_set(*key_map, "\eO3R", to_key("#f3+a")); table_set(*key_map, "\eO4R", to_key("#f3+A")); table_set(*key_map, "\eO5R", to_key("#f3+c")); table_set(*key_map, "\eO6R", to_key("#f3+C")); table_set(*key_map, "\eO7R", to_key("#f3+x")); table_set(*key_map, "\eO8R", to_key("#f3+X")); // F3 - xterm table_set(*key_map, "\e[1;2R", to_key("#f3+s")); table_set(*key_map, "\e[1;3R", to_key("#f3+a")); table_set(*key_map, "\e[1;4R", to_key("#f3+A")); table_set(*key_map, "\e[1;5R", to_key("#f3+c")); table_set(*key_map, "\e[1;6R", to_key("#f3+C")); table_set(*key_map, "\e[1;7R", to_key("#f3+x")); table_set(*key_map, "\e[1;8R", to_key("#f3+X")); // F3 - kitty table_set(*key_map, "\e[1;9R", to_key("#f3+m")); // F3 - linux console table_set(*key_map, "\e[[C", to_key("#f3")); table_set(*key_map, "\e[28~", to_key("#f3+s")); // F4 table_set(*key_map, "\eOS", to_key("#f4")); table_set(*key_map, "\eO1S", to_key("#f4+m")); table_set(*key_map, "\eO2S", to_key("#f4+s")); table_set(*key_map, "\eO3S", to_key("#f4+a")); table_set(*key_map, "\eO4S", to_key("#f4+A")); table_set(*key_map, "\eO5S", to_key("#f4+c")); table_set(*key_map, "\eO6S", to_key("#f4+C")); table_set(*key_map, "\eO7S", to_key("#f4+x")); table_set(*key_map, "\eO8S", to_key("#f4+X")); // F4 - xterm table_set(*key_map, "\e[1;2S", to_key("#f4+s")); table_set(*key_map, "\e[1;3S", to_key("#f4+a")); table_set(*key_map, "\e[1;4S", to_key("#f4+A")); table_set(*key_map, "\e[1;5S", to_key("#f4+c")); table_set(*key_map, "\e[1;6S", to_key("#f4+C")); table_set(*key_map, "\e[1;7S", to_key("#f4+x")); table_set(*key_map, "\e[1;8S", to_key("#f4+X")); // F4 - kitty table_set(*key_map, "\e[1;9S", to_key("#f4+m")); // F4 - linux console table_set(*key_map, "\e[[D", to_key("#f4")); table_set(*key_map, "\e[29~", to_key("#f4+s")); // F5 table_set(*key_map, "\e[15~", to_key("#f5")); table_set(*key_map, "\e[15;1~", to_key("#f5+m")); table_set(*key_map, "\e[15;2~", to_key("#f5+s")); table_set(*key_map, "\e[15;3~", to_key("#f5+a")); table_set(*key_map, "\e[15;4~", to_key("#f5+A")); table_set(*key_map, "\e[15;5~", to_key("#f5+c")); table_set(*key_map, "\e[15;6~", to_key("#f5+C")); table_set(*key_map, "\e[15;7~", to_key("#f5+x")); table_set(*key_map, "\e[15;8~", to_key("#f5+X")); // F5 - kitty table_set(*key_map, "\e[15;9~", to_key("#f5+m")); // F5 - linux console table_set(*key_map, "\e[[E", to_key("#f5")); table_set(*key_map, "\e[31~", to_key("#f5+s")); // F6 table_set(*key_map, "\e[17~", to_key("#f6")); table_set(*key_map, "\e[17;1~", to_key("#f6+m")); table_set(*key_map, "\e[17;2~", to_key("#f6+s")); table_set(*key_map, "\e[17;3~", to_key("#f6+a")); table_set(*key_map, "\e[17;4~", to_key("#f6+A")); table_set(*key_map, "\e[17;5~", to_key("#f6+c")); table_set(*key_map, "\e[17;6~", to_key("#f6+C")); table_set(*key_map, "\e[17;7~", to_key("#f6+x")); table_set(*key_map, "\e[17;8~", to_key("#f6+X")); // F6 - kitty table_set(*key_map, "\e[17;9~", to_key("#f6+m")); // F6 - linux console table_set(*key_map, "\e[32~", to_key("#f6+s")); // F7 table_set(*key_map, "\e[18~", to_key("#f7")); table_set(*key_map, "\e[18;1~", to_key("#f7+m")); table_set(*key_map, "\e[18;2~", to_key("#f7+s")); table_set(*key_map, "\e[18;3~", to_key("#f7+a")); table_set(*key_map, "\e[18;4~", to_key("#f7+A")); table_set(*key_map, "\e[18;5~", to_key("#f7+c")); table_set(*key_map, "\e[18;6~", to_key("#f7+C")); table_set(*key_map, "\e[18;7~", to_key("#f7+x")); table_set(*key_map, "\e[18;8~", to_key("#f7+X")); // F7 - kitty table_set(*key_map, "\e[18;9~", to_key("#f7+m")); // F7 - linux console table_set(*key_map, "\e[33~", to_key("#f7+s")); // F8 table_set(*key_map, "\e[19~", to_key("#f8")); table_set(*key_map, "\e[19;1~", to_key("#f8+m")); table_set(*key_map, "\e[19;2~", to_key("#f8+s")); table_set(*key_map, "\e[19;3~", to_key("#f8+a")); table_set(*key_map, "\e[19;4~", to_key("#f8+A")); table_set(*key_map, "\e[19;5~", to_key("#f8+c")); table_set(*key_map, "\e[19;6~", to_key("#f8+C")); table_set(*key_map, "\e[19;7~", to_key("#f8+x")); table_set(*key_map, "\e[19;8~", to_key("#f8+X")); // F8 - kitty table_set(*key_map, "\e[19;9~", to_key("#f8+m")); // F8 - linux console table_set(*key_map, "\e[34~", to_key("#f8+s")); // F9 table_set(*key_map, "\e[20~", to_key("#f9")); table_set(*key_map, "\e[20;1~", to_key("#f9+m")); table_set(*key_map, "\e[20;2~", to_key("#f9+s")); table_set(*key_map, "\e[20;3~", to_key("#f9+a")); table_set(*key_map, "\e[20;4~", to_key("#f9+A")); table_set(*key_map, "\e[20;5~", to_key("#f9+c")); table_set(*key_map, "\e[20;6~", to_key("#f9+C")); table_set(*key_map, "\e[20;7~", to_key("#f9+x")); table_set(*key_map, "\e[20;8~", to_key("#f9+X")); // F9 - kitty table_set(*key_map, "\e[20;9~", to_key("#f9+m")); // F10 table_set(*key_map, "\e[21~", to_key("#f10")); table_set(*key_map, "\e[21;1~", to_key("#f10+m")); table_set(*key_map, "\e[21;2~", to_key("#f10+s")); table_set(*key_map, "\e[21;3~", to_key("#f10+a")); table_set(*key_map, "\e[21;4~", to_key("#f10+A")); table_set(*key_map, "\e[21;5~", to_key("#f10+c")); table_set(*key_map, "\e[21;6~", to_key("#f10+C")); table_set(*key_map, "\e[21;7~", to_key("#f10+x")); table_set(*key_map, "\e[21;8~", to_key("#f10+X")); // F10 - kitty table_set(*key_map, "\e[21;9~", to_key("#f10+m")); // F11 table_set(*key_map, "\e[23~", to_key("#f11")); table_set(*key_map, "\e[23;1~", to_key("#f11+m")); table_set(*key_map, "\e[23;2~", to_key("#f11+s")); table_set(*key_map, "\e[23;3~", to_key("#f11+a")); table_set(*key_map, "\e[23;4~", to_key("#f11+A")); table_set(*key_map, "\e[23;5~", to_key("#f11+c")); table_set(*key_map, "\e[23;6~", to_key("#f11+C")); table_set(*key_map, "\e[23;7~", to_key("#f11+x")); table_set(*key_map, "\e[23;8~", to_key("#f11+X")); // F11 - kitty table_set(*key_map, "\e[23;9~", to_key("#f11+m")); // F12 table_set(*key_map, "\e[24~", to_key("#f12")); table_set(*key_map, "\e[24;1~", to_key("#f12+m")); table_set(*key_map, "\e[24;2~", to_key("#f12+s")); table_set(*key_map, "\e[24;3~", to_key("#f12+a")); table_set(*key_map, "\e[24;4~", to_key("#f12+A")); table_set(*key_map, "\e[24;5~", to_key("#f12+c")); table_set(*key_map, "\e[24;6~", to_key("#f12+C")); table_set(*key_map, "\e[24;7~", to_key("#f12+x")); table_set(*key_map, "\e[24;8~", to_key("#f12+X")); // F12 - kitty table_set(*key_map, "\e[24;9~", to_key("#f12+m")); } 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; #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(); /* TODO get_key already deals with utf8 codes, but we don't know when we're receiving ANSI escape codes. If initial key is escape and other keys are awaiting in the input buffer, we need to parse them as escaped sequences. See wikiedia* for help on that. Lets use the escape sequences used on windows amd forget all others. Those should be the most used ones; at least they are the cross-platform compatible ones :P * https://en.m.wikipedia.org/wiki/ANSI_escape_code Check this https://devmemo.io/cheatsheets/terminal_escape_code/ */ // 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; should_read_input := false; is_input_available := false; if input_string.count == 0 { should_read_input = true; is_input_available = OS_wait_for_input(timeout_milliseconds); } else if input_string.count < KEY_SIZE { should_read_input = true; is_input_available = OS_wait_for_input(0); } if OS_was_terminal_resized() return xx Keys.Resize; if should_read_input && is_input_available { // 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]; } // Read input into remaining part of buffer. bytes_read := OS_read_input(input_buffer.data + input_string.count, input_buffer.count - input_string.count); input_string.data = input_buffer.data; input_string.count += bytes_read; } if input_string.count == 0 return Keys.None; // Assume we're parsing just a single char. to_parse := input_string; to_parse.count = 1; // Try to parse UTF8 character. if is_utf8_continuation_byte(input_string[0]) { to_parse.count = count_utf8_bytes(input_string[0]); } // Try to parse escape code. if input_string[0] == #char "\e" && input_string.count > 1 { 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!? // WIP HERE // A possible way to solve this is to create a LUT, and then, grow the to_parse.count from 2 to KEY_SIZE and return as soon // as we ding a match on the LUT. // If the LUT is too big... maybe use a hash-table. // TEMPORARY HACK key, success := table_find(*key_map, to_parse); if success { advance(*input_string, to_parse.count); return key; } } advance(*input_string, to_parse.count); return to_key(to_parse); } // 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 { builder: String_Builder; init_string_builder(*builder); while read_loop := true { buffer := get_current_buffer(*builder); buffer_data := get_buffer_data(buffer); previous_count := buffer.count; buffer.count += OS_read_input(buffer_data + buffer.count, buffer.allocated - buffer.count); for previous_count..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 = 0; while read_loop := true { previous_count := buffer.count; buffer.count += OS_read_input(buffer.data + buffer.count, count_limit - buffer.count); if buffer.count == count_limit then break; for previous_count..buffer.count-1 { for t: terminators { if buffer[it] == t then break read_loop; } } OS_wait_for_input(); } return buffer; } } // TODO UNTESTED 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); // 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; setup_key_map(); // TODO This is being called multiple times... please fix me! 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, Commands.CursorNormalMode, Commands.KeypadNumMode); 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(); input_string.data = input_buffer.data; input_string.count = 0; } // 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(); auto_release_temp(); flush_input(); write_string(Commands.QueryWindowSizeInChars); rows, columns: int = ---; if OS_wait_for_input(0) { // 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); // 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 { flush_input(); cursor_row, cursor_column := get_cursor_position(); defer set_cursor_position(cursor_row, cursor_column); set_cursor_position(0xFFFF, 0xFFFF); rows, columns = get_cursor_position(); } return rows, columns; } set_cursor_position :: (row: int, column: int) { assert_is_initialized(); auto_release_temp(); print(Commands.SetCursorPosition, row, column); } get_cursor_position :: () -> row: int, column: int { assert_is_initialized(); auto_release_temp(); flush_input(); write_string(Commands.QueryCursorPosition); // 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); } // 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! }