From d828e742d7702c7e2698db4371012a49dfeb95d5 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 28 Feb 2024 00:03:27 +0000 Subject: Moved custom modules to newly supported local modules folder. --- modules/Integer_Saturating_Arithmetic.jai | 416 +++++++++++++++ modules/TUI/module.jai | 823 ++++++++++++++++++++++++++++++ modules/TUI/unix.jai | 286 +++++++++++ modules/TUI/windows.jai | 406 +++++++++++++++ 4 files changed, 1931 insertions(+) create mode 100644 modules/Integer_Saturating_Arithmetic.jai create mode 100644 modules/TUI/module.jai create mode 100644 modules/TUI/unix.jai create mode 100644 modules/TUI/windows.jai (limited to 'modules') diff --git a/modules/Integer_Saturating_Arithmetic.jai b/modules/Integer_Saturating_Arithmetic.jai new file mode 100644 index 0000000..74643e0 --- /dev/null +++ b/modules/Integer_Saturating_Arithmetic.jai @@ -0,0 +1,416 @@ +// Integer saturating arighmetic (with assembly branch-free procedures for x64 - expecting signed values in two's complement). + +#import "Basic"; +#import "Math"; +#import "String"; + + +INTEGER_ARITHMETIC_TYPES_CHECK :: #string DONE + type_info_x := cast(*Type_Info)Tx; + type_info_y := cast(*Type_Info)Ty; + if type_info_x.type != .INTEGER || type_info_y.type != .INTEGER return false, "Non integers values passed."; + tx := cast(*Type_Info_Integer)type_info_x; + ty := cast(*Type_Info_Integer)type_info_y; + + largest_type := + ifx tx.runtime_size > ty.runtime_size then Tx else + ifx ty.runtime_size > tx.runtime_size then Ty else + ifx tx.signed == ty.signed then Tx else + void; + + // Only allow to add different signedness values if largest type is the signed one (as in JAI). + if tx.signed == ty.signed { + Tx = largest_type; + Ty = largest_type; + Tr = largest_type; + } + else if tx.signed && Tx == largest_type { + Ty = largest_type; + Tr = largest_type; + } + else if ty.signed && Ty == largest_type { + Tx = largest_type; + Tr = largest_type; + } + else return false, "Number signedness mismatch."; + + return true; +DONE + +add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if (y > 0 && x > MAX - y) then return MAX, true; + if (y < 0 && x < MIN - y) then return MIN, true; + + } else { + + #if Tr == u8 { MAX :: U8_MAX; } + #if Tr == u16 { MAX :: U16_MAX; } + #if Tr == u32 { MAX :: U32_MAX; } + #if Tr == u64 { MAX :: U64_MAX; } + + if (x > MAX - y) then return MAX, true; + + } + + return x + y, false; + + } else { + + result: Tr = ---; + saturated: bool = ---; + + + ADD_SIGNED_ASM :: #string DONE + #asm { + mov result, -1; // Pre-set result with signed maximum (set all bits... + shr.SIZE result, 1; // ...then, clear MSB). + bt x, SIGN_BIT; // Test sign bit (affect CF). + adc result, 0; // Overflow signed maximum to signed minimum if CF is set. + + add.SIZE x, y; // Add values (affect OF). + seto saturated; // Set saturated flag if OF. + cmovno result, x; // Move add-result to result if NOT OF. + } + DONE + + #if Tr == s8 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); + #if Tr == s16 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); + #if Tr == s32 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); + #if Tr == s64 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); + + + ADD_UNSIGNED_ASM :: #string DONE + #asm { + mov result, -1; // Pre-set result with unsigned maximum. + add.SIZE x, y; // Add values (affect CF). + setc saturated; // Set saturated flag if CF. + cmovnc result, x; // Move add-result to result if NOT CF. + } + DONE + + #if Tr == u8 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".b"); + #if Tr == u16 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".q"); + + + return result, saturated; + + } +} + +sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if (y < 0 && x > MAX + y) then return MAX, true; + if (y > 0 && x < MIN + y) then return MIN, true; + + } else { + + if (y > x) then return 0, true; + + } + + return x - y, false; + + } else { + + result: Tr = ---; + saturated: bool = ---; + + + SUB_SIGNED_ASM :: #string DONE + #asm { + mov result, -1; // Pre-set result with signed maximum (set all bits... + shr.SIZE result, 1; // ...then, clear MSB). + bt x, SIGN_BIT; // Test signal bit (affect CF). + adc result, 0; // Overflow signed maximum to signed minimum if CF is set. + + sub.SIZE x, y; // Subtract values (affect OF). + seto saturated; // Set saturated flag if OF. + cmovno result, x; // Move subtract-result to result if NOT OF. + } + DONE + + #if Tr == s8 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); + #if Tr == s16 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); + #if Tr == s32 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); + #if Tr == s64 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); + + + SUB_UNSIGNED_ASM :: #string DONE + #asm { + xor result, result; // Pre-set result with usigned minimum (zero). + sub.SIZE x, y; // Subtract values (affect CF). + setc saturated; // Set saturated flag if CF. + cmovnc result, x; // Move subtract-result to result if NOT CF. + } + DONE + + #if Tr == u8 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".b"); + #if Tr == u16 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".q"); + + + return result, saturated; + + } + +} + +mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if x == 0 || y == 0 then return 0, false; + if x > 0 && y > 0 && x > MAX / y then return MAX, true; + if x < 0 && y < 0 && x < MAX / y then return MAX, true; + if (y < 0 && x > 0 && y < MIN / x) || (x < 0 && y > 0 && x < MIN / y) then return MIN, true; + + } else { + + #if Tr == u8 { MAX :: U8_MAX; } + #if Tr == u16 { MAX :: U16_MAX; } + #if Tr == u32 { MAX :: U32_MAX; } + #if Tr == u64 { MAX :: U64_MAX; } + + if x == 0 || y == 0 then return 0, false; + if x > MAX / y then return MAX, true; + + } + + return x * y, false; + + } else { + + result: Tr = ---; + saturated: bool = ---; + + MUL_SIGNED_ASM :: #string DONE + #asm { + // Using two copies of the x value (x_, sign) seems to be a bit faster (not sure why). + mov x_: gpr === a, x; // Pin copy of x value to register A. + + mov result, -1; // Pre-set result with signed maximum (set all bits... + shr.SIZE result, 1; // ...then, clear MSB). + mov sign:, x; // Use copy of x value. + xor sign, y; // Calculate result signal bit using xor. + bt sign, SIGN_BIT; // Test signal bit (affect CF). + adc result, 0; // Overflow signed maximum to signed minimum if CF is set. + + imul.SIZE x_, y; // Multiply values (affect OF). + seto saturated; // Set saturated flag if OF. + cmovno result, x_; // Move multiply-result to result if NOT OF. + } + DONE + + #if Tr == s8 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); + #if Tr == s16 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); + #if Tr == s32 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); + #if Tr == s64 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); + + + MUL_UNSIGNED_ASM :: #string DONE + #asm { + result === a; // Pin result to register A. + + mov result, x; // Move value x to result. + mul.SIZE reg_d:, result, y; // Multiply values (affect CF). + setc saturated; // Set saturated flag if CF. + sbb mask:, mask; // If CF: mask = -1 (all bits set); else: mask = 0. + or result, mask; // If CF was set, then result will be set to unsigned maximum (all bits set). + } + DONE + + #if Tr == u8 + #insert #run replace(replace(MUL_UNSIGNED_ASM, ".SIZE", ".b"), "reg_d:,", ""); // For 8bits mul, we do not need D register. + #if Tr == u16 + #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".q"); + + + return result, saturated; + + } +} + +div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if x == MIN && y == -1 then return MAX, -1, true; + + } + + result := x / y; + remainder := x - (y * result); + return result, remainder, false; + + } else { + + result: Tr = ---; + remainder: Tr = ---; + saturated: bool = ---; + + DIV_SIGNED_ASM :: #string DONE + #asm { + result === a; // Pin result to register A (to be used as dividend on idiv). + remainder === d; // Pin remainder to register D. + + xor saturated, saturated; // Clear saturated. + + // Detect div(MIN/-1) and flag it on ZF. + mov t_dividend:, -1; // Pre-set t_dividend with signed minimum (set all bits... + shr.SIZE t_dividend, 1; // ...then, clear MSB... + not t_dividend; // ...then, negate to obtain MSB set and all other bits cleared). + // + mov limit:, t_dividend; // Keep copy of signed minimum on limit. + add limit, 1; // Set limit as signed minimum + 1. + // + xor.SIZE t_dividend, x; // Clear dividend if x value is equal to signed minimum. + // + mov t_divisor:, -1; // Pre-set test_divisor with -1. + xor.SIZE t_divisor, y; // Clear test_divisor if y value is equal to -1. + // + or.SIZE t_dividend, t_divisor; // Or t_dividend with t_divisor (affect ZF). + + setz saturated; // Set saturated flag if ZF. + mov result, x; // Copy x value to result (dividend). + cmovz result, limit; // If ZF: copy limit (signed minimum + 1) to result (dividend). + + DIVIDE_PLACEHOLDER + + sub.SIZE remainder, saturated; // If saturated: remainder = 0 - 1; otherwise: remainder = x - 0. + } + DONE + + DIV_SIGNED_CALC_8BITS :: #string DONE + cbw result; // Prepare dividend high bits (sign-extend). + idiv.SIZE result, y; // Divide values. + mov remainder, result; // Extract remainder from result's high bits. + sar remainder, 8; // Shift remainder from high to low bits. + DONE + + DIV_SIGNED_CALC_16BITS :: #string DONE + cwd remainder, result; // Prepare dividend high bits (sign-extend). + idiv.SIZE remainder, result, y; // Divide values. + DONE + + DIV_SIGNED_CALC_32BITS :: #string DONE + cdq remainder, result; // Prepare dividend high bits (sign-extend). + idiv.SIZE remainder, result, y; // Divide values. + DONE + + DIV_SIGNED_CALC_64BITS :: #string DONE + cqo remainder, result; // Prepare dividend high bits (sign-extend). + idiv.SIZE remainder, result, y; // Divide values. + DONE + + #if Tr == s8 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_8BITS), ".SIZE", ".b"); + #if Tr == s16 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_16BITS), ".SIZE", ".w"); + #if Tr == s32 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_32BITS), ".SIZE", ".d"); + #if Tr == s64 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_64BITS), ".SIZE", ".q"); + + + DIV_UNSIGNED_ASM :: #string DONE + #asm { + result === a; // Pin result to register A. + remainder === d; // Pin remainder to register D. + + xor result, result; // Clear result. + xor remainder, remainder; // Clear remainder (required when used as dividend's high bits). + xor saturated, saturated; // Clear saturated (unsigned division never saturates). + mov result, x; // Copy x value to result. + + DIVIDE_PLACEHOLDER + } + DONE + + DIV_UNSIGNED_CALC_8BITS :: #string DONE + div.SIZE result, y; // Divide values. + mov remainder, result; // Extract remainder from result's high bits. + sar remainder, 8; // Shift remainder from high to low bits. + DONE + + DIV_UNSIGNED_CALC :: #string DONE + div.SIZE remainder, result, y; // Divide values. + DONE + + #if Tr == u8 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC_8BITS), ".SIZE", ".b"); + #if Tr == u16 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".q"); + + + return result, remainder, saturated; + + } +} diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai new file mode 100644 index 0000000..d986eaf --- /dev/null +++ b/modules/TUI/module.jai @@ -0,0 +1,823 @@ +// 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"; + +// 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; + +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"; + + 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~"); + + /* TODO On get_key, convert F1 to F4 into the format "\e[1X~" + so that: + F1 : 1b 4f 50 : ^OP : -> \e[11~ + Shift+ F1 : 1b 4f 32 50 : ^O2P : -> \e[11;2~ + F2 : 1b 4f 51 : ^OQ : -> \e[12~ + Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ + F3 : 1b 4f 52 : ^OR : -> \e[13~ + Shift+ F3 : 1b 4f 32 52 : ^O2R : -> \e[13;2~ + F4 : 1b 4f 53 : ^OS : -> \e[14~ + Meta+ Fx : 1b 4f 31 53 : ^O1S : -> \e[14;1~ + Shift+ F4 : 1b 4f 32 53 : ^O2S : -> \e[14;2~ + Alt+ F4 : 1b 4f 33 53 : ^O3S : -> \e[14;3~ + S+A F4 : 1b 4f 34 53 : ^O4S : -> \e[14;4~ + Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ + Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ + ... + */ + + // WIP HERE + + 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("\e[1;1P"); + MetaF2 : Key : #run to_key("\e[1;1Q"); + MetaF3 : Key : #run to_key("\e[1;1R"); + MetaF4 : Key : #run to_key("\e[1;1S"); + 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("\e[1;2P"); + ShiftF2 : Key : #run to_key("\e[1;2Q"); + ShiftF3 : Key : #run to_key("\e[1;2R"); + ShiftF4 : Key : #run to_key("\e[1;2S"); + 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("\e[1;3P"); + AltF2 : Key : #run to_key("\e[1;3Q"); + AltF3 : Key : #run to_key("\e[1;3R"); + AltF4 : Key : #run to_key("\e[1;3S"); + 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("\e[1;4P"); + AltShiftF2 : Key : #run to_key("\e[1;4Q"); + AltShiftF3 : Key : #run to_key("\e[1;4R"); + AltShiftF4 : Key : #run to_key("\e[1;4S"); + 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("\e[1;5P"); + CtrlF2 : Key : #run to_key("\e[1;5Q"); + CtrlF3 : Key : #run to_key("\e[1;5R"); + CtrlF4 : Key : #run to_key("\e[1;5S"); + 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("\e[1;6P"); + CtrlShiftF2 : Key : #run to_key("\e[1;6Q"); + CtrlShiftF3 : Key : #run to_key("\e[1;6R"); + CtrlShiftF4 : Key : #run to_key("\e[1;6S"); + 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("\e[1;7P"); + CtrlAltF2 : Key : #run to_key("\e[1;7Q"); + CtrlAltF3 : Key : #run to_key("\e[1;7R"); + CtrlAltF4 : Key : #run to_key("\e[1;7S"); + 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("\e[1;8P"); + CtrlAltShiftF2 : Key : #run to_key("\e[1;8Q"); + CtrlAltShiftF3 : Key : #run to_key("\e[1;8R"); + CtrlAltShiftF4 : Key : #run to_key("\e[1;8S"); + 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; +} + +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 + { + utf8_bytes := count_utf8_bytes(input_string[0]); + 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; + } + + // TODO try_parse_escape_code + // { + // assert(false, "TODO try_parse_escape_code"); + // } + + return xx Keys.None; +} + +// 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; + + 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! +} diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai new file mode 100644 index 0000000..861fe11 --- /dev/null +++ b/modules/TUI/unix.jai @@ -0,0 +1,286 @@ +#scope_file + +#import "Atomics"; +#import "System"; +#import "POSIX"; + + // Required to do unlocking input. + libc :: #system_library "libc"; + + // TODO Remote this. + // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; + /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 + void + cfmakeraw (struct termios *t) + { + t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + t->c_oflag &= ~OPOST; + t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + t->c_cflag &= ~(CSIZE|PARENB); + t->c_cflag |= CS8; + t->c_cc[VMIN] = 1; // read returns when one char is available. + t->c_cc[VTIME] = 0; + } + */ + + // 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; + tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { + // TODO IMPLEMENT ME + + #if OS == .LINUX { + TCSETS :: 0x5402; + TCSETSW :: 0x5403; + TCSETSF :: 0x5404; + tcflag_t :: u32; + cc_t :: u8; + __KERNEL_NCCS :: 19; + __kernel_termios :: struct { + c_iflag : tcflag_t; // input mode flags + c_oflag : tcflag_t; // output mode flags + c_cflag : tcflag_t; // control mode flags + c_lflag : tcflag_t; // local mode flags + c_line : cc_t; // line discipline + c_cc : [__KERNEL_NCCS]cc_t; // control characters + }; + + k_termios: __kernel_termios; + cmd: u64; + if optional_actions == { + case xx SetAttributesActions.TCSANOW; + cmd = TCSETS; + + case xx SetAttributesActions.TCSADRAIN; + cmd = TCSETSW; + + case xx SetAttributesActions.TCSAFLUSH; + cmd = TCSETSF; + + case; + return EINVAL; + } + // k_termios.c_iflag = termios_p.c_iflag & ~IBAUD0; + k_termios.c_iflag = xx termios_p.c_iflag; + k_termios.c_oflag = xx termios_p.c_oflag; + k_termios.c_cflag = xx termios_p.c_cflag; + k_termios.c_lflag = xx termios_p.c_lflag; + k_termios.c_line = xx termios_p.c_line; + // #if _HAVE_C_ISPEED && _HAVE_STRUCT_TERMIOS_C_ISPEED + // k_termios.c_ispeed = termios_p->c_ispeed; + // #endif + // #if _HAVE_C_OSPEED && _HAVE_STRUCT_TERMIOS_C_OSPEED + // k_termios.c_ospeed = termios_p->c_ospeed; + // #endif + memcpy(*k_termios.c_cc[0], *termios_p.c_cc[0], __KERNEL_NCCS * 1);//size_of(cc_t)); + return ioctl(fd, cmd, *k_termios); + } + #if OS == .MACOS { + // return __ioctl (fd, TIOCSETAF, termios_p); + #assert(false, "NOT IMPLEMENTED"); + } + return 0; + } + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html + tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; + // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { + // TODO IMPLEMENT ME + // } + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html + // tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; + tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { + TCFLSH :: 0x540B; + return ioctl(fd, TCFLSH, queue_selector); + } + + // Information for the termios.h enums is platform dependent and was retrieved from: + // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h + + // Set Attributes Actions. + SetAttributesActions :: enum u32 { + TCSANOW :: 0; // Change immediately. + TCSADRAIN :: 1; // Change when pending output is written. + TCSAFLUSH :: 2; // Flush pending input before changing. + } + + // Input modes. + Input_Modes :: enum_flags u32 { + IGNBRK :: 0000001; // Ignore break condition. + BRKINT :: 0000002; // Signal interrupt on break. + IGNPAR :: 0000004; // Ignore characters with parity errors. + PARMRK :: 0000010; // Mark parity and framing errors. + INPCK :: 0000020; // Enable input parity check. + ISTRIP :: 0000040; // Strip 8th bit off characters. + INLCR :: 0000100; // Map NL to CR on input. + IGNCR :: 0000200; // Ignore CR. + ICRNL :: 0000400; // Map CR to NL on input. + IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). + IXON :: 0002000; // Enable start/stop output control. + IXANY :: 0004000; // Any character will restart after stop. + IXOFF :: 0010000; // Enable start/stop input control. + IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). + IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). + } + + // Output modes. + Output_Modes :: enum_flags u32 { + OPOST :: 0000001; // Perform output processing. + OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). + ONLCR :: 0000004; // Map NL to CR-NL on output. + OCRNL :: 0000010; // Map CR to NL. + ONOCR :: 0000020; // Discard CR's when on column 0. + ONLRET :: 0000040; // Move to column 0 on NL. + OFILL :: 0000100; // Send fill characters for delays. + OFDEL :: 0000200; // Fill is DEL. + VTDLY :: 0040000; // Select vertical-tab delays: + VT0 :: 0000000; // Vertical-tab delay type 0. + VT1 :: 0040000; // Vertical-tab delay type 1. + } + + // Control modes. + Control_Modes :: enum u32 { + CS5 :: 0000000; // 5 bits per byte. + CS6 :: 0000020; // 6 bits per byte. + CS7 :: 0000040; // 7 bits per byte. + CS8 :: 0000060; // 8 bits per byte. + CSIZE :: 0000060; // Number of bits per byte (mask). + CSTOPB :: 0000100; // Two stop bits instead of one. + CREAD :: 0000200; // Enable receiver. + PARENB :: 0000400; // Parity enable. + PARODD :: 0001000; // Odd parity instead of even. + HUPCL :: 0002000; // Hang up on last close. + CLOCAL :: 0004000; + } + + // Local modes. + Local_Modes :: enum_flags u32 { + ISIG :: 0000001; // Enable signals. + ICANON :: 0000002; // Do erase and kill processing. + ECHO :: 0000010; // Enable echo. + ECHOE :: 0000020; // Visual erase for ERASE. + ECHOK :: 0000040; // Echo NL after KILL. + ECHONL :: 0000100; // Echo NL even if ECHO is off. + NOFLSH :: 0000200; // Disable flush after interrupt. + TOSTOP :: 0000400; // Send SIGTTOU for background output. + IEXTEN :: 0100000; // Enable DISCARD and LNEXT. + } + + // Control Characters + Control_Chars :: enum u8 { + VINTR :: 0; + VQUIT :: 1; + VERASE :: 2; + VKILL :: 3; + VEOF :: 4; + VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. + VSWTC :: 7; + VSTART :: 8; + VSTOP :: 9; + VSUSP :: 10; + VEOL :: 11; + VREPRINT :: 12; + VDISCARD :: 13; + VWERASE :: 14; + VLNEXT :: 15; + VEOL2 :: 16; + } + + // The struct termios. + Terminal_IO_Mode :: struct { + c_iflag : Input_Modes; // Input mode flags. + c_oflag : Output_Modes; // Output mode flags. + c_cflag : Control_Modes; // Control modes flags. + c_lflag : Local_Modes; // Local modes flags. + c_line : u8; // Line discipline. + c_cc : [32]Control_Chars;// Control characters. + c_ispeed : u32; // Input speed (baud rates). + c_ospeed : u32; // Output speed (baud rates). + } + + initial_tio_mode: Terminal_IO_Mode; + raw_tio_mode: Terminal_IO_Mode; + +//////////////////////////////////////////////////////////////////////////////// +// Resize detection +was_resized : bool; + +resize_handler :: (signal_code : s32) #c_call { + new_context : Context; + push_context new_context { + if signal_code != SIGWINCH then return; + atomic_swap(*was_resized, true); + } +} + +prepare_resize_handler :: () { + sa : sigaction_t; + sa.sa_handler = resize_handler; + sigemptyset(*(sa.sa_mask)); + sa.sa_flags = SA_RESTART; + sigaction(SIGWINCH, *sa, null); +} + +restore_resize_handler :: () { + sa : sigaction_t; + sa.sa_handler = SIG_DFL; + sigaction(SIGWINCH, null, *sa); +} + +//////////////////////////////////////////////////////////////////////////////// + +#scope_export + +OS_prepare_terminal :: () { + tcgetattr(STDIN_FILENO, *initial_tio_mode); // TODO Log error using `log()` from jai/modules/Basic/Print.jai ? + raw_tio_mode = initial_tio_mode; + raw_tio_mode.c_iflag &= ~(.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON); + raw_tio_mode.c_oflag &= ~(.OPOST); + raw_tio_mode.c_lflag &= ~(.ECHO | .ECHONL | .ICANON | .ISIG | .IEXTEN); + raw_tio_mode.c_cflag &= ~(.CSIZE | .PARENB); + raw_tio_mode.c_cflag |= .CS8; + raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; + raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; + tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); // TODO Log on error. + + was_resized = false; + prepare_resize_handler(); +} + +OS_reset_terminal :: () { + restore_resize_handler(); + tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // TODO Log on error. +} + +OS_flush_input :: inline () { + TCIFLUSH :: 0; // TODO Is this always zero in all systems? + 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 { + error_code, error_message := get_error_value_and_string(); + return -1, true, error_message; + } + return bytes_read; +} + +// timeout_milliseconds +// 0: do not wait +// -1: wait indefinitely +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. + error_code, error_message := get_error_value_and_string(); // FIXME Not used. + return ifx poll_return > 0 then true else false; +} + +// TODO This procedure hides the behaviour of reseting on read. +// We should have the `was_resized` on module.jai so that we know it may be used in another thread. +OS_was_terminal_resized :: () -> bool { + return atomic_swap(*was_resized, false); // TODO If the windows implementation is similar, we may push this into the main module file. +} diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai new file mode 100644 index 0000000..f79a5cf --- /dev/null +++ b/modules/TUI/windows.jai @@ -0,0 +1,406 @@ +#scope_file + +#import "Basic"; +#import "Atomics"; +#import "System"; +#import "Windows"; + + // https://learn.microsoft.com/windows/win32/winprog/windows-data-types + LPVOID :: *void; + BOOL :: bool; + CHAR :: s8; + WCHAR :: s16; + SHORT :: s16; + USHORT :: u16; + WORD :: u16; + DWORD :: s32; + LPDWORD :: *s32; + UINT :: u32; + + + PINPUT_RECORD :: *INPUT_RECORD; + +// https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences +// https://learn.microsoft.com/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"; + + // TODO Cleanup unused foreign procedures. + + // https://learn.microsoft.com/windows/console/getconsolescreenbufferinfo + GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> bool #foreign kernel32; + + // https://learn.microsoft.com/en-us/windows/console/readconsoleinput + ReadConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/readconsole + ReadConsoleA :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfCharsToRead: DWORD, lpNumberOfCharsRead: LPVOID, pInputControl := LPVOID) -> success: bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/getconsolemode + GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *DWORD) -> BOOL #foreign kernel32; + + // https://learn.microsoft.com/windows/console/setconsolemode + SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL #foreign kernel32; + + // https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + GetLastError :: () -> s32 #foreign kernel32; + + // https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject + WaitForSingleObject :: (hHandle: HANDLE, dwMilliseconds: DWORD) -> s32 #foreign kernel32; + + // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer + FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents + GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; + + // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput + PeekConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + + // https://learn.microsoft.com/en-us/windows/console/setconsolemode + // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes + Console_Input_Mode :: enum_flags u32 { + ENABLE_PROCESSED_INPUT; // If enable, control keys (Ctrl+C, Backspace, ...) are processed by the system. + ENABLE_LINE_INPUT; // If enable, ReadFile or ReadConsole function return on CR; otherwise they return when one or more characters are available. + ENABLE_ECHO_INPUT; // Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. + ENABLE_WINDOW_INPUT; + ENABLE_MOUSE_INPUT; // + ENABLE_INSERT_MODE; // If enabled, text entered will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten. + _UNUSED_0040_; + _UNUSED_0080_; + _UNUSED_0100_; + ENABLE_VIRTUAL_TERMINAL_INPUT; // + _UNUSED_0400_; + _UNUSED_0800_; + } + + // https://learn.microsoft.com/en-us/windows/console/setconsolemode + // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes + Console_Output_Mode :: enum_flags u32 { + ENABLE_PROCESSED_OUTPUT; // + ENABLE_WRAP_AT_EOL_OUTPUT; // + ENABLE_VIRTUAL_TERMINAL_PROCESSING; // + DISABLE_NEWLINE_AUTO_RETURN; // + ENABLE_LVB_GRID_WORLDWIDE; // + _UNUSED_0020_; + _UNUSED_0040_; + _UNUSED_0080_; + } + + COORD :: struct { + X : SHORT; + Y : SHORT; + } + + SMALL_RECT :: struct { + Left : SHORT; + Top : SHORT; + Right : SHORT; + Bottom : SHORT; + } + + CONSOLE_SCREEN_BUFFER_INFO :: struct { + dwSize : COORD; + dwCursorPosition : COORD; + wAttributes : WORD; + srWindow : SMALL_RECT; + dwMaximumWindowSize : COORD; + } + + INPUT_RECORD_EVENT_TYPE :: enum u16 { + KEY_EVENT :: 0x0001; + MOUSE_EVENT :: 0x0002; + WINDOW_BUFFER_SIZE_EVENT :: 0x0004; + MENU_EVENT :: 0x0008; + FOCUS_EVENT :: 0x0010; + } + + INPUT_RECORD :: struct { + EventType : INPUT_RECORD_EVENT_TYPE; + union { + KeyEvent : KEY_EVENT_RECORD; + MouseEvent : MOUSE_EVENT_RECORD; + WindowBufferSizeEvent : WINDOW_BUFFER_SIZE_RECORD; + // These events are used internally and should be ignored. + MenuEvent : MENU_EVENT_RECORD; + FocusEvent : FOCUS_EVENT_RECORD; + } + } + + KEY_EVENT_RECORD :: struct { + bKeyDown : BOOL; + wRepeatCount : WORD #align 4; + wVirtualKeyCode : WORD; + wVirtualScanCode : WORD; + union { + UnicodeChar : WCHAR; + AsciiChar : CHAR; + } + dwControlKeyState : DWORD; + } + + MOUSE_EVENT_RECORD :: struct { + dwMousePosition : COORD; + dwButtonState : DWORD; + dwControlKeyState : DWORD; + dwEventFlags : DWORD; + } + + WINDOW_BUFFER_SIZE_RECORD :: struct { + dwSize : COORD; + } + + MENU_EVENT_RECORD :: struct { + dwCommandId : UINT; + } + + FOCUS_EVENT_RECORD :: struct { + bSetFocus : BOOL; + } + + stdin: HANDLE; + initial_stdin_mode: u32; + raw_stdin_mode: Console_Input_Mode; + + stdout: HANDLE; + initial_stdout_mode: u32; + raw_stdout_mode: Console_Output_Mode; + + +//////////////////////////////////////////////////////////////////////////////// +// Resize detection +was_resized : bool; + +resize_handler :: (signal_code : s32) #c_call { + /* TODO + Try to implement using: + https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events + https://learn.microsoft.com/en-us/windows/console/readconsoleinput + https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents + */ + new_context : Context; + push_context new_context { + if signal_code != SIGWINCH then return; + atomic_swap(*was_resized, true); + } +} + +prepare_resize_handler :: () { + /* TODO + sa : sigaction_t; + sa.sa_handler = resize_handler; + sigemptyset(*(sa.sa_mask)); + sa.sa_flags = SA_RESTART; + sigaction(SIGWINCH, *sa, null); + */ +} + +restore_resize_handler :: () { + /* TODO + sa : sigaction_t; + sa.sa_handler = SIG_DFL; + sigaction(SIGWINCH, null, *sa); + */ +} + +peek_input :: inline () -> INPUT_RECORD { + record: INPUT_RECORD; + records_read: s32; + if PeekConsoleInputA(stdin, *record, 1, *records_read) == false { + _, error_message := get_error_value_and_string(); + assert(false, error_message); + } + return record; +} + +read_input :: inline () -> INPUT_RECORD { + record: INPUT_RECORD; + records_read: s32; + if ReadConsoleInputA(stdin, *record, 1, *records_read) == false { + _, error_message := get_error_value_and_string(); + assert(false, error_message); + } + return record; +} + +count_input :: inline () -> s32 { + count: s32; + if GetNumberOfConsoleInputEvents(stdin, *count) == false { + _, error_message := get_error_value_and_string(); + assert(false, error_message); + } + return count; +} + + +//////////////////////////////////////////////////////////////////////////////// + +#scope_export + +OS_prepare_terminal :: () { + + // stdin + stdin = GetStdHandle(STD_INPUT_HANDLE); + if stdin == INVALID_HANDLE_VALUE { + print("Invalid input handler.", to_standard_error = true); + return; + } + if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { + print("Failed to get input mode.", to_standard_error = true); + return; + } + raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); + raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT); + raw_stdin_mode &= ~(.ENABLE_LINE_INPUT | .ENABLE_PROCESSED_INPUT | .ENABLE_ECHO_INPUT); + + if SetConsoleMode(stdin, xx raw_stdin_mode) == false { + print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); + return; + } + + // stdout + stdout = GetStdHandle(STD_OUTPUT_HANDLE); + if stdout == INVALID_HANDLE_VALUE { + print("Invalid output handler.", to_standard_error = true); + return; + } + if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { + print("Failed to get output mode.", to_standard_error = true); + return; + } + raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); + raw_stdout_mode |= (.ENABLE_VIRTUAL_TERMINAL_PROCESSING | .ENABLE_PROCESSED_OUTPUT | .ENABLE_WRAP_AT_EOL_OUTPUT); + + if SetConsoleMode(stdout, xx raw_stdout_mode) == false { + print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); + return; + } +} + +OS_reset_terminal :: () { + if xx SetConsoleMode(stdin, initial_stdin_mode) == false { + print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); + return; + } + if xx SetConsoleMode(stdout, initial_stdout_mode) == false { + print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); + return; + } +} + +OS_flush_input :: inline () { + /* NOTE + This API is not recommended and does not have a virtual terminal equivalent. + Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. + */ + success := FlushConsoleInputBuffer(stdin); + if success == false { + _, error_message := get_error_value_and_string(); + assert(false, error_message); // TODO A bit harsh arent we? + } +} + +OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" { + + assert(bytes_to_read <= 0x7fff_ffff, "The Windows API only allows to read up to s32 bytes from the standard input."); + + bytes_read: s32 = 0; + available_inputs := count_input(); + + while bytes_to_read > 0 && available_inputs > 0 { + record := read_input(); + + if record.EventType == .WINDOW_BUFFER_SIZE_EVENT { + was_resized = true; + } + + if record.EventType == .KEY_EVENT && record.KeyEvent.bKeyDown == true { + buffer[bytes_read] = xx record.KeyEvent.AsciiChar; + bytes_to_read -= 1; + bytes_read += 1; + } + available_inputs -= 1; + } + return bytes_read; +} + +// timeout_milliseconds +// 0: do not wait +// -1: wait indefinitely +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { + + /* TODO + Add a good comment explaining how the windows part of the module was implemented... what's the idea behind it. + Something like, Since windows provides a single input buffer with all events, we need to peek through them and + discard the ones that are of no use for us. + Because it's a single buffer, all functions need to do repeated work (see if it's resize, see if it's a key press) + ... and so on. + */ + + expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); + + // Possible values for poll_return TODO NOT BEING USED + WAIT_ABANDONED :: 0x00000080; // Mutex stuff. + WAIT_OBJECT_0 :: 0x00000000; // Detected input. + WAIT_TIMEOUT :: 0x00000102; // Reached timeout. + WAIT_FAILED :: 0xFFFFFFFF; // Something went wrong. + + while true { + poll_return := WaitForSingleObject(stdin, timeout_milliseconds); + + // TODO Weird looking code... + if poll_return == { + case WAIT_TIMEOUT; + return false; + case WAIT_ABANDONED; + print("MUTEX STUFF"); // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject + return false; + case WAIT_FAILED; + _, error_message := get_error_value_and_string(); + assert(false, error_message); + } + + // Discard invalid input events. + count := count_input(); + while count > 0 { + record := peek_input(); + + if record.EventType == .WINDOW_BUFFER_SIZE_EVENT { + // Discard any additional resize event. + while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { + was_resized = true; + read_input(); + } + return false; + } + + if record.EventType == .KEY_EVENT && record.KeyEvent.bKeyDown == true { + return true; + } + + read_input(); + count -= 1; + } + + // When waiting indefinitely... just continue. + if timeout_milliseconds < 0 then continue; + + // Either break due to timeout, or update the remaining timeout. + now := current_time_monotonic(); + if now >= expiration then return false; + timeout_milliseconds = xx to_milliseconds(expiration - now); + } + + return false; +} + +OS_was_terminal_resized :: () -> bool { + while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { + was_resized = true; + read_input(); + } + + defer was_resized = false; + return was_resized; +} -- cgit v1.2.3 From c181df188164853845441c7798e8aa45f843ee65 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 28 Feb 2024 09:21:18 +0000 Subject: WIP --- modules/TUI/module.jai | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d986eaf..2f32752 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -234,17 +234,17 @@ Keys :: struct #type_info_none { Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ F3 : 1b 4f 52 : ^OR : -> \e[13~ Shift+ F3 : 1b 4f 32 52 : ^O2R : -> \e[13;2~ + F4 : 1b 4f 53 : ^OS : -> \e[14~ - Meta+ Fx : 1b 4f 31 53 : ^O1S : -> \e[14;1~ + Meta+ F4 : 1b 4f 31 53 : ^O1S : -> \e[14;1~ Shift+ F4 : 1b 4f 32 53 : ^O2S : -> \e[14;2~ Alt+ F4 : 1b 4f 33 53 : ^O3S : -> \e[14;3~ S+A F4 : 1b 4f 34 53 : ^O4S : -> \e[14;4~ Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ - Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ ... */ - // WIP HERE + WIP HERE F1 : Key : #run to_key("\eOP"); F2 : Key : #run to_key("\eOQ"); -- cgit v1.2.3 From ce40906a177708e54ea1067ca157a308a185a164 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 29 Feb 2024 01:08:40 +0000 Subject: WIP : Improving get_key to deal with escape codes. --- modules/TUI/module.jai | 56 ++++++++++++++++++++++++++++++++------------------ ttt.jai | 4 ++-- 2 files changed, 38 insertions(+), 22 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 2f32752..3008d23 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -244,8 +244,6 @@ Keys :: struct #type_info_none { ... */ - WIP HERE - F1 : Key : #run to_key("\eOP"); F2 : Key : #run to_key("\eOQ"); F3 : Key : #run to_key("\eOR"); @@ -477,29 +475,47 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { input_string.count += bytes_read; } - if input_string.count > 0 - { - utf8_bytes := count_utf8_bytes(input_string[0]); - to_parse := input_string; - to_parse.count = utf8_bytes; + if input_string.count == 0 return Keys.None; - // 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!? - } + // Assume we're parsing just a single char. + to_parse := input_string; + to_parse.count = 1; - key := to_key(to_parse); - advance(*input_string, to_parse.count); - return key; + // Try to parse UTF8 character. + if is_utf8_continuation_byte(input_string[0]) { + to_parse.count = count_utf8_bytes(input_string[0]); } - // TODO try_parse_escape_code - // { - // assert(false, "TODO try_parse_escape_code"); - // } + // 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. + + if compare(to_parse, "\e[A") == 0 { + advance(*input_string, to_parse.count); + return to_key("#UP"); + } + else if compare(to_parse, "\e[B") == 0 { + advance(*input_string, to_parse.count); + return to_key("#DOWN"); + } + else if compare(to_parse, "\e[C") == 0 { + advance(*input_string, to_parse.count); + return to_key("#RIGHT"); + } + else if compare(to_parse, "\e[D") == 0 { + advance(*input_string, to_parse.count); + return to_key("#LEFT"); + } + } - return xx Keys.None; + advance(*input_string, to_parse.count); + return to_key(to_parse); } // TODO Review me! diff --git a/ttt.jai b/ttt.jai index 7c21a1f..194bd06 100644 --- a/ttt.jai +++ b/ttt.jai @@ -14,8 +14,8 @@ // this program. If not, see . // Compilation commands: -// - release : jai ttt.jai -import_dir . -quiet -x64 -release -// - debug : jai ttt.jai -import_dir . -quiet -x64 +// - release : jai ttt.jai -quiet -x64 -release +// - debug : jai ttt.jai -quiet -x64 #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. #import "System"; -- cgit v1.2.3 From 45e56e387b713cebd78c3789ed7c234e588fbe48 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 1 Mar 2024 02:08:36 +0000 Subject: WIP : First attempt to use hash table on escape codes. --- modules/TUI/module.jai | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- ttt.jai | 4 ++-- 2 files changed, 58 insertions(+), 3 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3008d23..ed3f517 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -13,6 +13,7 @@ #import "Basic"; #import "String"; #import "Thread"; +#import "Hash_Table"; // Special Graphics Characters Drawings :: struct { @@ -362,6 +363,52 @@ Keys :: struct #type_info_none { CtrlAltShiftF12 : Key : #run to_key("\e[24;8~"); } +key_map: Table(string, Key); + +setup_key_map :: () { + + table_set(*key_map, "\e[A", to_key("#UP")); + table_set(*key_map, "\e[A", to_key("#UP")); + + table_set(*key_map, "\eOQ", to_key("#F2")); + table_set(*key_map, "\e[12~", to_key("#F2")); + + table_set(*key_map, "\eO2Q", to_key("#SF2")); + table_set(*key_map, "\e[1;2Q", to_key("#SF2")); + table_set(*key_map, "\e[12;2~", to_key("#SF2")); + + table_set(*key_map, "\e\e[12~", to_key("#AF2")); + table_set(*key_map, "\e\e[24~", to_key("#AF12")); + + table_set(*key_map, "\e[12^", to_key("#CF2")); + + // A good example + table_set(*key_map, "\e[21~", to_key("#F10")); + table_set(*key_map, "\e[21;1~", to_key("#MF10")); + table_set(*key_map, "\e[21;2~", to_key("#SF10")); + table_set(*key_map, "\e[21;3~", to_key("#AF10")); + table_set(*key_map, "\e[21;4~", to_key("#SAF10")); + table_set(*key_map, "\e[21;5~", to_key("#CF10")); + table_set(*key_map, "\e[21;6~", to_key("#SCF10")); + table_set(*key_map, "\e[21;7~", to_key("#ACF10")); + table_set(*key_map, "\e[21;8~", to_key("#SACF10")); + + table_set(*key_map, "\e[24~", to_key("#F12")); + table_set(*key_map, "\e[24;2~", to_key("#SF12")); + table_set(*key_map, "\e[24;3~", to_key("#AF12")); + + // TODO Try with: + // - konsole + // - xterm + // - rxvt-unicode + // - kittyterminal + // - termux + + // F2 : Key : #run to_key("\eOQ"); + // F2 : 1b 4f 51 : ^OQ : -> \e[12~ + // Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ +} + to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { k: Key; // #if DEBUG { @@ -491,10 +538,16 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { 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 + // 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. + + key, success := table_find(*key_map, to_parse); + if success { + advance(*input_string, to_parse.count); + return key; + } if compare(to_parse, "\e[A") == 0 { advance(*input_string, to_parse.count); @@ -649,6 +702,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, 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; diff --git a/ttt.jai b/ttt.jai index 194bd06..94e868c 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1367,11 +1367,11 @@ main :: () { print(": % : ", string_to_print); for 0..str.count-1 { if str[it] == #char "\e" { - str[it] = #char "^"; + str[it] = #char "#"; } } write_string(str); - write_string(" : ~DAM"); + write_string(" :"); drop_down += 1; } } -- cgit v1.2.3 From 76eab65ffe14674a7b7fdece67c2a25c2a044dc7 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 2 Mar 2024 03:58:33 +0000 Subject: Setup mappings for several different terminals. --- modules/TUI/module.jai | 663 +++++++++++++++++++++++++++++-------------------- ttt.jai | 8 +- 2 files changed, 400 insertions(+), 271 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index ed3f517..a5f5837 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -163,252 +163,6 @@ Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. KEY_SIZE :: #run type_info(Key).runtime_size; -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"; - - 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~"); - - /* TODO On get_key, convert F1 to F4 into the format "\e[1X~" - so that: - F1 : 1b 4f 50 : ^OP : -> \e[11~ - Shift+ F1 : 1b 4f 32 50 : ^O2P : -> \e[11;2~ - F2 : 1b 4f 51 : ^OQ : -> \e[12~ - Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ - F3 : 1b 4f 52 : ^OR : -> \e[13~ - Shift+ F3 : 1b 4f 32 52 : ^O2R : -> \e[13;2~ - - F4 : 1b 4f 53 : ^OS : -> \e[14~ - Meta+ F4 : 1b 4f 31 53 : ^O1S : -> \e[14;1~ - Shift+ F4 : 1b 4f 32 53 : ^O2S : -> \e[14;2~ - Alt+ F4 : 1b 4f 33 53 : ^O3S : -> \e[14;3~ - S+A F4 : 1b 4f 34 53 : ^O4S : -> \e[14;4~ - Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ - ... - */ - - 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("\e[1;1P"); - MetaF2 : Key : #run to_key("\e[1;1Q"); - MetaF3 : Key : #run to_key("\e[1;1R"); - MetaF4 : Key : #run to_key("\e[1;1S"); - 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("\e[1;2P"); - ShiftF2 : Key : #run to_key("\e[1;2Q"); - ShiftF3 : Key : #run to_key("\e[1;2R"); - ShiftF4 : Key : #run to_key("\e[1;2S"); - 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("\e[1;3P"); - AltF2 : Key : #run to_key("\e[1;3Q"); - AltF3 : Key : #run to_key("\e[1;3R"); - AltF4 : Key : #run to_key("\e[1;3S"); - 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("\e[1;4P"); - AltShiftF2 : Key : #run to_key("\e[1;4Q"); - AltShiftF3 : Key : #run to_key("\e[1;4R"); - AltShiftF4 : Key : #run to_key("\e[1;4S"); - 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("\e[1;5P"); - CtrlF2 : Key : #run to_key("\e[1;5Q"); - CtrlF3 : Key : #run to_key("\e[1;5R"); - CtrlF4 : Key : #run to_key("\e[1;5S"); - 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("\e[1;6P"); - CtrlShiftF2 : Key : #run to_key("\e[1;6Q"); - CtrlShiftF3 : Key : #run to_key("\e[1;6R"); - CtrlShiftF4 : Key : #run to_key("\e[1;6S"); - 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("\e[1;7P"); - CtrlAltF2 : Key : #run to_key("\e[1;7Q"); - CtrlAltF3 : Key : #run to_key("\e[1;7R"); - CtrlAltF4 : Key : #run to_key("\e[1;7S"); - 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("\e[1;8P"); - CtrlAltShiftF2 : Key : #run to_key("\e[1;8Q"); - CtrlAltShiftF3 : Key : #run to_key("\e[1;8R"); - CtrlAltShiftF4 : Key : #run to_key("\e[1;8S"); - 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~"); -} - -key_map: Table(string, Key); - -setup_key_map :: () { - - table_set(*key_map, "\e[A", to_key("#UP")); - table_set(*key_map, "\e[A", to_key("#UP")); - - table_set(*key_map, "\eOQ", to_key("#F2")); - table_set(*key_map, "\e[12~", to_key("#F2")); - - table_set(*key_map, "\eO2Q", to_key("#SF2")); - table_set(*key_map, "\e[1;2Q", to_key("#SF2")); - table_set(*key_map, "\e[12;2~", to_key("#SF2")); - - table_set(*key_map, "\e\e[12~", to_key("#AF2")); - table_set(*key_map, "\e\e[24~", to_key("#AF12")); - - table_set(*key_map, "\e[12^", to_key("#CF2")); - - // A good example - table_set(*key_map, "\e[21~", to_key("#F10")); - table_set(*key_map, "\e[21;1~", to_key("#MF10")); - table_set(*key_map, "\e[21;2~", to_key("#SF10")); - table_set(*key_map, "\e[21;3~", to_key("#AF10")); - table_set(*key_map, "\e[21;4~", to_key("#SAF10")); - table_set(*key_map, "\e[21;5~", to_key("#CF10")); - table_set(*key_map, "\e[21;6~", to_key("#SCF10")); - table_set(*key_map, "\e[21;7~", to_key("#ACF10")); - table_set(*key_map, "\e[21;8~", to_key("#SACF10")); - - table_set(*key_map, "\e[24~", to_key("#F12")); - table_set(*key_map, "\e[24;2~", to_key("#SF12")); - table_set(*key_map, "\e[24;3~", to_key("#AF12")); - - // TODO Try with: - // - konsole - // - xterm - // - rxvt-unicode - // - kittyterminal - // - termux - - // F2 : Key : #run to_key("\eOQ"); - // F2 : 1b 4f 51 : ^OQ : -> \e[12~ - // Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ -} - to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { k: Key; // #if DEBUG { @@ -432,14 +186,411 @@ to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY } 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 ^ Keys.Escape) == 0); + 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!!! @@ -543,28 +694,12 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // 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; } - - if compare(to_parse, "\e[A") == 0 { - advance(*input_string, to_parse.count); - return to_key("#UP"); - } - else if compare(to_parse, "\e[B") == 0 { - advance(*input_string, to_parse.count); - return to_key("#DOWN"); - } - else if compare(to_parse, "\e[C") == 0 { - advance(*input_string, to_parse.count); - return to_key("#RIGHT"); - } - else if compare(to_parse, "\e[D") == 0 { - advance(*input_string, to_parse.count); - return to_key("#LEFT"); - } } advance(*input_string, to_parse.count); diff --git a/ttt.jai b/ttt.jai index 94e868c..1a74b37 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1348,12 +1348,6 @@ main :: () { TUI.clear_terminal(); drop_down = 0; } - - case TUI.Keys.MetaF7; { - TUI.set_cursor_position(3+drop_down, 2); - drop_down += 1; - write_string("META F7"); - } case; { TUI.set_cursor_position(3+drop_down, 2); @@ -1420,7 +1414,7 @@ main :: () { TUI.stop(); } - write_string("DONE"); + write_string("DONE\n"); exit(0); // -- -- -- Testing TUI -- STOP -- cgit v1.2.3 From c9cad034362a1b433579e8424514e4246c721b92 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 13 Mar 2024 00:43:47 +0000 Subject: WIP : Adding more key_map entries --- modules/TUI/module.jai | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index a5f5837..49cdec0 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -510,16 +510,26 @@ setup_key_map :: () { // 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")); + +TODO FINISH THIS... WIP + + // table_set(*key_map, "\e[18;1~", to_key("#f7+m")); + table_set(*key_map, "\e[18;2~", to_key("#f7+s")); // #f7+^ + table_set(*key_map, "\e[18;3~", to_key("#f7+a")); // #f7+a + table_set(*key_map, "\e[18;4~", to_key("#f7+A")); // #f7+A + table_set(*key_map, "\e[18;5~", to_key("#f7+c")); // #f7+s 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")); + table_set(*key_map, "\e[18;7~", to_key("#f7+x")); // x + table_set(*key_map, "\e[18;8~", to_key("#f7+X")); // X // F7 - kitty - table_set(*key_map, "\e[18;9~", to_key("#f7+m")); + // table_set(*key_map, "\e[18;9~", to_key("#f7+m")); // m + // table_set(*key_map, "\e[18;10~",to_key("#f7+M")); // M + // table_set(*key_map, "\e[18;11~",to_key("#f7+ma")); // ma -> y + // table_set(*key_map, "\e[18;12~",to_key("#f7+mA")); // MA -> Y + // table_set(*key_map, "\e[18;13~",to_key("#f7+mc")); // mc -> z + // table_set(*key_map, "\e[18;14~",to_key("#f7+mC")); // MC -> Z + // table_set(*key_map, "\e[18;15~",to_key("#f7+mx")); // mac-> w + // table_set(*key_map, "\e[18;16~",to_key("#f7+mX")); // MAC-> W // F7 - linux console table_set(*key_map, "\e[33~", to_key("#f7+s")); -- cgit v1.2.3 From 0087918da456195648012e790d8f3a96a6dde403 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 14 Mar 2024 00:22:58 +0000 Subject: WIP : Mapping the keyboard codes. --- modules/TUI/module.jai | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 49cdec0..11b5247 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -247,6 +247,24 @@ setup_key_map :: () { - kitty - xterm - linux console + + To signal modifier keys, a letter is appended after a + (plus sign): + "#f1" -> F1 + "#f1+$" -> F1+Shift + "#f1+a" -> F1+Alt + "#f1+A" -> F1+Shift+Alt + "#f1+c" -> F1+Ctrl + "#f1+C" -> F1+Shift+Ctrl + "#f1+w" -> F1+Alt+Ctrl + "#f1+W" -> F1+Shift+Alt+Ctrl + "#f1+s" -> F1+Super + "#f1+S" -> F1+Shift+Super + "#f1+x" -> F1+Alt+Super + "#f1+X" -> F1+Shift+Alt+Super + "#f1+y" -> F1+Ctrl+Super + "#f1+Y" -> F1+Shift+Ctrl+Super + "#f1+z" -> F1+Alt+Ctrl+Super + "#f1+Z" -> F1+Shift+Alt+Ctrl+Super */ // Up -- cgit v1.2.3 From ac92da96856603d450e39fa536f287bcf67c0fd7 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 15 Mar 2024 01:46:01 +0000 Subject: Improved key map. --- modules/TUI/key_map.jai | 501 ++++++++++++++++++++++++++++++++++++++++++++++++ modules/TUI/module.jai | 388 +------------------------------------ ttt.jai | 8 +- 3 files changed, 510 insertions(+), 387 deletions(-) create mode 100644 modules/TUI/key_map.jai (limited to 'modules') diff --git a/modules/TUI/key_map.jai b/modules/TUI/key_map.jai new file mode 100644 index 0000000..42acabf --- /dev/null +++ b/modules/TUI/key_map.jai @@ -0,0 +1,501 @@ +#import "Hash_Table"; + +key_map: Table(string, Key); + +setup_key_map :: () { + /* + This table was created/tested using the following terminals: + - g: gnome (terminal) + - i: kitty + - k: konsole + - l: linux (console) + - x: xterm + + To signal modifier keys, a letter is appended after a + (plus sign): + "#f1" -> F1 + "#f1+$" -> F1 + Shift + "#f1+a" -> F1 + Alt + "#f1+A" -> F1 + Shift + Alt + "#f1+c" -> F1 + Ctrl + "#f1+C" -> F1 + Shift + Ctrl + "#f1+w" -> F1 + Alt + Ctrl + "#f1+W" -> F1 + Shift + Alt + Ctrl + "#f1+s" -> F1 + Super + "#f1+S" -> F1 + Shift + Super + "#f1+x" -> F1 + Alt + Super + "#f1+X" -> F1 + Shift + Alt + Super + "#f1+y" -> F1 + Ctrl + Super + "#f1+Y" -> F1 + Shift + Ctrl + Super + "#f1+z" -> F1 + Alt + Ctrl + Super + "#f1+Z" -> F1 + Shift + Alt + Ctrl + Super + */ + + // Up // g i k l x + // table_set(*key_map, "\e[A", to_key("#up")); // + + + + + + table_set(*key_map, "\e[1;1A", to_key("#up")); // + table_set(*key_map, "\e[1;2A", to_key("#up+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8A", to_key("#up+W")); // + + + + + table_set(*key_map, "\e[1;9A", to_key("#up+s")); // + + table_set(*key_map, "\e[1;10A", to_key("#up+S")); // + + table_set(*key_map, "\e[1;11A", to_key("#up+x")); // + + table_set(*key_map, "\e[1;12A", to_key("#up+X")); // + + table_set(*key_map, "\e[1;13A", to_key("#up+y")); // + + table_set(*key_map, "\e[1;14A", to_key("#up+Y")); // + + table_set(*key_map, "\e[1;15A", to_key("#up+z")); // + + table_set(*key_map, "\e[1;16A", to_key("#up+Z")); // + + + // Down // g i k l x + table_set(*key_map, "\e[B", to_key("#down")); // + + + + + + table_set(*key_map, "\e[1;1B", to_key("#down")); // + table_set(*key_map, "\e[1;2B", to_key("#down+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8B", to_key("#down+W")); // + + + + + table_set(*key_map, "\e[1;9B", to_key("#down+s")); // + + table_set(*key_map, "\e[1;10B", to_key("#down+S")); // + + table_set(*key_map, "\e[1;11B", to_key("#down+x")); // + + table_set(*key_map, "\e[1;12B", to_key("#down+X")); // + + table_set(*key_map, "\e[1;13B", to_key("#down+y")); // + + table_set(*key_map, "\e[1;14B", to_key("#down+Y")); // + + table_set(*key_map, "\e[1;15B", to_key("#down+z")); // + + table_set(*key_map, "\e[1;16B", to_key("#down+Z")); // + + + // Right // g i k l x + table_set(*key_map, "\e[C", to_key("#right")); // + + + + + + table_set(*key_map, "\e[1;1C", to_key("#right")); // + table_set(*key_map, "\e[1;2C", to_key("#right+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8C", to_key("#right+W")); // + + + + + table_set(*key_map, "\e[1;9C", to_key("#right+s")); // + + table_set(*key_map, "\e[1;10C", to_key("#right+S")); // + + table_set(*key_map, "\e[1;11C", to_key("#right+x")); // + + table_set(*key_map, "\e[1;12C", to_key("#right+X")); // + + table_set(*key_map, "\e[1;13C", to_key("#right+y")); // + + table_set(*key_map, "\e[1;14C", to_key("#right+Y")); // + + table_set(*key_map, "\e[1;15C", to_key("#right+z")); // + + table_set(*key_map, "\e[1;16C", to_key("#right+Z")); // + + + // Left // g i k l x + table_set(*key_map, "\e[D", to_key("#left")); // + + + + + + table_set(*key_map, "\e[1;1D", to_key("#left")); // + table_set(*key_map, "\e[1;2D", to_key("#left+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8D", to_key("#left+W")); // + + + + + table_set(*key_map, "\e[1;9D", to_key("#left+s")); // + + table_set(*key_map, "\e[1;10D", to_key("#left+S")); // + + table_set(*key_map, "\e[1;11D", to_key("#left+x")); // + + table_set(*key_map, "\e[1;12D", to_key("#left+X")); // + + table_set(*key_map, "\e[1;13D", to_key("#left+y")); // + + table_set(*key_map, "\e[1;14D", to_key("#left+Y")); // + + table_set(*key_map, "\e[1;15D", to_key("#left+z")); // + + table_set(*key_map, "\e[1;16D", to_key("#left+Z")); // + + + // Home // g i k l x + table_set(*key_map, "\e[1~", to_key("#home")); // + + table_set(*key_map, "\e[H", to_key("#home")); // + + + + + table_set(*key_map, "\e[1;1H", to_key("#home")); // + table_set(*key_map, "\e[1;2H", to_key("#home+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8H", to_key("#home+W")); // + + + + + table_set(*key_map, "\e[1;9H", to_key("#home+s")); // + + table_set(*key_map, "\e[1;10H", to_key("#home+S")); // + + table_set(*key_map, "\e[1;11H", to_key("#home+x")); // + + table_set(*key_map, "\e[1;12H", to_key("#home+X")); // + + table_set(*key_map, "\e[1;13H", to_key("#home+y")); // + + table_set(*key_map, "\e[1;14H", to_key("#home+Y")); // + + table_set(*key_map, "\e[1;15H", to_key("#home+z")); // + + table_set(*key_map, "\e[1;16H", to_key("#home+Z")); // + + + // End // g i k l x + table_set(*key_map, "\e[4~", to_key("#end")); // + + table_set(*key_map, "\e[F", to_key("#end")); // + + + + + table_set(*key_map, "\e[1;1F", to_key("#end")); // + table_set(*key_map, "\e[1;2F", to_key("#end+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8F", to_key("#end+W")); // + + + + + table_set(*key_map, "\e[1;9F", to_key("#end+s")); // + + table_set(*key_map, "\e[1;10F", to_key("#end+S")); // + + table_set(*key_map, "\e[1;11F", to_key("#end+x")); // + + table_set(*key_map, "\e[1;12F", to_key("#end+X")); // + + table_set(*key_map, "\e[1;13F", to_key("#end+y")); // + + table_set(*key_map, "\e[1;14F", to_key("#end+Y")); // + + table_set(*key_map, "\e[1;15F", to_key("#end+z")); // + + table_set(*key_map, "\e[1;16F", to_key("#end+Z")); // + + + // Insert // g i k l x + table_set(*key_map, "\e[2~", to_key("#ins")); // + + + + + + table_set(*key_map, "\e[2;1~", to_key("#ins")); // + table_set(*key_map, "\e[2;2~", to_key("#ins+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[2;8~", to_key("#ins+W")); // + + + + + table_set(*key_map, "\e[2;9~", to_key("#ins+s")); // + + table_set(*key_map, "\e[2;10~", to_key("#ins+S")); // + + table_set(*key_map, "\e[2;11~", to_key("#ins+x")); // + + table_set(*key_map, "\e[2;12~", to_key("#ins+X")); // + + table_set(*key_map, "\e[2;13~", to_key("#ins+y")); // + + table_set(*key_map, "\e[2;14~", to_key("#ins+Y")); // + + table_set(*key_map, "\e[2;15~", to_key("#ins+z")); // + + table_set(*key_map, "\e[2;16~", to_key("#ins+Z")); // + + + // Delete // g i k l x + table_set(*key_map, "\e[3~", to_key("#del")); // + + + + + + table_set(*key_map, "\e[3;1~", to_key("#del")); // + table_set(*key_map, "\e[3;2~", to_key("#del+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[3;8~", to_key("#del+W")); // + + + + + table_set(*key_map, "\e[3;9~", to_key("#del+s")); // + + table_set(*key_map, "\e[3;10~", to_key("#del+S")); // + + table_set(*key_map, "\e[3;11~", to_key("#del+x")); // + + table_set(*key_map, "\e[3;12~", to_key("#del+X")); // + + table_set(*key_map, "\e[3;13~", to_key("#del+y")); // + + table_set(*key_map, "\e[3;14~", to_key("#del+Y")); // + + table_set(*key_map, "\e[3;15~", to_key("#del+z")); // + + table_set(*key_map, "\e[3;16~", to_key("#del+Z")); // + + + // Page Up // g i k l x + table_set(*key_map, "\e[5~", to_key("#pup")); // + + + + + + table_set(*key_map, "\e[5;1~", to_key("#pup")); // + table_set(*key_map, "\e[5;2~", to_key("#pup+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[5;8~", to_key("#pup+W")); // + + + + + table_set(*key_map, "\e[5;9~", to_key("#pup+s")); // + + table_set(*key_map, "\e[5;10~", to_key("#pup+S")); // + + table_set(*key_map, "\e[5;11~", to_key("#pup+x")); // + + table_set(*key_map, "\e[5;12~", to_key("#pup+X")); // + + table_set(*key_map, "\e[5;13~", to_key("#pup+y")); // + + table_set(*key_map, "\e[5;14~", to_key("#pup+Y")); // + + table_set(*key_map, "\e[5;15~", to_key("#pup+z")); // + + table_set(*key_map, "\e[5;16~", to_key("#pup+Z")); // + + + // Page Down // g i k l x + table_set(*key_map, "\e[6~", to_key("#pdown")); // + + + + + + table_set(*key_map, "\e[6;1~", to_key("#pdown")); // + table_set(*key_map, "\e[6;2~", to_key("#pdown+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[6;8~", to_key("#pdown+W")); // + + + + + table_set(*key_map, "\e[6;9~", to_key("#pdown+s")); // + + table_set(*key_map, "\e[6;10~", to_key("#pdown+S")); // + + table_set(*key_map, "\e[6;11~", to_key("#pdown+x")); // + + table_set(*key_map, "\e[6;12~", to_key("#pdown+X")); // + + table_set(*key_map, "\e[6;13~", to_key("#pdown+y")); // + + table_set(*key_map, "\e[6;14~", to_key("#pdown+Y")); // + + table_set(*key_map, "\e[6;15~", to_key("#pdown+z")); // + + table_set(*key_map, "\e[6;16~", to_key("#pdown+Z")); // + + + // F1 // g i k l x + table_set(*key_map, "\e[[A", to_key("#f1")); // + + table_set(*key_map, "\e[25~", to_key("#f1+$")); // + + table_set(*key_map, "\eOP", to_key("#f1")); // + + + + + table_set(*key_map, "\eO1P", to_key("#f1+s")); // + + table_set(*key_map, "\eO2P", to_key("#f1+$")); // + + 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+w")); // + + table_set(*key_map, "\eO8P", to_key("#f1+W")); // + + table_set(*key_map, "\e[1P", to_key("#f1")); // + table_set(*key_map, "\e[1;1P", to_key("#f1")); // + table_set(*key_map, "\e[1;2P", to_key("#f1+$")); // + + + + 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+w")); // + + + + table_set(*key_map, "\e[1;8P", to_key("#f1+W")); // + + + + table_set(*key_map, "\e[1;9P", to_key("#f1+s")); // + + table_set(*key_map, "\e[1;10P", to_key("#f1+S")); // + + table_set(*key_map, "\e[1;11P", to_key("#f1+x")); // + + table_set(*key_map, "\e[1;12P", to_key("#f1+X")); // + + table_set(*key_map, "\e[1;13P", to_key("#f1+y")); // + + table_set(*key_map, "\e[1;14P", to_key("#f1+Y")); // + + table_set(*key_map, "\e[1;15P", to_key("#f1+z")); // + + table_set(*key_map, "\e[1;16P", to_key("#f1+Z")); // + + + // F2 // g i k l x + table_set(*key_map, "\e[[B", to_key("#f2")); // + + table_set(*key_map, "\e[26~", to_key("#f2+$")); // + + table_set(*key_map, "\eOQ", to_key("#f2")); // + + + + + table_set(*key_map, "\eO1Q", to_key("#f2+s")); // + + table_set(*key_map, "\eO2Q", to_key("#f2+$")); // + + 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+w")); // + + table_set(*key_map, "\eO8Q", to_key("#f2+W")); // + + table_set(*key_map, "\e[1Q", to_key("#f2")); // + table_set(*key_map, "\e[1;1Q", to_key("#f2")); // + table_set(*key_map, "\e[1;2Q", to_key("#f2+$")); // + + + + 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+w")); // + + + + table_set(*key_map, "\e[1;8Q", to_key("#f2+W")); // + + + + table_set(*key_map, "\e[1;9Q", to_key("#f2+s")); // + + table_set(*key_map, "\e[1;10Q", to_key("#f2+S")); // + + table_set(*key_map, "\e[1;11Q", to_key("#f2+x")); // + + table_set(*key_map, "\e[1;12Q", to_key("#f2+X")); // + + table_set(*key_map, "\e[1;13Q", to_key("#f2+y")); // + + table_set(*key_map, "\e[1;14Q", to_key("#f2+Y")); // + + table_set(*key_map, "\e[1;15Q", to_key("#f2+z")); // + + table_set(*key_map, "\e[1;16Q", to_key("#f2+Z")); // + + + // F3 // g i k l x + table_set(*key_map, "\e[[C", to_key("#f3")); // + + table_set(*key_map, "\e[28~", to_key("#f3+$")); // + + table_set(*key_map, "\eOR", to_key("#f3")); // + + + + + table_set(*key_map, "\eO1R", to_key("#f3+s")); // + + table_set(*key_map, "\eO2R", to_key("#f3+$")); // + + 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+w")); // + + table_set(*key_map, "\eO8R", to_key("#f3+W")); // + + table_set(*key_map, "\e[1R", to_key("#f3")); // + table_set(*key_map, "\e[1;1R", to_key("#f3")); // + table_set(*key_map, "\e[1;2R", to_key("#f3+$")); // + + + + 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+w")); // + + + + table_set(*key_map, "\e[1;8R", to_key("#f3+W")); // + + + + table_set(*key_map, "\e[1;9R", to_key("#f3+s")); // + + table_set(*key_map, "\e[1;10R", to_key("#f3+S")); // + + table_set(*key_map, "\e[1;11R", to_key("#f3+x")); // + + table_set(*key_map, "\e[1;12R", to_key("#f3+X")); // + + table_set(*key_map, "\e[1;13R", to_key("#f3+y")); // + + table_set(*key_map, "\e[1;14R", to_key("#f3+Y")); // + + table_set(*key_map, "\e[1;15R", to_key("#f3+z")); // + + table_set(*key_map, "\e[1;16R", to_key("#f3+Z")); // + + + // F4 // g i k l x + table_set(*key_map, "\e[[D", to_key("#f4")); // + + table_set(*key_map, "\e[29~", to_key("#f4+$")); // + + table_set(*key_map, "\eOS", to_key("#f4")); // + + + + + table_set(*key_map, "\eO1S", to_key("#f4+s")); // + + table_set(*key_map, "\eO2S", to_key("#f4+$")); // + + 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+w")); // + + table_set(*key_map, "\eO8S", to_key("#f4+W")); // + + table_set(*key_map, "\e[1S", to_key("#f4")); // + table_set(*key_map, "\e[1;1S", to_key("#f4")); // + table_set(*key_map, "\e[1;2S", to_key("#f4+$")); // + + + + 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+w")); // + + + + table_set(*key_map, "\e[1;8S", to_key("#f4+W")); // + + + + table_set(*key_map, "\e[1;9S", to_key("#f4+s")); // + + table_set(*key_map, "\e[1;10S", to_key("#f4+S")); // + + table_set(*key_map, "\e[1;11S", to_key("#f4+x")); // + + table_set(*key_map, "\e[1;12S", to_key("#f4+X")); // + + table_set(*key_map, "\e[1;13S", to_key("#f4+y")); // + + table_set(*key_map, "\e[1;14S", to_key("#f4+Y")); // + + table_set(*key_map, "\e[1;15S", to_key("#f4+z")); // + + table_set(*key_map, "\e[1;16S", to_key("#f4+Z")); // + + + // F5 // g i k l x + table_set(*key_map, "\e[[E", to_key("#f5")); // + + table_set(*key_map, "\e[31~", to_key("#f5+$")); // + + table_set(*key_map, "\e[15~", to_key("#f5")); // + + + + + table_set(*key_map, "\e[15;1~", to_key("#f5")); // + table_set(*key_map, "\e[15;2~", to_key("#f5+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[15;8~", to_key("#f5+W")); // + + + + + table_set(*key_map, "\e[15;9~", to_key("#f5+s")); // + + table_set(*key_map, "\e[15;10~",to_key("#f5+S")); // + + table_set(*key_map, "\e[15;11~",to_key("#f5+x")); // + + table_set(*key_map, "\e[15;12~",to_key("#f5+X")); // + + table_set(*key_map, "\e[15;13~",to_key("#f5+y")); // + + table_set(*key_map, "\e[15;14~",to_key("#f5+Y")); // + + table_set(*key_map, "\e[15;15~",to_key("#f5+z")); // + + table_set(*key_map, "\e[15;16~",to_key("#f5+Z")); // + + + // F6 // g i k l x + table_set(*key_map, "\e[32~", to_key("#f6+$")); // + + table_set(*key_map, "\e[17~", to_key("#f6")); // + + + + + + table_set(*key_map, "\e[17;1~", to_key("#f6")); // + table_set(*key_map, "\e[17;2~", to_key("#f6+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[17;8~", to_key("#f6+W")); // + + + + + table_set(*key_map, "\e[17;9~", to_key("#f6+s")); // + + table_set(*key_map, "\e[17;10~",to_key("#f6+S")); // + + table_set(*key_map, "\e[17;11~",to_key("#f6+x")); // + + table_set(*key_map, "\e[17;12~",to_key("#f6+X")); // + + table_set(*key_map, "\e[17;13~",to_key("#f6+y")); // + + table_set(*key_map, "\e[17;14~",to_key("#f6+Y")); // + + table_set(*key_map, "\e[17;15~",to_key("#f6+z")); // + + table_set(*key_map, "\e[17;16~",to_key("#f6+Z")); // + + + // F7 // g i k l x + table_set(*key_map, "\e[33~", to_key("#f7+$")); // + + table_set(*key_map, "\e[18~", to_key("#f7")); // + + + + + + table_set(*key_map, "\e[18;1~", to_key("#f7")); // + table_set(*key_map, "\e[18;2~", to_key("#f7+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[18;8~", to_key("#f7+W")); // + + + + + table_set(*key_map, "\e[18;9~", to_key("#f7+s")); // + + table_set(*key_map, "\e[18;10~",to_key("#f7+S")); // + + table_set(*key_map, "\e[18;11~",to_key("#f7+x")); // + + table_set(*key_map, "\e[18;12~",to_key("#f7+X")); // + + table_set(*key_map, "\e[18;13~",to_key("#f7+y")); // + + table_set(*key_map, "\e[18;14~",to_key("#f7+Y")); // + + table_set(*key_map, "\e[18;15~",to_key("#f7+z")); // + + table_set(*key_map, "\e[18;16~",to_key("#f7+Z")); // + + + // F8 // g i k l x + table_set(*key_map, "\e[34~", to_key("#f8+$")); // + + table_set(*key_map, "\e[19~", to_key("#f8")); // + + + + + + table_set(*key_map, "\e[19;1~", to_key("#f8")); // + table_set(*key_map, "\e[19;2~", to_key("#f8+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[19;8~", to_key("#f8+W")); // + + + + + table_set(*key_map, "\e[19;9~", to_key("#f8+s")); // + + table_set(*key_map, "\e[19;10~",to_key("#f8+S")); // + + table_set(*key_map, "\e[19;11~",to_key("#f8+x")); // + + table_set(*key_map, "\e[19;12~",to_key("#f8+X")); // + + table_set(*key_map, "\e[19;13~",to_key("#f8+y")); // + + table_set(*key_map, "\e[19;14~",to_key("#f8+Y")); // + + table_set(*key_map, "\e[19;15~",to_key("#f8+z")); // + + table_set(*key_map, "\e[19;16~",to_key("#f8+Z")); // + + + // F9 // g i k l x + table_set(*key_map, "\e[20~", to_key("#f9")); // + + + + + + table_set(*key_map, "\e[20;1~", to_key("#f9")); // + table_set(*key_map, "\e[20;2~", to_key("#f9+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[20;8~", to_key("#f9+W")); // + + + + + table_set(*key_map, "\e[20;9~", to_key("#f9+s")); // + + table_set(*key_map, "\e[20;10~",to_key("#f9+S")); // + + table_set(*key_map, "\e[20;11~",to_key("#f9+x")); // + + table_set(*key_map, "\e[20;12~",to_key("#f9+X")); // + + table_set(*key_map, "\e[20;13~",to_key("#f9+y")); // + + table_set(*key_map, "\e[20;14~",to_key("#f9+Y")); // + + table_set(*key_map, "\e[20;15~",to_key("#f9+z")); // + + table_set(*key_map, "\e[20;16~",to_key("#f9+Z")); // + + + // F10 // g i k l x + table_set(*key_map, "\e[21~", to_key("#f10")); // + + + + + + table_set(*key_map, "\e[21;1~", to_key("#f10")); // + table_set(*key_map, "\e[21;2~", to_key("#f10+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[21;8~", to_key("#f10+W")); // + + + + + table_set(*key_map, "\e[21;9~", to_key("#f10+s")); // + + table_set(*key_map, "\e[21;10~",to_key("#f10+S")); // + + table_set(*key_map, "\e[21;11~",to_key("#f10+x")); // + + table_set(*key_map, "\e[21;12~",to_key("#f10+X")); // + + table_set(*key_map, "\e[21;13~",to_key("#f10+y")); // + + table_set(*key_map, "\e[21;14~",to_key("#f10+Y")); // + + table_set(*key_map, "\e[21;15~",to_key("#f10+z")); // + + table_set(*key_map, "\e[21;16~",to_key("#f10+Z")); // + + + // F11 // g i k l x + table_set(*key_map, "\e[23~", to_key("#f11")); // + + + + + + table_set(*key_map, "\e[23;1~", to_key("#f11")); // + table_set(*key_map, "\e[23;2~", to_key("#f11+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[23;8~", to_key("#f11+W")); // + + + + + table_set(*key_map, "\e[23;9~", to_key("#f11+s")); // + + table_set(*key_map, "\e[23;10~",to_key("#f11+S")); // + + table_set(*key_map, "\e[23;11~",to_key("#f11+x")); // + + table_set(*key_map, "\e[23;12~",to_key("#f11+X")); // + + table_set(*key_map, "\e[23;13~",to_key("#f11+y")); // + + table_set(*key_map, "\e[23;14~",to_key("#f11+Y")); // + + table_set(*key_map, "\e[23;15~",to_key("#f11+z")); // + + table_set(*key_map, "\e[23;16~",to_key("#f11+Z")); // + + + // F12 // g i k l x + table_set(*key_map, "\e[24~", to_key("#f12")); // + + + + + + table_set(*key_map, "\e[24;1~", to_key("#f12")); // + table_set(*key_map, "\e[24;2~", to_key("#f12+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[24;8~", to_key("#f12+W")); // + + + + + table_set(*key_map, "\e[24;9~", to_key("#f12+s")); // + + table_set(*key_map, "\e[24;10~",to_key("#f12+S")); // + + table_set(*key_map, "\e[24;11~",to_key("#f12+x")); // + + table_set(*key_map, "\e[24;12~",to_key("#f12+X")); // + + table_set(*key_map, "\e[24;13~",to_key("#f12+y")); // + + table_set(*key_map, "\e[24;14~",to_key("#f12+Y")); // + + table_set(*key_map, "\e[24;15~",to_key("#f12+z")); // + + table_set(*key_map, "\e[24;16~",to_key("#f12+Z")); // + +} diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 11b5247..d93e4ff 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,3 @@ -// TODO Move TUI into ./modules/TUI so we can stop calling --import_dir on compile. #if OS == { case .LINUX; #load "unix.jai"; @@ -13,7 +12,7 @@ #import "Basic"; #import "String"; #import "Thread"; -#import "Hash_Table"; +#load "key_map.jai"; // Special Graphics Characters Drawings :: struct { @@ -238,387 +237,6 @@ Keys :: struct #type_info_none { 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 - - To signal modifier keys, a letter is appended after a + (plus sign): - "#f1" -> F1 - "#f1+$" -> F1+Shift - "#f1+a" -> F1+Alt - "#f1+A" -> F1+Shift+Alt - "#f1+c" -> F1+Ctrl - "#f1+C" -> F1+Shift+Ctrl - "#f1+w" -> F1+Alt+Ctrl - "#f1+W" -> F1+Shift+Alt+Ctrl - "#f1+s" -> F1+Super - "#f1+S" -> F1+Shift+Super - "#f1+x" -> F1+Alt+Super - "#f1+X" -> F1+Shift+Alt+Super - "#f1+y" -> F1+Ctrl+Super - "#f1+Y" -> F1+Shift+Ctrl+Super - "#f1+z" -> F1+Alt+Ctrl+Super - "#f1+Z" -> F1+Shift+Alt+Ctrl+Super - */ - - // 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")); - -TODO FINISH THIS... WIP - - // table_set(*key_map, "\e[18;1~", to_key("#f7+m")); - table_set(*key_map, "\e[18;2~", to_key("#f7+s")); // #f7+^ - table_set(*key_map, "\e[18;3~", to_key("#f7+a")); // #f7+a - table_set(*key_map, "\e[18;4~", to_key("#f7+A")); // #f7+A - table_set(*key_map, "\e[18;5~", to_key("#f7+c")); // #f7+s - table_set(*key_map, "\e[18;6~", to_key("#f7+C")); - table_set(*key_map, "\e[18;7~", to_key("#f7+x")); // x - table_set(*key_map, "\e[18;8~", to_key("#f7+X")); // X - // F7 - kitty - // table_set(*key_map, "\e[18;9~", to_key("#f7+m")); // m - // table_set(*key_map, "\e[18;10~",to_key("#f7+M")); // M - // table_set(*key_map, "\e[18;11~",to_key("#f7+ma")); // ma -> y - // table_set(*key_map, "\e[18;12~",to_key("#f7+mA")); // MA -> Y - // table_set(*key_map, "\e[18;13~",to_key("#f7+mc")); // mc -> z - // table_set(*key_map, "\e[18;14~",to_key("#f7+mC")); // MC -> Z - // table_set(*key_map, "\e[18;15~",to_key("#f7+mx")); // mac-> w - // table_set(*key_map, "\e[18;16~",to_key("#f7+mX")); // MAC-> W - // 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!!! @@ -728,6 +346,10 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { advance(*input_string, to_parse.count); return key; } + TODO If failed to parse... lets return a single char... but it's not working! + else { + to_parse.count = 1; + } } advance(*input_string, to_parse.count); diff --git a/ttt.jai b/ttt.jai index 1a74b37..2921ff0 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1249,7 +1249,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); TUI.start(); @@ -1277,7 +1277,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); TUI.start(); // TODO Should start() call flush_input internally? @@ -1292,7 +1292,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : get terminal size\n", to_standard_error = true); auto_release_temp(); TUI.start(); @@ -1309,7 +1309,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : set terminal title\n", to_standard_error = true); TUI.start(); title := "BAZINGA"; -- cgit v1.2.3 From b47287aaa3cfa384ec683ff58e0d247e8bad32b1 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 21 Mar 2024 00:50:42 +0000 Subject: Fix get_key to support escape codes. --- modules/TUI/key_map.jai | 2 +- modules/TUI/module.jai | 48 +++++++++++++++++------------------------------- ttt.jai | 13 ++++++++++--- 3 files changed, 28 insertions(+), 35 deletions(-) (limited to 'modules') diff --git a/modules/TUI/key_map.jai b/modules/TUI/key_map.jai index 42acabf..b272253 100644 --- a/modules/TUI/key_map.jai +++ b/modules/TUI/key_map.jai @@ -31,7 +31,7 @@ setup_key_map :: () { */ // Up // g i k l x - // table_set(*key_map, "\e[A", to_key("#up")); // + + + + + + table_set(*key_map, "\e[A", to_key("#up")); // + + + + + table_set(*key_map, "\e[1;1A", to_key("#up")); // table_set(*key_map, "\e[1;2A", to_key("#up+$")); // + + + + table_set(*key_map, "\e[1;3A", to_key("#up+a")); // + + + + diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d93e4ff..93e410e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -126,6 +126,8 @@ Colors8b :: struct { White :: 15; } +// Fix color tables using this: https://devmemo.io/cheatsheets/terminal_escape_code/ + // TODO Maybe rename. set_style_colors :: (foreground: u8, background: u8) { print( @@ -261,15 +263,6 @@ set_next_key :: (key: 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 { @@ -321,38 +314,31 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { if input_string.count == 0 return Keys.None; - // Assume we're parsing just a single char. + // By default, parse a single UTF8 character (1 to 4 bytes). 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]); - } - + to_parse.count = count_utf8_bytes(input_string[0]); + defer advance(*input_string, to_parse.count); // Advance over parsed input. + // 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. + // Limit number of chars to parse. + to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; - // TEMPORARY HACK + // Search for the longest escape code. key, success := table_find(*key_map, to_parse); + while success == false && to_parse.count > 1 { + to_parse.count -= 1; + key, success = table_find(*key_map, to_parse); + } + + // If an escape code was found, use it. if success { - advance(*input_string, to_parse.count); return key; } - TODO If failed to parse... lets return a single char... but it's not working! - else { - to_parse.count = 1; - } + // else { ... } If we entered this block and did not succeed, then we'll return a single escape character.â } - - advance(*input_string, to_parse.count); + return to_key(to_parse); } diff --git a/ttt.jai b/ttt.jai index 2921ff0..9b08f6d 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1332,7 +1332,12 @@ main :: () { TUI.set_terminal_title("bazinga"); key: TUI.Key = #char "d"; last_none_char := "X"; + + size_r, size_c := TUI.get_terminal_size(); + TUI.clear_terminal(); + TUI.draw_box(1, 1, size_c, size_r); drop_down := 0; + while(key != #char "q") { __mark := get_temporary_storage_mark(); @@ -1345,7 +1350,9 @@ main :: () { case TUI.Keys.Resize; #through; case #char "c"; { + size_r, size_c = TUI.get_terminal_size(); TUI.clear_terminal(); + TUI.draw_box(1, 1, size_c, size_r); drop_down = 0; } @@ -1369,13 +1376,13 @@ main :: () { drop_down += 1; } } - size_r, size_c := TUI.get_terminal_size(); - TUI.draw_box(1, 1, size_c, size_r); + + x := ifx size_r > 1 then size_r-1 else 1; y := ifx size_c > 24 then size_c-24 else 1; + TUI.set_cursor_position(x, y); print("size(CxR): %x%\n", size_c, size_r); - key = TUI.get_key(1000); set_temporary_storage_mark(__mark); -- cgit v1.2.3 From f1fe4f6bcf27e0843d424f4885b0913e220ade2e Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 00:50:01 +0000 Subject: Improved robustness and code clarity on get_key. --- modules/TUI/module.jai | 8 +++++--- ttt.jai | 7 +++---- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 93e410e..6aeac68 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -332,11 +332,13 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { key, success = table_find(*key_map, to_parse); } - // If an escape code was found, use it. + if success { - return key; + return key; // Escape code found, return it. + } + else { + to_parse.count = 1; // No escape code found, return a single (escape) character. } - // else { ... } If we entered this block and did not succeed, then we'll return a single escape character.â } return to_key(to_parse); diff --git a/ttt.jai b/ttt.jai index 9b08f6d..593e30f 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1324,9 +1324,8 @@ main :: () { assert(key == #char "y", "# Failed to set terminal title.\n"); print("- success\n", to_standard_error = true); } - - // TODO Setup this test... check for Meta+FX or Ctrl+FX compatibility between windows and *nix. - if 1 { + + if 0 { print("TEST : print keys and set terminal title\n", to_standard_error = true); TUI.start(); TUI.set_terminal_title("bazinga"); @@ -1390,7 +1389,7 @@ main :: () { TUI.stop(); } - if 0 { + if 1 { print("TEST : user input\n", to_standard_error = true); auto_release_temp(); TUI.start(); -- cgit v1.2.3 From 26b6d4c48be97e20b25063f136ec978753fead67 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:20:32 +0000 Subject: WIP : Fixing read_input_line. --- modules/TUI/module.jai | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 6aeac68..97a520b 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -187,19 +187,9 @@ to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY } 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; + beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; + hasSomethingElse := (key & (~0xFF)) != 0; + return beginsWithEscape && hasSomethingElse; } Keys :: struct #type_info_none { @@ -215,8 +205,8 @@ Keys :: struct #type_info_none { Up : Key : #run to_key("#up"); Down : Key : #run to_key("#down"); - Right : Key : #run to_key("right"); - Left : Key : #run to_key("left"); + Right : Key : #run to_key("#right"); + Left : Key : #run to_key("#left"); Home : Key : #run to_key("#home"); End : Key : #run to_key("#end"); @@ -412,11 +402,13 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // 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 { + while true { // TODO How to alloc/release temporary memory here? key = get_key(); if key == { + case Keys.Resize; #through; + case Keys.Escape; #through; case Keys.Enter; break; @@ -424,10 +416,16 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if idx == 0 continue; idx -= 1; + case Keys.Home; + idx = 0; + case Keys.Right; if idx == str.count-1 continue; idx += 1; + case Keys.End; + idx = str.count-1; + case Keys.Delete; if idx == str.count-1 continue; for idx..str.count-2 { @@ -442,10 +440,11 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } 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; + if !is_escape_code(key) { + key_str := to_string(key); + str.data[idx] = key_str.data[0]; + idx += 1; + } } -- cgit v1.2.3 From b5b70e2977e2ef6510e2b4e3d117cde25cac7c27 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:36:11 +0000 Subject: Finished base implementation of read_input_line. --- modules/TUI/module.jai | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 97a520b..9ef0a7e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -425,12 +425,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { case Keys.End; idx = str.count-1; + while str[idx] == 0 && idx > 0 { + idx -= 1; + } + idx += 1; case Keys.Delete; if idx == str.count-1 continue; for idx..str.count-2 { str.data[it] = str.data[it+1]; } + str.data[str.count-1] = 0; case Keys.Backspace; if idx == 0 continue; @@ -438,10 +443,15 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for idx..str.count-2 { str.data[it] = str.data[it+1]; } + str.data[str.count-1] = 0; case; + if idx >= str.count continue; if !is_escape_code(key) { key_str := to_string(key); + for < str.count-1..idx { + str.data[it] = str.data[it-1]; + } str.data[idx] = key_str.data[0]; idx += 1; } -- cgit v1.2.3 From 95646a4d1e62fe70461d2ad6adc878442ea464d1 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:38:35 +0000 Subject: Fixed typo on comment. --- modules/TUI/module.jai | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 9ef0a7e..5993676 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -473,7 +473,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* 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. + Escape should discard the input returning an empty string and a Escape 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 ""; -- cgit v1.2.3 From 1ea51da155882a946cc0d9af8e7f90d674682c3b Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:57:38 +0000 Subject: Implemented hidden input on read_input_line. --- modules/TUI/module.jai | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5993676..98afe3d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -389,9 +389,8 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // 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); + str := alloc_string(count_limit); // TODO Alloc like a boss... - // TODO Show blinking cursor write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); row, col := get_cursor_position(); @@ -461,10 +460,19 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // 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); + if is_visible { + set_cursor_position(row, col+idx); + for idx..count_limit print_character(#char " "); + set_cursor_position(row, col); + write_string(str); + } + else { + set_cursor_position(row, col); + for 0..str.count-1 { + char := cast(u8) ifx str[it] != 0 then #char "*" else #char " "; + print_character(char); + } + } // print(">%<", builder_to_string(*builder,, temporary_allocator)); set_cursor_position(row, col+idx); } -- cgit v1.2.3 From 10215fb3b2d5a178b8ab0419d58d30af3abab625 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 29 Mar 2024 01:04:23 +0000 Subject: Fixed cursor control on read_input_line. --- modules/TUI/module.jai | 51 +++++++++++++++++++++++++------------------------- ttt.jai | 10 ++++++---- 2 files changed, 31 insertions(+), 30 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 98afe3d..5fdf3b9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -389,18 +389,18 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // 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 Alloc like a boss... - write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); - - row, col := get_cursor_position(); + str := alloc_string(count_limit); // TODO Alloc like a boss... + str.count = 0; 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 + row, col := get_cursor_position(); + write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); + + key := Keys.None; while true { // TODO How to alloc/release temporary memory here? key = get_key(); @@ -412,29 +412,24 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { break; case Keys.Left; - if idx == 0 continue; - idx -= 1; + if idx > 0 then idx -= 1; + + case Keys.Right; + if idx < str.count then idx += 1; case Keys.Home; idx = 0; - case Keys.Right; - if idx == str.count-1 continue; - idx += 1; - case Keys.End; - idx = str.count-1; - while str[idx] == 0 && idx > 0 { - idx -= 1; - } - idx += 1; + idx = str.count; case Keys.Delete; - if idx == str.count-1 continue; + if idx == str.count continue; for idx..str.count-2 { str.data[it] = str.data[it+1]; } str.data[str.count-1] = 0; + str.count -= 1; case Keys.Backspace; if idx == 0 continue; @@ -443,17 +438,21 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.data[it] = str.data[it+1]; } str.data[str.count-1] = 0; + str.count -= 1; case; - if idx >= str.count continue; - if !is_escape_code(key) { - key_str := to_string(key); - for < str.count-1..idx { - str.data[it] = str.data[it-1]; - } - str.data[idx] = key_str.data[0]; - idx += 1; + if idx >= count_limit continue; + if is_escape_code(key) continue; + + for < count_limit..idx+1 { + str.data[it] = str.data[it-1]; } + + key_str := to_string(key); + str.data[idx] = key_str.data[0]; + + if str.count < count_limit then str.count += 1; + idx += 1; } diff --git a/ttt.jai b/ttt.jai index f0a3a9e..6f0f426 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1396,7 +1396,8 @@ main :: () { TUI.clear_terminal(); 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); + r, c := TUI.get_cursor_position(); + TUI.set_cursor_position(r+1, 1); str, key := TUI.read_input_line(15); TUI.set_cursor_position(3, 1); if key == { @@ -1421,16 +1422,17 @@ main :: () { } // BUG - SECOND TIME CALLING TO read_input_line fails... + // SECOND TIME CALLING TO read_input_line fails... - if 1 { + if 0 { print("TEST : hidden user input\n", to_standard_error = true); auto_release_temp(); TUI.start(); TUI.clear_terminal(); 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); + r, c := TUI.get_cursor_position(); + TUI.set_cursor_position(r+1, 1); str, key := TUI.read_input_line(15, false); TUI.set_cursor_position(3, 1); if key == { -- cgit v1.2.3 From bc5b1e651e17e97177af29762ef7d0558f83db89 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 29 Mar 2024 01:18:46 +0000 Subject: Fixed input echo on read_input_line. --- modules/TUI/module.jai | 9 +++------ ttt.jai | 7 ++----- 2 files changed, 5 insertions(+), 11 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5fdf3b9..9cc7e5d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -460,17 +460,14 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // set_cursor_position(row, col); // write_builder(*builder, false); if is_visible { - set_cursor_position(row, col+idx); - for idx..count_limit print_character(#char " "); set_cursor_position(row, col); write_string(str); + for str.count..count_limit-1 print_character(#char " "); } else { set_cursor_position(row, col); - for 0..str.count-1 { - char := cast(u8) ifx str[it] != 0 then #char "*" else #char " "; - print_character(char); - } + for 0..str.count-1 print_character(#char "*"); + for str.count..count_limit-1 print_character(#char " "); } // print(">%<", builder_to_string(*builder,, temporary_allocator)); set_cursor_position(row, col+idx); diff --git a/ttt.jai b/ttt.jai index 6f0f426..7690c68 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1420,17 +1420,14 @@ main :: () { } TUI.stop(); } - - // BUG - // SECOND TIME CALLING TO read_input_line fails... - if 0 { + if 1 { print("TEST : hidden user input\n", to_standard_error = true); auto_release_temp(); TUI.start(); TUI.clear_terminal(); TUI.set_cursor_position(1, 1); - print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):"); + print("Enter some secret (use Enter to finish, Esc to cancel, or resize to abort):"); r, c := TUI.get_cursor_position(); TUI.set_cursor_position(r+1, 1); str, key := TUI.read_input_line(15, false); -- cgit v1.2.3 From b57c7beee2eb8830cbebdf7593001670e7029327 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 29 Mar 2024 01:26:00 +0000 Subject: First version of read_input_line. --- modules/TUI/module.jai | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 9cc7e5d..796334a 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -386,11 +386,18 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -// TODO UNTESTED read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { + /* + 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 Escape key. + Resize should discard the input returning an empty string and a Resize key. + */ + assert(count_limit >= 0, "Invalid value on count_limit parameter."); - str := alloc_string(count_limit); // TODO Alloc like a boss... + str := alloc_string(count_limit); str.count = 0; idx := 0; @@ -402,7 +409,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { key := Keys.None; while true { - // TODO How to alloc/release temporary memory here? + auto_release_temp(); + key = get_key(); if key == { @@ -455,10 +463,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { idx += 1; } - - // append(*builder, str); - // set_cursor_position(row, col); - // write_builder(*builder, false); if is_visible { set_cursor_position(row, col); write_string(str); @@ -469,18 +473,12 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for 0..str.count-1 print_character(#char "*"); for str.count..count_limit-1 print_character(#char " "); } - // 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 Escape 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; } -- cgit v1.2.3 From 3ea02310f77dcf6aac26152a13a7ec848c78077f Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 31 Mar 2024 23:47:35 +0100 Subject: WIP : Setup TUI styles. --- modules/TUI/module.jai | 87 ++++++++++++++++++++++++++++++++++++++------------ ttt.jai | 10 ++++-- 2 files changed, 73 insertions(+), 24 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 796334a..225b13e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -103,40 +103,77 @@ Commands :: struct { 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")); +Palette_8b :: enum u8 { + 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; } -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; +Color_24b :: struct { + r: u8; + g: u8; + b: u8; } // Fix color tables using this: https://devmemo.io/cheatsheets/terminal_escape_code/ + // TODO Maybe rename. -set_style_colors :: (foreground: u8, background: u8) { +set_style_colors :: (foreground: Palette_8b, background: Palette_8b) { print( #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - foreground, background); + cast(u8)foreground, cast(u8)background); +} + +set_colors_24b :: (foreground_r: Color_24b, background: Color_24b) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + foreground.r, foreground.g, foreground.b, + background.r, background.g, background.b); } // set_colors_24b :: (foreground_r: u255) // TODO https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +Style :: struct { + background: Palette_8b; + foreground: Palette_8b; + bold: bool; + underline: bool; + strike_through: bool; + negative: bool; +} + +test :: () { + start(); + set_cursor_position(2, 2); + set_style_colors(.RED, .BLACK); + print(" RED \n"); + set_style_colors(.MAGENTA, .BLACK); + print(" MAGENTA \n"); + set_style_colors(.TEAL, .BLACK); + print(" TEAL \n"); + sleep_milliseconds(3000); + stop(); +} + + +clear_style :: () { + write_string(#run sprint(Commands.SetGraphicsRendition, "0")); +} + set_style :: (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { print( #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), @@ -386,6 +423,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } +// TODO Provide an advanced read_input_line function that allows some styling and to set a placeholder text. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* Use the get_key to read user input and show it on screen. @@ -401,6 +439,13 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.count = 0; idx := 0; + // placeholder: string = "", + // { + // copy_size := ifx placeholder.count > str.count then str.count else placeholder.count; + // memcpy(str.data, placeholder.data, copy_size); + // idx = copy_size; + // } + // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line diff --git a/ttt.jai b/ttt.jai index 5229914..35a4bc2 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1059,7 +1059,7 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (task == active_task && task == selected_task) { // attron(COLOR_PAIR(xx Styles.ACTIVE_SELECTED) | A_BOLD); TODO DAM - TUI.set_style_colors(TUI.Colors8b.Red, 6); // TODO TESTING COLORS + TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS TUI.set_style(true, true, true, false); } else if (task == selected_task) { @@ -1067,7 +1067,7 @@ draw_tui :: (db: *Database, layout: *Layout) { TUI.set_style_colors(4, 6); // TODO TESTING COLORS } else if (task == active_task) { - TUI.set_style_colors(TUI.Colors8b.Red, 6); // TODO TESTING COLORS + TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS TUI.set_style(false, false, false, true); // TODO TESTING STYLE // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM } @@ -1224,7 +1224,7 @@ prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { TUI.set_cursor_position(row, 2); write_string(TUI.Commands.DrawingMode); - for 0..size_x-2 { + for 1..size_x-2 { print(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); @@ -1236,6 +1236,10 @@ prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { main :: () { + TUI.test(); + exit(0); + + // -- -- -- Testing TUI -- START perform_test := false; -- cgit v1.2.3 From ddb3e4f4192009a47fa094aaad5e57da9b8c2711 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 6 Apr 2024 01:51:16 +0100 Subject: Added styles. --- modules/TUI/module.jai | 110 ++++++++-------- modules/TUI/palette_24b.jai | 44 +++++++ modules/TUI/palette_4b.jai | 19 +++ modules/TUI/palette_8b.jai | 307 ++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 167 +++++++++++++----------- 5 files changed, 514 insertions(+), 133 deletions(-) create mode 100644 modules/TUI/palette_24b.jai create mode 100644 modules/TUI/palette_4b.jai create mode 100644 modules/TUI/palette_8b.jai (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 225b13e..21cd88a 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,3 +1,5 @@ +#module_parameters(COLOR_BIT_DEPTH := 24); + #if OS == { case .LINUX; #load "unix.jai"; @@ -103,84 +105,80 @@ Commands :: struct { QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } -Palette_8b :: enum u8 { - 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; +#if COLOR_BIT_DEPTH == 4 { + #load "palette_4b.jai"; + + set_colors :: inline (foreground: Palette, background: Palette) { + print( + #run sprint("% %", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + cast(u8)foreground + 30, cast(u8)background + 40); + } } +else #if COLOR_BIT_DEPTH == 8 { + #load "palette_8b.jai"; -Color_24b :: struct { - r: u8; - g: u8; - b: u8; + set_colors :: inline (foreground: Palette, background: Palette) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), + cast(u8)foreground, cast(u8)background); + } } +else { + #load "palette_24b.jai"; -// Fix color tables using this: https://devmemo.io/cheatsheets/terminal_escape_code/ - - -// TODO Maybe rename. -set_style_colors :: (foreground: Palette_8b, background: Palette_8b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)foreground, cast(u8)background); -} + Color_24b :: struct { + r: u8; + g: u8; + b: u8; + } -set_colors_24b :: (foreground_r: Color_24b, background: Color_24b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), - foreground.r, foreground.g, foreground.b, - background.r, background.g, background.b); + set_colors :: inline (foreground: Color_24b, background: Color_24b) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + foreground.r, foreground.g, foreground.b, + background.r, background.g, background.b); + } } -// set_colors_24b :: (foreground_r: u255) // TODO https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +#add_context tui_style: Style; Style :: struct { - background: Palette_8b; - foreground: Palette_8b; + #if COLOR_BIT_DEPTH == 4 || COLOR_BIT_DEPTH == 8 { + background: Palette = .BLACK; + foreground: Palette = .WHITE; + } else { + background: Color_24b; + foreground: Color_24b; + } bold: bool; underline: bool; strike_through: bool; negative: bool; } -test :: () { - start(); - set_cursor_position(2, 2); - set_style_colors(.RED, .BLACK); - print(" RED \n"); - set_style_colors(.MAGENTA, .BLACK); - print(" MAGENTA \n"); - set_style_colors(.TEAL, .BLACK); - print(" TEAL \n"); - sleep_milliseconds(3000); - stop(); +set_font_style :: inline (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); } +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; +} clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); } -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); +using_style :: (style: Style) #expand { + __style := context.tui_style; + set_style(style); + `defer set_style(__style); } // TODO Maybe make the OS_* procedures as inline?! diff --git a/modules/TUI/palette_24b.jai b/modules/TUI/palette_24b.jai new file mode 100644 index 0000000..7a263a0 --- /dev/null +++ b/modules/TUI/palette_24b.jai @@ -0,0 +1,44 @@ +// https://www.ditig.com/publications/256-colors-cheat-sheet +Palette :: struct { + BLACK :: Color_24b.{0x00, 0x00, 0x00}; + MAROON :: Color_24b.{0x80, 0x00, 0x00}; + GREEN :: Color_24b.{0x00, 0x80, 0x00}; + OLIVE :: Color_24b.{0x80, 0x80, 0x00}; + NAVY :: Color_24b.{0x00, 0x00, 0x80}; + PURPLE :: Color_24b.{0x80, 0x00, 0x80}; + TEAL :: Color_24b.{0x00, 0x80, 0x80}; + SILVER :: Color_24b.{0xC0, 0xC0, 0xC0}; + GRAY :: Color_24b.{0x80, 0x80, 0x80}; + RED :: Color_24b.{0xFF, 0x00, 0x00}; + LIME :: Color_24b.{0x00, 0xFF, 0x00}; + YELLOW :: Color_24b.{0xFF, 0xFF, 0x00}; + BLUE :: Color_24b.{0x00, 0x00, 0xFF}; + MAGENTA :: Color_24b.{0xFF, 0x00, 0xFF}; + CYAN :: Color_24b.{0x00, 0xFF, 0xFF}; + WHITE :: Color_24b.{0xFF, 0xFF, 0xFF}; + + GRAY_3 :: Color_24b.{0x08, 0x08, 0x08}; + GRAY_7 :: Color_24b.{0x12, 0x12, 0x12}; + GRAY_10 :: Color_24b.{0x1C, 0x1C, 0x1C}; + GRAY_14 :: Color_24b.{0x26, 0x26, 0x26}; + GRAY_18 :: Color_24b.{0x30, 0x30, 0x30}; + GRAY_22 :: Color_24b.{0x3A, 0x3A, 0x3A}; + GRAY_26 :: Color_24b.{0x44, 0x44, 0x44}; + GRAY_30 :: Color_24b.{0x4E, 0x4E, 0x4E}; + GRAY_34 :: Color_24b.{0x58, 0x58, 0x58}; + GRAY_37 :: Color_24b.{0x62, 0x62, 0x62}; + GRAY_40 :: Color_24b.{0x6C, 0x6C, 0x6C}; + GRAY_46 :: Color_24b.{0x76, 0x76, 0x76}; + GRAY_50 :: GRAY; + GRAY_54 :: Color_24b.{0x8A, 0x8A, 0x8A}; + GRAY_58 :: Color_24b.{0x94, 0x94, 0x94}; + GRAY_61 :: Color_24b.{0x9E, 0x9E, 0x9E}; + GRAY_65 :: Color_24b.{0xA8, 0xA8, 0xA8}; + GRAY_69 :: Color_24b.{0xB2, 0xB2, 0xB2}; + GRAY_73 :: Color_24b.{0xBC, 0xBC, 0xBC}; + GRAY_77 :: Color_24b.{0xC6, 0xC6, 0xC6}; + GRAY_81 :: Color_24b.{0xD0, 0xD0, 0xD0}; + GRAY_85 :: Color_24b.{0xDA, 0xDA, 0xDA}; + GRAY_89 :: Color_24b.{0xE4, 0xE4, 0xE4}; + GRAY_93 :: Color_24b.{0xEE, 0xEE, 0xEE}; +} diff --git a/modules/TUI/palette_4b.jai b/modules/TUI/palette_4b.jai new file mode 100644 index 0000000..b0317d2 --- /dev/null +++ b/modules/TUI/palette_4b.jai @@ -0,0 +1,19 @@ +// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit +Palette :: enum u8 { + BLACK :: 0; + MAROON :: 1; + GREEN :: 2; + OLIVE :: 3; + NAVY :: 4; + PURPLE :: 5; + TEAL :: 6; + SILVER :: 7; + GRAY :: 60; + RED :: 61; + LIME :: 62; + YELLOW :: 63; + BLUE :: 64; + MAGENTA :: 65; + CYAN :: 66; + WHITE :: 67; +} diff --git a/modules/TUI/palette_8b.jai b/modules/TUI/palette_8b.jai new file mode 100644 index 0000000..36a512f --- /dev/null +++ b/modules/TUI/palette_8b.jai @@ -0,0 +1,307 @@ +// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +Palette :: enum u8 { + 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; + + + x000000 :: 16; + x00005F :: 17; + x000087 :: 18; + x0000AF :: 19; + x0000D7 :: 20; + x0000FF :: 21; + + x005F00 :: 22; + x005F5F :: 23; + x005F87 :: 24; + x005FAF :: 25; + x005FD7 :: 26; + x005FFF :: 27; + + x008700 :: 28; + x00875F :: 29; + x008787 :: 30; + x0087AF :: 31; + x0087D7 :: 32; + x0087FF :: 33; + + x00AF00 :: 34; + x00AF5F :: 35; + x00AF87 :: 36; + x00AFAF :: 37; + x00AFD7 :: 38; + x00AFFF :: 39; + + x00D700 :: 40; + x00D75F :: 41; + x00D787 :: 42; + x00D7AF :: 43; + x00D7D7 :: 44; + x00D7FF :: 45; + + x00FF00 :: 46; + x00FF5F :: 47; + x00FF87 :: 48; + x00FFAF :: 49; + x00FFD7 :: 50; + x00FFFF :: 51; + + + x5F0000 :: 52; + x5F005F :: 53; + x5F0087 :: 54; + x5F00AF :: 55; + x5F00D7 :: 56; + x5F00FF :: 57; + + x5F5F00 :: 58; + x5F5F5F :: 59; + x5F5F87 :: 60; + x5F5FAF :: 61; + x5F5FD7 :: 62; + x5F5FFF :: 63; + + x5F8700 :: 64; + x5F875F :: 65; + x5F8787 :: 66; + x5F87AF :: 67; + x5F87D7 :: 68; + x5F87FF :: 69; + + x5FAF00 :: 70; + x5FAF5F :: 71; + x5FAF87 :: 72; + x5FAFAF :: 73; + x5FAFD7 :: 74; + x5FAFFF :: 75; + + x5FD700 :: 76; + x5FD75F :: 77; + x5FD787 :: 78; + x5FD7AF :: 79; + x5FD7D7 :: 80; + x5FD7FF :: 81; + + x5FFF00 :: 82; + x5FFF5F :: 83; + x5FFF87 :: 84; + x5FFFAF :: 85; + x5FFFD7 :: 86; + x5FFFFF :: 87; + + + x870000 :: 88; + x87005F :: 89; + x870087 :: 90; + x8700AF :: 91; + x8700D7 :: 92; + x8700FF :: 93; + + x875F00 :: 94; + x875F5F :: 95; + x875F87 :: 96; + x875FAF :: 97; + x875FD7 :: 98; + x875FFF :: 99; + + x878700 :: 100; + x87875F :: 101; + x878787 :: 102; + x8787AF :: 103; + x8787D7 :: 104; + x8787FF :: 105; + + x87AF00 :: 106; + x87AF5F :: 107; + x87AF87 :: 108; + x87AFAF :: 109; + x87AFD7 :: 110; + x87AFFF :: 111; + + x87D700 :: 112; + x87D75F :: 113; + x87D787 :: 114; + x87D7AF :: 115; + x87D7D7 :: 116; + x87D7FF :: 117; + + x87FF00 :: 118; + x87FF5F :: 119; + x87FF87 :: 120; + x87FFAF :: 121; + x87FFD7 :: 122; + x87FFFF :: 123; + + + xAF0000 :: 124; + xAF005F :: 125; + xAF0087 :: 126; + xAF00AF :: 127; + xAF00D7 :: 128; + xAF00FF :: 129; + + xAF5F00 :: 130; + xAF5F5F :: 131; + xAF5F87 :: 132; + xAF5FAF :: 133; + xAF5FD7 :: 134; + xAF5FFF :: 135; + + xAF8700 :: 136; + xAF875F :: 137; + xAF8787 :: 138; + xAF87AF :: 139; + xAF87D7 :: 140; + xAF87FF :: 141; + + xAFAF00 :: 142; + xAFAF5F :: 143; + xAFAF87 :: 144; + xAFAFAF :: 145; + xAFAFD7 :: 146; + xAFAFFF :: 147; + + xAFD700 :: 148; + xAFD75F :: 149; + xAFD787 :: 150; + xAFD7AF :: 151; + xAFD7D7 :: 152; + xAFD7FF :: 153; + + xAFFF00 :: 154; + xAFFF5F :: 155; + xAFFF87 :: 156; + xAFFFAF :: 157; + xAFFFD7 :: 158; + xAFFFFF :: 159; + + + xD70000 :: 160; + xD7005F :: 161; + xD70087 :: 162; + xD700AF :: 163; + xD700D7 :: 164; + xD700FF :: 165; + + xD75F00 :: 166; + xD75F5F :: 167; + xD75F87 :: 168; + xD75FAF :: 169; + xD75FD7 :: 170; + xD75FFF :: 171; + + xD78700 :: 172; + xD7875F :: 173; + xD78787 :: 174; + xD787AF :: 175; + xD787D7 :: 176; + xD787FF :: 177; + + xD7AF00 :: 178; + xD7AF5F :: 179; + xD7AF87 :: 180; + xD7AFAF :: 181; + xD7AFD7 :: 182; + xD7AFFF :: 183; + + xD7D700 :: 184; + xD7D75F :: 185; + xD7D787 :: 186; + xD7D7AF :: 187; + xD7D7D7 :: 188; + xD7D7FF :: 189; + + xD7FF00 :: 190; + xD7FF5F :: 191; + xD7FF87 :: 192; + xD7FFAF :: 193; + xD7FFD7 :: 194; + xD7FFFF :: 195; + + + xFF0000 :: 196; + xFF005F :: 197; + xFF0087 :: 198; + xFF00AF :: 199; + xFF00D7 :: 200; + xFF00FF :: 201; + + xFF5F00 :: 202; + xFF5F5F :: 203; + xFF5F87 :: 204; + xFF5FAF :: 205; + xFF5FD7 :: 206; + xFF5FFF :: 207; + + xFF8700 :: 208; + xFF875F :: 209; + xFF8787 :: 210; + xFF87AF :: 211; + xFF87D7 :: 212; + xFF87FF :: 213; + + xFFAF00 :: 214; + xFFAF5F :: 215; + xFFAF87 :: 216; + xFFAFAF :: 217; + xFFAFD7 :: 218; + xFFAFFF :: 219; + + xFFD700 :: 220; + xFFD75F :: 221; + xFFD787 :: 222; + xFFD7AF :: 223; + xFFD7D7 :: 224; + xFFD7FF :: 225; + + xFFFF00 :: 226; + xFFFF5F :: 227; + xFFFF87 :: 228; + xFFFFAF :: 229; + xFFFFD7 :: 230; + xFFFFFF :: 231; + + + // Grayscale + x080808 :: 232; + x121212 :: 233; + x1C1C1C :: 234; + x262626 :: 235; + x303030 :: 236; + x3A3A3A :: 237; + + x444444 :: 238; + x4E4E4E :: 239; + x585858 :: 240; + x636363 :: 241; + x6C6C6C :: 242; + x767676 :: 243; + + x808080 :: 244; + x8A8A8A :: 245; + x949494 :: 246; + x9E9E9E :: 247; + xA8A8A8 :: 248; + xB2B2B2 :: 249; + + xBCBCBC :: 250; + xC6C6C6 :: 251; + xD0D0D0 :: 252; + xDADADA :: 253; + xE4E4E4 :: 254; + xEEEEEE :: 255; +} diff --git a/ttt.jai b/ttt.jai index 35a4bc2..a07502f 100644 --- a/ttt.jai +++ b/ttt.jai @@ -25,7 +25,7 @@ #import "File_Utilities"; #import "String"; #import "Integer_Saturating_Arithmetic"; -TUI :: #import "TUI"; +TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); // TODO List: @@ -84,13 +84,39 @@ size_y : int; pos_x : int; pos_y : int; -Styles :: enum s16 { - SELECTED :: 1; - SELECTED_INVERTED; - ACTIVE; - ACTIVE_SELECTED; - ERROR; -} +style_default := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.WHITE, +}; + +style_selected := TUI.Style.{ + background = TUI.Palette.CYAN, + foreground = TUI.Palette.BLACK, +}; + +style_selected_inverted := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.CYAN, + bold = true, +}; + +style_active := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.BLUE, + bold = true, +}; + +style_active_selected := TUI.Style.{ + background = TUI.Palette.NAVY, + foreground = TUI.Palette.WHITE, + bold = true, +}; + +style_error := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.RED, + bold = true, +}; Layouts :: enum u8 { NORMAL; @@ -157,7 +183,7 @@ trigger_autosave :: () { show_processing :: () { TUI.set_cursor_position(1, 1); - // TODO Maybe add some color? + TUI.using_style(style_active); write_strings(TUI.Commands.DrawingMode, TUI.Drawings.Diamond, TUI.Commands.TextMode); } @@ -915,22 +941,7 @@ initialize_tui :: () { } } - // TODO DAM TUI.start(); - // stdscr = initscr(); // Start curses mode. TODO DAM - // cbreak(); // Line buffering disabled; pass on everty thing to me. TODO DAM - // keypad(stdscr, true); // I need those nifty F1..F12. TODO DAM - // curs_set(0); // Set cursor invisible. TODO DAM - // noecho(); // Disable echoing input characters. TODO DAM - - // Initialize pairs of colors. - //start_color(); TODO DAM - //use_default_colors(); // Using default (-1) instead of COLOR_BLACK. TODO DAM - //init_pair(xx Styles.SELECTED, COLOR_BLACK, COLOR_CYAN); TODO DAM - //init_pair(xx Styles.SELECTED_INVERTED, COLOR_CYAN, -1); TODO DAM - //init_pair(xx Styles.ACTIVE, COLOR_BLUE, -1); TODO DAM - //init_pair(xx Styles.ACTIVE_SELECTED, COLOR_WHITE, COLOR_BLUE); TODO DAM - //init_pair(xx Styles.ERROR, COLOR_RED, -1); TODO DAM } update_layout :: () { @@ -1015,10 +1026,13 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (idx == now_week_day && active_task != null) { - // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM + TUI.set_style(style_active); } else if (idx == now_week_day) { - // attron(COLOR_PAIR(xx Styles.SELECTED_INVERTED) | A_BOLD); TODO DAM + TUI.set_style(style_selected_inverted); + } + else { + TUI.set_style(style_default); } col = *layout.columns[L_DAYS_IDX + idx]; @@ -1026,9 +1040,6 @@ draw_tui :: (db: *Database, layout: *Layout) { TUI.set_cursor_position(y, x + col.alignment_offset); write_string(col.header); x += col.width; - - // Reset theme. - //attrset(A_NORMAL); TODO DAM } // Headers : total @@ -1058,18 +1069,13 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (task == active_task && task == selected_task) { - // attron(COLOR_PAIR(xx Styles.ACTIVE_SELECTED) | A_BOLD); TODO DAM - TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS - TUI.set_style(true, true, true, false); + TUI.set_style(style_active_selected); } else if (task == selected_task) { - // attron(COLOR_PAIR(xx Styles.SELECTED)); TODO DAM - TUI.set_style_colors(4, 6); // TODO TESTING COLORS + TUI.set_style(style_selected); } else if (task == active_task) { - TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS - TUI.set_style(false, false, false, true); // TODO TESTING STYLE - // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM + TUI.set_style(style_active); } // Task title. @@ -1136,19 +1142,19 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (idx == now_week_day && active_task != null) { - // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM + TUI.set_style(style_active); } else if (idx == now_week_day) { - // attron(COLOR_PAIR(xx Styles.SELECTED_INVERTED) | A_BOLD); TODO DAM + TUI.set_style(style_selected_inverted); + } + else { + TUI.set_style(style_default); } column_width = layout.columns[L_DAYS_IDX + idx].width; total_time = add(total_time, daily_total); print_time(y, x, daily_total, column_width); x += column_width; - - // Reset theme. - //attrset(A_NORMAL); TODO DAM } x += 1; print_time(y, x, total_time, layout.columns[L_TOTAL_IDX].width); @@ -1164,9 +1170,7 @@ free_memory :: () { //reset_temporary_storage(); } -read_input_string :: (row: int, column: int, style: s32, input_limit: int, padding: int = 0) -> value: string, success: bool { - - // TODO We still need to apply the style. +read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) -> value: string, success: bool { // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? @@ -1188,7 +1192,7 @@ read_input_string :: (row: int, column: int, style: s32, input_limit: int, paddi } // Returns success. -read_input_int :: (row: int, style: s32, message: string) -> value: int, success: bool { +read_input_int :: (row: int, message: string) -> value: int, success: bool { // attron(xx style); TODO DAM //move(xx row, 1); TODO DAM //addch(ACS_CKBOARD); TODO DAM @@ -1198,14 +1202,14 @@ read_input_int :: (row: int, style: s32, message: string) -> value: int, success // Get line number. input_pos_x := 1;//getcurx(stdscr); TODO DAM input_width := size_x - input_pos_x; - str := read_input_string(row, input_pos_x, style, input_width); + str := read_input_string(row, input_pos_x, input_width); value, success := parse_int(*str); return value, success; } // Shows message to user and waits for user key press. -prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { +prompt_user_key :: (row: int, message: string) -> TUI.Key { /* assert(message.data != null, ASSERT_NOT_NULL, "message"); attron(xx style); @@ -1236,10 +1240,6 @@ prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { main :: () { - TUI.test(); - exit(0); - - // -- -- -- Testing TUI -- START perform_test := false; @@ -1656,9 +1656,14 @@ main :: () { db := *database; layout := *layouts[Layouts.COMPACT]; + action_style: TUI.Style; + + TUI.flush_input(); TUI.set_next_key(TUI.Keys.Resize); while (true) { + + TUI.set_style(style_default); if (is_terminal_too_small) { INVALID_WINDOW_MESSAGE :: "Terminal is too small: minimum 60x3."; @@ -1679,18 +1684,14 @@ main :: () { // TODO WIP Remove `selected_task` and `active_task` and helper functions. selected_task := get_selected_task(db); active_task := get_active_task(db); - action_style: s32; - error_style: s32; selected_task_row: int; { // TODO Recheck this code. using db; - action_style = A_BOLD | COLOR_PAIR(xx - ifx selected_idx == active_idx && selected_idx != -1 then Styles.ACTIVE - else Styles.SELECTED_INVERTED); - error_style = A_BOLD | COLOR_PAIR(xx Styles.ERROR); - selected_task_row = ifx is_terminal_too_small then 0 - else ifx (selected_idx < 0) then 1 - else (selected_idx % layout_tasks_rows) + NUM_HEADER_ROWS + 1; + action_style = ifx selected_idx == active_idx && selected_idx != -1 then style_active else style_selected_inverted; + + selected_task_row = ifx is_terminal_too_small then 0 + else ifx (selected_idx < 0) then 1 + else (selected_idx % layout_tasks_rows) + NUM_HEADER_ROWS + 1; } if key == { @@ -1736,7 +1737,8 @@ main :: () { case #char "n"; #through; case #char "N"; if is_database_full(db) { - prompt_user_key(selected_task_row, error_style, "Unable to create task: database is full."); + TUI.using_style(style_error); + prompt_user_key(selected_task_row, "Unable to create task: database is full."); continue; } @@ -1762,7 +1764,8 @@ main :: () { // Change task name. // TODO WIPWIPWIP - input := read_input_string(selected_task_row, 1, action_style, Task.name.count, size_x - 2 - Task.name.count); + TUI.using_style(action_style); + input := read_input_string(selected_task_row, 1, Task.name.count, size_x - 2 - Task.name.count); if is_empty_string(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); @@ -1773,16 +1776,18 @@ main :: () { // Reset task timers. case TUI.Keys.Backspace; if (selected_task == null) continue; - - if (prompt_user_key(selected_task_row, action_style, "Press enter to reset task.") == TUI.Keys.Enter) { + + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to reset task.") == TUI.Keys.Enter) { reset_task_times(db, db.selected_idx); trigger_autosave(); } case TUI.Keys.Delete; if (selected_task == null || selected_task == active_task) continue; - - if (prompt_user_key(selected_task_row, action_style, "Press enter to delete task.") == TUI.Keys.Enter) { + + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to delete task.") == TUI.Keys.Enter) { delete_task(db, db.selected_idx); trigger_autosave(); } @@ -1808,7 +1813,8 @@ main :: () { input_pos_x += 1; // Get input string. - input := read_input_string(selected_task_row, input_pos_x, action_style, input_width); // TODO Temp stringzes. + TUI.using_style(action_style); + input := read_input_string(selected_task_row, input_pos_x, input_width); // TODO Temp stringzes. // Abort if input if empty. if is_empty_string(input) continue; @@ -1850,8 +1856,9 @@ main :: () { case #char "m"; #through; case #char "M"; if selected_task == null continue; - - value, success := read_input_int(selected_task_row, action_style, " Move to: "); + + TUI.using_style(action_style); + value, success := read_input_int(selected_task_row, " Move to: "); if success == false continue; move_task(db, db.selected_idx, value-1); // -1 to adjust for zero based index trigger_autosave(); @@ -1860,8 +1867,9 @@ main :: () { case #char "g"; #through; case #char "G"; if selected_task == null continue; - - value, success := read_input_int(selected_task_row, action_style, " Go to: "); + + TUI.using_style(action_style); + value, success := read_input_int(selected_task_row, " Go to: "); if success == false continue; target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1; select_task(db, target_index); @@ -1872,7 +1880,8 @@ main :: () { if selected_task == null continue; if is_database_full(db) { - prompt_user_key(selected_task_row, error_style, "Unable to duplicate task: database is full."); + TUI.using_style(style_error); + prompt_user_key(selected_task_row, "Unable to duplicate task: database is full."); continue; } @@ -1938,7 +1947,8 @@ main :: () { if (db != *archive || selected_task == null) continue; if is_database_full(*database) { - prompt_user_key(selected_task_row, error_style, "Unable to restore task: database is full."); + TUI.using_style(style_error); + prompt_user_key(selected_task_row, "Unable to restore task: database is full."); continue; } @@ -1953,7 +1963,8 @@ main :: () { case #char "s"; #through; case #char "S"; // TODO The initial part should only decide what's the sorting procedure... then we would would all in a single place. - sort_by := prompt_user_key(selected_task_row, action_style, "Sort by (n) name, (1..7) day, or (t) total time."); + TUI.using_style(action_style); + sort_by := prompt_user_key(selected_task_row, "Sort by (n) name, (1..7) day, or (t) total time."); show_processing(); sort_procedure: (a: Task, b: Task) -> s64; @@ -2001,7 +2012,8 @@ main :: () { case #char "w"; #through; case #char "W"; if (db != *database || db.tasks.count <= 0) continue; - if (prompt_user_key(selected_task_row, action_style, "Press enter to archive duplicates and reset all.") != TUI.Keys.Enter) continue; + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to archive duplicates and reset all.") != TUI.Keys.Enter) continue; show_processing(); for db.tasks { @@ -2016,7 +2028,8 @@ main :: () { case #char "c"; #through; case #char "C"; if (db.tasks.count <= 0) continue; - if (prompt_user_key(selected_task_row, action_style, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue; + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue; show_processing(); head_idx := 0; -- cgit v1.2.3 From c8fb1f06ec6f83fc71c2f68bb2fc337e971e25b9 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 6 Apr 2024 02:15:41 +0100 Subject: Improved user input prompts. --- modules/TUI/module.jai | 8 +++++++- ttt.jai | 45 ++++++++++++++++++--------------------------- 2 files changed, 25 insertions(+), 28 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 21cd88a..4e67db5 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -448,7 +448,13 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line row, col := get_cursor_position(); - write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); + write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); + + // Clear line for input. + for 1..count_limit { + print_character(#char " "); + } + set_cursor_position(row, col); key := Keys.None; while true { diff --git a/ttt.jai b/ttt.jai index a07502f..517723d 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1175,8 +1175,6 @@ read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? - // TODO WIPWIPWIP - column += 1; // TODO FIX THE NCURSES INDEXING CHAOS TUI.set_cursor_position(row, column + input_limit); @@ -1193,15 +1191,24 @@ read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) // Returns success. read_input_int :: (row: int, message: string) -> value: int, success: bool { - // attron(xx style); TODO DAM - //move(xx row, 1); TODO DAM - //addch(ACS_CKBOARD); TODO DAM - //addstr(message.data); // TODO Convert to C type TODO DAM - //attrset(A_NORMAL); TODO DAM + TUI.set_cursor_position(row, 2); + write_string(TUI.Commands.DrawingMode); + for 1..size_x-2 { + print(TUI.Drawings.Checkerboard); + } + write_string(TUI.Commands.TextMode); + + TUI.set_cursor_position(row, 3); + write_strings(" ", message, " "); // Get line number. - input_pos_x := 1;//getcurx(stdscr); TODO DAM - input_width := size_x - input_pos_x; + input_pos_x := 1 + 1 + 1 + message.count + 1; + input_width := size_x - input_pos_x - 1; + + style_input := context.tui_style; + style_input.underline = true; + TUI.using_style(style_input); + str := read_input_string(row, input_pos_x, input_width); value, success := parse_int(*str); @@ -1210,22 +1217,6 @@ read_input_int :: (row: int, message: string) -> value: int, success: bool { // Shows message to user and waits for user key press. prompt_user_key :: (row: int, message: string) -> TUI.Key { -/* - assert(message.data != null, ASSERT_NOT_NULL, "message"); - attron(xx style); - move(xx row, 1); - for 0..size_x-3 { - // for (int idx = 0; idx < size_x - 2; idx++) { - addch(ACS_CKBOARD); - } - mvaddstr(xx row, 2, message.data); - attrset(A_NORMAL); - - return getch(); -*/ - - // TODO We still need to apply the style. - TUI.set_cursor_position(row, 2); write_string(TUI.Commands.DrawingMode); for 1..size_x-2 { @@ -1858,7 +1849,7 @@ main :: () { if selected_task == null continue; TUI.using_style(action_style); - value, success := read_input_int(selected_task_row, " Move to: "); + value, success := read_input_int(selected_task_row, "Move to:"); if success == false continue; move_task(db, db.selected_idx, value-1); // -1 to adjust for zero based index trigger_autosave(); @@ -1869,7 +1860,7 @@ main :: () { if selected_task == null continue; TUI.using_style(action_style); - value, success := read_input_int(selected_task_row, " Go to: "); + value, success := read_input_int(selected_task_row, "Go to:"); if success == false continue; target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1; select_task(db, target_index); -- cgit v1.2.3 From 8016c5c04e3d452dab583b2c9c2161a5a950ea65 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 6 Apr 2024 02:31:50 +0100 Subject: Code cleanup. --- modules/TUI/module.jai | 2 +- ttt.jai | 45 +++++++++++++++++++-------------------------- 2 files changed, 20 insertions(+), 27 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 4e67db5..fbf2dd9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -718,7 +718,7 @@ set_terminal_title :: (title: string) { print(Commands.SetWindowTitle, title); } - +// TODO #if OS == .WINDOWS { // Prototyping zone... keep clear! } diff --git a/ttt.jai b/ttt.jai index 517723d..9fa62d3 100644 --- a/ttt.jai +++ b/ttt.jai @@ -27,15 +27,6 @@ #import "Integer_Saturating_Arithmetic"; TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); - -// TODO List: -// [ ] Every time we add or remove tasks to the database, it may be reallocated, thus making the selected_task and active_task pointers invalid. Check if this is messing up the app. -stdscr: *void; // TODO DAM -A_BOLD: s32 = 0; // TODO DAM -COLOR_PAIR :: (a: s32) -> s32 { return 0; } // TODO DAM -WINDOW :: struct { } // TODO DAM - - VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; FIRST_DAY_OF_WEEK :: 1; // (0-6, Sunday = 0). @@ -123,35 +114,36 @@ Layouts :: enum u8 { COMPACT; } -error_window : *WINDOW = null; +// TODO NEXT Implement error window! + error_time_limit := Apollo_Time.{0, 0}; print_error :: (format :string, args : .. Any) { - if stdscr == null { // || isendwin() == true { // Not in ncurses mode? TODO DAM - print(format, ..args); - print("\n"); - } - else { - CHAR_SPACE :: #char " "; - w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; - w_size_y: int = 4; - if (error_window == null) { + // if stdscr == null { // || isendwin() == true { // Not in ncurses mode? TODO DAM + // print(format, ..args); + // print("\n"); + // } + // else { + // CHAR_SPACE :: #char " "; + // w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; + // w_size_y: int = 4; + // if (error_window == null) { //error_window = newwin(w_size_y, w_size_x, (size_y - w_size_y) / 2, (size_x - w_size_x) / 2); TODO DAM //wattron(error_window, COLOR_PAIR(xx Styles.ERROR)); TODO DAM //wborder(error_window, CHAR_SPACE, CHAR_SPACE, 0, 0, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE); TODO DAM //mvwprintw(error_window, 0, 1, " Error "); TODO DAM //wmove(error_window, 1, 0); TODO DAM - } - else { + // } + // else { //waddch(error_window, CHAR_SPACE); TODO DAM - } + // } //wprintw(error_window, temp_c_string(tprint(format, args))); TODO DAM - error_time_limit = current_time_monotonic() + seconds_to_apollo(5); - } + // error_time_limit = current_time_monotonic() + seconds_to_apollo(5); + // } } draw_error_window :: () { - if (error_window == null) return; + // if (error_window == null) return; // Hide error window after time-limit or if terminal is shrank. w_size_x: s32; @@ -162,7 +154,7 @@ draw_error_window :: () { || size_y - w_size_y < 2 ) { //delwin(error_window); TODO DAM - error_window = null; + // error_window = null; return; } @@ -1673,6 +1665,7 @@ main :: () { //timeout(INPUT_AWAIT_INF); TODO DAM // TODO WIP Remove `selected_task` and `active_task` and helper functions. + // TODO Every time we add or remove tasks to the database, it may be reallocated, thus making the selected_task and active_task pointers invalid. Check if this is messing up the app. selected_task := get_selected_task(db); active_task := get_active_task(db); selected_task_row: int; -- cgit v1.2.3 From 194d919eb827e6c6df03917b1732f253e5eac0b5 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 02:46:59 +0100 Subject: Implement error window. --- modules/TUI/module.jai | 32 ++++++++++----------- ttt.jai | 76 +++++++++++++++++++++++--------------------------- 2 files changed, 51 insertions(+), 57 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index fbf2dd9..764effe 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -264,7 +264,7 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -initialized := false; +active := false; //input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! input_buffer : [8] u8; // TODO FIXME Input buffer is too small!!! @@ -277,17 +277,17 @@ input_override : Key; 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."); +assert_is_active :: inline () { + assert(active, "TUI is not ready."); } set_next_key :: (key: Key) { - assert_is_initialized(); + assert_is_active(); input_override = key; } get_key :: (timeout_milliseconds: s32 = -1) -> Key { - assert_is_initialized(); + assert_is_active(); // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte is_utf8_continuation_byte :: inline (byte: u8) -> bool { @@ -371,7 +371,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // TODO Review me! read_input :: (count_limit: int = -1, terminators: .. u8) -> string { - assert_is_initialized(); + assert_is_active(); assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? @@ -533,7 +533,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } start :: () { - if initialized == true return; + if active == true return; setup_key_map(); // TODO This is being called multiple times... please fix me! @@ -551,12 +551,12 @@ start :: () { OS_prepare_terminal(); - initialized = true; + active = true; } stop :: () { - if initialized == false return; - initialized = false; + if active == false return; + active = false; OS_reset_terminal(); write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); @@ -574,7 +574,7 @@ flush_input :: () { // } draw_box :: (x: int, y: int, width: int, height: int) { - assert_is_initialized(); + assert_is_active(); // TODO Check if using a String_Builder improves performance (measure it)! // TODO Validate input parameters against the terminal size. @@ -620,13 +620,13 @@ draw_box :: (x: int, y: int, width: int, height: int) { // TODO Maybe rename to "clear()" clear_terminal :: inline () { - assert_is_initialized(); + assert_is_active(); write_string(Commands.ClearScreen); } // TODO Maybe rename to "get_size()" get_terminal_size :: () -> rows: int, columns: int { - assert_is_initialized(); + assert_is_active(); auto_release_temp(); @@ -674,13 +674,13 @@ get_terminal_size :: () -> rows: int, columns: int { } set_cursor_position :: (row: int, column: int) { - assert_is_initialized(); + assert_is_active(); auto_release_temp(); print(Commands.SetCursorPosition, row, column); } get_cursor_position :: () -> row: int, column: int { - assert_is_initialized(); + assert_is_active(); auto_release_temp(); @@ -714,7 +714,7 @@ get_cursor_position :: () -> row: int, column: int { } set_terminal_title :: (title: string) { - assert_is_initialized(); + assert_is_active(); print(Commands.SetWindowTitle, title); } diff --git a/ttt.jai b/ttt.jai index 9fa62d3..2bf3d28 100644 --- a/ttt.jai +++ b/ttt.jai @@ -114,59 +114,53 @@ Layouts :: enum u8 { COMPACT; } -// TODO NEXT Implement error window! -error_time_limit := Apollo_Time.{0, 0}; +error_message: string; + +error_time_limit := Apollo_Time.{0, 0}; print_error :: (format :string, args : .. Any) { - // if stdscr == null { // || isendwin() == true { // Not in ncurses mode? TODO DAM - // print(format, ..args); - // print("\n"); - // } - // else { - // CHAR_SPACE :: #char " "; - // w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; - // w_size_y: int = 4; - // if (error_window == null) { - //error_window = newwin(w_size_y, w_size_x, (size_y - w_size_y) / 2, (size_x - w_size_x) / 2); TODO DAM - //wattron(error_window, COLOR_PAIR(xx Styles.ERROR)); TODO DAM - //wborder(error_window, CHAR_SPACE, CHAR_SPACE, 0, 0, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE); TODO DAM - //mvwprintw(error_window, 0, 1, " Error "); TODO DAM - //wmove(error_window, 1, 0); TODO DAM - // } - // else { - //waddch(error_window, CHAR_SPACE); TODO DAM - // } - //wprintw(error_window, temp_c_string(tprint(format, args))); TODO DAM - // error_time_limit = current_time_monotonic() + seconds_to_apollo(5); - // } + + if TUI.active == false { + print(format, ..args, to_standard_error = true); + print("\n"); + return; + } + + if error_message.data != null { + free(error_message.data); + } + error_message = sprint(format, args); + + error_time_limit = current_time_monotonic() + seconds_to_apollo(5); } draw_error_window :: () { - // if (error_window == null) return; + if error_time_limit < current_time_monotonic() return; - // Hide error window after time-limit or if terminal is shrank. - w_size_x: s32; - w_size_y: s32; - //getmaxyx(error_window, *w_size_y, *w_size_x); TODO DAM + // Don't show error if main window is too small. + w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; + w_size_y: int = 3; if (current_time_monotonic() >= error_time_limit || size_x - w_size_x < 2 || size_y - w_size_y < 2 ) { - //delwin(error_window); TODO DAM - // error_window = null; return; } - // Adjust error window position. - pos_x := (size_x - w_size_x) / 2; - pos_y := (size_y - w_size_y) / 2; - //mvwin(error_window, pos_y, pos_x); TODO DAM - - // Avoid being overwritten by main window content. - //refresh(); TODO DAM - //touchwin(error_window); TODO DAM - //wrefresh(error_window); TODO DAM + pos_x := 1 + (size_x - w_size_x) / 2; + pos_y := 1 + (size_y - w_size_y) / 2; + + TUI.using_style(style_error); + TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y); + TUI.set_cursor_position(pos_y, pos_x + 1); + write_string(" Error "); + TUI.set_cursor_position(pos_y + 1, pos_x + 1); + for 1..w_size_x-2 { + print_character(#char " "); + } + TUI.set_cursor_position(pos_y + 1, pos_x + 1); + write_string(error_message); } trigger_autosave :: () { @@ -1857,8 +1851,8 @@ main :: () { if success == false continue; target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1; select_task(db, target_index); - - // Delete. + + // Duplicate. case #char "d"; #through; case #char "D"; if selected_task == null continue; -- cgit v1.2.3 From 7ddda0dac4b75adc30799eb1a36a84ecc1e41a86 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 04:14:51 +0100 Subject: Replaced row/column arguments by x/y which are more familiar. --- modules/TUI/module.jai | 34 +++++++------- ttt.jai | 118 ++++++++++++++++++++++++------------------------- 2 files changed, 76 insertions(+), 76 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 764effe..5b7861e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -447,14 +447,14 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line - row, col := get_cursor_position(); + x, y := get_cursor_position(); write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); // Clear line for input. for 1..count_limit { print_character(#char " "); } - set_cursor_position(row, col); + set_cursor_position(x, y); key := Keys.None; while true { @@ -513,17 +513,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } if is_visible { - set_cursor_position(row, col); + set_cursor_position(x, y); write_string(str); for str.count..count_limit-1 print_character(#char " "); } else { - set_cursor_position(row, col); + set_cursor_position(x, y); for 0..str.count-1 print_character(#char "*"); for str.count..count_limit-1 print_character(#char " "); } - set_cursor_position(row, col+idx); + set_cursor_position(x+idx, y); } write_strings(Commands.StopBlinking, Commands.DefaultShape); @@ -535,6 +535,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () { if active == true return; + // TODO Should start() call flush_input internally? + setup_key_map(); // TODO This is being called multiple times... please fix me! input_string.data = input_buffer.data; @@ -625,7 +627,7 @@ clear_terminal :: inline () { } // TODO Maybe rename to "get_size()" -get_terminal_size :: () -> rows: int, columns: int { +get_terminal_size :: () -> width: int, height: int { assert_is_active(); auto_release_temp(); @@ -659,27 +661,25 @@ get_terminal_size :: () -> rows: int, columns: int { rows = parse_int(*parts[1]); columns = parse_int(*parts[2]); } - // Some systems don't allow to query the terminal size directly. + // Some systems don't allow to query the terminal size directly... or the answer takes too much time. // 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); + x, y := get_cursor_position(); + defer set_cursor_position(x, y); set_cursor_position(0xFFFF, 0xFFFF); - rows, columns = get_cursor_position(); + columns, rows = get_cursor_position(); } - return rows, columns; + return columns, rows; } -set_cursor_position :: (row: int, column: int) { +set_cursor_position :: (x: int, y: int) { assert_is_active(); - auto_release_temp(); - print(Commands.SetCursorPosition, row, column); + print(Commands.SetCursorPosition, y, x); } -get_cursor_position :: () -> row: int, column: int { +get_cursor_position :: () -> x: int, y: int { assert_is_active(); auto_release_temp(); @@ -710,7 +710,7 @@ get_cursor_position :: () -> row: int, column: int { parts := split(input, ";",, temporary_allocator); row := parse_int(*parts[0]); column := parse_int(*parts[1]); - return row, column; + return column, row; } set_terminal_title :: (title: string) { diff --git a/ttt.jai b/ttt.jai index 8c8d31c..88f1323 100644 --- a/ttt.jai +++ b/ttt.jai @@ -153,13 +153,13 @@ draw_error_window :: () { TUI.using_style(style_error); TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y); - TUI.set_cursor_position(pos_y, pos_x + 1); + TUI.set_cursor_position(pos_x + 1, pos_y); write_string(" Error "); - TUI.set_cursor_position(pos_y + 1, pos_x + 1); + TUI.set_cursor_position(pos_x + 1, pos_y + 1); for 1..w_size_x-2 { print_character(#char " "); } - TUI.set_cursor_position(pos_y + 1, pos_x + 1); + TUI.set_cursor_position(pos_x + 1, pos_y + 1); write_string(error_message); } @@ -272,7 +272,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { } } - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); if time < 0 { print_padding(left_padding); @@ -980,13 +980,13 @@ draw_tui :: (db: *Database, layout: *Layout) { for 0..layout.columns.count-2 { column := layout.columns[it]; x += 1 + column.width; - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); write_string(TUI.Drawings.TeeT); for row: 2..size_y { - TUI.set_cursor_position(row, x); + TUI.set_cursor_position(x, row); write_string(TUI.Drawings.LineV); } - TUI.set_cursor_position(size_y, x); + TUI.set_cursor_position(x, size_y); write_string(TUI.Drawings.TeeB); } write_string(TUI.Commands.TextMode); @@ -1001,7 +1001,7 @@ draw_tui :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TITLE_IDX]; //mvaddstr(xx y, xx (x + col.alignment_offset), ifx db == *archive then layout.archive_title.data else col.header.data); TODO DAM - TUI.set_cursor_position(y, x + col.alignment_offset); + TUI.set_cursor_position(x + col.alignment_offset, y); write_string(ifx db == *archive then layout.archive_title else col.header); x += col.width; @@ -1022,7 +1022,7 @@ draw_tui :: (db: *Database, layout: *Layout) { } col = *layout.columns[L_DAYS_IDX + idx]; //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM - TUI.set_cursor_position(y, x + col.alignment_offset); + TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); x += col.width; } @@ -1032,7 +1032,7 @@ draw_tui :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TOTAL_IDX]; //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM - TUI.set_cursor_position(y, x + col.alignment_offset); + TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); @@ -1069,14 +1069,14 @@ draw_tui :: (db: *Database, layout: *Layout) { column_width = layout.columns[L_TITLE_IDX].width; // mvprintw(xx y, xx x, "%-*.*s", column_width, column_width, temp_c_string(xx task.name)); //task.name); TODO Fix required for LLVM/Cncurses. TODO DAM // TODO FIXME OH MY GOD SUCH BAD CODEZ - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); white_spaces := column_width; // FIXME Improve by using buffer instead of printing one char at the time. while white_spaces > 0 { print_character(#char " "); white_spaces -= 1; } - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); task_name: string = cast(string)task.name; task_name.count = ifx task_name.count > column_width then column_width else task_name.count; print("%", task_name); @@ -1107,7 +1107,7 @@ draw_tui :: (db: *Database, layout: *Layout) { /////////////////////////////////////////////////////////////////////////// // Draw selected/total tasks. size := 1 + count_digits(db.selected_idx + 1) + 1 + count_digits(db.tasks.count) + 1; // " XXX/YYY " - TUI.set_cursor_position(size_y, 2); + TUI.set_cursor_position(2, size_y); if (size <= layout.columns[L_TITLE_IDX].width) { print(" %/% ", db.selected_idx + 1, db.tasks.count); } @@ -1157,61 +1157,64 @@ free_memory :: () { //reset_temporary_storage(); } -read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) -> value: string, success: bool { +read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> value: string, success: bool { // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? - column += 1; // TODO FIX THE NCURSES INDEXING CHAOS - - TUI.set_cursor_position(row, column + input_limit); + TUI.set_cursor_position(x + input_limit, y); write_string(TUI.Commands.DrawingMode); for 1..padding { write_string(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); - TUI.set_cursor_position(row, column); + TUI.set_cursor_position(x, y); value, key := TUI.read_input_line(input_limit); return value, key == TUI.Keys.Enter; } // Returns success. -read_input_int :: (row: int, message: string) -> value: int, success: bool { - TUI.set_cursor_position(row, 2); +read_input_int :: (y: int, message: string) -> value: int, success: bool { + x :: 3; + + // Draw checkerboard. + TUI.set_cursor_position(2, y); write_string(TUI.Commands.DrawingMode); - for 1..size_x-2 { + for 2..x { print(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); - TUI.set_cursor_position(row, 3); + TUI.set_cursor_position(x, y); write_strings(" ", message, " "); - - // Get line number. - input_pos_x := 1 + 1 + 1 + message.count + 1; - input_width := size_x - input_pos_x - 1; + input_pos_x := x + message.count + 2; + input_width := size_x - input_pos_x - 1; + style_input := context.tui_style; style_input.underline = true; TUI.using_style(style_input); - str := read_input_string(row, input_pos_x, input_width); + str := read_input_string(input_pos_x, y, input_width); value, success := parse_int(*str); return value, success; } // Shows message to user and waits for user key press. -prompt_user_key :: (row: int, message: string) -> TUI.Key { - TUI.set_cursor_position(row, 2); +prompt_user_key :: (y: int, message: string) -> TUI.Key { + x :: 3; + + // Draw checkerboard. + TUI.set_cursor_position(2, y); write_string(TUI.Commands.DrawingMode); for 1..size_x-2 { print(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); - TUI.set_cursor_position(row, 3); + TUI.set_cursor_position(x, y); write_strings(" ", message, " "); return TUI.get_key(); } @@ -1234,19 +1237,19 @@ main :: () { } next_line :: inline () { - r, c := TUI.get_cursor_position(); - TUI.set_cursor_position(r+1, 1); + x, y := TUI.get_cursor_position(); + TUI.set_cursor_position(1, y+1); } if perform_test && 1 { print("TEST : set and get cursor position\n", to_standard_error = true); TUI.start(); - ROW :: 3; - COLUMN :: 3; - TUI.set_cursor_position(ROW, COLUMN); - row, column := TUI.get_cursor_position(); + X :: 2; + Y :: 3; + TUI.set_cursor_position(X, Y); + x, y := TUI.get_cursor_position(); TUI.stop(); - assert_result(row == ROW && column == COLUMN, "Failed set/get cursor position.\n"); + assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); } if perform_test && 1 { @@ -1278,7 +1281,7 @@ main :: () { if perform_test && 1 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); - TUI.start(); // TODO Should start() call flush_input internally? + TUI.start(); TUI.flush_input(); TUI.clear_terminal(); TUI.draw_box(1, 2, 5, 3); @@ -1294,9 +1297,9 @@ main :: () { auto_release_temp(); TUI.start(); TUI.clear_terminal(); - rows, columns := TUI.get_terminal_size(); + width, height := TUI.get_terminal_size(); TUI.set_cursor_position(1, 1); - print("Is terminal size % columns and % rows? (y/n)", columns, rows); + 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(); @@ -1328,9 +1331,9 @@ main :: () { key: TUI.Key = #char "d"; last_none_char := "X"; - size_r, size_c := TUI.get_terminal_size(); + width, height := TUI.get_terminal_size(); TUI.clear_terminal(); - TUI.draw_box(1, 1, size_c, size_r); + TUI.draw_box(1, 1, width, height); drop_down := 0; while(key != #char "q") { @@ -1344,14 +1347,14 @@ main :: () { case TUI.Keys.Resize; #through; case #char "c"; { - size_r, size_c = TUI.get_terminal_size(); + width, height = TUI.get_terminal_size(); TUI.clear_terminal(); - TUI.draw_box(1, 1, size_c, size_r); + TUI.draw_box(1, 1, width, height); drop_down = 0; } case; { - TUI.set_cursor_position(3+drop_down, 2); + TUI.set_cursor_position(2, 3+drop_down); str := TUI.to_string(key); array_to_print: [..] string; for 0..str.count-1 { @@ -1371,12 +1374,11 @@ main :: () { } } - - x := ifx size_r > 1 then size_r-1 else 1; - y := ifx size_c > 24 then size_c-24 else 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(CxR): %x%\n", size_c, size_r); + print("size = %x%\n", width, height); key = TUI.get_key(1000); // __mark := get_temporary_storage_mark(); @@ -1395,7 +1397,7 @@ main :: () { 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(3, 1); + TUI.set_cursor_position(1, 3); error_message: string; if key == { case TUI.Keys.Escape; { @@ -1427,7 +1429,7 @@ main :: () { 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(3, 1); + TUI.set_cursor_position(1, 3); error_message: string; if key == { case TUI.Keys.Escape; { @@ -1645,7 +1647,7 @@ main :: () { if (is_terminal_too_small) { INVALID_WINDOW_MESSAGE :: "Terminal is too small: minimum 60x3."; - TUI.set_cursor_position(size_y / 2, (size_x - INVALID_WINDOW_MESSAGE.count) / 2); + TUI.set_cursor_position((size_x - INVALID_WINDOW_MESSAGE.count) / 2, size_y / 2); write_strings(INVALID_WINDOW_MESSAGE); } else { @@ -1691,7 +1693,7 @@ main :: () { // When terminal is resized. case TUI.Keys.Resize; TUI.clear_terminal(); - size_y, size_x = TUI.get_terminal_size(); + size_x, size_y = TUI.get_terminal_size(); is_terminal_too_small = size_x < 60 || size_y < 3; update_layout(); layout = *layouts[ifx size_x > 100 then Layouts.NORMAL else Layouts.COMPACT]; @@ -1742,9 +1744,8 @@ main :: () { if (selected_task == null) continue; // Change task name. - // TODO WIPWIPWIP TUI.using_style(action_style); - input := read_input_string(selected_task_row, 1, Task.name.count, size_x - 2 - Task.name.count); + input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count); if is_empty_string(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); @@ -1784,16 +1785,15 @@ main :: () { // Prepare position to input time operation. selected_day := cast(int)(key - #char "1"); // TODO DAM this cast... input_width := layout.columns[L_DAYS_IDX + selected_day].width; - input_pos_x := 1 + layout.columns[L_TITLE_IDX].width; - + input_pos_x := 2 + layout.columns[L_TITLE_IDX].width; for 0..selected_day-1 { input_pos_x += 1 + layout.columns[L_DAYS_IDX + it].width; } input_pos_x += 1; - + // Get input string. TUI.using_style(action_style); - input := read_input_string(selected_task_row, input_pos_x, input_width); // TODO Temp stringzes. + input := read_input_string(input_pos_x, selected_task_row, input_width); // TODO Temp stringzes. // Abort if input if empty. if is_empty_string(input) continue; -- cgit v1.2.3 From 6aed607156f476937bc18a80540e2cb2e8c212ec Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 04:26:46 +0100 Subject: Add/remove TODO entries. --- modules/TUI/module.jai | 4 +++- ttt.jai | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5b7861e..5f143dd 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -421,7 +421,6 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -// TODO Provide an advanced read_input_line function that allows some styling and to set a placeholder text. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* Use the get_key to read user input and show it on screen. @@ -498,6 +497,9 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.count -= 1; case; + + // TODO FIXME Does not support UTF8 input... + if idx >= count_limit continue; if is_escape_code(key) continue; diff --git a/ttt.jai b/ttt.jai index 88f1323..364d161 100644 --- a/ttt.jai +++ b/ttt.jai @@ -966,7 +966,6 @@ draw_tui :: (db: *Database, layout: *Layout) { now_week_day := to_calendar(now_utc, .LOCAL).day_of_week_starting_at_0; // Reset theme and clear screen. - //attrset(A_NORMAL); TODO DAM TUI.clear_terminal(); // Draw outer border. @@ -1000,7 +999,6 @@ draw_tui :: (db: *Database, layout: *Layout) { // Headers : title x += 1; col = *layout.columns[L_TITLE_IDX]; - //mvaddstr(xx y, xx (x + col.alignment_offset), ifx db == *archive then layout.archive_title.data else col.header.data); TODO DAM TUI.set_cursor_position(x + col.alignment_offset, y); write_string(ifx db == *archive then layout.archive_title else col.header); x += col.width; @@ -1021,7 +1019,6 @@ draw_tui :: (db: *Database, layout: *Layout) { TUI.set_style(style_default); } col = *layout.columns[L_DAYS_IDX + idx]; - //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); x += col.width; @@ -1031,7 +1028,6 @@ draw_tui :: (db: *Database, layout: *Layout) { // Headers : total x += 1; col = *layout.columns[L_TOTAL_IDX]; - //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); @@ -1099,7 +1095,6 @@ draw_tui :: (db: *Database, layout: *Layout) { print_time(y, x, total_time, layout.columns[L_TOTAL_IDX].width); // Reset theme. - //attrset(A_NORMAL); TODO DAM TUI.clear_style(); } -- cgit v1.2.3 From a3cf506defb4a01759db6f920a363e23de63e984 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 04:35:43 +0100 Subject: Fixed input width. --- modules/TUI/module.jai | 2 +- ttt.jai | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5f143dd..bda326b 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,4 @@ -#module_parameters(COLOR_BIT_DEPTH := 24); +#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid. #if OS == { case .LINUX; diff --git a/ttt.jai b/ttt.jai index 364d161..414ab8c 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1164,6 +1164,11 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val } write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); + + style_input := context.tui_style; + style_input.underline = true; + TUI.using_style(style_input); + value, key := TUI.read_input_line(input_limit); return value, key == TUI.Keys.Enter; @@ -1185,11 +1190,7 @@ read_input_int :: (y: int, message: string) -> value: int, success: bool { write_strings(" ", message, " "); input_pos_x := x + message.count + 2; - input_width := size_x - input_pos_x - 1; - - style_input := context.tui_style; - style_input.underline = true; - TUI.using_style(style_input); + input_width := size_x - input_pos_x; str := read_input_string(input_pos_x, y, input_width); -- cgit v1.2.3 From 353d8b1145db12ffc42e4f6c2148a848ef8bba84 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 23:23:06 +0100 Subject: Preparing for UTF8 support on read_input_line. --- modules/TUI/module.jai | 18 ++----------- modules/UTF8.jai | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 63 +------------------------------------------- 3 files changed, 74 insertions(+), 78 deletions(-) create mode 100644 modules/UTF8.jai (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index bda326b..e7dc21a 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -14,6 +14,7 @@ #import "Basic"; #import "String"; #import "Thread"; +#import "UTF8"; #load "key_map.jai"; // Special Graphics Characters @@ -289,21 +290,6 @@ set_next_key :: (key: Key) { get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); - // 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; @@ -421,7 +407,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { +read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: string = "") -> string, Key { /* 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. diff --git a/modules/UTF8.jai b/modules/UTF8.jai new file mode 100644 index 0000000..fac1326 --- /dev/null +++ b/modules/UTF8.jai @@ -0,0 +1,71 @@ +// 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; +} + +// Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. +// Truncation is done by zeroing the tail of the string in place. +// Returns length of truncated string. +truncate_string :: (str: string, length: int) -> length: int { + if str.data == null then return -1; + + if str.count < length then length = str.count; + + data := str.data; + count := str.count; + + // Find index of first continuation byte. + idx := length; + // while (idx > 0 && ((data[idx - 1] & 0xC0) == 0x80)) { TODO REMOVE AFTER TESTING + while (idx > 0 && is_utf8_continuation_byte(data[idx - 1])) { + idx -= 1; + } + continuation_bytes := length - idx; + + // If string starts with continuation bytes, it's an invalid UTF8 string. + if (idx == 0 && continuation_bytes > 0) { + length = 0; + } + // If length truncates some continuation bytes, remove incomplete UTF8 character. + else if (idx > 0 // string is not empty + // continuation bytes are not complete + && !(continuation_bytes == 0 && (data[idx - 1] & 0x80) == 0x00) + && !(continuation_bytes == 1 && (data[idx - 1] & 0xE0) == 0xC0) + && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) + && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) + ) { + length -= (continuation_bytes + 1); // Remove start byte, ence '+ 1'. + } + + memset(data + length, 0, count - length); + return length; +} + +// Returns true when the string is empty or consists of space characters. +is_empty_string :: (str: string) -> bool { + for 0..str.count-1 { + if str[it] == { + case #char "\0"; #through; + case #char "\t"; #through; // horizontal tab + case #char "\n"; #through; // line feed + case #char "\x0B"; #through; // vertical tabulation + case #char "\x0C"; #through; // form feed + case #char "\r"; #through; // carriage return + case #char " "; + continue; + case; + return false; + } + } + return true; +} diff --git a/ttt.jai b/ttt.jai index 414ab8c..43a972f 100644 --- a/ttt.jai +++ b/ttt.jai @@ -25,6 +25,7 @@ #import "File_Utilities"; #import "String"; #import "Integer_Saturating_Arithmetic"; +#import "UTF8"; TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); VERSION :: "2.0"; // Use only 3 chars (to fit layouts). @@ -189,68 +190,6 @@ count_digits :: (number: s64, base: s64 = 10) -> s64 { return digits; } -Text_Encoding :: enum u8 #specified { - ASCII :: 1; - UTF8 :: 2; -} - -// Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. -// Truncation is done by zeroing the tail of the string in place. -// Returns length of truncated string. -truncate_string :: (str: string, length: s64, $encoding: Text_Encoding = .UTF8) -> length: s64 { - assert(str.data != null, ASSERT_NOT_NULL, "str"); - assert(str.count >= length, "'str.count' should be equal or greater to 'length'."); - - data := str.data; - count := str.count; - - #if encoding == .UTF8 { - // Find index of first continuation byte. - idx := length; - while (idx > 0 && ((data[idx - 1] & 0xC0) == 0x80)) { - idx -= 1; - } - continuation_bytes := length - idx; - - // If string starts with continuation bytes, it's an invalid UTF8 string. - if (idx == 0 && continuation_bytes > 0) { - length = 0; - } - // If length truncates some continuation bytes, remove incomplete UTF8 character. - else if (idx > 0 // string is not empty - // continuation bytes are not complete - && !(continuation_bytes == 0 && (data[idx - 1] & 0x80) == 0x00) - && !(continuation_bytes == 1 && (data[idx - 1] & 0xE0) == 0xC0) - && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) - && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) - ) { - length -= (continuation_bytes + 1); // Remove start byte, ence '+ 1'. - } - } - - memset(data + length, 0, count - length); - return length; -} - -// Returns true when the string is empty or consists of space characters. -is_empty_string :: (str: string) -> bool { - for 0..str.count-1 { - if str[it] == { - case #char "\0"; #through; - case #char "\t"; #through; // horizontal tab - case #char "\n"; #through; // line feed - case #char "\x0B"; #through; // vertical tabulation - case #char "\x0C"; #through; // form feed - case #char "\r"; #through; // carriage return - case #char " "; - continue; - case; - return false; - } - } - return true; -} - // Prints, on row y and column x, the time using 5 characters centered on space. // Returns the result of a call to mvprintw. print_time :: (y: int, x: int, time: s64, space: int) -> int { -- cgit v1.2.3 From 3491fff478a7ebf7e9281595230528726828f48c Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 9 Apr 2024 15:13:26 +0100 Subject: Clear style when stoping TUI. --- modules/TUI/module.jai | 3 ++- ttt.jai | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index e7dc21a..e0bd9b4 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -547,7 +547,8 @@ start :: () { stop :: () { if active == false return; active = false; - + + clear_style(); OS_reset_terminal(); write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); } diff --git a/ttt.jai b/ttt.jai index 43a972f..7fdd9af 100644 --- a/ttt.jai +++ b/ttt.jai @@ -26,7 +26,7 @@ #import "String"; #import "Integer_Saturating_Arithmetic"; #import "UTF8"; -TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); +TUI :: #import "TUI"(COLOR_BIT_DEPTH=4); VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; @@ -99,7 +99,7 @@ style_active := TUI.Style.{ }; style_active_selected := TUI.Style.{ - background = TUI.Palette.NAVY, + background = TUI.Palette.BLUE, foreground = TUI.Palette.WHITE, bold = true, }; -- cgit v1.2.3 From 52c375a02e663a87140ef34f140de16c71f7af5f Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 11 Apr 2024 02:46:03 +0100 Subject: Added UTF8 support to read_input_line. --- modules/TUI/module.jai | 117 +++++++++++++++++++++---------------------------- modules/UTF8.jai | 48 +++++++++++++++++++- ttt.jai | 2 +- 3 files changed, 97 insertions(+), 70 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index e0bd9b4..2d23bff 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,4 @@ -#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid. +#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid... and maybe rename to: COLOR_MODE with a enum. #if OS == { case .LINUX; @@ -111,7 +111,7 @@ Commands :: struct { set_colors :: inline (foreground: Palette, background: Palette) { print( - #run sprint("% %", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), cast(u8)foreground + 30, cast(u8)background + 40); } } @@ -407,13 +407,13 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: string = "") -> string, Key { +read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* - 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 Escape key. - Resize should discard the input returning an empty string and a Resize key. + Uses the get_key to read user input and show it on screen. + Allows to move the cursor left and right and to delete/backspace. + Enter ends the input, returning the input string and the Enter key. + Escape discards the input returning an empty string and a Escape key. + Resize discards the input returning an empty string and a Resize key. */ assert(count_limit >= 0, "Invalid value on count_limit parameter."); @@ -421,32 +421,34 @@ read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: stri str := alloc_string(count_limit); str.count = 0; idx := 0; - - // placeholder: string = "", - // { - // copy_size := ifx placeholder.count > str.count then str.count else placeholder.count; - // memcpy(str.data, placeholder.data, copy_size); - // idx = copy_size; - // } - + // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line x, y := get_cursor_position(); write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); - // Clear line for input. - for 1..count_limit { - print_character(#char " "); - } - set_cursor_position(x, y); - key := Keys.None; while true { auto_release_temp(); - - key = get_key(); + chars_count := count_characters(str); + + // Preview input. + if is_visible { + set_cursor_position(x, y); + write_string(str); + for chars_count..count_limit-1 print_character(#char " "); + } + else { + set_cursor_position(x, y); + for 0..chars_count print_character(#char "*"); + for chars_count..count_limit-1 print_character(#char " "); + } + set_cursor_position(x+idx, y); + + // Process input key. + key = get_key(); if key == { case Keys.Resize; #through; case Keys.Escape; #through; @@ -455,63 +457,47 @@ read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: stri case Keys.Left; if idx > 0 then idx -= 1; - + case Keys.Right; - if idx < str.count then idx += 1; - + if idx < chars_count then idx += 1; + case Keys.Home; idx = 0; case Keys.End; - idx = str.count; + idx = chars_count; case Keys.Delete; - if idx == str.count continue; - for idx..str.count-2 { - str.data[it] = str.data[it+1]; - } - str.data[str.count-1] = 0; - str.count -= 1; - + if idx == chars_count continue; + delete_character(*str, idx); + case Keys.Backspace; if idx == 0 continue; idx -= 1; - for idx..str.count-2 { - str.data[it] = str.data[it+1]; - } - str.data[str.count-1] = 0; - str.count -= 1; - + delete_character(*str, idx); + case; - - // TODO FIXME Does not support UTF8 input... - - if idx >= count_limit continue; if is_escape_code(key) continue; + + buff_idx := map_character_to_buffer_idx(str, idx); + key_str := to_string(key); - for < count_limit..idx+1 { - str.data[it] = str.data[it-1]; + // 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; + + // Move text to allow inserting new character. + for < count_limit..buff_idx+key_str.count { + str.data[it] = str.data[it-key_str.count]; } - key_str := to_string(key); - str.data[idx] = key_str.data[0]; + memcpy(*str.data[buff_idx], key_str.data, key_str.count); - if str.count < count_limit then str.count += 1; + if str.count < count_limit then str.count += key_str.count; idx += 1; + + // Truncate string to avoid incomplete utf8 codes on the string tail. + str.count = truncate_string(str, count_limit); } - - if is_visible { - set_cursor_position(x, y); - write_string(str); - for str.count..count_limit-1 print_character(#char " "); - } - else { - set_cursor_position(x, y); - for 0..str.count-1 print_character(#char "*"); - for str.count..count_limit-1 print_character(#char " "); - } - - set_cursor_position(x+idx, y); } write_strings(Commands.StopBlinking, Commands.DefaultShape); @@ -559,11 +545,6 @@ flush_input :: () { 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_active(); diff --git a/modules/UTF8.jai b/modules/UTF8.jai index fac1326..eba4585 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -1,4 +1,5 @@ // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte +// TODO Maybe rename to: is_continuation_byte is_utf8_continuation_byte :: inline (byte: u8) -> bool { return (byte & 0xC0) == 0x80; } @@ -6,6 +7,7 @@ is_utf8_continuation_byte :: inline (byte: u8) -> bool { // 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 +// TODO Maybe rename to: count_character_bytes count_utf8_bytes :: inline (byte: u8) -> int { if (byte & 0xE0) == 0xC0 return 1+1; if (byte & 0xF0) == 0xE0 return 1+2; @@ -16,6 +18,7 @@ count_utf8_bytes :: inline (byte: u8) -> int { // Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. // Truncation is done by zeroing the tail of the string in place. // Returns length of truncated string. +// TODO Maybe rename to: truncate truncate_string :: (str: string, length: int) -> length: int { if str.data == null then return -1; @@ -26,7 +29,6 @@ truncate_string :: (str: string, length: int) -> length: int { // Find index of first continuation byte. idx := length; - // while (idx > 0 && ((data[idx - 1] & 0xC0) == 0x80)) { TODO REMOVE AFTER TESTING while (idx > 0 && is_utf8_continuation_byte(data[idx - 1])) { idx -= 1; } @@ -48,10 +50,12 @@ truncate_string :: (str: string, length: int) -> length: int { } memset(data + length, 0, count - length); + // str.count = length; TODO We should be doing this... return length; } // Returns true when the string is empty or consists of space characters. +// TODO Maybe rename to: is_empty is_empty_string :: (str: string) -> bool { for 0..str.count-1 { if str[it] == { @@ -69,3 +73,45 @@ is_empty_string :: (str: string) -> bool { } return true; } + +// Counts number of characters in string. +count_characters :: (str: string) -> int { + characters := 0; + idx := 0; + while idx < str.count { + idx += count_utf8_bytes(str[idx]); + characters += 1; + } + return characters; +} + +// Delete character. +delete_character :: (str: *string, character_idx: int) { + buffer_idx := map_character_to_buffer_idx(str.*, character_idx); + bytes_to_delete := count_utf8_bytes(str.data[buffer_idx]); + + for buffer_idx..str.count-1-bytes_to_delete { + str.data[it] = str.data[it+bytes_to_delete]; + } + for str.count-bytes_to_delete..str.count-1 { + str.data[it] = 0; + } + + str.count -= bytes_to_delete; +} + +// Get character index. +// TODO Maybe rename to: map_character_to_byte_idx or get_character_byte_idx +map_character_to_buffer_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { + if character_idx < 0 then return -1, false; + if character_idx > str.count then return -2, false; + if character_idx == 0 then return 0, true; + + buff_idx := 0; + char_idx := 0; + while buff_idx < str.count && char_idx != character_idx { + buff_idx += count_utf8_bytes(str[buff_idx]); + char_idx += 1; + } + return buff_idx, char_idx == character_idx; +} diff --git a/ttt.jai b/ttt.jai index 7fdd9af..ffe9494 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1674,7 +1674,7 @@ main :: () { TUI.flush_input(); TUI.set_next_key(TUI.Keys.F2); - // Rename task. + // Rename. case TUI.Keys.F2; if (selected_task == null) continue; -- cgit v1.2.3 From 972508efbcfa8f65514fa115d4d49adc6f01683a Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 14 Apr 2024 02:45:03 +0100 Subject: Add UTF8 support on windows. --- modules/TUI/module.jai | 23 ++--- modules/TUI/windows.jai | 266 ++++++++++++++++++++++++------------------------ 2 files changed, 142 insertions(+), 147 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 2d23bff..7c9d7b1 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -267,21 +267,22 @@ Keys :: struct #type_info_none { active := false; -//input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! -input_buffer : [8] u8; // TODO FIXME Input buffer is too small!!! +input_buffer : [1024] u8; 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(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); } assert_is_active :: inline () { assert(active, "TUI is not ready."); } +//////////////////////////////////////////////////////////////////////////////// + +// #scope_export TODO Setup the scope_export and scope_file + set_next_key :: (key: Key) { assert_is_active(); input_override = key; @@ -295,7 +296,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { return input_override; } - if OS_was_terminal_resized() return xx Keys.Resize; + if OS_was_terminal_resized() return Keys.Resize; should_read_input := false; is_input_available := false; @@ -309,7 +310,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { is_input_available = OS_wait_for_input(0); } - if OS_was_terminal_resized() return xx Keys.Resize; + if OS_was_terminal_resized() return Keys.Resize; if should_read_input && is_input_available { // Copy buffered bytes to the start, and read the remaining ones from input. @@ -687,11 +688,3 @@ set_terminal_title :: (title: string) { assert_is_active(); print(Commands.SetWindowTitle, title); } - -// TODO -#if OS == .WINDOWS { - // Prototyping zone... keep clear! -} -else #if OS == .LINUX || OS == .MACOS { - // Prototyping zone... keep clear! -} diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index f79a5cf..5155a3f 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -1,9 +1,12 @@ #scope_file #import "Basic"; -#import "Atomics"; #import "System"; #import "Windows"; +#import "Windows_Utf8"; + + // Code page identifiers. + CP_UTF8 :: 65001; // https://learn.microsoft.com/windows/win32/winprog/windows-data-types LPVOID :: *void; @@ -13,42 +16,18 @@ SHORT :: s16; USHORT :: u16; WORD :: u16; - DWORD :: s32; - LPDWORD :: *s32; + DWORD :: u32; + LPDWORD :: *u32; UINT :: u32; - PINPUT_RECORD :: *INPUT_RECORD; -// https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences -// https://learn.microsoft.com/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"; - - // TODO Cleanup unused foreign procedures. - - // https://learn.microsoft.com/windows/console/getconsolescreenbufferinfo - GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> bool #foreign kernel32; - // https://learn.microsoft.com/en-us/windows/console/readconsoleinput - ReadConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; + ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; // https://learn.microsoft.com/windows/console/readconsole - ReadConsoleA :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfCharsToRead: DWORD, lpNumberOfCharsRead: LPVOID, pInputControl := LPVOID) -> success: bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/getconsolemode - GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *DWORD) -> BOOL #foreign kernel32; - - // https://learn.microsoft.com/windows/console/setconsolemode - SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL #foreign kernel32; - - // https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror - GetLastError :: () -> s32 #foreign kernel32; - - // https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject - WaitForSingleObject :: (hHandle: HANDLE, dwMilliseconds: DWORD) -> s32 #foreign kernel32; - + ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32; + // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; @@ -56,8 +35,8 @@ GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput - PeekConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; - + PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + // https://learn.microsoft.com/en-us/windows/console/setconsolemode // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes Console_Input_Mode :: enum_flags u32 { @@ -65,12 +44,12 @@ ENABLE_LINE_INPUT; // If enable, ReadFile or ReadConsole function return on CR; otherwise they return when one or more characters are available. ENABLE_ECHO_INPUT; // Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. ENABLE_WINDOW_INPUT; - ENABLE_MOUSE_INPUT; // + ENABLE_MOUSE_INPUT; // Makes mouse events available to the ReadConsoleInput function. ENABLE_INSERT_MODE; // If enabled, text entered will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten. _UNUSED_0040_; _UNUSED_0080_; _UNUSED_0100_; - ENABLE_VIRTUAL_TERMINAL_INPUT; // + ENABLE_VIRTUAL_TERMINAL_INPUT; // If enable, makes user input available to the ReadConsole function. _UNUSED_0400_; _UNUSED_0800_; } @@ -167,124 +146,111 @@ initial_stdout_mode: u32; raw_stdout_mode: Console_Output_Mode; + was_resized: bool; -//////////////////////////////////////////////////////////////////////////////// -// Resize detection -was_resized : bool; - -resize_handler :: (signal_code : s32) #c_call { - /* TODO - Try to implement using: - https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events - https://learn.microsoft.com/en-us/windows/console/readconsoleinput - https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents - */ - new_context : Context; - push_context new_context { - if signal_code != SIGWINCH then return; - atomic_swap(*was_resized, true); - } -} + windows_buffer: [512] u16; -prepare_resize_handler :: () { - /* TODO - sa : sigaction_t; - sa.sa_handler = resize_handler; - sigemptyset(*(sa.sa_mask)); - sa.sa_flags = SA_RESTART; - sigaction(SIGWINCH, *sa, null); - */ -} -restore_resize_handler :: () { - /* TODO - sa : sigaction_t; - sa.sa_handler = SIG_DFL; - sigaction(SIGWINCH, null, *sa); - */ -} - -peek_input :: inline () -> INPUT_RECORD { +peek_input :: inline () -> INPUT_RECORD, success := true { record: INPUT_RECORD; - records_read: s32; - if PeekConsoleInputA(stdin, *record, 1, *records_read) == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); + records_read: u32; + if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to peek input: code %, %", error_code, error_string); + return record, false; } return record; } -read_input :: inline () -> INPUT_RECORD { +read_input :: inline () -> INPUT_RECORD, success := true { record: INPUT_RECORD; - records_read: s32; - if ReadConsoleInputA(stdin, *record, 1, *records_read) == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); + records_read: u32; + if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to read input: code %, %", error_code, error_string); + return record, false; } return record; } -count_input :: inline () -> s32 { - count: s32; +count_input :: inline () -> u32, success := true { + count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to count input: code %, %", error_code, error_string); + return 0, false; } return count; } - //////////////////////////////////////////////////////////////////////////////// #scope_export +// TODO All the log_error calls will be hidden by the terminal setup... we should store the logs internally, or write it to a file. + OS_prepare_terminal :: () { // stdin stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { - print("Invalid input handler.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Invalid input handler: code %, %", error_code, error_string); return; } if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { - print("Failed to get input mode.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to get input mode: code %, %", error_code, error_string); return; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT); raw_stdin_mode &= ~(.ENABLE_LINE_INPUT | .ENABLE_PROCESSED_INPUT | .ENABLE_ECHO_INPUT); - if SetConsoleMode(stdin, xx raw_stdin_mode) == false { - print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); + if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set input mode: code %, %", error_code, error_string); return; } // stdout stdout = GetStdHandle(STD_OUTPUT_HANDLE); if stdout == INVALID_HANDLE_VALUE { - print("Invalid output handler.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Invalid output handler: code %, %", error_code, error_string); return; } if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { - print("Failed to get output mode.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to get output mode: code %, %", error_code, error_string); return; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); raw_stdout_mode |= (.ENABLE_VIRTUAL_TERMINAL_PROCESSING | .ENABLE_PROCESSED_OUTPUT | .ENABLE_WRAP_AT_EOL_OUTPUT); - if SetConsoleMode(stdout, xx raw_stdout_mode) == false { - print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); + if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set output mode: code %, %", error_code, error_string); return; } + + // Acording to [documentation](https://learn.microsoft.com/en-us/windows/win32/intl/code-pages) + // only the ANSI functions (ending in A) use the Code Page info. The Unicode functions (ending in W) + // already handle Unicode text. + // As long we use the Unicode functions, we shouldn't need to set the code page to UTF8. + // SetConsoleCP(CP_UTF8); + // SetConsoleOutputCP(CP_UTF8); } OS_reset_terminal :: () { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { - print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to reset input mode: code %, %", error_code, error_string); return; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { - print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to reset output mode: code %, %", error_code, error_string); return; } } @@ -294,33 +260,73 @@ OS_flush_input :: inline () { This API is not recommended and does not have a virtual terminal equivalent. Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. */ - success := FlushConsoleInputBuffer(stdin); - if success == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); // TODO A bit harsh arent we? + if FlushConsoleInputBuffer(stdin) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to flush input: code %, %", error_code, error_string); } } -OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" { - - assert(bytes_to_read <= 0x7fff_ffff, "The Windows API only allows to read up to s32 bytes from the standard input."); +OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { - bytes_read: s32 = 0; - available_inputs := count_input(); + S32_MAX :: 0x7fff_ffff; + if (bytes_to_read > S32_MAX) { + log_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); + bytes_to_read = S32_MAX; + } + success: bool; + bytes_read: s64 = 0; + available_inputs:, success = count_input(); + while bytes_to_read > 0 && available_inputs > 0 { - record := read_input(); - if record.EventType == .WINDOW_BUFFER_SIZE_EVENT { - was_resized = true; - } + record := peek_input(); + + if record.EventType == { + case .WINDOW_BUFFER_SIZE_EVENT; + was_resized = true; + read_input(); + available_inputs -= 1; + + case .KEY_EVENT; + if record.KeyEvent.bKeyDown == false { + read_input(); + available_inputs -= 1; + } + else { + + chars_view: [] u16; + chars_view.data = windows_buffer.data; + + chars_to_read := ifx available_inputs <= windows_buffer.count then available_inputs else windows_buffer.count; + + success = ReadConsoleW(stdin, chars_view.data, chars_to_read, *chars_view.count, null); + if success == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to read console: code %, %", error_code, error_string); + return 0, false; + } + + result:, success = wide_to_utf8_new(chars_view.data, xx chars_view.count); + if success == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); + return 0, false; + } + + memcpy(*buffer[bytes_read], result.data, result.count); + + bytes_to_read -= xx result.count; + bytes_read += result.count; - if record.EventType == .KEY_EVENT && record.KeyEvent.bKeyDown == true { - buffer[bytes_read] = xx record.KeyEvent.AsciiChar; - bytes_to_read -= 1; - bytes_read += 1; + } + + // Discard other input events. + case; + read_input(); + } - available_inputs -= 1; + available_inputs = count_input(); } return bytes_read; } @@ -328,7 +334,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 = -1) -> is_input_available: bool { +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { /* TODO Add a good comment explaining how the windows part of the module was implemented... what's the idea behind it. @@ -340,27 +346,22 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); - // Possible values for poll_return TODO NOT BEING USED - WAIT_ABANDONED :: 0x00000080; // Mutex stuff. + // Possible values for poll_return: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject WAIT_OBJECT_0 :: 0x00000000; // Detected input. WAIT_TIMEOUT :: 0x00000102; // Reached timeout. WAIT_FAILED :: 0xFFFFFFFF; // Something went wrong. - + while true { - poll_return := WaitForSingleObject(stdin, timeout_milliseconds); - - // TODO Weird looking code... - if poll_return == { - case WAIT_TIMEOUT; - return false; - case WAIT_ABANDONED; - print("MUTEX STUFF"); // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject - return false; - case WAIT_FAILED; - _, error_message := get_error_value_and_string(); - assert(false, error_message); + wait_result := WaitForSingleObject(stdin, cast,no_check(u32)timeout_milliseconds); + + if wait_result == WAIT_FAILED { + error_code, error_string := get_error_value_and_string(); + log_error("Error while waiting for input: code %, %", error_code, error_string); + return false, false; } - + + if wait_result != WAIT_OBJECT_0 then return false; + // Discard invalid input events. count := count_input(); while count > 0 { @@ -369,9 +370,10 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo if record.EventType == .WINDOW_BUFFER_SIZE_EVENT { // Discard any additional resize event. while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { - was_resized = true; read_input(); } + + was_resized = true; return false; } @@ -382,13 +384,13 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo read_input(); count -= 1; } - + // When waiting indefinitely... just continue. if timeout_milliseconds < 0 then continue; // Either break due to timeout, or update the remaining timeout. now := current_time_monotonic(); - if now >= expiration then return false; + if now >= expiration then break; timeout_milliseconds = xx to_milliseconds(expiration - now); } @@ -400,7 +402,7 @@ OS_was_terminal_resized :: () -> bool { was_resized = true; read_input(); } - + defer was_resized = false; return was_resized; } -- cgit v1.2.3 From 5844ab303d3d7fc396beb7c6d8a0104496608f08 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 14 Apr 2024 18:08:15 +0100 Subject: Add validation to COLOR_MODE module parameter. --- modules/TUI/module.jai | 17 +++++++++-------- ttt.jai | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 7c9d7b1..3843cd7 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,4 @@ -#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid... and maybe rename to: COLOR_MODE with a enum. +#module_parameters(COLOR_MODE := 24); #if OS == { case .LINUX; @@ -67,10 +67,10 @@ Commands :: struct { SetWindowTitle :: "\e]0;%\e\\"; - RefreshWindow :: "\e[7t"; // TODO Not yet tested. - - SetIEC2022 :: "\e%@"; - SetUTF8 :: "\e%G"; + RefreshWindow :: "\e[7t"; // TODO Not yet tested. Is this required? + + SetIEC2022 :: "\e%@"; // TODO Remove this!? + SetUTF8 :: "\e%G"; // TODO Remove this!? SetGraphicsRendition :: "\e[%m"; @@ -106,7 +106,7 @@ Commands :: struct { QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } -#if COLOR_BIT_DEPTH == 4 { +#if COLOR_MODE == 4 { #load "palette_4b.jai"; set_colors :: inline (foreground: Palette, background: Palette) { @@ -115,7 +115,7 @@ Commands :: struct { cast(u8)foreground + 30, cast(u8)background + 40); } } -else #if COLOR_BIT_DEPTH == 8 { +else #if COLOR_MODE == 8 { #load "palette_8b.jai"; set_colors :: inline (foreground: Palette, background: Palette) { @@ -144,7 +144,7 @@ else { #add_context tui_style: Style; Style :: struct { - #if COLOR_BIT_DEPTH == 4 || COLOR_BIT_DEPTH == 8 { + #if COLOR_MODE == 4 || COLOR_MODE == 8 { background: Palette = .BLACK; foreground: Palette = .WHITE; } else { @@ -272,6 +272,7 @@ input_string : string; input_override : Key; #run { + assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. Valid values are 4, 8, or 24 (default)."); assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); } diff --git a/ttt.jai b/ttt.jai index ffe9494..13c9fcf 100644 --- a/ttt.jai +++ b/ttt.jai @@ -26,7 +26,7 @@ #import "String"; #import "Integer_Saturating_Arithmetic"; #import "UTF8"; -TUI :: #import "TUI"(COLOR_BIT_DEPTH=4); +TUI :: #import "TUI"(COLOR_MODE=4); VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; -- cgit v1.2.3 From 3112131c24ede1ea343383ffd4f8ca7bc4783f47 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 15 Apr 2024 12:38:18 +0100 Subject: Add module logger that switches between main/alternate screen buffers to write logs. --- modules/TUI/module.jai | 27 ++++++++++++++++++++++----- modules/TUI/windows.jai | 44 +++++++++++++------------------------------- ttt.jai | 27 ++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 37 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3843cd7..070161c 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -17,6 +17,11 @@ #import "UTF8"; #load "key_map.jai"; +#run { + assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. Valid values are 4, 8, or 24 (default)."); + assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); +} + // Special Graphics Characters Drawings :: struct { Blank :: "\x5F"; @@ -271,9 +276,15 @@ input_buffer : [1024] u8; input_string : string; input_override : Key; -#run { - assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. Valid values are 4, 8, or 24 (default)."); - assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); +previous_logger : (message: string, data: *void, info: Log_Info); + +module_logger :: (message: string, data: *void, info: Log_Info) { + // print("%0%0\n%0", Commands.MainScreenBuffer, message, Commands.AlternateScreenBuffer); + x, y := get_cursor_position(); + write_string(Commands.MainScreenBuffer); + previous_logger(message, data, info); + write_string(Commands.AlternateScreenBuffer); + set_cursor_position(x, y); } assert_is_active :: inline () { @@ -444,7 +455,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } else { set_cursor_position(x, y); - for 0..chars_count print_character(#char "*"); + for 1..chars_count print_character(#char "*"); for chars_count..count_limit-1 print_character(#char " "); } set_cursor_position(x+idx, y); @@ -510,7 +521,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () { if active == true return; - + // TODO Should start() call flush_input internally? setup_key_map(); // TODO This is being called multiple times... please fix me! @@ -526,6 +537,9 @@ start :: () { Commands.SetUTF8, Commands.CursorNormalMode, Commands.KeypadNumMode); + + previous_logger = context.logger; + context.logger = module_logger; OS_prepare_terminal(); @@ -538,6 +552,9 @@ stop :: () { clear_style(); OS_reset_terminal(); + + context.logger = previous_logger; + write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 5155a3f..5755ef4 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -72,21 +72,6 @@ Y : SHORT; } - SMALL_RECT :: struct { - Left : SHORT; - Top : SHORT; - Right : SHORT; - Bottom : SHORT; - } - - CONSOLE_SCREEN_BUFFER_INFO :: struct { - dwSize : COORD; - dwCursorPosition : COORD; - wAttributes : WORD; - srWindow : SMALL_RECT; - dwMaximumWindowSize : COORD; - } - INPUT_RECORD_EVENT_TYPE :: enum u16 { KEY_EVENT :: 0x0001; MOUSE_EVENT :: 0x0002; @@ -148,7 +133,7 @@ was_resized: bool; - windows_buffer: [512] u16; + widechar_buffer: [512] u16; peek_input :: inline () -> INPUT_RECORD, success := true { @@ -187,8 +172,6 @@ count_input :: inline () -> u32, success := true { #scope_export -// TODO All the log_error calls will be hidden by the terminal setup... we should store the logs internally, or write it to a file. - OS_prepare_terminal :: () { // stdin @@ -256,7 +239,7 @@ OS_reset_terminal :: () { } OS_flush_input :: inline () { - /* NOTE + /* This API is not recommended and does not have a virtual terminal equivalent. Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. */ @@ -295,19 +278,19 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : } else { - chars_view: [] u16; - chars_view.data = windows_buffer.data; + widechar_view: [] u16; + widechar_view.data = widechar_buffer.data; - chars_to_read := ifx available_inputs <= windows_buffer.count then available_inputs else windows_buffer.count; + chars_to_read := ifx available_inputs <= widechar_buffer.count then available_inputs else widechar_buffer.count; - success = ReadConsoleW(stdin, chars_view.data, chars_to_read, *chars_view.count, null); + success = ReadConsoleW(stdin, widechar_view.data, chars_to_read, *widechar_view.count, null); if success == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to read console: code %, %", error_code, error_string); return 0, false; } - result:, success = wide_to_utf8_new(chars_view.data, xx chars_view.count); + result:, success = wide_to_utf8_new(widechar_view.data, xx widechar_view.count); if success == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); @@ -335,13 +318,12 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // 0: do not wait // -1: wait indefinitely OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { - - /* TODO - Add a good comment explaining how the windows part of the module was implemented... what's the idea behind it. - Something like, Since windows provides a single input buffer with all events, we need to peek through them and - discard the ones that are of no use for us. - Because it's a single buffer, all functions need to do repeated work (see if it's resize, see if it's a key press) - ... and so on. + /* + The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer. + To make it match this module's API, we need to do some pre-processing while waiting for input. + This means that OS_wait_for_input will peek at the input events, signal if a window resize is found, + and discard unwanted events (like button release events). + A similar logic is applied in OS_read_input. */ expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); diff --git a/ttt.jai b/ttt.jai index 13c9fcf..8dda204 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1157,7 +1157,7 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { main :: () { // -- -- -- Testing TUI -- START - + perform_test := false; assert_result :: (result: bool, error_message: string) { @@ -1187,6 +1187,31 @@ main :: () { assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); } + if perform_test && 1 { + print("TEST : module logger\n", to_standard_error = true); + log("- log: before module start."); + TUI.start(); + + TUI.set_cursor_position(3, 3); + print("wait"); + sleep_milliseconds(1000); + log("- log: while module is active."); + sleep_milliseconds(1000); + print(" a bit"); + sleep_milliseconds(1000); + + #import "Windows"; + handle: HANDLE = ---; + initial_stdin_mode: u32; + if xx GetConsoleMode(handle, *initial_stdin_mode) == false { + error_code, error_string := get_error_value_and_string(); + log_error("- log: error code %, %", error_code, error_string); + } + + TUI.stop(); + log("- log: after module stop."); + } + if perform_test && 1 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); -- cgit v1.2.3 From bb1d53daa526d734fb2e33c2090f8396f87544ec Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 15 Apr 2024 16:50:42 +0100 Subject: Improved TUI logger; added snake example using TUI. --- modules/TUI/module.jai | 72 ++++++++++++----------- modules/TUI/unix.jai | 2 +- snake.jai | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 10 ---- 4 files changed, 195 insertions(+), 46 deletions(-) create mode 100644 snake.jai (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 070161c..b5866d5 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -59,38 +59,44 @@ Drawings :: struct { } Commands :: struct { + // Screen buffers AlternateScreenBuffer :: "\e[?1049h"; - MainScreenBuffer :: "\e[?1049l"; + MainScreenBuffer :: "\e[?1049l"; + // Device. + Bell :: "\x07"; + QueryDeviceAttributes :: "\e[0c"; + + // Draw/text. DrawingMode :: "\e(0"; TextMode :: "\e(B"; ClearScreen :: "\e[2J"; ClearLine :: "\e[2K"; ClearScrollBack :: "\e[3J"; - - Bell :: "\x07"; - + SetGraphicsRendition :: "\e[%m"; + + // Character encoding. + EncodingIEC2022 :: "\e%@"; + EncodingUTF8 :: "\e%G"; + + // Window. SetWindowTitle :: "\e]0;%\e\\"; - - RefreshWindow :: "\e[7t"; // TODO Not yet tested. Is this required? + RefreshWindow :: "\e[7t"; + QueryWindowSizeInChars :: "\e[18t"; - SetIEC2022 :: "\e%@"; // TODO Remove this!? - SetUTF8 :: "\e%G"; // TODO Remove this!? - - SetGraphicsRendition :: "\e[%m"; - - // Cursor Position + // Cursor position. + SaveCursorPosition :: "\e7"; + RestoreCursorPosition :: "\e8"; SetCursorPosition :: "\e[%;%H"; - - // Cursor Visibility + QueryCursorPosition :: "\e[6n"; + + // Cursor visibility. ShowCursor :: "\e[?25h"; HideCursor :: "\e[?25l"; - StartBlinking :: "\e[?25h"; - StopBlinking :: "\e[?25l"; - SaveCursorPosition :: "\e7"; - RestoreCursorPosition :: "\e8"; - - // Cursor Shape + StartBlinking :: "\e[?12h"; + StopBlinking :: "\e[?12l"; + + // Cursor shape DefaultShape :: "\e[0 q"; BlinkingBlockShape :: "\e[1 q"; SteadyBlockShape :: "\e[2 q"; @@ -98,17 +104,12 @@ Commands :: struct { SteadyUnderlineShape :: "\e[4 q"; BlinkingBarShape :: "\e[5 q"; SteadyBarShape :: "\e[6 q"; - - // Input Mode + + // 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. } #if COLOR_MODE == 4 { @@ -150,12 +151,16 @@ else { Style :: struct { #if COLOR_MODE == 4 || COLOR_MODE == 8 { - background: Palette = .BLACK; - foreground: Palette = .WHITE; + background: Palette; + foreground: Palette; } else { background: Color_24b; foreground: Color_24b; } + + background = Palette.BLACK; + foreground = Palette.WHITE; + bold: bool; underline: bool; strike_through: bool; @@ -279,12 +284,9 @@ input_override : Key; previous_logger : (message: string, data: *void, info: Log_Info); module_logger :: (message: string, data: *void, info: Log_Info) { - // print("%0%0\n%0", Commands.MainScreenBuffer, message, Commands.AlternateScreenBuffer); - x, y := get_cursor_position(); - write_string(Commands.MainScreenBuffer); + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); previous_logger(message, data, info); - write_string(Commands.AlternateScreenBuffer); - set_cursor_position(x, y); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); } assert_is_active :: inline () { @@ -534,7 +536,7 @@ start :: () { Commands.HideCursor, Commands.SaveCursorPosition, Commands.AlternateScreenBuffer, - Commands.SetUTF8, + Commands.EncodingUTF8, Commands.CursorNormalMode, Commands.KeypadNumMode); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 861fe11..7103d47 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -7,7 +7,7 @@ // Required to do unlocking input. libc :: #system_library "libc"; - // TODO Remote this. + // TODO Remove this. // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 void diff --git a/snake.jai b/snake.jai new file mode 100644 index 0000000..ea8926f --- /dev/null +++ b/snake.jai @@ -0,0 +1,157 @@ +#import "Basic"; +#import "Random"; +TUI :: #import "TUI"; + +Vec2D :: struct { + x: int; + y: int; +} + +operator == :: (a: Vec2D, b: Vec2D) -> bool { + return a.x == b.x && a.y == b.y; +} + +screen_size_x: int = ---; +screen_size_y: int = ---; +player_name: string = ---; + +main :: () { + + game_loop :: () { + + LOOP_PERIOD_MS :: 30; + + score := 0; + dir := Vec2D.{1, 0}; + food := Vec2D.{5, 5}; + + random_food :: () -> Vec2D { + return Vec2D.{ + cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), + cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) + }; + } + + snake_parts: [..] Vec2D; + for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); + snake_parts[0].x += 1; + + TUI.flush_input(); + TUI.set_next_key(TUI.Keys.Resize); + timer := current_time_monotonic(); + while main_loop := true { + + timestamp := current_time_monotonic(); + key := TUI.get_key(LOOP_PERIOD_MS); + + if key == { + case TUI.Keys.Resize; + TUI.clear_terminal(); + screen_size_x, screen_size_y = TUI.get_terminal_size(); + TUI.draw_box(1, 1, screen_size_x, screen_size_y); + TUI.set_cursor_position(3, screen_size_y); + write_strings(" ", player_name, " "); + food = random_food(); + + case #char "q"; #through; + case #char "Q"; #through; + case TUI.Keys.Escape; + break main_loop; + + case TUI.Keys.Up; + if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; + + case TUI.Keys.Down; + if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; + + case TUI.Keys.Left; + if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; + + case TUI.Keys.Right; + if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; + } + + last_pos := snake_parts[snake_parts.count-1]; + + // Update position. + for < snake_parts.count-1..1 { + if snake_parts[it] != snake_parts[it-1] { + snake_parts[it] = snake_parts[it-1]; + } + } + snake_parts[0].x += dir.x; + snake_parts[0].y += dir.y; + + // Teleport on borders. + if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; + if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; + if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; + if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; + + // Check for game-over. + for 1..snake_parts.count-1 { + if snake_parts[it] == snake_parts[0] { + break main_loop; + } + } + + // Check for food. + if snake_parts[0] == food { + score += 1; + array_add(*snake_parts, snake_parts[snake_parts.count-1]); + food = random_food(); + } + + // Wait to match game loop time. + delta := to_milliseconds(current_time_monotonic() - timestamp); + if delta < LOOP_PERIOD_MS { + sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); + } + + // Draw snake. + write_string(TUI.Commands.DrawingMode); + TUI.set_cursor_position(last_pos.x, last_pos.y); + write_string(TUI.Drawings.Blank); + for snake_parts { + TUI.set_cursor_position(it.x, it.y); + write_string(TUI.Drawings.Checkerboard); + } + // Draw food. + { + TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); + TUI.set_cursor_position(food.x, food.y); + write_string(TUI.Drawings.Diamond); + } + write_string(TUI.Commands.TextMode); + + // Set score + TUI.set_cursor_position(3, 1); + print(" % ", score); + } + } + + GAME_OVER_TEXT :: "~ game over ~"; + INSTRUCTIONS_TEXT :: "(esc to exit)"; + + TUI.start(); + TUI.set_cursor_position(1, 1); + + write_string("Please enter player name: "); + player_name = TUI.read_input_line(64); + + while true { + game_loop(); + + // Game over screen. + TUI.draw_box(screen_size_x/3, screen_size_y/2-1, screen_size_x/3, 4); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, screen_size_y/2); + write_string(GAME_OVER_TEXT); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, screen_size_y/2+1); + write_string(INSTRUCTIONS_TEXT); + sleep_milliseconds(100); + TUI.flush_input(); + if TUI.get_key() == TUI.Keys.Escape then break; + } + + TUI.stop(); +} diff --git a/ttt.jai b/ttt.jai index 8dda204..8f39b56 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1191,7 +1191,6 @@ main :: () { print("TEST : module logger\n", to_standard_error = true); log("- log: before module start."); TUI.start(); - TUI.set_cursor_position(3, 3); print("wait"); sleep_milliseconds(1000); @@ -1199,15 +1198,6 @@ main :: () { sleep_milliseconds(1000); print(" a bit"); sleep_milliseconds(1000); - - #import "Windows"; - handle: HANDLE = ---; - initial_stdin_mode: u32; - if xx GetConsoleMode(handle, *initial_stdin_mode) == false { - error_code, error_string := get_error_value_and_string(); - log_error("- log: error code %, %", error_code, error_string); - } - TUI.stop(); log("- log: after module stop."); } -- cgit v1.2.3 From b8d743d5b307e0c230ebdb2fe314e041483d6a14 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 16 Apr 2024 10:10:59 +0100 Subject: Adding error logging on TUI/unix. --- modules/TUI/module.jai | 8 ++- modules/TUI/unix.jai | 140 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 112 insertions(+), 36 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index b5866d5..5fd29f6 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,5 +1,7 @@ #module_parameters(COLOR_MODE := 24); +#scope_file + #if OS == { case .LINUX; #load "unix.jai"; @@ -22,6 +24,8 @@ assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); } +#scope_export; + // Special Graphics Characters Drawings :: struct { Blank :: "\x5F"; @@ -441,7 +445,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line x, y := get_cursor_position(); - write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); + write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); key := Keys.None; while true { @@ -515,7 +519,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } } - write_strings(Commands.StopBlinking, Commands.DefaultShape); + write_strings(Commands.StopBlinking, Commands.DefaultShape, Commands.HideCursor); result := ifx key == Keys.Enter then str else ""; return result, key; diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 7103d47..8eeb6c0 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,5 +1,10 @@ #scope_file +/* +TODO : then log all error on unix... +TODO : then do a good implementation of the libc functions about attributes... +*/ + #import "Atomics"; #import "System"; #import "POSIX"; @@ -7,22 +12,6 @@ // Required to do unlocking input. libc :: #system_library "libc"; - // TODO Remove this. - // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; - /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 - void - cfmakeraw (struct termios *t) - { - t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); - t->c_oflag &= ~OPOST; - t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); - t->c_cflag &= ~(CSIZE|PARENB); - t->c_cflag |= CS8; - t->c_cc[VMIN] = 1; // read returns when one char is available. - t->c_cc[VTIME] = 0; - } - */ - // 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; tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { @@ -82,10 +71,68 @@ } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html - tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; - // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { - // TODO IMPLEMENT ME - // } + // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; + tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { + TCSETS :: 0x5402; + TCSETSW :: 0x5403; + TCSETSF :: 0x5404; + tcflag_t :: u32; + cc_t :: u8; + __KERNEL_NCCS :: 19; + __kernel_termios :: struct { + c_iflag : tcflag_t; // input mode flags + c_oflag : tcflag_t; // output mode flags + c_cflag : tcflag_t; // control mode flags + c_lflag : tcflag_t; // local mode flags + c_line : cc_t; // line discipline + c_cc : [__KERNEL_NCCS]cc_t; // control characters + }; + + + // int + // __tcgetattr (int fd, struct termios *termios_p) + // { + // struct __kernel_termios k_termios; + k_termios: __kernel_termios; + retval: int; + retval = ioctl(fd, TCGETS, *k_termios); + if retval == 0 { + termios_p.c_iflag = xx k_termios.c_iflag; + termios_p.c_oflag = xx k_termios.c_oflag; + termios_p.c_cflag = xx k_termios.c_cflag; + termios_p.c_lflag = xx k_termios.c_lflag; + termios_p.c_line = xx k_termios.c_line; + // #if _HAVE_STRUCT_TERMIOS_C_ISPEED + // # if _HAVE_C_ISPEED + // termios_p->c_ispeed = k_termios.c_ispeed; + // # else + // termios_p->c_ispeed = k_termios.c_cflag & (CBAUD | CBAUDEX); + // # endif + // #endif + // #if _HAVE_STRUCT_TERMIOS_C_OSPEED + // # if _HAVE_C_OSPEED + // termios_p->c_ospeed = k_termios.c_ospeed; + // # else + // termios_p->c_ospeed = k_termios.c_cflag & (CBAUD | CBAUDEX); + // # endif + // #endif + size_of_cc_t := __KERNEL_NCCS * 1; + memcpy(*termios_p.c_cc[0], *k_termios.c_cc[0], size_of_cc_t); + // memset(*termios_p.c_cc[0] + size_of_cc_t + 1, _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * 1); + // + // if (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0 || (unsigned char) _POSIX_VDISABLE == (unsigned char) -1) { + // memset (__mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)), _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t)); + // } + // else + // { + // memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)); + // for (size_t cnt = __KERNEL_NCCS; cnt < NCCS; ++cnt) { + // termios_p->c_cc[cnt] = _POSIX_VDISABLE; + // } + // } + } + return xx retval; + } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html // tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; @@ -232,8 +279,15 @@ restore_resize_handler :: () { #scope_export -OS_prepare_terminal :: () { - tcgetattr(STDIN_FILENO, *initial_tio_mode); // TODO Log error using `log()` from jai/modules/Basic/Print.jai ? +OS_prepare_terminal :: () -> success := true { // On critical paths, we may use the #must for the success return value. + error: int = ---; + + error = tcgetattr(STDIN_FILENO, *initial_tio_mode); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + } + raw_tio_mode = initial_tio_mode; raw_tio_mode.c_iflag &= ~(.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON); raw_tio_mode.c_oflag &= ~(.OPOST); @@ -242,28 +296,48 @@ OS_prepare_terminal :: () { raw_tio_mode.c_cflag |= .CS8; raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); // TODO Log on error. - + + error = tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); + return false; + } + was_resized = false; prepare_resize_handler(); + return; } -OS_reset_terminal :: () { +OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // TODO Log on error. + error := tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); + return false; + } + return; } -OS_flush_input :: inline () { +OS_flush_input :: inline () -> success := true { TCIFLUSH :: 0; // TODO Is this always zero in all systems? - tcflush(STDIN_FILENO, TCIFLUSH); + error := tcflush(STDIN_FILENO, TCIFLUSH); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to flush input: code %, %", error_code, error_string); + return false; + } + return; } // 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 = "" { +OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { 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; + error_code, error_string := get_error_value_and_string(); + log_error("Failed to read input: code %, %", error_code, error_string); + return 0, false; } return bytes_read; } @@ -279,8 +353,6 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return ifx poll_return > 0 then true else false; } -// TODO This procedure hides the behaviour of reseting on read. -// We should have the `was_resized` on module.jai so that we know it may be used in another thread. OS_was_terminal_resized :: () -> bool { - return atomic_swap(*was_resized, false); // TODO If the windows implementation is similar, we may push this into the main module file. + return atomic_swap(*was_resized, false); } -- cgit v1.2.3 From 1f2afe4db186342bda551e96bcd1d9b029d7c5ba Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 19 Apr 2024 12:36:18 +0100 Subject: Avoid multiple initializations of key_map. --- modules/TUI/key_map.jai | 3 +++ 1 file changed, 3 insertions(+) (limited to 'modules') diff --git a/modules/TUI/key_map.jai b/modules/TUI/key_map.jai index b272253..7a074b2 100644 --- a/modules/TUI/key_map.jai +++ b/modules/TUI/key_map.jai @@ -3,6 +3,9 @@ key_map: Table(string, Key); setup_key_map :: () { + + if key_map.count > 0 then return; + /* This table was created/tested using the following terminals: - g: gnome (terminal) -- cgit v1.2.3 From d7c2c312fe2ac08cadc3534a0a35357ef50cca20 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 00:05:26 +0100 Subject: WIP : Add logger and cleanup TUI module. --- modules/TUI/module.jai | 38 ++++--- modules/TUI/unix.jai | 297 ++++++++++++++++++++++++++++-------------------- modules/TUI/windows.jai | 118 +++++++++---------- snake.jai | 7 +- ttt.jai | 42 +++---- 5 files changed, 285 insertions(+), 217 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5fd29f6..121aa42 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -2,6 +2,9 @@ #scope_file +// - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls) +// - fix/implement/finish `TODO` on `TUI\module` (use `dirty_bit_flag` to only update ehat has been changed) + #if OS == { case .LINUX; #load "unix.jai"; @@ -525,16 +528,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { return result, key; } -start :: () { +start :: () -> success := true #must { if active == true return; - // TODO Should start() call flush_input internally? - - 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; + + previous_logger = context.logger; + context.logger = module_logger; + + setup_key_map(); write_strings( Commands.HideCursor, @@ -542,26 +546,34 @@ start :: () { Commands.AlternateScreenBuffer, Commands.EncodingUTF8, Commands.CursorNormalMode, - Commands.KeypadNumMode); - - previous_logger = context.logger; - context.logger = module_logger; + Commands.KeypadNumMode + ); - OS_prepare_terminal(); + if !OS_prepare_terminal() then return false; active = true; + + return; } -stop :: () { +stop :: () -> success := true #must { if active == false return; + active = false; clear_style(); - OS_reset_terminal(); + + if !OS_reset_terminal() then return false; context.logger = previous_logger; - write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); + write_strings( + Commands.MainScreenBuffer, + Commands.RestoreCursorPosition, + Commands.ShowCursor + ); + + return; } flush_input :: () { diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 8eeb6c0..a6cd467 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,22 +1,171 @@ #scope_file /* -TODO : then log all error on unix... TODO : then do a good implementation of the libc functions about attributes... */ +USE_LIBC :: true; + #import "Atomics"; #import "System"; #import "POSIX"; + // Set Attributes Actions. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + // OptionalActions :: enum s32 { + TCSANOW :: 0; // Change immediately. + TCSADRAIN :: 1; // Change when pending output is written. + TCSAFLUSH :: 2; // Flush pending input before changing. + // } + + // TODO + // QueueSelector :: enum s32 { + // queue_selector + #if OS == { + case .LINUX; + // https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + TCIFLUSH :: 0; // Discard data received but not yet read. + TCOFLUSH :: 1; // Discard data written but not yet sent. + TCIOFLUSH :: 2; // Discard all pending data. + + case .MACOS; + // https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + TCIFLUSH :: 1; // Discard data received but not yet read. + TCOFLUSH :: 2; // Discard data written but not yet sent. + TCIOFLUSH :: 3; // Discard all pending data. + } + } + + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-struct.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + #if OS == { + case .LINUX; + NCCS :: 32; + + case .MACOS; + NCCS :: 20; + } + + // Information for the termios.h enums is platform dependent and was retrieved from: + // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h + + + + // The struct termios. + Terminal_IO_Mode :: struct { + c_iflag : Input_Modes; // Input mode flags. + c_oflag : Output_Modes; // Output mode flags. + c_cflag : Control_Modes; // Control modes flags. + c_lflag : Local_Modes; // Local modes flags. + c_line : u8; // Line discipline. + c_cc : [NCCS]Control_Chars; // Control characters. + c_ispeed : u32; // Input speed (baud rates). + c_ospeed : u32; // Output speed (baud rates). + } + + // Input modes. + Input_Modes :: enum_flags u32 { + IGNBRK :: 0000001; // Ignore break condition. + BRKINT :: 0000002; // Signal interrupt on break. + IGNPAR :: 0000004; // Ignore characters with parity errors. + PARMRK :: 0000010; // Mark parity and framing errors. + INPCK :: 0000020; // Enable input parity check. + ISTRIP :: 0000040; // Strip 8th bit off characters. + INLCR :: 0000100; // Map NL to CR on input. + IGNCR :: 0000200; // Ignore CR. + ICRNL :: 0000400; // Map CR to NL on input. + IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). + IXON :: 0002000; // Enable start/stop output control. + IXANY :: 0004000; // Any character will restart after stop. + IXOFF :: 0010000; // Enable start/stop input control. + IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). + IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). + } + + // Output modes. + Output_Modes :: enum_flags u32 { + OPOST :: 0000001; // Perform output processing. + OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). + ONLCR :: 0000004; // Map NL to CR-NL on output. + OCRNL :: 0000010; // Map CR to NL. + ONOCR :: 0000020; // Discard CR's when on column 0. + ONLRET :: 0000040; // Move to column 0 on NL. + OFILL :: 0000100; // Send fill characters for delays. + OFDEL :: 0000200; // Fill is DEL. + VTDLY :: 0040000; // Select vertical-tab delays: + VT0 :: 0000000; // Vertical-tab delay type 0. + VT1 :: 0040000; // Vertical-tab delay type 1. + } + + // Control modes. + Control_Modes :: enum u32 { + CS5 :: 0000000; // 5 bits per byte. + CS6 :: 0000020; // 6 bits per byte. + CS7 :: 0000040; // 7 bits per byte. + CS8 :: 0000060; // 8 bits per byte. + CSIZE :: 0000060; // Number of bits per byte (mask). + CSTOPB :: 0000100; // Two stop bits instead of one. + CREAD :: 0000200; // Enable receiver. + PARENB :: 0000400; // Parity enable. + PARODD :: 0001000; // Odd parity instead of even. + HUPCL :: 0002000; // Hang up on last close. + CLOCAL :: 0004000; + } + + // Local modes. + Local_Modes :: enum_flags u32 { + ISIG :: 0000001; // Enable signals. + ICANON :: 0000002; // Do erase and kill processing. + ECHO :: 0000010; // Enable echo. + ECHOE :: 0000020; // Visual erase for ERASE. + ECHOK :: 0000040; // Echo NL after KILL. + ECHONL :: 0000100; // Echo NL even if ECHO is off. + NOFLSH :: 0000200; // Disable flush after interrupt. + TOSTOP :: 0000400; // Send SIGTTOU for background output. + IEXTEN :: 0100000; // Enable DISCARD and LNEXT. + } + + // Control Characters + Control_Chars :: enum u8 { + VINTR :: 0; + VQUIT :: 1; + VERASE :: 2; + VKILL :: 3; + VEOF :: 4; + VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. + VSWTC :: 7; + VSTART :: 8; + VSTOP :: 9; + VSUSP :: 10; + VEOL :: 11; + VREPRINT :: 12; + VDISCARD :: 13; + VWERASE :: 14; + VLNEXT :: 15; + VEOL2 :: 16; + } + + +#if USE_LIBC { // 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; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html + 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; +} +else { // 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; tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { - // TODO IMPLEMENT ME - + #if OS == .LINUX { TCSETS :: 0x5402; TCSETSW :: 0x5403; @@ -71,7 +220,6 @@ TODO : then do a good implementation of the libc functions about attributes... } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html - // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { TCSETS :: 0x5402; TCSETSW :: 0x5403; @@ -135,128 +283,25 @@ TODO : then do a good implementation of the libc functions about attributes... } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html - // tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { TCFLSH :: 0x540B; return ioctl(fd, TCFLSH, queue_selector); } - - // Information for the termios.h enums is platform dependent and was retrieved from: - // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h - - // Set Attributes Actions. - SetAttributesActions :: enum u32 { - TCSANOW :: 0; // Change immediately. - TCSADRAIN :: 1; // Change when pending output is written. - TCSAFLUSH :: 2; // Flush pending input before changing. - } - - // Input modes. - Input_Modes :: enum_flags u32 { - IGNBRK :: 0000001; // Ignore break condition. - BRKINT :: 0000002; // Signal interrupt on break. - IGNPAR :: 0000004; // Ignore characters with parity errors. - PARMRK :: 0000010; // Mark parity and framing errors. - INPCK :: 0000020; // Enable input parity check. - ISTRIP :: 0000040; // Strip 8th bit off characters. - INLCR :: 0000100; // Map NL to CR on input. - IGNCR :: 0000200; // Ignore CR. - ICRNL :: 0000400; // Map CR to NL on input. - IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). - IXON :: 0002000; // Enable start/stop output control. - IXANY :: 0004000; // Any character will restart after stop. - IXOFF :: 0010000; // Enable start/stop input control. - IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). - IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). - } - - // Output modes. - Output_Modes :: enum_flags u32 { - OPOST :: 0000001; // Perform output processing. - OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). - ONLCR :: 0000004; // Map NL to CR-NL on output. - OCRNL :: 0000010; // Map CR to NL. - ONOCR :: 0000020; // Discard CR's when on column 0. - ONLRET :: 0000040; // Move to column 0 on NL. - OFILL :: 0000100; // Send fill characters for delays. - OFDEL :: 0000200; // Fill is DEL. - VTDLY :: 0040000; // Select vertical-tab delays: - VT0 :: 0000000; // Vertical-tab delay type 0. - VT1 :: 0040000; // Vertical-tab delay type 1. - } - - // Control modes. - Control_Modes :: enum u32 { - CS5 :: 0000000; // 5 bits per byte. - CS6 :: 0000020; // 6 bits per byte. - CS7 :: 0000040; // 7 bits per byte. - CS8 :: 0000060; // 8 bits per byte. - CSIZE :: 0000060; // Number of bits per byte (mask). - CSTOPB :: 0000100; // Two stop bits instead of one. - CREAD :: 0000200; // Enable receiver. - PARENB :: 0000400; // Parity enable. - PARODD :: 0001000; // Odd parity instead of even. - HUPCL :: 0002000; // Hang up on last close. - CLOCAL :: 0004000; - } - - // Local modes. - Local_Modes :: enum_flags u32 { - ISIG :: 0000001; // Enable signals. - ICANON :: 0000002; // Do erase and kill processing. - ECHO :: 0000010; // Enable echo. - ECHOE :: 0000020; // Visual erase for ERASE. - ECHOK :: 0000040; // Echo NL after KILL. - ECHONL :: 0000100; // Echo NL even if ECHO is off. - NOFLSH :: 0000200; // Disable flush after interrupt. - TOSTOP :: 0000400; // Send SIGTTOU for background output. - IEXTEN :: 0100000; // Enable DISCARD and LNEXT. - } +} - // Control Characters - Control_Chars :: enum u8 { - VINTR :: 0; - VQUIT :: 1; - VERASE :: 2; - VKILL :: 3; - VEOF :: 4; - VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. - VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. - VSWTC :: 7; - VSTART :: 8; - VSTOP :: 9; - VSUSP :: 10; - VEOL :: 11; - VREPRINT :: 12; - VDISCARD :: 13; - VWERASE :: 14; - VLNEXT :: 15; - VEOL2 :: 16; - } +//////////////////////////////////////////////////////////////////////////////// - // The struct termios. - Terminal_IO_Mode :: struct { - c_iflag : Input_Modes; // Input mode flags. - c_oflag : Output_Modes; // Output mode flags. - c_cflag : Control_Modes; // Control modes flags. - c_lflag : Local_Modes; // Local modes flags. - c_line : u8; // Line discipline. - c_cc : [32]Control_Chars;// Control characters. - c_ispeed : u32; // Input speed (baud rates). - c_ospeed : u32; // Output speed (baud rates). - } - initial_tio_mode: Terminal_IO_Mode; raw_tio_mode: Terminal_IO_Mode; + was_resized : bool; + //////////////////////////////////////////////////////////////////////////////// -// Resize detection -was_resized : bool; resize_handler :: (signal_code : s32) #c_call { new_context : Context; push_context new_context { - if signal_code != SIGWINCH then return; + if signal_code != SIGWINCH then return; atomic_swap(*was_resized, true); } } @@ -279,13 +324,14 @@ restore_resize_handler :: () { #scope_export -OS_prepare_terminal :: () -> success := true { // On critical paths, we may use the #must for the success return value. +OS_prepare_terminal :: () -> success := true { error: int = ---; error = tcgetattr(STDIN_FILENO, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + return false; } raw_tio_mode = initial_tio_mode; @@ -297,7 +343,7 @@ OS_prepare_terminal :: () -> success := true { // On critical paths, we may use raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - error = tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); + error = tcsetattr(STDIN_FILENO, TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); @@ -311,7 +357,7 @@ OS_prepare_terminal :: () -> success := true { // On critical paths, we may use OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); @@ -321,7 +367,6 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - TCIFLUSH :: 0; // TODO Is this always zero in all systems? error := tcflush(STDIN_FILENO, TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); @@ -331,7 +376,6 @@ OS_flush_input :: inline () -> success := true { return; } -// TODO Nothing is checking for the errors returned by this... shame! OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { @@ -345,12 +389,21 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // timeout_milliseconds // 0: do not wait // -1: wait indefinitely -OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { 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. - error_code, error_message := get_error_value_and_string(); // FIXME Not used. - return ifx poll_return > 0 then true else false; + result := poll(fds.data, xx nfds, xx timeout_milliseconds); // Returns '-1' with errno '4 | Interrupted system call' on window resize. + + if result == -1 { + error_code, error_string := get_error_value_and_string(); + // Ignore window resize events (error_code 4). + if error_code != 4 { + log_error("Unexpected error while waiting for input: code %, %", error_code, error_string); + return false, false; + } + } + + return ifx result > 0 then true else false; } OS_was_terminal_resized :: () -> bool { diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 5755ef4..608266d 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -9,47 +9,30 @@ CP_UTF8 :: 65001; // https://learn.microsoft.com/windows/win32/winprog/windows-data-types - LPVOID :: *void; - BOOL :: bool; - CHAR :: s8; - WCHAR :: s16; - SHORT :: s16; - USHORT :: u16; - WORD :: u16; - DWORD :: u32; - LPDWORD :: *u32; - UINT :: u32; - - PINPUT_RECORD :: *INPUT_RECORD; - - // https://learn.microsoft.com/en-us/windows/console/readconsoleinput - ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/readconsole - ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer - FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents - GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; - - // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput - PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + LPVOID :: *void; + BOOL :: bool; + CHAR :: s8; + WCHAR :: s16; + SHORT :: s16; + WORD :: u16; + DWORD :: u32; + LPDWORD :: *u32; + UINT :: u32; + PINPUT_RECORD :: *INPUT_RECORD; // https://learn.microsoft.com/en-us/windows/console/setconsolemode // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes Console_Input_Mode :: enum_flags u32 { - ENABLE_PROCESSED_INPUT; // If enable, control keys (Ctrl+C, Backspace, ...) are processed by the system. - ENABLE_LINE_INPUT; // If enable, ReadFile or ReadConsole function return on CR; otherwise they return when one or more characters are available. - ENABLE_ECHO_INPUT; // Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. + ENABLE_PROCESSED_INPUT; // If set, control sequences are processed by the system. + ENABLE_LINE_INPUT; // If set, ReadFile and ReadConsole function return on CR; otherwise return when characters are available. + ENABLE_ECHO_INPUT; // If set, Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. ENABLE_WINDOW_INPUT; - ENABLE_MOUSE_INPUT; // Makes mouse events available to the ReadConsoleInput function. - ENABLE_INSERT_MODE; // If enabled, text entered will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten. + ENABLE_MOUSE_INPUT; + ENABLE_INSERT_MODE; _UNUSED_0040_; _UNUSED_0080_; _UNUSED_0100_; - ENABLE_VIRTUAL_TERMINAL_INPUT; // If enable, makes user input available to the ReadConsole function. + ENABLE_VIRTUAL_TERMINAL_INPUT; // If set, makes user input available to the ReadConsole function. _UNUSED_0400_; _UNUSED_0800_; } @@ -57,11 +40,11 @@ // https://learn.microsoft.com/en-us/windows/console/setconsolemode // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes Console_Output_Mode :: enum_flags u32 { - ENABLE_PROCESSED_OUTPUT; // - ENABLE_WRAP_AT_EOL_OUTPUT; // - ENABLE_VIRTUAL_TERMINAL_PROCESSING; // - DISABLE_NEWLINE_AUTO_RETURN; // - ENABLE_LVB_GRID_WORLDWIDE; // + ENABLE_PROCESSED_OUTPUT; // If set, ASCII control sequences are processed by the system. + ENABLE_WRAP_AT_EOL_OUTPUT; // If set, the cursor moves to the beginning of the next row when it reaches the end of the current row. + ENABLE_VIRTUAL_TERMINAL_PROCESSING; // If set, VT100 control sequences are processed by the system. + DISABLE_NEWLINE_AUTO_RETURN; + ENABLE_LVB_GRID_WORLDWIDE; _UNUSED_0020_; _UNUSED_0040_; _UNUSED_0080_; @@ -123,6 +106,23 @@ bSetFocus : BOOL; } + // https://learn.microsoft.com/en-us/windows/console/readconsoleinput + ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/readconsole + ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer + FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents + GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; + + // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput + PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + +//////////////////////////////////////////////////////////////////////////////// + stdin: HANDLE; initial_stdin_mode: u32; raw_stdin_mode: Console_Input_Mode; @@ -135,6 +135,7 @@ widechar_buffer: [512] u16; +//////////////////////////////////////////////////////////////////////////////// peek_input :: inline () -> INPUT_RECORD, success := true { record: INPUT_RECORD; @@ -172,19 +173,19 @@ count_input :: inline () -> u32, success := true { #scope_export -OS_prepare_terminal :: () { +OS_prepare_terminal :: () -> success := true { // stdin stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); log_error("Invalid input handler: code %, %", error_code, error_string); - return; + return false; } if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to get input mode: code %, %", error_code, error_string); - return; + return false; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT); @@ -193,7 +194,7 @@ OS_prepare_terminal :: () { if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to set input mode: code %, %", error_code, error_string); - return; + return false; } // stdout @@ -201,12 +202,12 @@ OS_prepare_terminal :: () { if stdout == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); log_error("Invalid output handler: code %, %", error_code, error_string); - return; + return false; } if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to get output mode: code %, %", error_code, error_string); - return; + return false; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); raw_stdout_mode |= (.ENABLE_VIRTUAL_TERMINAL_PROCESSING | .ENABLE_PROCESSED_OUTPUT | .ENABLE_WRAP_AT_EOL_OUTPUT); @@ -214,7 +215,7 @@ OS_prepare_terminal :: () { if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to set output mode: code %, %", error_code, error_string); - return; + return false; } // Acording to [documentation](https://learn.microsoft.com/en-us/windows/win32/intl/code-pages) @@ -223,26 +224,27 @@ OS_prepare_terminal :: () { // As long we use the Unicode functions, we shouldn't need to set the code page to UTF8. // SetConsoleCP(CP_UTF8); // SetConsoleOutputCP(CP_UTF8); + + return; } -OS_reset_terminal :: () { +OS_reset_terminal :: () -> success := true { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to reset input mode: code %, %", error_code, error_string); - return; + return false; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to reset output mode: code %, %", error_code, error_string); - return; + return false; } + return; } OS_flush_input :: inline () { - /* - This API is not recommended and does not have a virtual terminal equivalent. - Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. - */ + // This API is not recommended and does not have a virtual terminal equivalent. + // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. if FlushConsoleInputBuffer(stdin) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); @@ -318,13 +320,11 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // 0: do not wait // -1: wait indefinitely OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { - /* - The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer. - To make it match this module's API, we need to do some pre-processing while waiting for input. - This means that OS_wait_for_input will peek at the input events, signal if a window resize is found, - and discard unwanted events (like button release events). - A similar logic is applied in OS_read_input. - */ + // The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer. + // To make it match this module's API, we need to do some pre-processing while waiting for input. + // This means that OS_wait_for_input will peek at the input events, signal if a window resize is found, + // and discard unwanted events (like button release events). + // A similar logic is applied in OS_read_input. expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); diff --git a/snake.jai b/snake.jai index e3a15f6..465ae4d 100644 --- a/snake.jai +++ b/snake.jai @@ -136,8 +136,11 @@ main :: () { GAME_OVER_TEXT :: "~ game over ~"; INSTRUCTIONS_TEXT :: "(esc to exit)"; + + seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. + random_seed(seed); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.set_cursor_position(1, 1); write_string("Please enter player name: "); @@ -158,5 +161,5 @@ main :: () { if TUI.get_key() == TUI.Keys.Escape then break; } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); } diff --git a/ttt.jai b/ttt.jai index 8f39b56..45dc7f8 100644 --- a/ttt.jai +++ b/ttt.jai @@ -866,7 +866,7 @@ initialize_tui :: () { } } - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); } update_layout :: () { @@ -1165,7 +1165,7 @@ main :: () { print("- success\n", to_standard_error = true); } else { - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); print("- ERROR: %", error_message, to_standard_error = true); exit(1); } @@ -1178,19 +1178,19 @@ main :: () { if perform_test && 1 { print("TEST : set and get cursor position\n", to_standard_error = true); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); X :: 2; Y :: 3; TUI.set_cursor_position(X, Y); x, y := TUI.get_cursor_position(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); } if perform_test && 1 { print("TEST : module logger\n", to_standard_error = true); log("- log: before module start."); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.set_cursor_position(3, 3); print("wait"); sleep_milliseconds(1000); @@ -1198,14 +1198,14 @@ main :: () { sleep_milliseconds(1000); print(" a bit"); sleep_milliseconds(1000); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); log("- log: after module stop."); } if perform_test && 1 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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."); @@ -1224,28 +1224,28 @@ main :: () { write_string(TUI.to_string(key)); } } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); print("- success\n", to_standard_error = true); } if perform_test && 1 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(key == #char "y", "Failed to draw box.\n"); } if perform_test && 1 { print("TEST : get terminal size\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.clear_terminal(); width, height := TUI.get_terminal_size(); TUI.set_cursor_position(1, 1); @@ -1254,13 +1254,13 @@ main :: () { while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(key == #char "y", "Failed to get terminal size.\n"); } if perform_test && 1 { print("TEST : set terminal title\n", to_standard_error = true); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); title := "BAZINGA"; TUI.set_terminal_title(title); TUI.set_cursor_position(1, 1); @@ -1269,14 +1269,14 @@ main :: () { while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(key == #char "y", "Failed to set terminal title.\n"); } if perform_test && 1 { print("TEST : print keys and set terminal title\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.set_terminal_title("bazinga"); key: TUI.Key = #char "d"; last_none_char := "X"; @@ -1335,13 +1335,13 @@ main :: () { // set_temporary_storage_mark(__mark); } print("- success"); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); } if perform_test && 1 { print("TEST : user input\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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):"); @@ -1366,14 +1366,14 @@ main :: () { } } answer := TUI.get_key(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(answer == #char "y", error_message); } if perform_test && 1 { print("TEST : hidden user input\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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):"); @@ -1397,7 +1397,7 @@ main :: () { } } answer := TUI.get_key(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(answer == #char "y", error_message); } @@ -2018,7 +2018,7 @@ main :: () { TUI.get_key(); } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); exit(xx ifx error_saving then 1 else 0); } -- cgit v1.2.3 From c7a4f1c0c98a1df1e683b3de0454b3d981519e9e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 02:31:41 +0100 Subject: WIP : OMG I add copied octal values from C which became decimal ones in jai. --- modules/TUI/unix.jai | 217 ++++++++++++++++++++++++++++++++------------------- ttt.jai | 1 + 2 files changed, 136 insertions(+), 82 deletions(-) (limited to 'modules') diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index a6cd467..bb22030 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -10,50 +10,45 @@ USE_LIBC :: true; #import "System"; #import "POSIX"; - // Set Attributes Actions. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h + // Queue selector used in tcflush(...). + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-struct.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html - // OptionalActions :: enum s32 { - TCSANOW :: 0; // Change immediately. - TCSADRAIN :: 1; // Change when pending output is written. - TCSAFLUSH :: 2; // Flush pending input before changing. - // } - - // TODO - // QueueSelector :: enum s32 { - // queue_selector + QueueSelector :: enum s32 { #if OS == { case .LINUX; - // https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h - TCIFLUSH :: 0; // Discard data received but not yet read. - TCOFLUSH :: 1; // Discard data written but not yet sent. - TCIOFLUSH :: 2; // Discard all pending data. + TCIFLUSH :: 0; // Discard data received but not yet read. + TCOFLUSH :: 1; // Discard data written but not yet sent. + TCIOFLUSH :: 2; // Discard all pending data. case .MACOS; - // https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html - TCIFLUSH :: 1; // Discard data received but not yet read. - TCOFLUSH :: 2; // Discard data written but not yet sent. - TCIOFLUSH :: 3; // Discard all pending data. + TCIFLUSH :: 1; // Discard data received but not yet read. + TCOFLUSH :: 2; // Discard data written but not yet sent. + TCIOFLUSH :: 3; // Discard all pending data. } } + + // Optional actions used in tcsetattr(...). + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + OptionalActions :: enum s32 { + TCSANOW :: 0; // Change immediately. + TCSADRAIN :: 1; // Change when pending output is written. + TCSAFLUSH :: 2; // Flush pending input before changing. + } + // Terminal control (struct termios). // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-struct.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html - #if OS == { - case .LINUX; + Terminal_IO_Mode :: struct { + + #if OS == { + case .LINUX; NCCS :: 32; - case .MACOS; + case .MACOS; NCCS :: 20; - } + } - // Information for the termios.h enums is platform dependent and was retrieved from: - // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h - - - - // The struct termios. - Terminal_IO_Mode :: struct { c_iflag : Input_Modes; // Input mode flags. c_oflag : Output_Modes; // Output mode flags. c_cflag : Control_Modes; // Control modes flags. @@ -65,68 +60,126 @@ USE_LIBC :: true; } // Input modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_iflag.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Input_Modes :: enum_flags u32 { - IGNBRK :: 0000001; // Ignore break condition. - BRKINT :: 0000002; // Signal interrupt on break. - IGNPAR :: 0000004; // Ignore characters with parity errors. - PARMRK :: 0000010; // Mark parity and framing errors. - INPCK :: 0000020; // Enable input parity check. - ISTRIP :: 0000040; // Strip 8th bit off characters. - INLCR :: 0000100; // Map NL to CR on input. - IGNCR :: 0000200; // Ignore CR. - ICRNL :: 0000400; // Map CR to NL on input. - IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). - IXON :: 0002000; // Enable start/stop output control. - IXANY :: 0004000; // Any character will restart after stop. - IXOFF :: 0010000; // Enable start/stop input control. - IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). - IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). + IGNBRK :: 0x00000001; // Ignore break condition. + BRKINT :: 0x00000002; // Signal interrupt on break. + IGNPAR :: 0x00000004; // Ignore characters with parity errors. + PARMRK :: 0x00000008; // Mark parity and framing errors. + INPCK :: 0x00000010; // Enable input parity check. + ISTRIP :: 0x00000020; // Strip 8th bit off characters. + INLCR :: 0x00000040; // Map NL to CR on input. + IGNCR :: 0x00000080; // Ignore CR. + ICRNL :: 0x00000100; // Map CR to NL on input. + + #if OS == { + + case .LINUX; + IXON :: 0x00000400; // Enable start/stop output control. + IXANY :: 0x00000800; // Any character will restart after stop. + IXOFF :: 0x00001000; // Enable start/stop input control. + + case .MACOS; + IXON :: 0x00000200; // Enable start/stop output control. + IXANY :: 0x00000400; // Any character will restart after stop. + IXOFF :: 0x00000800; // Enable start/stop input control. + + } } // Output modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Output_Modes :: enum_flags u32 { - OPOST :: 0000001; // Perform output processing. - OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). - ONLCR :: 0000004; // Map NL to CR-NL on output. - OCRNL :: 0000010; // Map CR to NL. - ONOCR :: 0000020; // Discard CR's when on column 0. - ONLRET :: 0000040; // Move to column 0 on NL. - OFILL :: 0000100; // Send fill characters for delays. - OFDEL :: 0000200; // Fill is DEL. - VTDLY :: 0040000; // Select vertical-tab delays: - VT0 :: 0000000; // Vertical-tab delay type 0. - VT1 :: 0040000; // Vertical-tab delay type 1. + #if OS == { + + case .LINUX; + OPOST :: 0x00000001; // Perform output processing. + ONLCR :: 0x00000004; // Map NL to CR-NL on output. + OCRNL :: 0x00000008; // Map CR to NL. + ONOCR :: 0x00000010; // Discard CR's when on column 0. + ONLRET :: 0x00000020; // Move to column 0 on NL. + OFILL :: 0x00000040; // Send fill characters for delays. + + case .MACOS; + OPOST :: 0x00000001; // Perform output processing. + ONLCR :: 0x00000002; // Map NL to CR-NL on output. + OCRNL :: 0x00000010; // Map CR to NL. + ONOCR :: 0x00000020; // Discard CR's when on column 0. + ONLRET :: 0x00000040; // Move to column 0 on NL. + OFILL :: 0x00000080; // Send fill characters for delays. + } } // Control modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Control_Modes :: enum u32 { - CS5 :: 0000000; // 5 bits per byte. - CS6 :: 0000020; // 6 bits per byte. - CS7 :: 0000040; // 7 bits per byte. - CS8 :: 0000060; // 8 bits per byte. - CSIZE :: 0000060; // Number of bits per byte (mask). - CSTOPB :: 0000100; // Two stop bits instead of one. - CREAD :: 0000200; // Enable receiver. - PARENB :: 0000400; // Parity enable. - PARODD :: 0001000; // Odd parity instead of even. - HUPCL :: 0002000; // Hang up on last close. - CLOCAL :: 0004000; + #if OS == { + + case .LINUX; + CS5 :: 0x00000000; // 5 bits per byte. + CS6 :: 0x00000010; // 6 bits per byte. + CS7 :: 0x00000020; // 7 bits per byte. + CS8 :: 0x00000030; // 8 bits per byte. + CSIZE :: 0x00000030; // Number of bits per byte (mask). + CSTOPB :: 0x00000040; // Two stop bits instead of one. + CREAD :: 0x00000080; // Enable receiver. + PARENB :: 0x00000100; // Parity enable. + PARODD :: 0x00000200; // Odd parity instead of even. + HUPCL :: 0x00000400; // Hang up on last close. + CLOCAL :: 0x00000800; + + case .MACOS; + CS5 :: 0x00000000; // 5 bits per byte. + CS6 :: 0x00000100; // 6 bits per byte. + CS7 :: 0x00000200; // 7 bits per byte. + CS8 :: 0x00000300; // 8 bits per byte. + CSIZE :: 0x00000300; // Number of bits per byte (mask). + CSTOPB :: 0x00000400; // Two stop bits instead of one. + CREAD :: 0x00000800; // Enable receiver. + PARENB :: 0x00001000; // Parity enable. + PARODD :: 0x00002000; // Odd parity instead of even. + HUPCL :: 0x00004000; // Hang up on last close. + CLOCAL :: 0x00008000; + } } // Local modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Local_Modes :: enum_flags u32 { - ISIG :: 0000001; // Enable signals. - ICANON :: 0000002; // Do erase and kill processing. - ECHO :: 0000010; // Enable echo. - ECHOE :: 0000020; // Visual erase for ERASE. - ECHOK :: 0000040; // Echo NL after KILL. - ECHONL :: 0000100; // Echo NL even if ECHO is off. - NOFLSH :: 0000200; // Disable flush after interrupt. - TOSTOP :: 0000400; // Send SIGTTOU for background output. - IEXTEN :: 0100000; // Enable DISCARD and LNEXT. + #if OS == { + + case .LINUX; + ISIG :: 0x00000001; // Enable signals. + ICANON :: 0x00000002; // Do erase and kill processing. + ECHO :: 0x00000008; // Enable echo. + ECHOE :: 0x00000010; // Visual erase for ERASE. + ECHOK :: 0x00000020; // Echo NL after KILL. + ECHONL :: 0x00000040; // Echo NL even if ECHO is off. + NOFLSH :: 0x00000080; // Disable flush after interrupt. + TOSTOP :: 0x00000100; // Send SIGTTOU for background output. + IEXTEN :: 0x00008000; // Enable DISCARD and LNEXT. + + case .MACOS; + ISIG :: 0x00000080; // Enable signals. + ICANON :: 0x00000100; // Do erase and kill processing. + ECHO :: 0x00000008; // Enable echo. + ECHOE :: 0x00000002; // Visual erase for ERASE. + ECHOK :: 0x00000004; // Echo NL after KILL. + ECHONL :: 0x00000010; // Echo NL even if ECHO is off. + NOFLSH :: 0x80000000; // Disable flush after interrupt. + TOSTOP :: 0x00400000; // Send SIGTTOU for background output. + IEXTEN :: 0x00000400; // Enable DISCARD and LNEXT. + } } // Control Characters + TODO WIP + // LINUX : ??? + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Control_Chars :: enum u8 { VINTR :: 0; VQUIT :: 1; @@ -185,13 +238,13 @@ else { k_termios: __kernel_termios; cmd: u64; if optional_actions == { - case xx SetAttributesActions.TCSANOW; + case xx OptionalActions.TCSANOW; cmd = TCSETS; - case xx SetAttributesActions.TCSADRAIN; + case xx OptionalActions.TCSADRAIN; cmd = TCSETSW; - case xx SetAttributesActions.TCSAFLUSH; + case xx OptionalActions.TCSAFLUSH; cmd = TCSETSF; case; @@ -343,7 +396,7 @@ OS_prepare_terminal :: () -> success := true { raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - error = tcsetattr(STDIN_FILENO, TCSANOW, *raw_tio_mode); + error = tcsetattr(STDIN_FILENO, xx OptionalActions.TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); @@ -357,7 +410,7 @@ OS_prepare_terminal :: () -> success := true { OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, TCSANOW, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, xx OptionalActions.TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); @@ -367,7 +420,7 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - error := tcflush(STDIN_FILENO, TCIFLUSH); + error := tcflush(STDIN_FILENO, xx QueueSelector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); diff --git a/ttt.jai b/ttt.jai index 45dc7f8..63317d7 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1956,6 +1956,7 @@ main :: () { // Coalesce similar tasks. case #char "c"; #through; case #char "C"; + // TODO Active task is lost... if (db.tasks.count <= 0) continue; TUI.using_style(action_style); if (prompt_user_key(selected_task_row, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue; -- cgit v1.2.3 From 2037cba123cb98f367024346d2d882a505fd961e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 12:54:13 +0100 Subject: Fixed TUI\unix from c_octal-to-jai mis-conversion. --- modules/TUI/unix.jai | 201 +++++++-------------------------------------------- unused.jai | 145 +++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 173 deletions(-) (limited to 'modules') diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index bb22030..e1c0b3a 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,11 +1,5 @@ #scope_file -/* -TODO : then do a good implementation of the libc functions about attributes... -*/ - -USE_LIBC :: true; - #import "Atomics"; #import "System"; #import "POSIX"; @@ -13,7 +7,7 @@ USE_LIBC :: true; // Queue selector used in tcflush(...). // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-struct.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html - QueueSelector :: enum s32 { + Queue_Selector :: enum s32 { #if OS == { case .LINUX; TCIFLUSH :: 0; // Discard data received but not yet read. @@ -30,7 +24,7 @@ USE_LIBC :: true; // Optional actions used in tcsetattr(...). // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html - OptionalActions :: enum s32 { + Optional_Actions :: enum s32 { TCSANOW :: 0; // Change immediately. TCSADRAIN :: 1; // Change when pending output is written. TCSAFLUSH :: 2; // Flush pending input before changing. @@ -74,7 +68,7 @@ USE_LIBC :: true; ICRNL :: 0x00000100; // Map CR to NL on input. #if OS == { - + case .LINUX; IXON :: 0x00000400; // Enable start/stop output control. IXANY :: 0x00000800; // Any character will restart after stop. @@ -84,12 +78,11 @@ USE_LIBC :: true; IXON :: 0x00000200; // Enable start/stop output control. IXANY :: 0x00000400; // Any character will restart after stop. IXOFF :: 0x00000800; // Enable start/stop input control. - } } // Output modes. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_oflag.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Output_Modes :: enum_flags u32 { #if OS == { @@ -113,7 +106,7 @@ USE_LIBC :: true; } // Control modes. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_cflag.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Control_Modes :: enum u32 { #if OS == { @@ -147,64 +140,54 @@ USE_LIBC :: true; } // Local modes. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_lflag.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Local_Modes :: enum_flags u32 { #if OS == { case .LINUX; ISIG :: 0x00000001; // Enable signals. - ICANON :: 0x00000002; // Do erase and kill processing. + ICANON :: 0x00000002; // Canonical input (erase and kill processing). ECHO :: 0x00000008; // Enable echo. ECHOE :: 0x00000010; // Visual erase for ERASE. ECHOK :: 0x00000020; // Echo NL after KILL. ECHONL :: 0x00000040; // Echo NL even if ECHO is off. - NOFLSH :: 0x00000080; // Disable flush after interrupt. + NOFLSH :: 0x00000080; // Disable flush after interrupt or quit. TOSTOP :: 0x00000100; // Send SIGTTOU for background output. IEXTEN :: 0x00008000; // Enable DISCARD and LNEXT. case .MACOS; - ISIG :: 0x00000080; // Enable signals. - ICANON :: 0x00000100; // Do erase and kill processing. + ISIG :: 0x00000080; // Enable signals INTR, QUIT, [D]SUSP. + ICANON :: 0x00000100; // Canonicalize input lines. ECHO :: 0x00000008; // Enable echo. ECHOE :: 0x00000002; // Visual erase for ERASE. ECHOK :: 0x00000004; // Echo NL after KILL. ECHONL :: 0x00000010; // Echo NL even if ECHO is off. NOFLSH :: 0x80000000; // Disable flush after interrupt. - TOSTOP :: 0x00400000; // Send SIGTTOU for background output. + TOSTOP :: 0x00400000; // Stop background jobs from output. IEXTEN :: 0x00000400; // Enable DISCARD and LNEXT. } } // Control Characters - TODO WIP - // LINUX : ??? + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_cc.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Control_Chars :: enum u8 { - VINTR :: 0; - VQUIT :: 1; - VERASE :: 2; - VKILL :: 3; - VEOF :: 4; - VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. - VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. - VSWTC :: 7; - VSTART :: 8; - VSTOP :: 9; - VSUSP :: 10; - VEOL :: 11; - VREPRINT :: 12; - VDISCARD :: 13; - VWERASE :: 14; - VLNEXT :: 15; - VEOL2 :: 16; - } - + // Unused consts: + // VINTR, VQUIT, VERASE, VKILL, VEOF, VSWTC, VSTART, VSTOP, VSUSP, VEOL, VREPRINT, VDISCARD, VWERASE, VLNEXT, VEOL2 + + #if OS == { -#if USE_LIBC { - // Required to do unlocking input. - libc :: #system_library "libc"; + case .LINUX; + VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. + case .MACOS; + VTIME :: 17; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 16; // Minimum number of bytes read at once [!ICANON]. + } + } + // 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; @@ -213,134 +196,6 @@ USE_LIBC :: true; // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; -} -else { - - // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html - tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { - - #if OS == .LINUX { - TCSETS :: 0x5402; - TCSETSW :: 0x5403; - TCSETSF :: 0x5404; - tcflag_t :: u32; - cc_t :: u8; - __KERNEL_NCCS :: 19; - __kernel_termios :: struct { - c_iflag : tcflag_t; // input mode flags - c_oflag : tcflag_t; // output mode flags - c_cflag : tcflag_t; // control mode flags - c_lflag : tcflag_t; // local mode flags - c_line : cc_t; // line discipline - c_cc : [__KERNEL_NCCS]cc_t; // control characters - }; - - k_termios: __kernel_termios; - cmd: u64; - if optional_actions == { - case xx OptionalActions.TCSANOW; - cmd = TCSETS; - - case xx OptionalActions.TCSADRAIN; - cmd = TCSETSW; - - case xx OptionalActions.TCSAFLUSH; - cmd = TCSETSF; - - case; - return EINVAL; - } - // k_termios.c_iflag = termios_p.c_iflag & ~IBAUD0; - k_termios.c_iflag = xx termios_p.c_iflag; - k_termios.c_oflag = xx termios_p.c_oflag; - k_termios.c_cflag = xx termios_p.c_cflag; - k_termios.c_lflag = xx termios_p.c_lflag; - k_termios.c_line = xx termios_p.c_line; - // #if _HAVE_C_ISPEED && _HAVE_STRUCT_TERMIOS_C_ISPEED - // k_termios.c_ispeed = termios_p->c_ispeed; - // #endif - // #if _HAVE_C_OSPEED && _HAVE_STRUCT_TERMIOS_C_OSPEED - // k_termios.c_ospeed = termios_p->c_ospeed; - // #endif - memcpy(*k_termios.c_cc[0], *termios_p.c_cc[0], __KERNEL_NCCS * 1);//size_of(cc_t)); - return ioctl(fd, cmd, *k_termios); - } - #if OS == .MACOS { - // return __ioctl (fd, TIOCSETAF, termios_p); - #assert(false, "NOT IMPLEMENTED"); - } - return 0; - } - - // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html - tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { - TCSETS :: 0x5402; - TCSETSW :: 0x5403; - TCSETSF :: 0x5404; - tcflag_t :: u32; - cc_t :: u8; - __KERNEL_NCCS :: 19; - __kernel_termios :: struct { - c_iflag : tcflag_t; // input mode flags - c_oflag : tcflag_t; // output mode flags - c_cflag : tcflag_t; // control mode flags - c_lflag : tcflag_t; // local mode flags - c_line : cc_t; // line discipline - c_cc : [__KERNEL_NCCS]cc_t; // control characters - }; - - - // int - // __tcgetattr (int fd, struct termios *termios_p) - // { - // struct __kernel_termios k_termios; - k_termios: __kernel_termios; - retval: int; - retval = ioctl(fd, TCGETS, *k_termios); - if retval == 0 { - termios_p.c_iflag = xx k_termios.c_iflag; - termios_p.c_oflag = xx k_termios.c_oflag; - termios_p.c_cflag = xx k_termios.c_cflag; - termios_p.c_lflag = xx k_termios.c_lflag; - termios_p.c_line = xx k_termios.c_line; - // #if _HAVE_STRUCT_TERMIOS_C_ISPEED - // # if _HAVE_C_ISPEED - // termios_p->c_ispeed = k_termios.c_ispeed; - // # else - // termios_p->c_ispeed = k_termios.c_cflag & (CBAUD | CBAUDEX); - // # endif - // #endif - // #if _HAVE_STRUCT_TERMIOS_C_OSPEED - // # if _HAVE_C_OSPEED - // termios_p->c_ospeed = k_termios.c_ospeed; - // # else - // termios_p->c_ospeed = k_termios.c_cflag & (CBAUD | CBAUDEX); - // # endif - // #endif - size_of_cc_t := __KERNEL_NCCS * 1; - memcpy(*termios_p.c_cc[0], *k_termios.c_cc[0], size_of_cc_t); - // memset(*termios_p.c_cc[0] + size_of_cc_t + 1, _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * 1); - // - // if (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0 || (unsigned char) _POSIX_VDISABLE == (unsigned char) -1) { - // memset (__mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)), _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t)); - // } - // else - // { - // memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)); - // for (size_t cnt = __KERNEL_NCCS; cnt < NCCS; ++cnt) { - // termios_p->c_cc[cnt] = _POSIX_VDISABLE; - // } - // } - } - return xx retval; - } - - // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html - tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { - TCFLSH :: 0x540B; - return ioctl(fd, TCFLSH, queue_selector); - } -} //////////////////////////////////////////////////////////////////////////////// @@ -396,7 +251,7 @@ OS_prepare_terminal :: () -> success := true { raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - error = tcsetattr(STDIN_FILENO, xx OptionalActions.TCSANOW, *raw_tio_mode); + error = tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); @@ -410,7 +265,7 @@ OS_prepare_terminal :: () -> success := true { OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, xx OptionalActions.TCSANOW, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); @@ -420,7 +275,7 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - error := tcflush(STDIN_FILENO, xx QueueSelector.TCIFLUSH); + error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); diff --git a/unused.jai b/unused.jai index 9e05904..0425f8d 100644 --- a/unused.jai +++ b/unused.jai @@ -37,6 +37,151 @@ print_database :: (db: Database) { ); } } + +// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // + +// Implementation of tcsetattr, tcgetattr, and tcflush using only 'ioctl' which is provided by jai. + +#if USE_LIBC { + + 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; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html + 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; +} +else { + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html + tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { + + #if OS == .LINUX { + TCSETS :: 0x5402; + TCSETSW :: 0x5403; + TCSETSF :: 0x5404; + tcflag_t :: u32; + cc_t :: u8; + __KERNEL_NCCS :: 19; + __kernel_termios :: struct { + c_iflag : tcflag_t; // input mode flags + c_oflag : tcflag_t; // output mode flags + c_cflag : tcflag_t; // control mode flags + c_lflag : tcflag_t; // local mode flags + c_line : cc_t; // line discipline + c_cc : [__KERNEL_NCCS]cc_t; // control characters + }; + + k_termios: __kernel_termios; + cmd: u64; + if optional_actions == { + case xx Optional_Actions.TCSANOW; + cmd = TCSETS; + + case xx Optional_Actions.TCSADRAIN; + cmd = TCSETSW; + + case xx Optional_Actions.TCSAFLUSH; + cmd = TCSETSF; + + case; + return EINVAL; + } + // k_termios.c_iflag = termios_p.c_iflag & ~IBAUD0; + k_termios.c_iflag = xx termios_p.c_iflag; + k_termios.c_oflag = xx termios_p.c_oflag; + k_termios.c_cflag = xx termios_p.c_cflag; + k_termios.c_lflag = xx termios_p.c_lflag; + k_termios.c_line = xx termios_p.c_line; + // #if _HAVE_C_ISPEED && _HAVE_STRUCT_TERMIOS_C_ISPEED + // k_termios.c_ispeed = termios_p->c_ispeed; + // #endif + // #if _HAVE_C_OSPEED && _HAVE_STRUCT_TERMIOS_C_OSPEED + // k_termios.c_ospeed = termios_p->c_ospeed; + // #endif + memcpy(*k_termios.c_cc[0], *termios_p.c_cc[0], __KERNEL_NCCS * 1);//size_of(cc_t)); + return ioctl(fd, cmd, *k_termios); + } + #if OS == .MACOS { + // return __ioctl (fd, TIOCSETAF, termios_p); + #assert(false, "NOT IMPLEMENTED"); + } + return 0; + } + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html + tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { + TCSETS :: 0x5402; + TCSETSW :: 0x5403; + TCSETSF :: 0x5404; + tcflag_t :: u32; + cc_t :: u8; + __KERNEL_NCCS :: 19; + __kernel_termios :: struct { + c_iflag : tcflag_t; // input mode flags + c_oflag : tcflag_t; // output mode flags + c_cflag : tcflag_t; // control mode flags + c_lflag : tcflag_t; // local mode flags + c_line : cc_t; // line discipline + c_cc : [__KERNEL_NCCS]cc_t; // control characters + }; + + + // int + // __tcgetattr (int fd, struct termios *termios_p) + // { + // struct __kernel_termios k_termios; + k_termios: __kernel_termios; + retval: int; + retval = ioctl(fd, TCGETS, *k_termios); + if retval == 0 { + termios_p.c_iflag = xx k_termios.c_iflag; + termios_p.c_oflag = xx k_termios.c_oflag; + termios_p.c_cflag = xx k_termios.c_cflag; + termios_p.c_lflag = xx k_termios.c_lflag; + termios_p.c_line = xx k_termios.c_line; + // #if _HAVE_STRUCT_TERMIOS_C_ISPEED + // # if _HAVE_C_ISPEED + // termios_p->c_ispeed = k_termios.c_ispeed; + // # else + // termios_p->c_ispeed = k_termios.c_cflag & (CBAUD | CBAUDEX); + // # endif + // #endif + // #if _HAVE_STRUCT_TERMIOS_C_OSPEED + // # if _HAVE_C_OSPEED + // termios_p->c_ospeed = k_termios.c_ospeed; + // # else + // termios_p->c_ospeed = k_termios.c_cflag & (CBAUD | CBAUDEX); + // # endif + // #endif + size_of_cc_t := __KERNEL_NCCS * 1; + memcpy(*termios_p.c_cc[0], *k_termios.c_cc[0], size_of_cc_t); + // memset(*termios_p.c_cc[0] + size_of_cc_t + 1, _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * 1); + // + // if (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0 || (unsigned char) _POSIX_VDISABLE == (unsigned char) -1) { + // memset (__mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)), _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t)); + // } + // else + // { + // memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)); + // for (size_t cnt = __KERNEL_NCCS; cnt < NCCS; ++cnt) { + // termios_p->c_cc[cnt] = _POSIX_VDISABLE; + // } + // } + } + return xx retval; + } + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html + tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { + TCFLSH :: 0x540B; + return ioctl(fd, TCFLSH, queue_selector); + } +} // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // -- cgit v1.2.3 From 90d17cde7d92be5e2c1bedb97c8c8d5ca324a39d Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 18:32:28 +0100 Subject: Removed some 'inline' marks, and added some TODO entries. --- modules/TUI/module.jai | 5 +++-- modules/TUI/unix.jai | 6 +++--- modules/TUI/windows.jai | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 121aa42..f8b8265 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -381,7 +381,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? + assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? Or simply return success=false with #must if count_limit < 0 { builder: String_Builder; @@ -438,7 +438,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { Resize discards the input returning an empty string and a Resize key. */ - assert(count_limit >= 0, "Invalid value on count_limit parameter."); + assert(count_limit >= 0, "Invalid value on count_limit parameter."); // TODO Too agressive str := alloc_string(count_limit); str.count = 0; @@ -446,6 +446,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line + // At least... add the Ctrl+u to clear the entire line. x, y := get_cursor_position(); write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index e1c0b3a..a8e0edf 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -263,7 +263,7 @@ OS_prepare_terminal :: () -> success := true { return; } -OS_reset_terminal :: inline () -> success := true { +OS_reset_terminal :: () -> success := true { restore_resize_handler(); error := tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *initial_tio_mode); if error { @@ -274,7 +274,7 @@ OS_reset_terminal :: inline () -> success := true { return; } -OS_flush_input :: inline () -> success := true { +OS_flush_input :: () -> success := true { error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); @@ -314,6 +314,6 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return ifx result > 0 then true else false; } -OS_was_terminal_resized :: () -> bool { +OS_was_terminal_resized :: inline () -> bool { return atomic_swap(*was_resized, false); } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 608266d..fb50e49 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -137,7 +137,7 @@ //////////////////////////////////////////////////////////////////////////////// -peek_input :: inline () -> INPUT_RECORD, success := true { +peek_input :: () -> INPUT_RECORD, success := true { record: INPUT_RECORD; records_read: u32; if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { @@ -148,7 +148,7 @@ peek_input :: inline () -> INPUT_RECORD, success := true { return record; } -read_input :: inline () -> INPUT_RECORD, success := true { +read_input :: () -> INPUT_RECORD, success := true { record: INPUT_RECORD; records_read: u32; if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { @@ -159,7 +159,7 @@ read_input :: inline () -> INPUT_RECORD, success := true { return record; } -count_input :: inline () -> u32, success := true { +count_input :: () -> u32, success := true { count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { error_code, error_string := get_error_value_and_string(); @@ -242,7 +242,7 @@ OS_reset_terminal :: () -> success := true { return; } -OS_flush_input :: inline () { +OS_flush_input :: () { // This API is not recommended and does not have a virtual terminal equivalent. // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. if FlushConsoleInputBuffer(stdin) == false { @@ -379,7 +379,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return false; } -OS_was_terminal_resized :: () -> bool { +OS_was_terminal_resized :: inline () -> bool { while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { was_resized = true; read_input(); -- cgit v1.2.3 From 66dfee359cbe2c18ce8400edeb801a2373a771f4 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 2 May 2024 00:57:53 +0100 Subject: WIP : Using string builder to improve TUI performance. --- modules/TUI/module.jai | 87 ++++++++++++++++++++++++++++++++++++++++++++++---- ttt.jai | 2 ++ 2 files changed, 83 insertions(+), 6 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index f8b8265..991dc74 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -3,7 +3,6 @@ #scope_file // - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls) -// - fix/implement/finish `TODO` on `TUI\module` (use `dirty_bit_flag` to only update ehat has been changed) #if OS == { case .LINUX; @@ -297,7 +296,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - assert(active, "TUI is not ready."); + assert(active, "TUI is not ready. Please call TUI.start() before."); // TODO Improve error message... and maybe use the logger and return success=false flag } //////////////////////////////////////////////////////////////////////////////// @@ -444,10 +443,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.count = 0; idx := 0; - // TODO Some of these may be nice to have: - // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line - // At least... add the Ctrl+u to clear the entire line. - x, y := get_cursor_position(); write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); @@ -531,6 +526,11 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () -> success := true #must { if active == true return; + + if inited == false { + init_string_builder(*temp_builder,, temp); // TODO Testing + inited = true; + } input_string.data = input_buffer.data; input_string.count = 0; @@ -583,7 +583,19 @@ flush_input :: () { input_string.count = 0; } + + +// Using String_Builder improves: +// - procedure time is now ~12x faster; +// - reduces CPU usage by ~ +average: float64 = 0; +counter: float64 = 0; +inited:=false; +temp_builder: String_Builder; + +#if false { draw_box :: (x: int, y: int, width: int, height: int) { + t0 := current_time_monotonic(); assert_is_active(); // TODO Check if using a String_Builder improves performance (measure it)! @@ -626,6 +638,69 @@ draw_box :: (x: int, y: int, width: int, height: int) { write_string(Drawings.CornerBR); write_string(Commands.TextMode); + + t1 := current_time_monotonic(); + set_cursor_position(2, 47); + sample := cast(float64)to_nanoseconds(t1-t0)/1000; + average = (sample + counter * average) / (counter + 1); + counter += 1; + print(">%<", average); +} + +} else { + + +// TODO Not sure how... but this seems to be leaking memory... maybe it's the String_Builder!? +draw_box :: (x: int, y: int, width: int, height: int) { + t0 := current_time_monotonic(); + + assert_is_active(); + + // 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(); + + builder := temp_builder; + // init_string_builder(*builder); + + append(*builder, Commands.DrawingMode); + append(*builder, tprint(Commands.SetCursorPosition, y, x)); + append(*builder, Drawings.CornerTL); + + for 1..width-2 { + append(*builder, Drawings.LineH); + } + append(*builder, Drawings.CornerTR); + + for idx: y+1..y+height-2 { + tmpL := tprint(Commands.SetCursorPosition, idx, x); + tmpR := tprint(Commands.SetCursorPosition, idx, x+width-1); + append(*builder, tmpL); + append(*builder, Drawings.LineV); + append(*builder, tmpR); + append(*builder, Drawings.LineV); + } + + append(*builder, tprint(Commands.SetCursorPosition, y+height-1, x)); + append(*builder, Drawings.CornerBL); + for 1..width-2 { + append(*builder, Drawings.LineH); + } + append(*builder, Drawings.CornerBR); + + append(*builder, Commands.TextMode); + + write_string(builder_to_string(*builder)); + + t1 := current_time_monotonic(); + set_cursor_position(2, 47); + sample := cast(float64)to_nanoseconds(t1-t0)/1000; + average = (sample + counter * average) / (counter + 1); + counter += 1; + print(">%<", average); +} } // TODO Maybe rename to "clear()" diff --git a/ttt.jai b/ttt.jai index 63317d7..50fa198 100644 --- a/ttt.jai +++ b/ttt.jai @@ -28,6 +28,8 @@ #import "UTF8"; TUI :: #import "TUI"(COLOR_MODE=4); +// - fix/implement/finish TODO : use `dirty_bit_flag` to only update ehat has been changed + VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; FIRST_DAY_OF_WEEK :: 1; // (0-6, Sunday = 0). -- cgit v1.2.3 From 8b312bad33617f9b718232141d2855d80cb8b912 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 3 May 2024 00:32:06 +0100 Subject: WIP : Cleaning up TUI module. --- modules/TUI/module.jai | 178 +++++++++++++------------------------------- modules/TUI/palette_24b.jai | 2 +- snake.jai | 6 +- ttt.jai | 51 ++++++------- unused.jai | 15 ++++ 5 files changed, 95 insertions(+), 157 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 991dc74..d44edab 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,9 +1,7 @@ -#module_parameters(COLOR_MODE := 24); +#module_parameters(COLOR_MODE_BITS := 24); #scope_file -// - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls) - #if OS == { case .LINUX; #load "unix.jai"; @@ -22,14 +20,14 @@ #load "key_map.jai"; #run { - assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. Valid values are 4, 8, or 24 (default)."); + assert(COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 || COLOR_MODE_BITS == 24, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); } #scope_export; -// Special Graphics Characters -Drawings :: struct { +// Special Graphics Characters. +Drawings :: struct #type_info_none { Blank :: "\x5F"; Diamond :: "\x60"; Checkerboard :: "\x61"; @@ -64,7 +62,9 @@ Drawings :: struct { CenteredDot :: "\x7E"; } -Commands :: struct { +// Terminal Escape Codes. +Commands :: struct #type_info_none { + // Screen buffers AlternateScreenBuffer :: "\e[?1049h"; MainScreenBuffer :: "\e[?1049l"; @@ -118,7 +118,7 @@ Commands :: struct { CursorNormalMode :: "\e[?1l"; } -#if COLOR_MODE == 4 { +#if COLOR_MODE_BITS == 4 { #load "palette_4b.jai"; set_colors :: inline (foreground: Palette, background: Palette) { @@ -127,7 +127,7 @@ Commands :: struct { cast(u8)foreground + 30, cast(u8)background + 40); } } -else #if COLOR_MODE == 8 { +else #if COLOR_MODE_BITS == 8 { #load "palette_8b.jai"; set_colors :: inline (foreground: Palette, background: Palette) { @@ -153,19 +153,21 @@ else { } } -#add_context tui_style: Style; - Style :: struct { - #if COLOR_MODE == 4 || COLOR_MODE == 8 { + #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { background: Palette; foreground: Palette; } else { background: Color_24b; foreground: Color_24b; } - + background = Palette.BLACK; foreground = Palette.WHITE; + + // TODO Make this work... + use_default_background_color := false; + use_default_foreground_color := false; bold: bool; underline: bool; @@ -198,9 +200,6 @@ using_style :: (style: Style) #expand { `defer set_style(__style); } -// 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. @@ -238,7 +237,7 @@ to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY return str; } -is_escape_code :: inline (key: Key) -> bool { +is_escape_code :: (key: Key) -> bool { beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; hasSomethingElse := (key & (~0xFF)) != 0; return beginsWithEscape && hasSomethingElse; @@ -281,6 +280,8 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } +#add_context tui_style: Style; + active := false; input_buffer : [1024] u8; @@ -296,7 +297,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - assert(active, "TUI is not ready. Please call TUI.start() before."); // TODO Improve error message... and maybe use the logger and return success=false flag + 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 } //////////////////////////////////////////////////////////////////////////////// @@ -376,12 +377,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { return to_key(to_parse); } -// TODO Review me! -read_input :: (count_limit: int = -1, terminators: .. u8) -> string { +// TODO Review me and add some comments. +read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := true { assert_is_active(); - - assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? Or simply return success=false with #must - + + if count_limit < 0 && terminators.count <= 0 { + log_error("Invalid arguments passed to read_input(): (%).\n", count_limit); // TODO Improve error message. + return "", false; + } + if count_limit < 0 { builder: String_Builder; init_string_builder(*builder); @@ -436,7 +440,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { Escape discards the input returning an empty string and a Escape key. Resize discards the input returning an empty string and a Resize key. */ - + assert_is_active(); assert(count_limit >= 0, "Invalid value on count_limit parameter."); // TODO Too agressive str := alloc_string(count_limit); @@ -452,7 +456,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { chars_count := count_characters(str); - // Preview input. + // Preview input. TODO Optimize using temp_builder if is_visible { set_cursor_position(x, y); write_string(str); @@ -524,13 +528,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { return result, key; } -start :: () -> success := true #must { +setup_terminal :: () -> success := true #must { if active == true return; - - if inited == false { - init_string_builder(*temp_builder,, temp); // TODO Testing - inited = true; - } input_string.data = input_buffer.data; input_string.count = 0; @@ -557,7 +556,7 @@ start :: () -> success := true #must { return; } -stop :: () -> success := true #must { +reset_terminal :: () -> success := true #must { if active == false return; active = false; @@ -578,138 +577,61 @@ stop :: () -> success := true #must { } flush_input :: () { + assert_is_active(); OS_flush_input(); input_string.data = input_buffer.data; input_string.count = 0; } - - -// Using String_Builder improves: -// - procedure time is now ~12x faster; -// - reduces CPU usage by ~ -average: float64 = 0; -counter: float64 = 0; -inited:=false; -temp_builder: String_Builder; - -#if false { +// TODO What if we return success and fail when input arguments are invalid? draw_box :: (x: int, y: int, width: int, height: int) { - t0 := current_time_monotonic(); - assert_is_active(); - - // 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 - ); + assert_is_active(); //TODO NOT NEEDED - 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); + 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; } - write_string(Drawings.CornerBR); - write_string(Commands.TextMode); - - t1 := current_time_monotonic(); - set_cursor_position(2, 47); - sample := cast(float64)to_nanoseconds(t1-t0)/1000; - average = (sample + counter * average) / (counter + 1); - counter += 1; - print(">%<", average); -} - -} else { - - -// TODO Not sure how... but this seems to be leaking memory... maybe it's the String_Builder!? -draw_box :: (x: int, y: int, width: int, height: int) { - t0 := current_time_monotonic(); - - assert_is_active(); - - // 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(); - builder := temp_builder; - // init_string_builder(*builder); - + builder := String_Builder.{ allocator = temporary_allocator }; + append(*builder, Commands.DrawingMode); + + // Draw top line append(*builder, tprint(Commands.SetCursorPosition, y, x)); append(*builder, Drawings.CornerTL); - for 1..width-2 { append(*builder, Drawings.LineH); } append(*builder, Drawings.CornerTR); + // Draw left and right sides. for idx: y+1..y+height-2 { - tmpL := tprint(Commands.SetCursorPosition, idx, x); - tmpR := tprint(Commands.SetCursorPosition, idx, x+width-1); - append(*builder, tmpL); + append(*builder, tprint(Commands.SetCursorPosition, idx, x)); append(*builder, Drawings.LineV); - append(*builder, tmpR); + append(*builder, tprint(Commands.SetCursorPosition, idx, x+width-1)); append(*builder, Drawings.LineV); } + // Draw bottom line. append(*builder, tprint(Commands.SetCursorPosition, y+height-1, x)); append(*builder, Drawings.CornerBL); for 1..width-2 { append(*builder, Drawings.LineH); } append(*builder, Drawings.CornerBR); - + append(*builder, Commands.TextMode); write_string(builder_to_string(*builder)); - - t1 := current_time_monotonic(); - set_cursor_position(2, 47); - sample := cast(float64)to_nanoseconds(t1-t0)/1000; - average = (sample + counter * average) / (counter + 1); - counter += 1; - print(">%<", average); -} } -// TODO Maybe rename to "clear()" clear_terminal :: inline () { assert_is_active(); write_string(Commands.ClearScreen); } -// TODO Maybe rename to "get_size()" get_terminal_size :: () -> width: int, height: int { assert_is_active(); @@ -735,7 +657,7 @@ get_terminal_size :: () -> width: int, height: int { 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."); @@ -757,7 +679,7 @@ get_terminal_size :: () -> width: int, height: int { return columns, rows; } -set_cursor_position :: (x: int, y: int) { +set_cursor_position :: inline (x: int, y: int) { assert_is_active(); print(Commands.SetCursorPosition, y, x); } @@ -784,7 +706,7 @@ get_cursor_position :: () -> x: int, y: int { 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."); @@ -796,7 +718,7 @@ get_cursor_position :: () -> x: int, y: int { return column, row; } -set_terminal_title :: (title: string) { +set_terminal_title :: inline (title: string) { assert_is_active(); print(Commands.SetWindowTitle, title); } diff --git a/modules/TUI/palette_24b.jai b/modules/TUI/palette_24b.jai index 7a263a0..ea0191c 100644 --- a/modules/TUI/palette_24b.jai +++ b/modules/TUI/palette_24b.jai @@ -1,5 +1,5 @@ // https://www.ditig.com/publications/256-colors-cheat-sheet -Palette :: struct { +Palette :: struct #type_info_none { BLACK :: Color_24b.{0x00, 0x00, 0x00}; MAROON :: Color_24b.{0x80, 0x00, 0x00}; GREEN :: Color_24b.{0x00, 0x80, 0x00}; diff --git a/snake.jai b/snake.jai index 465ae4d..b35c0fb 100644 --- a/snake.jai +++ b/snake.jai @@ -1,6 +1,6 @@ #import "Basic"; #import "Random"; -TUI :: #import "TUI"(COLOR_MODE = 8); +TUI :: #import "TUI"(COLOR_MODE_BITS = 8); Vec2D :: struct { x: int; @@ -140,7 +140,7 @@ main :: () { seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. random_seed(seed); - assert(TUI.start(), "Failed to start TUI."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); TUI.set_cursor_position(1, 1); write_string("Please enter player name: "); @@ -161,5 +161,5 @@ main :: () { if TUI.get_key() == TUI.Keys.Escape then break; } - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); } diff --git a/ttt.jai b/ttt.jai index 50fa198..3512d20 100644 --- a/ttt.jai +++ b/ttt.jai @@ -17,7 +17,8 @@ // - release : jai ttt.jai -quiet -x64 -release // - debug : jai ttt.jai -quiet -x64 -#import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. +// #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. +#import "Basic"; #import "System"; #import "Sort"; #import "Math"; @@ -26,7 +27,7 @@ #import "String"; #import "Integer_Saturating_Arithmetic"; #import "UTF8"; -TUI :: #import "TUI"(COLOR_MODE=4); +TUI :: #import "TUI"(COLOR_MODE_BITS=4); // - fix/implement/finish TODO : use `dirty_bit_flag` to only update ehat has been changed @@ -868,7 +869,7 @@ initialize_tui :: () { } } - assert(TUI.start(), "Failed to start TUI."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); } update_layout :: () { @@ -1167,7 +1168,7 @@ main :: () { print("- success\n", to_standard_error = true); } else { - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); print("- ERROR: %", error_message, to_standard_error = true); exit(1); } @@ -1180,19 +1181,19 @@ main :: () { if perform_test && 1 { print("TEST : set and get cursor position\n", to_standard_error = true); - assert(TUI.start(), "Failed to start TUI."); + 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.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); } if perform_test && 1 { print("TEST : module logger\n", to_standard_error = true); log("- log: before module start."); - assert(TUI.start(), "Failed to start TUI."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); TUI.set_cursor_position(3, 3); print("wait"); sleep_milliseconds(1000); @@ -1200,14 +1201,14 @@ main :: () { sleep_milliseconds(1000); print(" a bit"); sleep_milliseconds(1000); - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); log("- log: after module stop."); } if perform_test && 1 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); - assert(TUI.start(), "Failed to start TUI."); + 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."); @@ -1226,28 +1227,28 @@ main :: () { write_string(TUI.to_string(key)); } } - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); print("- success\n", to_standard_error = true); } if perform_test && 1 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); - assert(TUI.start(), "Failed to start TUI."); + 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.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); assert_result(key == #char "y", "Failed to draw box.\n"); } if perform_test && 1 { print("TEST : get terminal size\n", to_standard_error = true); auto_release_temp(); - assert(TUI.start(), "Failed to start TUI."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); TUI.clear_terminal(); width, height := TUI.get_terminal_size(); TUI.set_cursor_position(1, 1); @@ -1256,13 +1257,13 @@ main :: () { while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); } - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); assert_result(key == #char "y", "Failed to get terminal size.\n"); } if perform_test && 1 { print("TEST : set terminal title\n", to_standard_error = true); - assert(TUI.start(), "Failed to start TUI."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); title := "BAZINGA"; TUI.set_terminal_title(title); TUI.set_cursor_position(1, 1); @@ -1271,14 +1272,14 @@ main :: () { while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); } - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); assert_result(key == #char "y", "Failed to set terminal title.\n"); } if perform_test && 1 { print("TEST : print keys and set terminal title\n", to_standard_error = true); auto_release_temp(); - assert(TUI.start(), "Failed to start TUI."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); TUI.set_terminal_title("bazinga"); key: TUI.Key = #char "d"; last_none_char := "X"; @@ -1337,13 +1338,13 @@ main :: () { // set_temporary_storage_mark(__mark); } print("- success"); - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); } if perform_test && 1 { print("TEST : user input\n", to_standard_error = true); auto_release_temp(); - assert(TUI.start(), "Failed to start TUI."); + 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):"); @@ -1368,14 +1369,14 @@ main :: () { } } answer := TUI.get_key(); - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); assert_result(answer == #char "y", error_message); } if perform_test && 1 { print("TEST : hidden user input\n", to_standard_error = true); auto_release_temp(); - assert(TUI.start(), "Failed to start TUI."); + 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):"); @@ -1399,14 +1400,14 @@ main :: () { } } answer := TUI.get_key(); - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); assert_result(answer == #char "y", error_message); } // -- -- -- Testing TUI -- STOP - defer report_memory_leaks(); // TODO Remove after final debug sessions. + // defer report_memory_leaks(); // TODO Remove after final debug sessions. defer free_memory(); @@ -1833,7 +1834,7 @@ main :: () { if (active_task == null) continue; select_task(db, db.active_idx); - // Start/Stop + // Start and stop. case TUI.Keys.Enter; #through; case TUI.Keys.Space; if (db != *database || selected_task == null) continue; @@ -2021,7 +2022,7 @@ main :: () { TUI.get_key(); } - assert(TUI.stop(), "Failed to stop TUI."); + assert(TUI.reset_terminal(), "Failed to reset TUI."); exit(xx ifx error_saving then 1 else 0); } diff --git a/unused.jai b/unused.jai index 0425f8d..a8dddfc 100644 --- a/unused.jai +++ b/unused.jai @@ -38,6 +38,21 @@ print_database :: (db: Database) { } } +// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // + + +// Average cumulative calculation. +average: float64 = 0; +counter: float64 = 0; +t0 := current_time_monotonic(); +t1 := current_time_monotonic(); +set_cursor_position(2, 47); +sample := cast(float64)to_nanoseconds(t1-t0)/1000; +average = (sample + counter * average) / (counter + 1); +counter += 1; +print(">%us<", average); + + // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // // Implementation of tcsetattr, tcgetattr, and tcflush using only 'ioctl' which is provided by jai. -- cgit v1.2.3 From 6b90a43d69142ff684b792ba2da951e5bbddc8a6 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 3 May 2024 01:00:54 +0100 Subject: WIP : Cleanup read_input_line on TUI module. --- modules/TUI/module.jai | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d44edab..464b0d9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -19,6 +19,8 @@ #import "UTF8"; #load "key_map.jai"; +KEY_SIZE :: #run type_info(Key).runtime_size; + #run { assert(COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 || COLOR_MODE_BITS == 24, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); @@ -213,8 +215,6 @@ using_style :: (style: Style) #expand { 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 { @@ -304,7 +304,7 @@ assert_is_active :: inline () { // #scope_export TODO Setup the scope_export and scope_file -set_next_key :: (key: Key) { +set_next_key :: inline (key: Key) { assert_is_active(); input_override = key; } @@ -432,42 +432,50 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := } } +// Uses the get_key to read user input and show it on screen. +// Allows to move the cursor left and right and to delete/backspace. +// Enter ends the input, returning the input string and the Enter key. +// Escape discards the input returning an empty string and a Escape key. +// Resize discards the input returning an empty string and a Resize key. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { - /* - Uses the get_key to read user input and show it on screen. - Allows to move the cursor left and right and to delete/backspace. - Enter ends the input, returning the input string and the Enter key. - Escape discards the input returning an empty string and a Escape key. - Resize discards the input returning an empty string and a Resize key. - */ assert_is_active(); - assert(count_limit >= 0, "Invalid value on count_limit parameter."); // TODO Too agressive + // 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; + // } + + builder := String_Builder.{ allocator = temporary_allocator }; + str := alloc_string(count_limit); str.count = 0; idx := 0; x, y := get_cursor_position(); - write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); - key := Keys.None; + + write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); + while true { auto_release_temp(); chars_count := count_characters(str); - // Preview input. TODO Optimize using temp_builder + // Draw preview. if is_visible { - set_cursor_position(x, y); - write_string(str); - for chars_count..count_limit-1 print_character(#char " "); + append(*builder, tprint(Commands.SetCursorPosition, y, x)); + append(*builder, str); + for chars_count..count_limit-1 append(*builder, " "); } else { - set_cursor_position(x, y); - for 1..chars_count print_character(#char "*"); - for chars_count..count_limit-1 print_character(#char " "); + append(*builder, tprint(Commands.SetCursorPosition, y, x)); + for 1..chars_count append(*builder, "*"); + for chars_count..count_limit-1 append(*builder, " "); } - set_cursor_position(x+idx, y); + append(*builder, tprint(Commands.SetCursorPosition, y, x+idx)); + write_string(builder_to_string(*builder)); // Process input key. key = get_key(); -- cgit v1.2.3 From 675b0a5fea60dc33a97b0dc1871bb9a0b61818cc Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 4 May 2024 01:43:34 +0100 Subject: WIP : Cleanup TUI module. Finally decided to go with hard-asserts (vs soft-errors/logs). --- modules/TUI/module.jai | 70 ++++++-------- modules/TUI/tests.jai | 247 +++++++++++++++++++++++++++++++++++++++++++++++ modules/TUI/unix.jai | 2 +- ttt.jai | 255 +------------------------------------------------ 4 files changed, 282 insertions(+), 292 deletions(-) create mode 100644 modules/TUI/tests.jai (limited to 'modules') 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; //////////////////////////////////////////////////////////////////////////////// diff --git a/ttt.jai b/ttt.jai index 3512d20..d471661 100644 --- a/ttt.jai +++ b/ttt.jai @@ -17,8 +17,7 @@ // - release : jai ttt.jai -quiet -x64 -release // - debug : jai ttt.jai -quiet -x64 -// #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. -#import "Basic"; +#import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. #import "System"; #import "Sort"; #import "Math"; @@ -1107,7 +1106,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); - style_input := context.tui_style; + style_input := context.terminal_style; style_input.underline = true; TUI.using_style(style_input); @@ -1158,256 +1157,8 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { } main :: () { - - // -- -- -- Testing TUI -- START - - perform_test := false; - - 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 perform_test && 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 perform_test && 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(1000); - log("- log: while module is active."); - sleep_milliseconds(1000); - print(" a bit"); - sleep_milliseconds(1000); - assert(TUI.reset_terminal(), "Failed to reset TUI."); - log("- log: after module stop."); - } - - if perform_test && 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 perform_test && 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 perform_test && 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 perform_test && 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 perform_test && 1 { - print("TEST : print keys and set terminal title\n", to_standard_error = true); - auto_release_temp(); - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_terminal_title("bazinga"); - 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_string(last_none_char); - } - - 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; - for 0..str.count-1 { - tmp := tprint("%", FormatInt.{value = cast(u8)str[it], base=16},, temporary_allocator); - array_add(*array_to_print, tmp); - } - string_to_print := join(..array_to_print, separator = " "); - print(": % : ", string_to_print); - 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 perform_test && 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 perform_test && 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 - - // defer report_memory_leaks(); // TODO Remove after final debug sessions. + defer report_memory_leaks(); // TODO Remove after final debug sessions. defer free_memory(); -- cgit v1.2.3 From b87e4a141da53cf903a0ab9fc9eb693c85a5c0b4 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 4 May 2024 22:58:39 +0100 Subject: Documented get_key procedure and small cleanup. --- modules/TUI/module.jai | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index a6a3d11..772b30c 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -299,7 +299,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - assert(active, "Please call setup_terminal() before using the module procedures."); + assert(active, "Please call setup_terminal() to start using this module."); } //////////////////////////////////////////////////////////////////////////////// @@ -311,7 +311,6 @@ set_next_key :: inline (key: Key) { input_override = key; } -// TODO Provide some documentation comments. get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); @@ -319,25 +318,22 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { defer input_override = xx Keys.None; return input_override; } - + if OS_was_terminal_resized() return Keys.Resize; - should_read_input := false; - is_input_available := false; + // If there's nothing on the input_string buffer, await for input to be available, + // otherwise, if we have less than a complete Key, check if there's more to read. + should_read_input := false; if input_string.count == 0 { - should_read_input = true; - is_input_available = OS_wait_for_input(timeout_milliseconds); + should_read_input = 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); + should_read_input = OS_wait_for_input(0); } - if OS_was_terminal_resized() return Keys.Resize; - - if should_read_input && is_input_available { - // Copy buffered bytes to the start, and read the remaining ones from input. + if should_read_input { + // Copy data to the start of the input_string buffer. for 0..input_string.count-1 { input_buffer[it] = input_string[it]; } @@ -348,6 +344,9 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { input_string.count += bytes_read; } + // The terminal may have been resized while waiting for or reading the input; check it again. + if OS_was_terminal_resized() return Keys.Resize; + if input_string.count == 0 return Keys.None; // By default, parse a single UTF8 character (1 to 4 bytes). @@ -368,12 +367,13 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { key, success = table_find(*key_map, to_parse); } - + + // If found, return the escape code, otherwise return a single escape character. if success { - return key; // Escape code found, return it. + return key; } else { - to_parse.count = 1; // No escape code found, return a single (escape) character. + to_parse.count = 1; } } @@ -381,14 +381,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { } // 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 +// If any ASCII 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(); assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); - // TODO Provide some documentation comments. + // Read until one of the terminator characters is found. + // Since we don't know the resulting size of the returned string, we must keep the string builder growing. if count_limit < 0 { builder: String_Builder; init_string_builder(*builder); @@ -411,6 +412,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } return builder_to_string(*builder); } + // Do the same but limit the number of bytes in the returned string. else { buffer := alloc_string(count_limit); buffer.count = 0; -- cgit v1.2.3 From cb6c3caaa285bc8bd12c356d57bbfad86d70c0eb Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 00:31:46 +0100 Subject: WIP : Cleanup TUI module scope. --- modules/TUI/module.jai | 222 +++++++++++++++++++++++--------------------- modules/TUI/palette_24b.jai | 6 ++ ttt.jai | 2 +- 3 files changed, 124 insertions(+), 106 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 772b30c..ab2567d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,7 +1,9 @@ #module_parameters(COLOR_MODE_BITS := 24); + #scope_file + #if OS == { case .LINUX; #load "unix.jai"; @@ -26,8 +28,27 @@ KEY_SIZE :: #run type_info(Key).runtime_size; assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); } +active := false; +input_buffer : [1024] u8; +input_string : string; +input_override : Key; + +previous_logger : (message: string, data: *void, info: Log_Info); + +module_logger :: (message: string, data: *void, info: Log_Info) { + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); + previous_logger(message, data, info); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +} + +assert_is_active :: inline () { + assert(active, "Please call setup_terminal() to start using this module."); +} + + #scope_export; + // Special Graphics Characters. Drawings :: struct #type_info_none { Blank :: "\x5F"; @@ -123,6 +144,12 @@ Commands :: struct #type_info_none { // TODO Check which procedures need the assert_is_active call. // TODO Review the error messages on the asserts. +//////////////////////////////////////////////////////////////////////////////// + + +#scope_file + + #if COLOR_MODE_BITS == 4 { #load "palette_4b.jai"; @@ -144,12 +171,6 @@ else #if COLOR_MODE_BITS == 8 { else { #load "palette_24b.jai"; - Color_24b :: struct { - r: u8; - g: u8; - b: u8; - } - set_colors :: inline (foreground: Color_24b, background: Color_24b) { print( #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), @@ -158,6 +179,19 @@ else { } } +set_font_style :: inline (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); +} + + +#scope_export + + Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { background: Palette; @@ -180,16 +214,9 @@ Style :: struct { negative: bool; } -set_font_style :: inline (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); -} +#add_context terminal_style: Style; -set_style :: (style: Style) { +set_style :: inline (style: Style) { set_font_style(style.bold, style.underline, style.strike_through, style.negative); set_colors(style.foreground, style.background); context.terminal_style = style; @@ -205,6 +232,9 @@ using_style :: (style: Style) #expand { `defer set_style(__style); } + +//////////////////////////////////////////////////////////////////////////////// + /* 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. @@ -216,34 +246,7 @@ using_style :: (style: Style) #expand { 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. - -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; - for 0..str.count-1 { - k |= ((cast(u64)str[it]) << (it*8)); - } - return k; -} - -to_string :: (key: Key) -> string { - str := alloc_string(KEY_SIZE); - str.count = 0; - while key != 0 { - str.count += 1; - str[str.count-1] = xx key & 0xFF; - key >>= 8; - } - return str; -} - -is_escape_code :: (key: Key) -> bool { - beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; - hasSomethingElse := (key & (~0xFF)) != 0; - return beginsWithEscape && hasSomethingElse; -} +Key :: u64; Keys :: struct #type_info_none { None : Key : #run to_key("#none"); @@ -282,29 +285,86 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -#add_context terminal_style: Style; +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; + for 0..str.count-1 { + k |= ((cast(u64)str[it]) << (it*8)); + } + return k; +} -active := false; +to_string :: (key: Key) -> string { + str := alloc_string(KEY_SIZE); + str.count = 0; + while key != 0 { + str.count += 1; + str[str.count-1] = xx key & 0xFF; + key >>= 8; + } + return str; +} -input_buffer : [1024] u8; -input_string : string; -input_override : Key; +is_escape_code :: (key: Key) -> bool { + beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; + hasSomethingElse := (key & (~0xFF)) != 0; + return beginsWithEscape && hasSomethingElse; +} -previous_logger : (message: string, data: *void, info: Log_Info); +//////////////////////////////////////////////////////////////////////////////// -module_logger :: (message: string, data: *void, info: Log_Info) { - write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - previous_logger(message, data, info); - write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +is_active :: inline () -> bool { + return active; } -assert_is_active :: inline () { - assert(active, "Please call setup_terminal() to start using this module."); +setup_terminal :: () -> success := true #must { + if active == true return; + + input_string.data = input_buffer.data; + input_string.count = 0; + input_override = xx Keys.None; + + previous_logger = context.logger; + context.logger = module_logger; + + setup_key_map(); + + write_strings( + Commands.HideCursor, + Commands.SaveCursorPosition, + Commands.AlternateScreenBuffer, + Commands.EncodingUTF8, + Commands.CursorNormalMode, + Commands.KeypadNumMode + ); + + if !OS_prepare_terminal() then return false; + + active = true; + + return; } -//////////////////////////////////////////////////////////////////////////////// +reset_terminal :: () -> success := true #must { + if active == false return; + + active = false; -// #scope_export TODO Setup the scope_export and scope_file + clear_style(); + + if !OS_reset_terminal() then return false; + + context.logger = previous_logger; + + write_strings( + Commands.MainScreenBuffer, + Commands.RestoreCursorPosition, + Commands.ShowCursor + ); + + return; +} set_next_key :: inline (key: Key) { assert_is_active(); @@ -535,54 +595,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { return result, 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; - - previous_logger = context.logger; - context.logger = module_logger; - - setup_key_map(); - - write_strings( - Commands.HideCursor, - Commands.SaveCursorPosition, - Commands.AlternateScreenBuffer, - Commands.EncodingUTF8, - Commands.CursorNormalMode, - Commands.KeypadNumMode - ); - - if !OS_prepare_terminal() then return false; - - active = true; - - return; -} - -reset_terminal :: () -> success := true #must { - if active == false return; - - active = false; - - clear_style(); - - if !OS_reset_terminal() then return false; - - context.logger = previous_logger; - - write_strings( - Commands.MainScreenBuffer, - Commands.RestoreCursorPosition, - Commands.ShowCursor - ); - - return; -} - flush_input :: () { assert_is_active(); OS_flush_input(); diff --git a/modules/TUI/palette_24b.jai b/modules/TUI/palette_24b.jai index ea0191c..7545a0b 100644 --- a/modules/TUI/palette_24b.jai +++ b/modules/TUI/palette_24b.jai @@ -1,4 +1,10 @@ // https://www.ditig.com/publications/256-colors-cheat-sheet +Color_24b :: struct { + r: u8; + g: u8; + b: u8; +} + Palette :: struct #type_info_none { BLACK :: Color_24b.{0x00, 0x00, 0x00}; MAROON :: Color_24b.{0x80, 0x00, 0x00}; diff --git a/ttt.jai b/ttt.jai index d471661..f3f1905 100644 --- a/ttt.jai +++ b/ttt.jai @@ -124,7 +124,7 @@ error_time_limit := Apollo_Time.{0, 0}; print_error :: (format :string, args : .. Any) { - if TUI.active == false { + if TUI.is_active() == false { print(format, ..args, to_standard_error = true); print("\n"); return; -- cgit v1.2.3 From 36af624cdd9cb54454587bfae21b30096986d22e Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 02:34:33 +0100 Subject: WIP : Cleanup TUI module and improved assert/error messages. --- modules/TUI/module.jai | 137 ++++++++++++++++++++---------------------------- modules/TUI/unix.jai | 14 ++--- modules/TUI/windows.jai | 34 ++++++------ 3 files changed, 81 insertions(+), 104 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index ab2567d..07d121f 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -15,36 +15,46 @@ #assert(false, "Unsupported OS."); } +#if COLOR_MODE_BITS == { + case 4; + #load "palette_4b.jai"; + case 8; + #load "palette_8b.jai"; + case 24; + #load "palette_24b.jai"; + _; + assert(false, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); +} + #import "Basic"; #import "String"; #import "Thread"; #import "UTF8"; #load "key_map.jai"; +active := false; +input_override : Key; +input_string : string; +input_buffer : [1024] u8; + KEY_SIZE :: #run type_info(Key).runtime_size; -#run { - assert(COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 || COLOR_MODE_BITS == 24, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); - assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); -} +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. -active := false; -input_buffer : [1024] u8; -input_string : string; -input_override : Key; -previous_logger : (message: string, data: *void, info: Log_Info); +#scope_module -module_logger :: (message: string, data: *void, info: Log_Info) { - write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - previous_logger(message, data, info); - write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); -} assert_is_active :: inline () { assert(active, "Please call setup_terminal() to start using this module."); } +log_tui_error :: (format_string: string, args: .. Any) { + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); + log_error(format_string, args); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +} + #scope_export; @@ -141,56 +151,7 @@ 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. - -//////////////////////////////////////////////////////////////////////////////// - - -#scope_file - - -#if COLOR_MODE_BITS == 4 { - #load "palette_4b.jai"; - - set_colors :: inline (foreground: Palette, background: Palette) { - print( - #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), - cast(u8)foreground + 30, cast(u8)background + 40); - } -} -else #if COLOR_MODE_BITS == 8 { - #load "palette_8b.jai"; - - set_colors :: inline (foreground: Palette, background: Palette) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)foreground, cast(u8)background); - } -} -else { - #load "palette_24b.jai"; - - set_colors :: inline (foreground: Color_24b, background: Color_24b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), - foreground.r, foreground.g, foreground.b, - background.r, background.g, background.b); - } -} - -set_font_style :: inline (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); -} - - -#scope_export - +#add_context terminal_style: Style; Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { @@ -214,11 +175,32 @@ Style :: struct { negative: bool; } -#add_context terminal_style: Style; - set_style :: inline (style: Style) { - set_font_style(style.bold, style.underline, style.strike_through, style.negative); - set_colors(style.foreground, style.background); + print( + #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), + ifx style.bold then 1 else 22, + ifx style.underline then 4 else 24, + ifx style.strike_through then 9 else 29, + ifx style.negative then 7 else 27); + + #if COLOR_MODE_BITS == { + case 4; + print( + #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + cast(u8)style.foreground + 30, cast(u8)style.background + 40); + + case 8; + print( + #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), + cast(u8)style.foreground, cast(u8)style.background); + + case 24; + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + style.foreground.r, style.foreground.g, style.foreground.b, + style.background.r, style.background.g, style.background.b); + } + context.terminal_style = style; } @@ -286,7 +268,7 @@ Keys :: struct #type_info_none { } 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); + assert(str.count <= KEY_SIZE, "Invalid arguments passed to to_key(): 'str' has more than % bytes and cannot be stored as a Key.", KEY_SIZE); k: Key; for 0..str.count-1 { @@ -325,9 +307,6 @@ setup_terminal :: () -> success := true #must { input_string.count = 0; input_override = xx Keys.None; - previous_logger = context.logger; - context.logger = module_logger; - setup_key_map(); write_strings( @@ -355,8 +334,6 @@ reset_terminal :: () -> success := true #must { if !OS_reset_terminal() then return false; - context.logger = previous_logger; - write_strings( Commands.MainScreenBuffer, Commands.RestoreCursorPosition, @@ -446,8 +423,8 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // 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(); - assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); - + assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input(): when 'count_limit' is less-than 0 (ignored), you need to provide 'terminators' to avoid an infinite-loop."); + // Read until one of the terminator characters is found. // Since we don't know the resulting size of the returned string, we must keep the string builder growing. if count_limit < 0 { @@ -504,7 +481,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // 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(); - assert(count_limit >= 0, "Invalid value passed to count_limit(): %.", count_limit); + assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); builder := String_Builder.{ allocator = temporary_allocator }; @@ -604,7 +581,7 @@ flush_input :: () { draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); - assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); + assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); auto_release_temp(); @@ -674,7 +651,7 @@ get_terminal_size :: () -> width: int, height: int { 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."); + "Failed to query window size: invalid response."); parts := split(input, ";",, temporary_allocator); rows = parse_int(*parts[1]); @@ -723,7 +700,7 @@ get_cursor_position :: () -> x: int, y: int { 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."); + "Failed to query cursor position: invalid response."); advance(*input, 2); parts := split(input, ";",, temporary_allocator); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 940ac80..99cc61d 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -230,7 +230,7 @@ restore_resize_handler :: () { //////////////////////////////////////////////////////////////////////////////// -#scope_export +#scope_module OS_prepare_terminal :: () -> success := true { error: int = ---; @@ -238,7 +238,7 @@ OS_prepare_terminal :: () -> success := true { error = tcgetattr(STDIN_FILENO, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + log_tui_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); return false; } @@ -254,7 +254,7 @@ OS_prepare_terminal :: () -> success := true { error = tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); + log_tui_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); return false; } @@ -268,7 +268,7 @@ OS_reset_terminal :: () -> success := true { error := tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); + log_tui_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); return false; } return; @@ -278,7 +278,7 @@ OS_flush_input :: () -> success := true { error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to flush input: code %, %", error_code, error_string); + log_tui_error("Failed to flush input: code %, %", error_code, error_string); return false; } return; @@ -288,7 +288,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read input: code %, %", error_code, error_string); + log_tui_error("Failed to read input: code %, %", error_code, error_string); return 0, false; } return bytes_read; @@ -306,7 +306,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo error_code, error_string := get_error_value_and_string(); // Ignore window resize events (error_code 4). if error_code != 4 { - log_error("Unexpected error while waiting for input: code %, %", error_code, error_string); + log_tui_error("Unexpected error while waiting for input: code %, %", error_code, error_string); return false, false; } } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index fb50e49..f8d8bc8 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -142,7 +142,7 @@ peek_input :: () -> INPUT_RECORD, success := true { records_read: u32; if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to peek input: code %, %", error_code, error_string); + log_tui_error("Failed to peek input: code %, %", error_code, error_string); return record, false; } return record; @@ -153,7 +153,7 @@ read_input :: () -> INPUT_RECORD, success := true { records_read: u32; if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read input: code %, %", error_code, error_string); + log_tui_error("Failed to read input: code %, %", error_code, error_string); return record, false; } return record; @@ -163,7 +163,7 @@ count_input :: () -> u32, success := true { count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to count input: code %, %", error_code, error_string); + log_tui_error("Failed to count input: code %, %", error_code, error_string); return 0, false; } return count; @@ -171,7 +171,7 @@ count_input :: () -> u32, success := true { //////////////////////////////////////////////////////////////////////////////// -#scope_export +#scope_module OS_prepare_terminal :: () -> success := true { @@ -179,12 +179,12 @@ OS_prepare_terminal :: () -> success := true { stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); - log_error("Invalid input handler: code %, %", error_code, error_string); + log_tui_error("Invalid input handler: code %, %", error_code, error_string); return false; } if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to get input mode: code %, %", error_code, error_string); + log_tui_error("Failed to get input mode: code %, %", error_code, error_string); return false; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); @@ -193,7 +193,7 @@ OS_prepare_terminal :: () -> success := true { if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set input mode: code %, %", error_code, error_string); + log_tui_error("Failed to set input mode: code %, %", error_code, error_string); return false; } @@ -201,12 +201,12 @@ OS_prepare_terminal :: () -> success := true { stdout = GetStdHandle(STD_OUTPUT_HANDLE); if stdout == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); - log_error("Invalid output handler: code %, %", error_code, error_string); + log_tui_error("Invalid output handler: code %, %", error_code, error_string); return false; } if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to get output mode: code %, %", error_code, error_string); + log_tui_error("Failed to get output mode: code %, %", error_code, error_string); return false; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); @@ -214,7 +214,7 @@ OS_prepare_terminal :: () -> success := true { if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set output mode: code %, %", error_code, error_string); + log_tui_error("Failed to set output mode: code %, %", error_code, error_string); return false; } @@ -231,12 +231,12 @@ OS_prepare_terminal :: () -> success := true { OS_reset_terminal :: () -> success := true { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to reset input mode: code %, %", error_code, error_string); + log_tui_error("Failed to reset input mode: code %, %", error_code, error_string); return false; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to reset output mode: code %, %", error_code, error_string); + log_tui_error("Failed to reset output mode: code %, %", error_code, error_string); return false; } return; @@ -247,7 +247,7 @@ OS_flush_input :: () { // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. if FlushConsoleInputBuffer(stdin) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to flush input: code %, %", error_code, error_string); + log_tui_error("Failed to flush input: code %, %", error_code, error_string); } } @@ -255,7 +255,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : S32_MAX :: 0x7fff_ffff; if (bytes_to_read > S32_MAX) { - log_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); + log_tui_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); bytes_to_read = S32_MAX; } @@ -288,14 +288,14 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : success = ReadConsoleW(stdin, widechar_view.data, chars_to_read, *widechar_view.count, null); if success == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read console: code %, %", error_code, error_string); + log_tui_error("Failed to read console: code %, %", error_code, error_string); return 0, false; } result:, success = wide_to_utf8_new(widechar_view.data, xx widechar_view.count); if success == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); + log_tui_error("Failed to convert from wide to UTF8: code %, %", error_code, error_string); return 0, false; } @@ -338,7 +338,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo if wait_result == WAIT_FAILED { error_code, error_string := get_error_value_and_string(); - log_error("Error while waiting for input: code %, %", error_code, error_string); + log_tui_error("Error while waiting for input: code %, %", error_code, error_string); return false, false; } -- cgit v1.2.3 From 1ae2933bcbda43d16d2583a28c3c5a74adaaa9e4 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 03:22:42 +0100 Subject: Implemented default background and foreground colors. --- modules/TUI/module.jai | 36 +++++++++++++++++++++++------------- ttt.jai | 15 +++++++++++++-- 2 files changed, 36 insertions(+), 15 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 07d121f..3f824a4 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -165,7 +165,7 @@ Style :: struct { background = Palette.BLACK; foreground = Palette.WHITE; - // TODO Make this work... + // TODO Make this work... and now that is implemented... test it. use_default_background_color := false; use_default_foreground_color := false; @@ -176,31 +176,41 @@ Style :: struct { } set_style :: inline (style: Style) { - print( - #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), - ifx style.bold then 1 else 22, - ifx style.underline then 4 else 24, - ifx style.strike_through then 9 else 29, - ifx style.negative then 7 else 27); + auto_release_temp(); + builder := String_Builder.{ allocator = temporary_allocator }; + #if COLOR_MODE_BITS == { case 4; - print( + print_to_builder(*builder, #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), - cast(u8)style.foreground + 30, cast(u8)style.background + 40); + cast(u8)style.foreground + 30, cast(u8)style.background + 40 + ); case 8; - print( + print_to_builder(*builder, #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)style.foreground, cast(u8)style.background); + cast(u8)style.foreground, cast(u8)style.background + ); case 24; - print( + print_to_builder(*builder, #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), style.foreground.r, style.foreground.g, style.foreground.b, - style.background.r, style.background.g, style.background.b); + style.background.r, style.background.g, style.background.b + ); + } + + if style.use_default_foreground_color { + append(*builder, #run sprint(Commands.SetGraphicsRendition, "39")); + } + + if style.use_default_background_color { + append(*builder, #run sprint(Commands.SetGraphicsRendition, "49")); } + write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + context.terminal_style = style; } diff --git a/ttt.jai b/ttt.jai index f3f1905..0715544 100644 --- a/ttt.jai +++ b/ttt.jai @@ -17,7 +17,12 @@ // - release : jai ttt.jai -quiet -x64 -release // - debug : jai ttt.jai -quiet -x64 +DEBUG_MEMORY :: true; +#if DEBUG_MEMORY { #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. +} else { +#import "Basic"; +} #import "System"; #import "Sort"; #import "Math"; @@ -1157,8 +1162,10 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { } main :: () { - + +#if DEBUG_MEMORY { defer report_memory_leaks(); // TODO Remove after final debug sessions. +} defer free_memory(); @@ -1774,7 +1781,11 @@ main :: () { } assert(TUI.reset_terminal(), "Failed to reset TUI."); - + +#if DEBUG_MEMORY { + return; +} else { exit(xx ifx error_saving then 1 else 0); } +} -- cgit v1.2.3 From 54103773365e06d6da8518d135c396949e683960 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 04:19:06 +0100 Subject: Fixed memory leaks. --- modules/TUI/module.jai | 26 +++++++++++++------------- ttt.jai | 32 ++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 27 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3f824a4..dc9643c 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -511,17 +511,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // Draw preview. if is_visible { - append(*builder, tprint(Commands.SetCursorPosition, y, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, str); for chars_count..count_limit-1 append(*builder, " "); } else { - append(*builder, tprint(Commands.SetCursorPosition, y, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x); for 1..chars_count append(*builder, "*"); for chars_count..count_limit-1 append(*builder, " "); } - append(*builder, tprint(Commands.SetCursorPosition, y, x+idx)); - write_string(builder_to_string(*builder)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); + write_string(builder_to_string(*builder,, allocator = temporary_allocator)); // Process input key. key = get_key(); @@ -600,7 +600,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(*builder, Commands.DrawingMode); // Draw top line - append(*builder, tprint(Commands.SetCursorPosition, y, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, Drawings.CornerTL); for 1..width-2 { append(*builder, Drawings.LineH); @@ -609,14 +609,14 @@ draw_box :: (x: int, y: int, width: int, height: int) { // Draw left and right sides. for idx: y+1..y+height-2 { - append(*builder, tprint(Commands.SetCursorPosition, idx, x)); + print_to_builder(*builder, Commands.SetCursorPosition, idx, x); append(*builder, Drawings.LineV); - append(*builder, tprint(Commands.SetCursorPosition, idx, x+width-1)); + print_to_builder(*builder, Commands.SetCursorPosition, idx, x+width-1); append(*builder, Drawings.LineV); } // Draw bottom line. - append(*builder, tprint(Commands.SetCursorPosition, y+height-1, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y+height-1, x); append(*builder, Drawings.CornerBL); for 1..width-2 { append(*builder, Drawings.LineH); @@ -625,7 +625,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(*builder, Commands.TextMode); - write_string(builder_to_string(*builder)); + write_string(builder_to_string(*builder,, allocator = temporary_allocator)); } clear_terminal :: inline () { @@ -647,7 +647,7 @@ get_terminal_size :: () -> width: int, height: int { // 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); + input := read_input(64, #char "t",, allocator = temporary_allocator); // Discard head noise. while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { @@ -663,7 +663,7 @@ get_terminal_size :: () -> width: int, height: int { input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], "Failed to query window size: invalid response."); - parts := split(input, ";",, temporary_allocator); + parts := split(input, ";",, allocator = temporary_allocator); rows = parse_int(*parts[1]); columns = parse_int(*parts[2]); } @@ -696,7 +696,7 @@ get_cursor_position :: () -> x: int, y: int { // Expected response format: \e[;R // where is the number of rows and of columns. FORMAT :: "\e[;R"; - input := read_input(64, #char "R"); + input := read_input(64, #char "R",, allocator = temporary_allocator); // Discard head noise. while input.count >= 2 && (input[0] != FORMAT[0] || input[1] != FORMAT[1]) { @@ -713,7 +713,7 @@ get_cursor_position :: () -> x: int, y: int { "Failed to query cursor position: invalid response."); advance(*input, 2); - parts := split(input, ";",, temporary_allocator); + parts := split(input, ";",, allocator = temporary_allocator); row := parse_int(*parts[0]); column := parse_int(*parts[1]); return column, row; diff --git a/ttt.jai b/ttt.jai index 0715544..929a736 100644 --- a/ttt.jai +++ b/ttt.jai @@ -590,12 +590,14 @@ load_database :: (db: *Database, path: string) -> success: bool { // Returns success. export_to_csv :: (db: Database, path: string) -> success: bool { assert(xx path, ASSERT_NOT_EMPTY, "path"); + + auto_release_temp(); builder: String_Builder; defer reset(*builder); CSV_HEADER :: string.[ "task", "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ]; - print_to_builder(*builder, "%\n", join(..CSV_HEADER, separator = ",")); + print_to_builder(*builder, "%\n", join(..CSV_HEADER, separator = ",",, temporary_allocator)); buffer: [Task.name.count] u8; name: string = xx buffer; @@ -701,15 +703,16 @@ import_from_csv :: (db: *Database, path: string) -> bool { } { // Parse CSV lines. - auto_release_temp(); // TODO Needs to be tested. line := csv; while (true) { + auto_release_temp(); + line, success := consume_next_line(*csv); // line, success := next_line(*csv); if success == false break; task: Task; - csv_values := split(line, ",",, temporary_allocator); // TODO Temporary memory... if file is too big this may break. + csv_values := split(line, ",",, temporary_allocator); // Import task name. name_length := min(task.name.count, csv_values[0].count); @@ -891,6 +894,8 @@ update_layout :: () { draw_tui :: (db: *Database, layout: *Layout) { + // TODO During dirty_flag revamp...we may also start using the String_Builder. + adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, (1 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, @@ -990,7 +995,6 @@ draw_tui :: (db: *Database, layout: *Layout) { // Display up to rows allowed by the layout, or less if reached end of database. idx_stop := idx_start + (ifx layout_tasks_rows > db.tasks.count - idx_start then db.tasks.count - idx_start else layout_tasks_rows); for task_idx: idx_start..idx_stop-1 { - auto_release_temp(); // TODO Temporary memory being trashed?! task := *db.tasks[task_idx]; y += 1; x = 1; @@ -1138,7 +1142,7 @@ read_input_int :: (y: int, message: string) -> value: int, success: bool { input_pos_x := x + message.count + 2; input_width := size_x - input_pos_x; - str := read_input_string(input_pos_x, y, input_width); + str := read_input_string(input_pos_x, y, input_width,, temporary_allocator); value, success := parse_int(*str); return value, success; @@ -1170,7 +1174,6 @@ main :: () { defer free_memory(); { // Initialize app directory. - auto_release_temp(); home_dir, success_dir := get_home_directory(); // Returns system owned memory. if success_dir == false { home_dir = "."; @@ -1195,7 +1198,6 @@ main :: () { } { // Initialize database and archive files if needed. - auto_release_temp(); if (file_exists(db_file_path) == false) { if (store_database(database, db_file_path) == false) { print_error("Failed to initialize database."); @@ -1333,7 +1335,8 @@ main :: () { } if is_exit_requested { - exit(0); + // exit(0); // TODO fucking exit does not report memory leaks. + return; } } @@ -1353,7 +1356,9 @@ main :: () { TUI.flush_input(); TUI.set_next_key(TUI.Keys.Resize); while (true) { - + + reset_temporary_storage(); + TUI.set_style(style_default); if (is_terminal_too_small) { @@ -1366,7 +1371,6 @@ main :: () { draw_error_window(); } - reset_temporary_storage(); key := TUI.get_key(INPUT_TIMEOUT_MS); if key == #char "q" || key == #char "Q" break; update_times(*database); @@ -1456,7 +1460,7 @@ main :: () { // Change task name. TUI.using_style(action_style); - input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count); + input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count,, temporary_allocator); if is_empty_string(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); @@ -1492,9 +1496,9 @@ main :: () { case #char "6"; #through; case #char "7"; if (selected_task == null) continue; - + // Prepare position to input time operation. - selected_day := cast(int)(key - #char "1"); // TODO DAM this cast... + selected_day := cast(int)(key - #char "1"); input_width := layout.columns[L_DAYS_IDX + selected_day].width; input_pos_x := 2 + layout.columns[L_TITLE_IDX].width; for 0..selected_day-1 { @@ -1504,7 +1508,7 @@ main :: () { // Get input string. TUI.using_style(action_style); - input := read_input_string(input_pos_x, selected_task_row, input_width); // TODO Temp stringzes. + input := read_input_string(input_pos_x, selected_task_row, input_width,, temporary_allocator); // Abort if input if empty. if is_empty_string(input) continue; -- cgit v1.2.3 From 3ae6ce04674daaef4f3bf69adbfe4333402cb423 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 04:28:56 +0100 Subject: Added TODO entry. --- modules/TUI/module.jai | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index dc9643c..27c2fcc 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -175,7 +175,7 @@ Style :: struct { negative: bool; } -set_style :: inline (style: Style) { +set_style :: (style: Style) { auto_release_temp(); builder := String_Builder.{ allocator = temporary_allocator }; @@ -214,8 +214,9 @@ set_style :: inline (style: Style) { context.terminal_style = style; } -clear_style :: inline () { +clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); + // TODO Maybe reset context.terminal_style? } using_style :: (style: Style) #expand { -- cgit v1.2.3 From b32a697e1898a37497cfc504a2746dcf1f0710da Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 15:55:30 +0100 Subject: Finished first version of TUI. --- modules/TUI/module.jai | 5 ++--- ttt.jai | 13 +++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 27c2fcc..e25672f 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -22,7 +22,7 @@ #load "palette_8b.jai"; case 24; #load "palette_24b.jai"; - _; + case; assert(false, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); } @@ -165,7 +165,6 @@ Style :: struct { background = Palette.BLACK; foreground = Palette.WHITE; - // TODO Make this work... and now that is implemented... test it. use_default_background_color := false; use_default_foreground_color := false; @@ -216,7 +215,7 @@ set_style :: (style: Style) { clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); - // TODO Maybe reset context.terminal_style? + context.terminal_style = .{ }; } using_style :: (style: Style) #expand { diff --git a/ttt.jai b/ttt.jai index 929a736..3f7ac59 100644 --- a/ttt.jai +++ b/ttt.jai @@ -86,6 +86,7 @@ pos_y : int; style_default := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.WHITE, + // use_default_background_color = true, TODO }; style_selected := TUI.Style.{ @@ -97,12 +98,14 @@ style_selected_inverted := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.CYAN, bold = true, + // use_default_background_color = true, TODO }; style_active := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.BLUE, bold = true, + // use_default_background_color = true, TODO }; style_active_selected := TUI.Style.{ @@ -115,6 +118,7 @@ style_error := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.RED, bold = true, + // use_default_background_color = true, TODO }; Layouts :: enum u8 { @@ -1009,6 +1013,9 @@ draw_tui :: (db: *Database, layout: *Layout) { else if (task == active_task) { TUI.set_style(style_active); } + else { + TUI.set_style(style_default); + } // Task title. x += 1; @@ -1043,10 +1050,8 @@ draw_tui :: (db: *Database, layout: *Layout) { // Task total. x += 1; print_time(y, x, total_time, layout.columns[L_TOTAL_IDX].width); - - // Reset theme. - TUI.clear_style(); } + TUI.set_style(style_default); /////////////////////////////////////////////////////////////////////////// @@ -1099,7 +1104,7 @@ free_memory :: () { free(app_directory); free(db_file_path); free(ar_file_path); - //reset_temporary_storage(); + // reset_temporary_storage(); // TODO Not needed... I guess. } read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> value: string, success: bool { -- cgit v1.2.3 From 65227c476071914b82720084a2a775b44e4de885 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 6 May 2024 12:50:22 +0100 Subject: Cleanup UTF helper. --- modules/TUI/module.jai | 6 ++--- modules/UTF8.jai | 60 ++++++++++++++++++++++++++------------------------ ttt.jai | 11 +++++---- 3 files changed, 39 insertions(+), 38 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index e25672f..7c3a71d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -398,7 +398,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // By default, parse a single UTF8 character (1 to 4 bytes). to_parse := input_string; - to_parse.count = count_utf8_bytes(input_string[0]); + to_parse.count = count_character_bytes(input_string[0]); defer advance(*input_string, to_parse.count); // Advance over parsed input. // Try to parse escape code. @@ -555,7 +555,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { case; if is_escape_code(key) continue; - buff_idx := map_character_to_buffer_idx(str, idx); + buff_idx := get_byte_idx(str, idx); 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). @@ -572,7 +572,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { idx += 1; // Truncate string to avoid incomplete utf8 codes on the string tail. - str.count = truncate_string(str, count_limit); + truncate(*str, count_limit); } } diff --git a/modules/UTF8.jai b/modules/UTF8.jai index eba4585..b583809 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -1,25 +1,29 @@ -// BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte -// TODO Maybe rename to: is_continuation_byte -is_utf8_continuation_byte :: inline (byte: u8) -> bool { +// Some procedures to help working with UTF8 strings. +// https://en.wikipedia.org/wiki/UTF-8 + +// Returns true if argument is a continuation byte. +is_continuation_byte :: inline (byte: u8) -> bool { + // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte 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 -// TODO Maybe rename to: count_character_bytes -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; +// Given a leading_byte, returns the number of bytes on the character. +count_character_bytes :: inline (leading_byte: u8) -> int { + // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte + if (leading_byte & 0xE0) == 0xC0 return 1+1; + + // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte + if (leading_byte & 0xF0) == 0xE0 return 1+2; + + // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte + if (leading_byte & 0xF8) == 0xF0 return 1+3; + return 1; } -// Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. -// Truncation is done by zeroing the tail of the string in place. -// Returns length of truncated string. -// TODO Maybe rename to: truncate -truncate_string :: (str: string, length: int) -> length: int { +// Truncates the string to the provided length and zeroes the discarded bytes. +// Returns the length of truncated string or -1 if string has no data. +truncate :: (str: *string, length: int) -> length: int { if str.data == null then return -1; if str.count < length then length = str.count; @@ -29,7 +33,7 @@ truncate_string :: (str: string, length: int) -> length: int { // Find index of first continuation byte. idx := length; - while (idx > 0 && is_utf8_continuation_byte(data[idx - 1])) { + while (idx > 0 && is_continuation_byte(data[idx - 1])) { idx -= 1; } continuation_bytes := length - idx; @@ -50,13 +54,12 @@ truncate_string :: (str: string, length: int) -> length: int { } memset(data + length, 0, count - length); - // str.count = length; TODO We should be doing this... + str.count = length; return length; } // Returns true when the string is empty or consists of space characters. -// TODO Maybe rename to: is_empty -is_empty_string :: (str: string) -> bool { +is_empty :: (str: string) -> bool { for 0..str.count-1 { if str[it] == { case #char "\0"; #through; @@ -74,21 +77,21 @@ is_empty_string :: (str: string) -> bool { return true; } -// Counts number of characters in string. +// Counts the number of characters. count_characters :: (str: string) -> int { characters := 0; idx := 0; while idx < str.count { - idx += count_utf8_bytes(str[idx]); + idx += count_character_bytes(str[idx]); characters += 1; } return characters; } -// Delete character. +// Deletes character by it's index, and moves tail data to take its place. delete_character :: (str: *string, character_idx: int) { - buffer_idx := map_character_to_buffer_idx(str.*, character_idx); - bytes_to_delete := count_utf8_bytes(str.data[buffer_idx]); + buffer_idx := get_byte_idx(str.*, character_idx); + bytes_to_delete := count_character_bytes(str.data[buffer_idx]); for buffer_idx..str.count-1-bytes_to_delete { str.data[it] = str.data[it+bytes_to_delete]; @@ -100,9 +103,8 @@ delete_character :: (str: *string, character_idx: int) { str.count -= bytes_to_delete; } -// Get character index. -// TODO Maybe rename to: map_character_to_byte_idx or get_character_byte_idx -map_character_to_buffer_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { +// Searches for the given character index and returns its byte index on the string. +get_byte_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { if character_idx < 0 then return -1, false; if character_idx > str.count then return -2, false; if character_idx == 0 then return 0, true; @@ -110,7 +112,7 @@ map_character_to_buffer_idx :: (str: string, character_idx: int) -> buffer_idx: buff_idx := 0; char_idx := 0; while buff_idx < str.count && char_idx != character_idx { - buff_idx += count_utf8_bytes(str[buff_idx]); + buff_idx += count_character_bytes(str[buff_idx]); char_idx += 1; } return buff_idx, char_idx == character_idx; diff --git a/ttt.jai b/ttt.jai index 3f7ac59..0802220 100644 --- a/ttt.jai +++ b/ttt.jai @@ -718,10 +718,9 @@ import_from_csv :: (db: *Database, path: string) -> bool { task: Task; csv_values := split(line, ",",, temporary_allocator); - // Import task name. - name_length := min(task.name.count, csv_values[0].count); - memcpy(task.name.data, csv_values[0].data, name_length); - truncate_string(xx task.name, name_length); + // Truncate and import task name. + truncate(*csv_values[0], task.name.count); + memcpy(task.name.data, csv_values[0].data, csv_values[0].count); advance(*csv_values); for csv_values @@ -1466,7 +1465,7 @@ main :: () { // Change task name. TUI.using_style(action_style); input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count,, temporary_allocator); - if is_empty_string(input) == false { + if is_empty(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); memcpy(selected_task.name.data, input.data, min(Task.name.count, input.count)); @@ -1516,7 +1515,7 @@ main :: () { input := read_input_string(input_pos_x, selected_task_row, input_width,, temporary_allocator); // Abort if input if empty. - if is_empty_string(input) continue; + if is_empty(input) continue; // Search for assign '=' operator and discard everything before it. assign_idx := find_index_from_left(input, "="); -- cgit v1.2.3 From 8779553b877a2bb728ddf9d7c21b272c2eedb653 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 7 May 2024 10:27:22 +0100 Subject: Fixed biggest performance bottleneck on draw_user_interface: print white spaces. --- modules/TUI/module.jai | 2 +- modules/UTF8.jai | 40 ++++++++++++++++++++++++---------------- ttt.jai | 38 +++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 36 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 7c3a71d..a5db3bf 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -572,7 +572,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { idx += 1; // Truncate string to avoid incomplete utf8 codes on the string tail. - truncate(*str, count_limit); + str.count = truncate(str, count_limit).count; } } diff --git a/modules/UTF8.jai b/modules/UTF8.jai index b583809..205e57d 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -21,15 +21,15 @@ count_character_bytes :: inline (leading_byte: u8) -> int { return 1; } -// Truncates the string to the provided length and zeroes the discarded bytes. -// Returns the length of truncated string or -1 if string has no data. -truncate :: (str: *string, length: int) -> length: int { - if str.data == null then return -1; +// Returns the string (using same str.data) truncated to the provided length. +truncate :: (str: string, length: int) -> string { - if str.count < length then length = str.count; + if str.data == null then return ""; + + if str.count < length then return .{str.count, str.data}; - data := str.data; - count := str.count; + data := str.data; + count := str.count; // Find index of first continuation byte. idx := length; @@ -50,12 +50,10 @@ truncate :: (str: *string, length: int) -> length: int { && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) ) { - length -= (continuation_bytes + 1); // Remove start byte, ence '+ 1'. + length -= (continuation_bytes + 1); // Remove start byte, hence '+ 1'. } - - memset(data + length, 0, count - length); - str.count = length; - return length; + + return .{length, str.data}; } // Returns true when the string is empty or consists of space characters. @@ -78,13 +76,23 @@ is_empty :: (str: string) -> bool { } // Counts the number of characters. -count_characters :: (str: string) -> int { +count_characters :: (str: string, $is_null_terminated := false) -> int { characters := 0; idx := 0; - while idx < str.count { - idx += count_character_bytes(str[idx]); - characters += 1; + + #if is_null_terminated { + while idx < str.count && str[idx] != 0 { + idx += count_character_bytes(str[idx]); + characters += 1; + } + } + else { + while idx < str.count { + idx += count_character_bytes(str[idx]); + characters += 1; + } } + return characters; } diff --git a/ttt.jai b/ttt.jai index d8233f9..7d1559b 100644 --- a/ttt.jai +++ b/ttt.jai @@ -719,8 +719,8 @@ import_from_csv :: (db: *Database, path: string) -> bool { csv_values := split(line, ",",, temporary_allocator); // Truncate and import task name. - truncate(*csv_values[0], task.name.count); - memcpy(task.name.data, csv_values[0].data, csv_values[0].count); + task_name := truncate(csv_values[0], task.name.count); + memcpy(task.name.data, task_name.data, task_name.count); advance(*csv_values); for csv_values @@ -901,11 +901,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { start := current_time_monotonic(); // DEBUG - - - - // TODO During dirty_flag revamp...we may also start using the String_Builder. - + auto_release_temp(); + + empty_line := talloc_string(size_x); + memset(empty_line.data, #char " ", size_x); + adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, (1 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, @@ -1004,6 +1004,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { idx_start := (db.selected_idx / layout_tasks_rows) * layout_tasks_rows; // Display up to rows allowed by the layout, or less if reached end of database. idx_stop := idx_start + (ifx layout_tasks_rows > db.tasks.count - idx_start then db.tasks.count - idx_start else layout_tasks_rows); + for task_idx: idx_start..idx_stop-1 { task := *db.tasks[task_idx]; y += 1; @@ -1026,19 +1027,18 @@ draw_user_interface :: (db: *Database, layout: *Layout) { // Task title. x += 1; column_width = layout.columns[L_TITLE_IDX].width; - // mvprintw(xx y, xx x, "%-*.*s", column_width, column_width, temp_c_string(xx task.name)); //task.name); TODO Fix required for LLVM/Cncurses. TODO DAM - // TODO FIXME OH MY GOD SUCH BAD CODEZ + // Print title. + // When the column is wider than the name, we end up with the trailing zeros. + // Thankfully, the trailing zeros are not printed so, it's all good. + task_name := cast(string)task.name; + task_name = truncate(task_name, column_width); TUI.set_cursor_position(x, y); - white_spaces := column_width; - // FIXME Improve by using buffer instead of printing one char at the time. - while white_spaces > 0 { - print_character(#char " "); - white_spaces -= 1; - } - TUI.set_cursor_position(x, y); - task_name: string = cast(string)task.name; - task_name.count = ifx task_name.count > column_width then column_width else task_name.count; - print("%", task_name); + write_string(task_name); + // Paint the remaining column space. + task_name_char_count := count_characters(task_name, is_null_terminated = true); + paint_remaining := string.{ column_width - task_name_char_count, empty_line.data }; + write_string(paint_remaining); + x += column_width; // Task times. -- cgit v1.2.3 From 63c21762a8d8323facaf729a1bd17e9449eea72e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 7 May 2024 16:54:23 +0100 Subject: WIP : Improve draw_user_interface by using buffer on the print/write calls. --- modules/TUI/module.jai | 122 +++++++++++++++++++++++++++++++++---------------- ttt.jai | 61 ++++++++++++++----------- 2 files changed, 116 insertions(+), 67 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index a5db3bf..f2a3a23 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -32,14 +32,17 @@ #import "UTF8"; #load "key_map.jai"; +#add_context tui_style : Style; // This contains the last style applied by the module. +#add_context tui_buffer : *String_Builder; // If set, this buffer will be used as output target of module procedures. + +KEY_SIZE :: #run type_info(Key).runtime_size; +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. + active := false; input_override : Key; input_string : string; input_buffer : [1024] u8; - -KEY_SIZE :: #run type_info(Key).runtime_size; - -#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. +temp_buffer := String_Builder.{ allocator = temporary_allocator }; #scope_module @@ -51,7 +54,7 @@ assert_is_active :: inline () { log_tui_error :: (format_string: string, args: .. Any) { write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - log_error(format_string, args); + log_error(format_string, ..args); write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); } @@ -151,8 +154,6 @@ Commands :: struct #type_info_none { CursorNormalMode :: "\e[?1l"; } -#add_context terminal_style: Style; - Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { background: Palette; @@ -175,25 +176,26 @@ Style :: struct { } set_style :: (style: Style) { - auto_release_temp(); - builder := String_Builder.{ allocator = temporary_allocator }; + auto_release_temp(); + + builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; #if COLOR_MODE_BITS == { case 4; - print_to_builder(*builder, + print_to_builder(builder, #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), cast(u8)style.foreground + 30, cast(u8)style.background + 40 ); case 8; - print_to_builder(*builder, + print_to_builder(builder, #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), cast(u8)style.foreground, cast(u8)style.background ); case 24; - print_to_builder(*builder, + print_to_builder(builder, #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), style.foreground.r, style.foreground.g, style.foreground.b, style.background.r, style.background.g, style.background.b @@ -201,25 +203,27 @@ set_style :: (style: Style) { } if style.use_default_foreground_color { - append(*builder, #run sprint(Commands.SetGraphicsRendition, "39")); + append(builder, #run sprint(Commands.SetGraphicsRendition, "39")); } if style.use_default_background_color { - append(*builder, #run sprint(Commands.SetGraphicsRendition, "49")); + append(builder, #run sprint(Commands.SetGraphicsRendition, "49")); } - write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + if context.tui_buffer == null { + write_builder(builder); + } - context.terminal_style = style; + context.tui_style = style; } clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); - context.terminal_style = .{ }; + context.tui_style = .{ }; } using_style :: (style: Style) #expand { - __style := context.terminal_style; + __style := context.tui_style; set_style(style); `defer set_style(__style); } @@ -493,7 +497,11 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); - builder := String_Builder.{ allocator = temporary_allocator }; + // builder := String_Builder.{ allocator = temporary_allocator }; + // builder := String_Builder.{}; + // init_string_builder(*builder, 10000); + // reset(*builder); + builder := temp_buffer; str := alloc_string(count_limit); str.count = 0; @@ -509,7 +517,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { chars_count := count_characters(str); - // Draw preview. + // Preview input line. if is_visible { print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, str); @@ -521,6 +529,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for chars_count..count_limit-1 append(*builder, " "); } print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); + // write_builder(*builder); // TODO Not sure why this is not working... write_string(builder_to_string(*builder,, allocator = temporary_allocator)); // Process input key. @@ -594,38 +603,40 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); auto_release_temp(); - - builder := String_Builder.{ allocator = temporary_allocator }; - - append(*builder, Commands.DrawingMode); + + builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; + + append(builder, Commands.DrawingMode); // Draw top line - print_to_builder(*builder, Commands.SetCursorPosition, y, x); - append(*builder, Drawings.CornerTL); + print_to_builder(builder, Commands.SetCursorPosition, y, x); + append(builder, Drawings.CornerTL); for 1..width-2 { - append(*builder, Drawings.LineH); + append(builder, Drawings.LineH); } - append(*builder, Drawings.CornerTR); + append(builder, Drawings.CornerTR); // Draw left and right sides. for idx: y+1..y+height-2 { - print_to_builder(*builder, Commands.SetCursorPosition, idx, x); - append(*builder, Drawings.LineV); - print_to_builder(*builder, Commands.SetCursorPosition, idx, x+width-1); - append(*builder, Drawings.LineV); + print_to_builder(builder, Commands.SetCursorPosition, idx, x); + append(builder, Drawings.LineV); + print_to_builder(builder, Commands.SetCursorPosition, idx, x+width-1); + append(builder, Drawings.LineV); } // Draw bottom line. - print_to_builder(*builder, Commands.SetCursorPosition, y+height-1, x); - append(*builder, Drawings.CornerBL); + print_to_builder(builder, Commands.SetCursorPosition, y+height-1, x); + append(builder, Drawings.CornerBL); for 1..width-2 { - append(*builder, Drawings.LineH); + append(builder, Drawings.LineH); } - append(*builder, Drawings.CornerBR); + append(builder, Drawings.CornerBR); - append(*builder, Commands.TextMode); - - write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + append(builder, Commands.TextMode); + + if context.tui_buffer == null { + write_builder(builder); + } } clear_terminal :: inline () { @@ -682,7 +693,12 @@ get_terminal_size :: () -> width: int, height: int { set_cursor_position :: inline (x: int, y: int) { assert_is_active(); - print(Commands.SetCursorPosition, y, x); + if context.tui_buffer == null { + print(Commands.SetCursorPosition, y, x); + } + else { + print_to_builder(context.tui_buffer, Commands.SetCursorPosition, y, x); + } } get_cursor_position :: () -> x: int, y: int { @@ -723,3 +739,29 @@ set_terminal_title :: inline (title: string) { assert_is_active(); print(Commands.SetWindowTitle, title); } + +using_buffer :: (buffer: *String_Builder) #expand { + __buffer := context.tui_buffer; + context.tui_buffer = buffer; + `defer context.tui_buffer = __buffer; +} + +// TODO Maybe we should have a different name for this...? +tui_print :: inline (format_string: string, args: .. Any) { + if context.tui_buffer == null { + print(format_string, ..args, to_standard_error = false); + } + else { + print_to_builder(context.tui_buffer, format_string, ..args); + } +} + +// TODO Maybe we should have a different name for this...? +tui_write :: inline (format_string: string) { + if context.tui_buffer == null { + write_string(format_string); + } + else { + append(context.tui_buffer, format_string); + } +} diff --git a/ttt.jai b/ttt.jai index 7d1559b..ff3c757 100644 --- a/ttt.jai +++ b/ttt.jai @@ -216,29 +216,29 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { print_padding :: (size: int, char: u8 = #char " ") { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); - while size > 0 { - print_character(char); - size -= 1; - } + auto_release_temp(); + padding := talloc_string(size); + memset(padding.data, char, size); + TUI.tui_write(padding); } TUI.set_cursor_position(x, y); if time < 0 { print_padding(left_padding); - write_string(" - "); + TUI.tui_write(" - "); print_padding(right_padding); return 0; } else if time == 0 { print_padding(left_padding); - write_string(" 0 "); + TUI.tui_write(" 0 "); print_padding(right_padding); return 0; } else if time < SECONDS_IN_MINUTE { print_padding(left_padding); - print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); + TUI.tui_print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); print_padding(right_padding); return 0; } @@ -246,7 +246,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { hours := time / SECONDS_IN_HOUR; minutes := (time - (hours * SECONDS_IN_HOUR) ) / SECONDS_IN_MINUTE; print_padding(left_padding); - print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); + TUI.tui_print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); print_padding(right_padding); return 0; } @@ -257,7 +257,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_DAY) then 1 else 2; print_padding(left_padding); - print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); + TUI.tui_print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } @@ -268,13 +268,13 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_YEAR) then 1 else 2; print_padding(left_padding); - print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); + TUI.tui_print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } else { print_padding(left_padding); - write_string(" ∞ "); + TUI.tui_write(" ∞ "); print_padding(right_padding); return 0; } @@ -897,6 +897,7 @@ update_layout :: () { dbg_average := 0; // DEBUG dbg_count := 0; // DEBUG +buffer: String_Builder; // TODO draw_user_interface :: (db: *Database, layout: *Layout) { start := current_time_monotonic(); // DEBUG @@ -905,6 +906,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { empty_line := talloc_string(size_x); memset(empty_line.data, #char " ", size_x); + + init_string_builder(*buffer, 100000); + // builder := String_Builder.{ allocator = temporary_allocator }; + builder := buffer; + TUI.using_buffer(*builder); adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, @@ -931,25 +937,25 @@ draw_user_interface :: (db: *Database, layout: *Layout) { // Draw outer border. TUI.draw_box(1, 1, size_x, size_y); - + // Draw table grids. // TODO Maybe this could be simplified? y = 1; x = 1; - write_string(TUI.Commands.DrawingMode); + TUI.tui_write(TUI.Commands.DrawingMode); // append(*builder, TUI.Commands.DrawingMode); TODO for 0..layout.columns.count-2 { column := layout.columns[it]; x += 1 + column.width; TUI.set_cursor_position(x, y); - write_string(TUI.Drawings.TeeT); + TUI.tui_write(TUI.Drawings.TeeT); // TODO append(*builder, TUI.Drawings.TeeT); for row: 2..size_y { TUI.set_cursor_position(x, row); - write_string(TUI.Drawings.LineV); + TUI.tui_write(TUI.Drawings.LineV); // TODO append(*builder, TUI.Drawings.LineV); } TUI.set_cursor_position(x, size_y); - write_string(TUI.Drawings.TeeB); + TUI.tui_write(TUI.Drawings.TeeB); // TODO append(*builder, TUI.Drawings.TeeB); } - write_string(TUI.Commands.TextMode); + TUI.tui_write(TUI.Commands.TextMode); // TODO append(*builder, TUI.Commands.TextMode); /////////////////////////////////////////////////////////////////////////// @@ -961,7 +967,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TITLE_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - write_string(ifx db == *archive then layout.archive_title else col.header); + TUI.tui_write(ifx db == *archive then layout.archive_title else col.header); // TODO append(*builder, ifx db == *archive then layout.archive_title else col.header); x += col.width; // Headers : days @@ -981,7 +987,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { } col = *layout.columns[L_DAYS_IDX + idx]; TUI.set_cursor_position(x + col.alignment_offset, y); - write_string(col.header); + TUI.tui_write(col.header); // TODO append(*builder, col.header); x += col.width; } TUI.set_style(style_default); @@ -990,7 +996,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TOTAL_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - write_string(col.header); + TUI.tui_write(col.header); // TODO append(*builder, col.header); /////////////////////////////////////////////////////////////////////////// @@ -1033,11 +1039,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { task_name := cast(string)task.name; task_name = truncate(task_name, column_width); TUI.set_cursor_position(x, y); - write_string(task_name); + TUI.tui_write(task_name); // TODO append(*builder, task_name); // Paint the remaining column space. task_name_char_count := count_characters(task_name, is_null_terminated = true); paint_remaining := string.{ column_width - task_name_char_count, empty_line.data }; - write_string(paint_remaining); + TUI.tui_write(paint_remaining); // TODO append(*builder, paint_remaining); x += column_width; @@ -1065,10 +1071,10 @@ draw_user_interface :: (db: *Database, layout: *Layout) { size := 1 + count_digits(db.selected_idx + 1) + 1 + count_digits(db.tasks.count) + 1; // " XXX/YYY " TUI.set_cursor_position(2, size_y); if (size <= layout.columns[L_TITLE_IDX].width) { - print(" %/% ", db.selected_idx + 1, db.tasks.count); + TUI.tui_print(" %/% ", db.selected_idx + 1, db.tasks.count); } else { - print("%", db.selected_idx + 1); + TUI.tui_print("%", db.selected_idx + 1); } @@ -1107,7 +1113,9 @@ draw_user_interface :: (db: *Database, layout: *Layout) { dbg_average = (dbg_sample + dbg_count * dbg_average) / (dbg_count + 1); // DEBUG dbg_count += 1; // DEBUG TUI.set_cursor_position(3, 1); - print("Average % us ---------", dbg_average/1000); // DEBUG + TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, context.temporary_storage.total_bytes_occupied, context.temporary_storage.high_water_mark, context.temporary_storage.size); // DEBUG + // write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + write_builder(*builder); } free_memory :: () { @@ -1117,7 +1125,6 @@ free_memory :: () { free(app_directory); free(db_file_path); free(ar_file_path); - // reset_temporary_storage(); // TODO Not needed... I guess. } read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> value: string, success: bool { @@ -1133,7 +1140,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); - style_input := context.terminal_style; + style_input := context.tui_style; style_input.underline = true; TUI.using_style(style_input); -- cgit v1.2.3 From 89157d00fc4110055d5816cd1897e4def120bcaf Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 7 May 2024 18:14:45 +0100 Subject: Fixed two very nasty bugs. --- modules/TUI/module.jai | 18 +++++++----------- ttt.jai | 2 ++ 2 files changed, 9 insertions(+), 11 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index f2a3a23..612f93e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -177,8 +177,9 @@ Style :: struct { set_style :: (style: Style) { - auto_release_temp(); - + auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? + + // TODO If context.tui_buffer is temporary... this will fail.... right? builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; #if COLOR_MODE_BITS == { @@ -497,10 +498,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); - // builder := String_Builder.{ allocator = temporary_allocator }; - // builder := String_Builder.{}; - // init_string_builder(*builder, 10000); - // reset(*builder); builder := temp_buffer; str := alloc_string(count_limit); @@ -529,8 +526,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for chars_count..count_limit-1 append(*builder, " "); } print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); - // write_builder(*builder); // TODO Not sure why this is not working... - write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + write_builder(*builder); // Process input key. key = get_key(); @@ -560,7 +556,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if idx == 0 continue; idx -= 1; delete_character(*str, idx); - + case; if is_escape_code(key) continue; @@ -571,7 +567,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if buff_idx > count_limit - key_str.count then continue; // Move text to allow inserting new character. - for < count_limit..buff_idx+key_str.count { + for < count_limit-1..buff_idx + key_str.count-1 { str.data[it] = str.data[it-key_str.count]; } @@ -602,7 +598,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); - auto_release_temp(); + auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; diff --git a/ttt.jai b/ttt.jai index ff3c757..612e6b6 100644 --- a/ttt.jai +++ b/ttt.jai @@ -218,6 +218,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); auto_release_temp(); padding := talloc_string(size); + padding.count = size; memset(padding.data, char, size); TUI.tui_write(padding); } @@ -1131,6 +1132,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? + // TODO COULD CACHE... but there's not need... TUI.set_cursor_position(x + input_limit, y); write_string(TUI.Commands.DrawingMode); -- cgit v1.2.3 From 2dd22e4847b7bc239f129c5ed2e7d0bdff38006e Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 8 May 2024 16:20:07 +0100 Subject: Improved code readability. --- modules/TUI/module.jai | 5 ++++- modules/UTF8.jai | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 612f93e..112c666 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -560,9 +560,12 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { case; if is_escape_code(key) continue; - buff_idx := get_byte_idx(str, idx); key_str := to_string(key,, allocator = temporary_allocator); + // Get the buffer index to insert the next character. + buff_idx, success := get_byte_index(str, idx); + if success == false then buff_idx = str.count; + // 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; diff --git a/modules/UTF8.jai b/modules/UTF8.jai index 205e57d..72d3d75 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -97,8 +97,11 @@ count_characters :: (str: string, $is_null_terminated := false) -> int { } // Deletes character by it's index, and moves tail data to take its place. -delete_character :: (str: *string, character_idx: int) { - buffer_idx := get_byte_idx(str.*, character_idx); +delete_character :: (str: *string, character_idx: int) -> success := true { + buffer_idx := get_byte_index(str.*, character_idx); + + if buffer_idx < 0 return false; + bytes_to_delete := count_character_bytes(str.data[buffer_idx]); for buffer_idx..str.count-1-bytes_to_delete { @@ -109,19 +112,17 @@ delete_character :: (str: *string, character_idx: int) { } str.count -= bytes_to_delete; + return; } // Searches for the given character index and returns its byte index on the string. -get_byte_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { - if character_idx < 0 then return -1, false; - if character_idx > str.count then return -2, false; - if character_idx == 0 then return 0, true; - +get_byte_index :: (str: string, character_index: int) -> buffer_index: int, success := true { buff_idx := 0; char_idx := 0; - while buff_idx < str.count && char_idx != character_idx { + while buff_idx < str.count { + if char_idx == character_index return buff_idx; buff_idx += count_character_bytes(str[buff_idx]); char_idx += 1; } - return buff_idx, char_idx == character_idx; + return -1, false; } -- cgit v1.2.3 From f5fc7f6e700d272c2d60e4a1da219c993f01cad3 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 9 May 2024 11:43:31 +0100 Subject: Fixed TUI module to allow returning results in temporary memory. --- modules/TUI/module.jai | 58 +++++++++++++++++++++++++++++--------------------- ttt.jai | 20 +++++++---------- unused.jai | 1 + 3 files changed, 43 insertions(+), 36 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 112c666..f8e745e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -33,7 +33,7 @@ #load "key_map.jai"; #add_context tui_style : Style; // This contains the last style applied by the module. -#add_context tui_buffer : *String_Builder; // If set, this buffer will be used as output target of module procedures. +#add_context tui_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. KEY_SIZE :: #run type_info(Key).runtime_size; #assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. @@ -42,7 +42,7 @@ active := false; input_override : Key; input_string : string; input_buffer : [1024] u8; -temp_buffer := String_Builder.{ allocator = temporary_allocator }; +temp_builder := String_Builder.{ allocator = temporary_allocator }; #scope_module @@ -176,11 +176,13 @@ Style :: struct { } set_style :: (style: Style) { - - auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? - - // TODO If context.tui_buffer is temporary... this will fail.... right? - builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; + // If no tui_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_builder; + temp_mark: Temporary_Storage_State = ---; + if context.tui_builder == null { + builder = *temp_builder; + temp_mark = get_temporary_storage_mark(); + } #if COLOR_MODE_BITS == { case 4; @@ -211,8 +213,9 @@ set_style :: (style: Style) { append(builder, #run sprint(Commands.SetGraphicsRendition, "49")); } - if context.tui_buffer == null { + if context.tui_builder == null { write_builder(builder); + set_temporary_storage_mark(temp_mark); } context.tui_style = style; @@ -497,9 +500,9 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); - - builder := temp_buffer; - + + // The returned memory must be allocated before we start to use temporary memory. + // Otherwise, the returned memory would be invalid on calls of type (,, temporary_allocator). str := alloc_string(count_limit); str.count = 0; idx := 0; @@ -510,6 +513,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); while true { + builder := temp_builder; + auto_release_temp(); chars_count := count_characters(str); @@ -601,9 +606,13 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); - auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? - - builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; + // If no tui_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_builder; + temp_mark: Temporary_Storage_State = ---; + if context.tui_builder == null { + builder = *temp_builder; + temp_mark = get_temporary_storage_mark(); + } append(builder, Commands.DrawingMode); @@ -633,8 +642,9 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(builder, Commands.TextMode); - if context.tui_buffer == null { + if context.tui_builder == null { write_builder(builder); + set_temporary_storage_mark(temp_mark); } } @@ -692,11 +702,11 @@ get_terminal_size :: () -> width: int, height: int { set_cursor_position :: inline (x: int, y: int) { assert_is_active(); - if context.tui_buffer == null { + if context.tui_builder == null { print(Commands.SetCursorPosition, y, x); } else { - print_to_builder(context.tui_buffer, Commands.SetCursorPosition, y, x); + print_to_builder(context.tui_builder, Commands.SetCursorPosition, y, x); } } @@ -740,27 +750,27 @@ set_terminal_title :: inline (title: string) { } using_buffer :: (buffer: *String_Builder) #expand { - __buffer := context.tui_buffer; - context.tui_buffer = buffer; - `defer context.tui_buffer = __buffer; + __buffer := context.tui_builder; + context.tui_builder = buffer; + `defer context.tui_builder = __buffer; } // TODO Maybe we should have a different name for this...? tui_print :: inline (format_string: string, args: .. Any) { - if context.tui_buffer == null { + if context.tui_builder == null { print(format_string, ..args, to_standard_error = false); } else { - print_to_builder(context.tui_buffer, format_string, ..args); + print_to_builder(context.tui_builder, format_string, ..args); } } // TODO Maybe we should have a different name for this...? tui_write :: inline (format_string: string) { - if context.tui_buffer == null { + if context.tui_builder == null { write_string(format_string); } else { - append(context.tui_buffer, format_string); + append(context.tui_builder, format_string); } } diff --git a/ttt.jai b/ttt.jai index 2dbc5eb..efafb00 100644 --- a/ttt.jai +++ b/ttt.jai @@ -214,13 +214,12 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { left_padding := (space - TIME_CHARS) / 2; right_padding := space - TIME_CHARS - left_padding; - print_padding :: (size: int, char: u8 = #char " ") { + print_padding :: (size: int) { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); - auto_release_temp(); - padding := talloc_string(size); - padding.count = size; - memset(padding.data, char, size); - TUI.tui_write(padding); + while size > 0 { + TUI.tui_write(" "); + size -= 1; + } } TUI.set_cursor_position(x, y); @@ -908,9 +907,9 @@ draw_user_interface :: (db: *Database, layout: *Layout) { empty_line := talloc_string(size_x); memset(empty_line.data, #char " ", size_x); - init_string_builder(*buffer, 100000); - // builder := String_Builder.{ allocator = temporary_allocator }; - builder := buffer; + // init_string_builder(*buffer, 100000); + // builder := buffer; + builder := String_Builder.{ allocator = temporary_allocator }; TUI.using_buffer(*builder); adjust_first_day_of_week := int.[ @@ -1194,9 +1193,6 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { main :: () { -// TODO WIP WIP WIP WIP WIP WIP WIP WIP - make some tests to see how auto_release_temporary works... check if something allocated right before are kept or discarddled... and how temp allocated StringBuilder behaves... - - #if DEBUG_MEMORY { defer report_memory_leaks(); // TODO Remove after final debug sessions. } diff --git a/unused.jai b/unused.jai index f9bb2f9..1f71b37 100644 --- a/unused.jai +++ b/unused.jai @@ -1,6 +1,7 @@ auto_release_temp(); // automatically release temporary memory. print(">%<", S64_MIN + delta, to_standard_error = true); print_to_builder(*builder, "Average % us (% / % bytes) ---------", dbg_average/1000, context.temporary_storage.total_bytes_occupied, context.temporary_storage.high_water_mark); // DEBUG +print("temp [% .. % .. %] \n", context.temporary_storage.data, get_temporary_storage_mark(), context.temporary_storage.data + context.temporary_storage.size-1); // MEASURE PERFORMANCE -- cgit v1.2.3 From c0dc68c4672f53f2b9c60074bfb1eb10ab87d980 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 9 May 2024 12:39:46 +0100 Subject: WIP : Renaming TUI print/write procedures. --- modules/TUI/module.jai | 6 +++--- ttt.jai | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index f8e745e..5399503 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -766,11 +766,11 @@ tui_print :: inline (format_string: string, args: .. Any) { } // TODO Maybe we should have a different name for this...? -tui_write :: inline (format_string: string) { +tui_write_string :: inline (s: string) { if context.tui_builder == null { - write_string(format_string); + write_string(s, to_standard_error = false); } else { - append(context.tui_builder, format_string); + append(context.tui_builder, s); } } diff --git a/ttt.jai b/ttt.jai index efafb00..e5ade13 100644 --- a/ttt.jai +++ b/ttt.jai @@ -217,7 +217,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { print_padding :: (size: int) { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); while size > 0 { - TUI.tui_write(" "); + TUI.tui_write_string(" "); size -= 1; } } @@ -226,13 +226,13 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { if time < 0 { print_padding(left_padding); - TUI.tui_write(" - "); + TUI.tui_write_string(" - "); print_padding(right_padding); return 0; } else if time == 0 { print_padding(left_padding); - TUI.tui_write(" 0 "); + TUI.tui_write_string(" 0 "); print_padding(right_padding); return 0; } @@ -274,7 +274,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { } else { print_padding(left_padding); - TUI.tui_write(" ∞ "); + TUI.tui_write_string(" ∞ "); print_padding(right_padding); return 0; } @@ -942,20 +942,20 @@ draw_user_interface :: (db: *Database, layout: *Layout) { // TODO Maybe this could be simplified? y = 1; x = 1; - TUI.tui_write(TUI.Commands.DrawingMode); // append(*builder, TUI.Commands.DrawingMode); TODO + TUI.tui_write_string(TUI.Commands.DrawingMode); // append(*builder, TUI.Commands.DrawingMode); TODO for 0..layout.columns.count-2 { column := layout.columns[it]; x += 1 + column.width; TUI.set_cursor_position(x, y); - TUI.tui_write(TUI.Drawings.TeeT); // TODO append(*builder, TUI.Drawings.TeeT); + TUI.tui_write_string(TUI.Drawings.TeeT); // TODO append(*builder, TUI.Drawings.TeeT); for row: 2..size_y { TUI.set_cursor_position(x, row); - TUI.tui_write(TUI.Drawings.LineV); // TODO append(*builder, TUI.Drawings.LineV); + TUI.tui_write_string(TUI.Drawings.LineV); // TODO append(*builder, TUI.Drawings.LineV); } TUI.set_cursor_position(x, size_y); - TUI.tui_write(TUI.Drawings.TeeB); // TODO append(*builder, TUI.Drawings.TeeB); + TUI.tui_write_string(TUI.Drawings.TeeB); // TODO append(*builder, TUI.Drawings.TeeB); } - TUI.tui_write(TUI.Commands.TextMode); // TODO append(*builder, TUI.Commands.TextMode); + TUI.tui_write_string(TUI.Commands.TextMode); // TODO append(*builder, TUI.Commands.TextMode); /////////////////////////////////////////////////////////////////////////// @@ -967,7 +967,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TITLE_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - TUI.tui_write(ifx db == *archive then layout.archive_title else col.header); // TODO append(*builder, ifx db == *archive then layout.archive_title else col.header); + TUI.tui_write_string(ifx db == *archive then layout.archive_title else col.header); // TODO append(*builder, ifx db == *archive then layout.archive_title else col.header); x += col.width; // Headers : days @@ -987,7 +987,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { } col = *layout.columns[L_DAYS_IDX + idx]; TUI.set_cursor_position(x + col.alignment_offset, y); - TUI.tui_write(col.header); // TODO append(*builder, col.header); + TUI.tui_write_string(col.header); // TODO append(*builder, col.header); x += col.width; } TUI.set_style(style_default); @@ -996,7 +996,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TOTAL_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - TUI.tui_write(col.header); // TODO append(*builder, col.header); + TUI.tui_write_string(col.header); // TODO append(*builder, col.header); /////////////////////////////////////////////////////////////////////////// @@ -1039,11 +1039,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { task_name := cast(string)task.name; task_name = truncate(task_name, column_width); TUI.set_cursor_position(x, y); - TUI.tui_write(task_name); // TODO append(*builder, task_name); + TUI.tui_write_string(task_name); // TODO append(*builder, task_name); // Paint the remaining column space. task_name_char_count := count_characters(task_name, is_null_terminated = true); paint_remaining := string.{ column_width - task_name_char_count, empty_line.data }; - TUI.tui_write(paint_remaining); // TODO append(*builder, paint_remaining); + TUI.tui_write_string(paint_remaining); // TODO append(*builder, paint_remaining); x += column_width; -- cgit v1.2.3 From 18cd73bb1830fd49089eb82667ba6ae14606938c Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 9 May 2024 22:24:16 +0100 Subject: Renamed stuff to make TUI output builder purpose more clear. --- modules/TUI/module.jai | 40 +++++++++++++++++++--------------------- ttt.jai | 29 ++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 30 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5399503..70675c7 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -33,7 +33,7 @@ #load "key_map.jai"; #add_context tui_style : Style; // This contains the last style applied by the module. -#add_context tui_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. +#add_context tui_output_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. KEY_SIZE :: #run type_info(Key).runtime_size; #assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. @@ -176,10 +176,10 @@ Style :: struct { } set_style :: (style: Style) { - // If no tui_builder is provided, use a temporary one and discard it afterwards. - builder := context.tui_builder; + // If no tui_output_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_output_builder; temp_mark: Temporary_Storage_State = ---; - if context.tui_builder == null { + if context.tui_output_builder == null { builder = *temp_builder; temp_mark = get_temporary_storage_mark(); } @@ -213,7 +213,7 @@ set_style :: (style: Style) { append(builder, #run sprint(Commands.SetGraphicsRendition, "49")); } - if context.tui_builder == null { + if context.tui_output_builder == null { write_builder(builder); set_temporary_storage_mark(temp_mark); } @@ -606,10 +606,10 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); - // If no tui_builder is provided, use a temporary one and discard it afterwards. - builder := context.tui_builder; + // If no tui_output_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_output_builder; temp_mark: Temporary_Storage_State = ---; - if context.tui_builder == null { + if context.tui_output_builder == null { builder = *temp_builder; temp_mark = get_temporary_storage_mark(); } @@ -642,7 +642,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(builder, Commands.TextMode); - if context.tui_builder == null { + if context.tui_output_builder == null { write_builder(builder); set_temporary_storage_mark(temp_mark); } @@ -702,11 +702,11 @@ get_terminal_size :: () -> width: int, height: int { set_cursor_position :: inline (x: int, y: int) { assert_is_active(); - if context.tui_builder == null { + if context.tui_output_builder == null { print(Commands.SetCursorPosition, y, x); } else { - print_to_builder(context.tui_builder, Commands.SetCursorPosition, y, x); + print_to_builder(context.tui_output_builder, Commands.SetCursorPosition, y, x); } } @@ -749,28 +749,26 @@ set_terminal_title :: inline (title: string) { print(Commands.SetWindowTitle, title); } -using_buffer :: (buffer: *String_Builder) #expand { - __buffer := context.tui_builder; - context.tui_builder = buffer; - `defer context.tui_builder = __buffer; +using_builder_as_output :: (builder: *String_Builder) #expand { + __builder := context.tui_output_builder; + context.tui_output_builder = builder; + `defer context.tui_output_builder = __builder; } -// TODO Maybe we should have a different name for this...? tui_print :: inline (format_string: string, args: .. Any) { - if context.tui_builder == null { + if context.tui_output_builder == null { print(format_string, ..args, to_standard_error = false); } else { - print_to_builder(context.tui_builder, format_string, ..args); + print_to_builder(context.tui_output_builder, format_string, ..args); } } -// TODO Maybe we should have a different name for this...? tui_write_string :: inline (s: string) { - if context.tui_builder == null { + if context.tui_output_builder == null { write_string(s, to_standard_error = false); } else { - append(context.tui_builder, s); + append(context.tui_output_builder, s); } } diff --git a/ttt.jai b/ttt.jai index e5ade13..01803b2 100644 --- a/ttt.jai +++ b/ttt.jai @@ -204,6 +204,11 @@ count_digits :: (number: s64, base: s64 = 10) -> s64 { // Prints, on row y and column x, the time using 5 characters centered on space. // Returns the result of a call to mvprintw. print_time :: (y: int, x: int, time: s64, space: int) -> int { + + // Use TUI stubs so that callers may choose to use the tui_builder buffer. + print :: TUI.tui_print; + write_string :: TUI.tui_write_string; + TIME_CHARS :: 5; assert(space >= TIME_CHARS); @@ -217,7 +222,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { print_padding :: (size: int) { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); while size > 0 { - TUI.tui_write_string(" "); + write_string(" "); size -= 1; } } @@ -226,19 +231,19 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { if time < 0 { print_padding(left_padding); - TUI.tui_write_string(" - "); + write_string(" - "); print_padding(right_padding); return 0; } else if time == 0 { print_padding(left_padding); - TUI.tui_write_string(" 0 "); + write_string(" 0 "); print_padding(right_padding); return 0; } else if time < SECONDS_IN_MINUTE { print_padding(left_padding); - TUI.tui_print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); + print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); print_padding(right_padding); return 0; } @@ -246,7 +251,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { hours := time / SECONDS_IN_HOUR; minutes := (time - (hours * SECONDS_IN_HOUR) ) / SECONDS_IN_MINUTE; print_padding(left_padding); - TUI.tui_print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); + print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); print_padding(right_padding); return 0; } @@ -257,7 +262,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_DAY) then 1 else 2; print_padding(left_padding); - TUI.tui_print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); + print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } @@ -268,13 +273,13 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_YEAR) then 1 else 2; print_padding(left_padding); - TUI.tui_print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); + print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } else { print_padding(left_padding); - TUI.tui_write_string(" ∞ "); + write_string(" ∞ "); print_padding(right_padding); return 0; } @@ -907,10 +912,16 @@ draw_user_interface :: (db: *Database, layout: *Layout) { empty_line := talloc_string(size_x); memset(empty_line.data, #char " ", size_x); + /* TODO + It's not safe to use temporary memory here because the console resolution may increase and use more than what we have in temporary memory. + And temporary memory is configured at compile time. + We should dynamically allocate memory with some headroom and, at beggining of function... adjust it if necessary. + // init_string_builder(*buffer, 100000); // builder := buffer; + */ builder := String_Builder.{ allocator = temporary_allocator }; - TUI.using_buffer(*builder); + TUI.using_builder_as_output(*builder); adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, -- cgit v1.2.3 From a5e4f8725d74c2dbc3df35249b6dbdd061e44997 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 14 May 2024 10:14:26 +0100 Subject: Added note/documentation about allowWindowOps on xterm. --- modules/TUI/module.jai | 1 + 1 file changed, 1 insertion(+) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 70675c7..fb742da 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -689,6 +689,7 @@ get_terminal_size :: () -> width: int, height: int { } // Some systems don't allow to query the terminal size directly... or the answer takes too much time. // In such cases, measure it indirectly by the maximum possible cursor position. + // (e.g.: allowWindowOps/disallowedWindowOps properties in xterm) else { x, y := get_cursor_position(); defer set_cursor_position(x, y); -- cgit v1.2.3 From bdea73c8349c2c918befad4120f49f9826d270dc Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 16 May 2024 02:42:04 +0100 Subject: Imported new module structure. --- Test_IntSatArith.jai | 304 ---------------------- modules/Integer_Saturating_Arithmetic.jai | 416 ------------------------------ modules/Saturation/module.jai | 416 ++++++++++++++++++++++++++++++ modules/Saturation/tests.jai | 304 ++++++++++++++++++++++ modules/TUI/key_map.jai | 401 ++++++++++++++-------------- modules/TUI/tests.jai | 19 +- modules/UTF8.jai | 128 --------- modules/UTF8/module.jai | 128 +++++++++ modules/UTF8/tests.jai | 5 + sizeof.c | 50 ---- ttt.jai | 2 +- 11 files changed, 1057 insertions(+), 1116 deletions(-) delete mode 100644 Test_IntSatArith.jai delete mode 100644 modules/Integer_Saturating_Arithmetic.jai create mode 100644 modules/Saturation/module.jai create mode 100644 modules/Saturation/tests.jai delete mode 100644 modules/UTF8.jai create mode 100644 modules/UTF8/module.jai create mode 100644 modules/UTF8/tests.jai delete mode 100644 sizeof.c (limited to 'modules') diff --git a/Test_IntSatArith.jai b/Test_IntSatArith.jai deleted file mode 100644 index 2128f7b..0000000 --- a/Test_IntSatArith.jai +++ /dev/null @@ -1,304 +0,0 @@ -// Tests for integer saturating arighmetic procedures. - -#import "Basic"; -#import "Compiler"; -#import "Math"; -#import "String"; -#import "Integer_Saturating_Arithmetic"; - -main :: () { - - write_strings( - "#=======================#\n", - "# Basic tests #\n" - ); - - test_op :: (operation: string, x: $Tx, y: $Ty, result: $Tr, type: Type, saturated: bool, remainder: Tr = 0) -> errors_found: int #expand { - - print_test_call :: (operation: string) -> string { - str: string = ---; - if operation != "div" { - TEST_CALL :: #string DONE - t_result, t_saturated := OP(cast(Tx)x, cast(Ty)y); - if result != t_result print("%_%(%, %) = %0%0\n", operation, type, x, y, result, ifx saturated then " : saturated"); - DONE - str = replace(TEST_CALL, "OP", operation); - } else { - TEST_CALL :: #string DONE - t_result, t_remainder, t_saturated := OP(cast(Tx)x, cast(Ty)y); - if result != t_result print("%_%(%, %) = % + %0%0\n", operation, type, x, y, result, remainder, ifx saturated then " : saturated"); - DONE - str = replace(TEST_CALL, "OP", operation); - } - return str; - } - - #insert #run print_test_call(operation); - - errors := 0; - if result != t_result { errors += 1; print(" > incorrect result value: got % expected %\n", t_result, result); }; - if type != type_of(t_result) { errors += 1; print(" > incorrect result type: got % expected %\n", type_of(t_result), type); }; - if saturated != t_saturated { errors += 1; print(" > incorrect saturated flag: got % expected %\n", t_saturated, saturated); }; - #if operation == "div" { - if remainder != t_remainder { errors += 1; print(" > incorrect remainder value: got % expected %\n", t_remainder, remainder); }; - } - return errors; - } - - errors := 0; - - // Test signed add. - errors += test_op("add", cast( s8) S8_MAX, cast( s8)1, S8_MAX, s8, true); - errors += test_op("add", cast(s16)S16_MAX, cast( u8)1, S16_MAX, s16, true); - errors += test_op("add", cast(s32)S32_MAX, cast(s32)1, S32_MAX, s32, true); - errors += test_op("add", cast(s64)S64_MAX, cast(u32)1, S64_MAX, s64, true); - - errors += test_op("add", cast( s8) S8_MAX, cast( s8) S8_MIN, -1, s8, false); - errors += test_op("add", cast(s16)S16_MAX, cast(s16)S16_MIN, -1, s16, false); - errors += test_op("add", cast(s32)S32_MAX, cast(s32)S32_MIN, -1, s32, false); - errors += test_op("add", cast(s64)S64_MAX, cast(s64)S64_MIN, -1, s64, false); - - // Test unsigned add. - errors += test_op("add", cast( u8) U8_MAX, cast( u8)1, U8_MAX, u8, true); - errors += test_op("add", cast(u16)U16_MAX, cast(u16)1, U16_MAX, u16, true); - errors += test_op("add", cast(u32)U32_MAX, cast(u32)1, U32_MAX, u32, true); - errors += test_op("add", cast(u64)U64_MAX, cast(u64)1, U64_MAX, u64, true); - - errors += test_op("add", cast( u8) U8_MAX, cast( u8)0, U8_MAX, u8, false); - errors += test_op("add", cast(u16)U16_MAX, cast(u16)0, U16_MAX, u16, false); - errors += test_op("add", cast(u32)U32_MAX, cast(u32)0, U32_MAX, u32, false); - errors += test_op("add", cast(u64)U64_MAX, cast(u64)0, U64_MAX, u64, false); - - // Test signed sub. - errors += test_op("sub", cast( s8) S8_MIN, cast( s8)1, S8_MIN, s8, true); - errors += test_op("sub", cast(s16)S16_MIN, cast( u8)1, S16_MIN, s16, true); - errors += test_op("sub", cast(s32)S32_MIN, cast(s32)1, S32_MIN, s32, true); - errors += test_op("sub", cast(s64)S64_MIN, cast(u32)1, S64_MIN, s64, true); - - errors += test_op("sub", cast( s8)-1, cast( s8) S8_MAX, S8_MIN, s8, false); - errors += test_op("sub", cast(s16)-1, cast(s16)S16_MAX, S16_MIN, s16, false); - errors += test_op("sub", cast(s32)-1, cast(s32)S32_MAX, S32_MIN, s32, false); - errors += test_op("sub", cast(s64)-1, cast(s64)S64_MAX, S64_MIN, s64, false); - - // Test unsigned sub. - errors += test_op("sub", cast( u8)1, cast( u8) U8_MAX, 0, u8, true); - errors += test_op("sub", cast( u8)1, cast(u16)U16_MAX, 0, u16, true); - errors += test_op("sub", cast(u32)1, cast(u32)U32_MAX, 0, u32, true); - errors += test_op("sub", cast(u32)1, cast(u64)U64_MAX, 0, u64, true); - - errors += test_op("sub", cast( u8) U8_MAX, cast( u8)0, U8_MAX, u8, false); - errors += test_op("sub", cast(u16)U16_MAX, cast( u8)0, U16_MAX, u16, false); - errors += test_op("sub", cast(u32)U32_MAX, cast(u32)0, U32_MAX, u32, false); - errors += test_op("sub", cast(u64)U64_MAX, cast(u32)0, U64_MAX, u64, false); - - // Test signed mul. - errors += test_op("mul", cast( s8) S8_MIN, cast( s8)-1, S8_MAX, s8, true); - errors += test_op("mul", cast(s16)S16_MIN, cast( s8)-1, S16_MAX, s16, true); - errors += test_op("mul", cast(s32)S32_MIN, cast(s32)-1, S32_MAX, s32, true); - errors += test_op("mul", cast(s64)S64_MIN, cast(s32)-1, S64_MAX, s64, true); - - errors += test_op("mul", cast( s8) S8_MAX, cast( s8)-2, S8_MIN, s8, true); - errors += test_op("mul", cast(s16)S16_MAX, cast( s8)-2, S16_MIN, s16, true); - errors += test_op("mul", cast(s32)S32_MAX, cast(s32)-2, S32_MIN, s32, true); - errors += test_op("mul", cast(s64)S64_MAX, cast(s32)-2, S64_MIN, s64, true); - - errors += test_op("mul", cast( s8)-2, cast( s8) S8_MAX, S8_MIN, s8, true); - errors += test_op("mul", cast( s8)-2, cast(s16)S16_MAX, S16_MIN, s16, true); - errors += test_op("mul", cast(s32)-2, cast(s32)S32_MAX, S32_MIN, s32, true); - errors += test_op("mul", cast(s32)-2, cast(s64)S64_MAX, S64_MIN, s64, true); - - errors += test_op("mul", cast( s8) S8_MAX, cast( s8)2, S8_MAX, s8, true); - errors += test_op("mul", cast(s16)S16_MAX, cast( s8)2, S16_MAX, s16, true); - errors += test_op("mul", cast(s32)S32_MAX, cast(s32)2, S32_MAX, s32, true); - errors += test_op("mul", cast(s64)S64_MAX, cast(s32)2, S64_MAX, s64, true); - - errors += test_op("mul", cast( s8) S8_MAX, cast( s8)-1, -S8_MAX, s8, false); - errors += test_op("mul", cast(s16)S16_MAX, cast( s8)-1, -S16_MAX, s16, false); - errors += test_op("mul", cast(s32)S32_MAX, cast(s32)-1, -S32_MAX, s32, false); - errors += test_op("mul", cast(s64)S64_MAX, cast(s32)-1, -S64_MAX, s64, false); - - errors += test_op("mul", cast( s8) S8_MAX, cast( s8)0, 0, s8, false); - errors += test_op("mul", cast(s16)S16_MAX, cast( u8)0, 0, s16, false); - errors += test_op("mul", cast(s32)S32_MAX, cast(s32)0, 0, s32, false); - errors += test_op("mul", cast(s64)S64_MAX, cast(u32)0, 0, s64, false); - - // Test unsigned mul. - errors += test_op("mul", cast( u8) U8_MAX, cast( u8)1, U8_MAX, u8, false); - errors += test_op("mul", cast(u16)U16_MAX, cast( u8)1, U16_MAX, u16, false); - errors += test_op("mul", cast(u32)U32_MAX, cast(u32)1, U32_MAX, u32, false); - errors += test_op("mul", cast(u64)U64_MAX, cast(u32)1, U64_MAX, u64, false); - - errors += test_op("mul", cast( u8) U8_MAX, cast( u8)2, U8_MAX, u8, true); - errors += test_op("mul", cast(u16)U16_MAX, cast( u8)2, U16_MAX, u16, true); - errors += test_op("mul", cast(u32)U32_MAX, cast(u32)2, U32_MAX, u32, true); - errors += test_op("mul", cast(u64)U64_MAX, cast(u32)2, U64_MAX, u64, true); - - // Test signed div. - errors += test_op("div", cast( s8) S8_MIN, cast( s8)-1, S8_MAX, s8, true, -1); - errors += test_op("div", cast(s16)S16_MIN, cast( s8)-1, S16_MAX, s16, true, -1); - errors += test_op("div", cast(s32)S32_MIN, cast(s32)-1, S32_MAX, s32, true, -1); - errors += test_op("div", cast(s64)S64_MIN, cast(s32)-1, S64_MAX, s64, true, -1); - - errors += test_op("div", cast( s8) S8_MAX, cast( s8)-2, - S8_MAX/2, s8, false, 1); - errors += test_op("div", cast(s16)S16_MAX, cast( s8)-2, -S16_MAX/2, s16, false, 1); - errors += test_op("div", cast(s32)S32_MAX, cast(s32)-2, -S32_MAX/2, s32, false, 1); - errors += test_op("div", cast(s64)S64_MAX, cast(s32)-2, -S64_MAX/2, s64, false, 1); - - errors += test_op("div", cast( s8)15, cast( s8)5, 3, s8, false, 0); - errors += test_op("div", cast( u8)15, cast(s16)7, 2, s16, false, 1); - errors += test_op("div", cast(s16)15, cast(s32)13, 1, s32, false, 2); - errors += test_op("div", cast(u16)100, cast(s64)3, 33, s64, false, 1); - - // Test unsigned div. - errors += test_op("div", cast( u8) U8_MAX, cast( u8)2, U8_MAX/2, u8, false, 1); - errors += test_op("div", cast(u16)U16_MAX, cast( u8)2, U16_MAX/2, u16, false, 1); - errors += test_op("div", cast(u32)U32_MAX, cast(u32)2, U32_MAX/2, u32, false, 1); - errors += test_op("div", cast(u64)U64_MAX, cast(u32)2, U64_MAX/2, u64, false, 1); - - if errors > 0 print("# Found % %!\n", errors, ifx errors == 1 then "error" else "errors"); else print(" No errors found.\n"); - - write_strings( - "#=======================#\n", - "# Benchmarks #\n" - ); - - #import "Random"; - - performance_test :: ($operation: string, $type: Type, print_result: bool = true) -> ops_per_us_gen: float, ops_per_us_asm: float { - - #if type == u8 { MIN :: 0; MAX :: U8_MAX; } - #if type == u16 { MIN :: 0; MAX :: U16_MAX; } - #if type == u32 { MIN :: 0; MAX :: U32_MAX; } - #if type == u64 { MIN :: 0; MAX :: U64_MAX; } - #if type == s8 { MIN :: S8_MIN; MAX :: S8_MAX; } - #if type == s16 { MIN :: S16_MIN; MAX :: S16_MAX; } - #if type == s32 { MIN :: S32_MIN; MAX :: S32_MAX; } - #if type == s64 { MIN :: S64_MIN; MAX :: S64_MAX; } - - NUM_TESTS :: 50000; - DATA_SIZE_BITS :: 64*1024*8; - #if type == s8 || type == u8 then - DATA_SIZE :: DATA_SIZE_BITS/8; - else #if type == s16 || type == u16 then - DATA_SIZE :: DATA_SIZE_BITS/16; - else #if type == s32 || type == u32 then - DATA_SIZE :: DATA_SIZE_BITS/32; - else #if type == s64 || type == u64 then - DATA_SIZE :: DATA_SIZE_BITS/64; - - best_gen := 0.0; - best_asm := 0.0; - numbers_x: [..] type; - numbers_y: [..] type; - array_reserve(*numbers_x, DATA_SIZE); - array_reserve(*numbers_y, DATA_SIZE); - - // Comment the line bellow to use the same "random" values. - random_seed(cast(u64)to_nanoseconds(current_time_monotonic())); - - for 0..DATA_SIZE-1 { - x := cast(type) random_get_within_range(xx MIN, xx MAX); - y := cast(type) random_get_within_range(xx MIN, xx MAX); - if y == 0 && operation == "div" { - y = 1; - } - array_add(*numbers_x, x); - array_add(*numbers_y, y); - } - - for 0..NUM_TESTS-1 { - - r_gen: type = 0; - r_asm: type = 0; - - time_gen := current_time_monotonic(); - for idx: 0..DATA_SIZE-1 #insert #run replace("r_gen ^= OP(numbers_x[idx], numbers_y[idx], true);", "OP", operation); - time_gen = current_time_monotonic() - time_gen; - - time_asm := current_time_monotonic(); - for idx: 0..DATA_SIZE-1 #insert #run replace("r_asm ^= OP(numbers_x[idx], numbers_y[idx]);", "OP", operation); - time_asm = current_time_monotonic() - time_asm; - - assert(r_gen == r_asm); - - perf_gen := cast(float)DATA_SIZE/cast(float)to_nanoseconds(time_gen); - perf_asm := cast(float)DATA_SIZE/cast(float)to_nanoseconds(time_asm); - best_gen = max(best_gen, perf_gen); - best_asm = max(best_asm, perf_asm); - } - - tmp_context := context; - push_context tmp_context { - ff := *context.print_style.default_format_float; - ff.zero_removal = .NO; - ff.width = 7; - ff.trailing_width = 2; - - fi := *context.print_style.default_format_int; - fi.minimum_digits = 3; - - if print_result { - if type == s8 || type == u8 write_string(" "); - print("% | % | % | %\n", type, best_gen, best_asm, cast(int)(100*best_asm/best_gen)); - } - } - return best_gen, best_asm; - } - - write_strings( - " | (ops / nsec) |\n", - " T | generic | x64 asm | %\n" - ); - - write_strings( - "--- | ----------------- | ---\n", - " | add |\n" - ); - performance_test("add", u8); - performance_test("add", u16); - performance_test("add", u32); - performance_test("add", u64); - performance_test("add", s8); - performance_test("add", s16); - performance_test("add", s32); - performance_test("add", s64); - - write_strings( - "--- | ----------------- | ---\n", - " | sub |\n" - ); - performance_test("sub", u8); - performance_test("sub", u16); - performance_test("sub", u32); - performance_test("sub", u64); - performance_test("sub", s8); - performance_test("sub", s16); - performance_test("sub", s32); - performance_test("sub", s64); - - write_strings( - "--- | ----------------- | ---\n", - " | mul |\n" - ); - performance_test("mul", u8); - performance_test("mul", u16); - performance_test("mul", u32); - performance_test("mul", u64); - performance_test("mul", s8); - performance_test("mul", s16); - performance_test("mul", s32); - performance_test("mul", s64); - - write_strings( - "--- | ----------------- | ---\n", - " | div |\n" - ); - performance_test("div", u8); - performance_test("div", u16); - performance_test("div", u32); - performance_test("div", u64); - performance_test("div", s8); - performance_test("div", s16); - performance_test("div", s32); - performance_test("div", s64); -} diff --git a/modules/Integer_Saturating_Arithmetic.jai b/modules/Integer_Saturating_Arithmetic.jai deleted file mode 100644 index 74643e0..0000000 --- a/modules/Integer_Saturating_Arithmetic.jai +++ /dev/null @@ -1,416 +0,0 @@ -// Integer saturating arighmetic (with assembly branch-free procedures for x64 - expecting signed values in two's complement). - -#import "Basic"; -#import "Math"; -#import "String"; - - -INTEGER_ARITHMETIC_TYPES_CHECK :: #string DONE - type_info_x := cast(*Type_Info)Tx; - type_info_y := cast(*Type_Info)Ty; - if type_info_x.type != .INTEGER || type_info_y.type != .INTEGER return false, "Non integers values passed."; - tx := cast(*Type_Info_Integer)type_info_x; - ty := cast(*Type_Info_Integer)type_info_y; - - largest_type := - ifx tx.runtime_size > ty.runtime_size then Tx else - ifx ty.runtime_size > tx.runtime_size then Ty else - ifx tx.signed == ty.signed then Tx else - void; - - // Only allow to add different signedness values if largest type is the signed one (as in JAI). - if tx.signed == ty.signed { - Tx = largest_type; - Ty = largest_type; - Tr = largest_type; - } - else if tx.signed && Tx == largest_type { - Ty = largest_type; - Tr = largest_type; - } - else if ty.signed && Ty == largest_type { - Tx = largest_type; - Tr = largest_type; - } - else return false, "Number signedness mismatch."; - - return true; -DONE - -add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } -{ - - #if USE_GENERIC || CPU != .X64 { - - #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { - - #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } - #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } - #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } - #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } - - if (y > 0 && x > MAX - y) then return MAX, true; - if (y < 0 && x < MIN - y) then return MIN, true; - - } else { - - #if Tr == u8 { MAX :: U8_MAX; } - #if Tr == u16 { MAX :: U16_MAX; } - #if Tr == u32 { MAX :: U32_MAX; } - #if Tr == u64 { MAX :: U64_MAX; } - - if (x > MAX - y) then return MAX, true; - - } - - return x + y, false; - - } else { - - result: Tr = ---; - saturated: bool = ---; - - - ADD_SIGNED_ASM :: #string DONE - #asm { - mov result, -1; // Pre-set result with signed maximum (set all bits... - shr.SIZE result, 1; // ...then, clear MSB). - bt x, SIGN_BIT; // Test sign bit (affect CF). - adc result, 0; // Overflow signed maximum to signed minimum if CF is set. - - add.SIZE x, y; // Add values (affect OF). - seto saturated; // Set saturated flag if OF. - cmovno result, x; // Move add-result to result if NOT OF. - } - DONE - - #if Tr == s8 - #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); - #if Tr == s16 - #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); - #if Tr == s32 - #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); - #if Tr == s64 - #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); - - - ADD_UNSIGNED_ASM :: #string DONE - #asm { - mov result, -1; // Pre-set result with unsigned maximum. - add.SIZE x, y; // Add values (affect CF). - setc saturated; // Set saturated flag if CF. - cmovnc result, x; // Move add-result to result if NOT CF. - } - DONE - - #if Tr == u8 - #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".b"); - #if Tr == u16 - #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".w"); - #if Tr == u32 - #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".d"); - #if Tr == u64 - #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".q"); - - - return result, saturated; - - } -} - -sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } -{ - - #if USE_GENERIC || CPU != .X64 { - - #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { - - #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } - #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } - #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } - #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } - - if (y < 0 && x > MAX + y) then return MAX, true; - if (y > 0 && x < MIN + y) then return MIN, true; - - } else { - - if (y > x) then return 0, true; - - } - - return x - y, false; - - } else { - - result: Tr = ---; - saturated: bool = ---; - - - SUB_SIGNED_ASM :: #string DONE - #asm { - mov result, -1; // Pre-set result with signed maximum (set all bits... - shr.SIZE result, 1; // ...then, clear MSB). - bt x, SIGN_BIT; // Test signal bit (affect CF). - adc result, 0; // Overflow signed maximum to signed minimum if CF is set. - - sub.SIZE x, y; // Subtract values (affect OF). - seto saturated; // Set saturated flag if OF. - cmovno result, x; // Move subtract-result to result if NOT OF. - } - DONE - - #if Tr == s8 - #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); - #if Tr == s16 - #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); - #if Tr == s32 - #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); - #if Tr == s64 - #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); - - - SUB_UNSIGNED_ASM :: #string DONE - #asm { - xor result, result; // Pre-set result with usigned minimum (zero). - sub.SIZE x, y; // Subtract values (affect CF). - setc saturated; // Set saturated flag if CF. - cmovnc result, x; // Move subtract-result to result if NOT CF. - } - DONE - - #if Tr == u8 - #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".b"); - #if Tr == u16 - #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".w"); - #if Tr == u32 - #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".d"); - #if Tr == u64 - #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".q"); - - - return result, saturated; - - } - -} - -mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } -{ - - #if USE_GENERIC || CPU != .X64 { - - #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { - - #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } - #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } - #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } - #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } - - if x == 0 || y == 0 then return 0, false; - if x > 0 && y > 0 && x > MAX / y then return MAX, true; - if x < 0 && y < 0 && x < MAX / y then return MAX, true; - if (y < 0 && x > 0 && y < MIN / x) || (x < 0 && y > 0 && x < MIN / y) then return MIN, true; - - } else { - - #if Tr == u8 { MAX :: U8_MAX; } - #if Tr == u16 { MAX :: U16_MAX; } - #if Tr == u32 { MAX :: U32_MAX; } - #if Tr == u64 { MAX :: U64_MAX; } - - if x == 0 || y == 0 then return 0, false; - if x > MAX / y then return MAX, true; - - } - - return x * y, false; - - } else { - - result: Tr = ---; - saturated: bool = ---; - - MUL_SIGNED_ASM :: #string DONE - #asm { - // Using two copies of the x value (x_, sign) seems to be a bit faster (not sure why). - mov x_: gpr === a, x; // Pin copy of x value to register A. - - mov result, -1; // Pre-set result with signed maximum (set all bits... - shr.SIZE result, 1; // ...then, clear MSB). - mov sign:, x; // Use copy of x value. - xor sign, y; // Calculate result signal bit using xor. - bt sign, SIGN_BIT; // Test signal bit (affect CF). - adc result, 0; // Overflow signed maximum to signed minimum if CF is set. - - imul.SIZE x_, y; // Multiply values (affect OF). - seto saturated; // Set saturated flag if OF. - cmovno result, x_; // Move multiply-result to result if NOT OF. - } - DONE - - #if Tr == s8 - #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); - #if Tr == s16 - #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); - #if Tr == s32 - #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); - #if Tr == s64 - #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); - - - MUL_UNSIGNED_ASM :: #string DONE - #asm { - result === a; // Pin result to register A. - - mov result, x; // Move value x to result. - mul.SIZE reg_d:, result, y; // Multiply values (affect CF). - setc saturated; // Set saturated flag if CF. - sbb mask:, mask; // If CF: mask = -1 (all bits set); else: mask = 0. - or result, mask; // If CF was set, then result will be set to unsigned maximum (all bits set). - } - DONE - - #if Tr == u8 - #insert #run replace(replace(MUL_UNSIGNED_ASM, ".SIZE", ".b"), "reg_d:,", ""); // For 8bits mul, we do not need D register. - #if Tr == u16 - #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".w"); - #if Tr == u32 - #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".d"); - #if Tr == u64 - #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".q"); - - - return result, saturated; - - } -} - -div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } -{ - - #if USE_GENERIC || CPU != .X64 { - - #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { - - #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } - #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } - #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } - #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } - - if x == MIN && y == -1 then return MAX, -1, true; - - } - - result := x / y; - remainder := x - (y * result); - return result, remainder, false; - - } else { - - result: Tr = ---; - remainder: Tr = ---; - saturated: bool = ---; - - DIV_SIGNED_ASM :: #string DONE - #asm { - result === a; // Pin result to register A (to be used as dividend on idiv). - remainder === d; // Pin remainder to register D. - - xor saturated, saturated; // Clear saturated. - - // Detect div(MIN/-1) and flag it on ZF. - mov t_dividend:, -1; // Pre-set t_dividend with signed minimum (set all bits... - shr.SIZE t_dividend, 1; // ...then, clear MSB... - not t_dividend; // ...then, negate to obtain MSB set and all other bits cleared). - // - mov limit:, t_dividend; // Keep copy of signed minimum on limit. - add limit, 1; // Set limit as signed minimum + 1. - // - xor.SIZE t_dividend, x; // Clear dividend if x value is equal to signed minimum. - // - mov t_divisor:, -1; // Pre-set test_divisor with -1. - xor.SIZE t_divisor, y; // Clear test_divisor if y value is equal to -1. - // - or.SIZE t_dividend, t_divisor; // Or t_dividend with t_divisor (affect ZF). - - setz saturated; // Set saturated flag if ZF. - mov result, x; // Copy x value to result (dividend). - cmovz result, limit; // If ZF: copy limit (signed minimum + 1) to result (dividend). - - DIVIDE_PLACEHOLDER - - sub.SIZE remainder, saturated; // If saturated: remainder = 0 - 1; otherwise: remainder = x - 0. - } - DONE - - DIV_SIGNED_CALC_8BITS :: #string DONE - cbw result; // Prepare dividend high bits (sign-extend). - idiv.SIZE result, y; // Divide values. - mov remainder, result; // Extract remainder from result's high bits. - sar remainder, 8; // Shift remainder from high to low bits. - DONE - - DIV_SIGNED_CALC_16BITS :: #string DONE - cwd remainder, result; // Prepare dividend high bits (sign-extend). - idiv.SIZE remainder, result, y; // Divide values. - DONE - - DIV_SIGNED_CALC_32BITS :: #string DONE - cdq remainder, result; // Prepare dividend high bits (sign-extend). - idiv.SIZE remainder, result, y; // Divide values. - DONE - - DIV_SIGNED_CALC_64BITS :: #string DONE - cqo remainder, result; // Prepare dividend high bits (sign-extend). - idiv.SIZE remainder, result, y; // Divide values. - DONE - - #if Tr == s8 - #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_8BITS), ".SIZE", ".b"); - #if Tr == s16 - #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_16BITS), ".SIZE", ".w"); - #if Tr == s32 - #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_32BITS), ".SIZE", ".d"); - #if Tr == s64 - #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_64BITS), ".SIZE", ".q"); - - - DIV_UNSIGNED_ASM :: #string DONE - #asm { - result === a; // Pin result to register A. - remainder === d; // Pin remainder to register D. - - xor result, result; // Clear result. - xor remainder, remainder; // Clear remainder (required when used as dividend's high bits). - xor saturated, saturated; // Clear saturated (unsigned division never saturates). - mov result, x; // Copy x value to result. - - DIVIDE_PLACEHOLDER - } - DONE - - DIV_UNSIGNED_CALC_8BITS :: #string DONE - div.SIZE result, y; // Divide values. - mov remainder, result; // Extract remainder from result's high bits. - sar remainder, 8; // Shift remainder from high to low bits. - DONE - - DIV_UNSIGNED_CALC :: #string DONE - div.SIZE remainder, result, y; // Divide values. - DONE - - #if Tr == u8 - #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC_8BITS), ".SIZE", ".b"); - #if Tr == u16 - #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".w"); - #if Tr == u32 - #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".d"); - #if Tr == u64 - #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".q"); - - - return result, remainder, saturated; - - } -} diff --git a/modules/Saturation/module.jai b/modules/Saturation/module.jai new file mode 100644 index 0000000..74643e0 --- /dev/null +++ b/modules/Saturation/module.jai @@ -0,0 +1,416 @@ +// Integer saturating arighmetic (with assembly branch-free procedures for x64 - expecting signed values in two's complement). + +#import "Basic"; +#import "Math"; +#import "String"; + + +INTEGER_ARITHMETIC_TYPES_CHECK :: #string DONE + type_info_x := cast(*Type_Info)Tx; + type_info_y := cast(*Type_Info)Ty; + if type_info_x.type != .INTEGER || type_info_y.type != .INTEGER return false, "Non integers values passed."; + tx := cast(*Type_Info_Integer)type_info_x; + ty := cast(*Type_Info_Integer)type_info_y; + + largest_type := + ifx tx.runtime_size > ty.runtime_size then Tx else + ifx ty.runtime_size > tx.runtime_size then Ty else + ifx tx.signed == ty.signed then Tx else + void; + + // Only allow to add different signedness values if largest type is the signed one (as in JAI). + if tx.signed == ty.signed { + Tx = largest_type; + Ty = largest_type; + Tr = largest_type; + } + else if tx.signed && Tx == largest_type { + Ty = largest_type; + Tr = largest_type; + } + else if ty.signed && Ty == largest_type { + Tx = largest_type; + Tr = largest_type; + } + else return false, "Number signedness mismatch."; + + return true; +DONE + +add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if (y > 0 && x > MAX - y) then return MAX, true; + if (y < 0 && x < MIN - y) then return MIN, true; + + } else { + + #if Tr == u8 { MAX :: U8_MAX; } + #if Tr == u16 { MAX :: U16_MAX; } + #if Tr == u32 { MAX :: U32_MAX; } + #if Tr == u64 { MAX :: U64_MAX; } + + if (x > MAX - y) then return MAX, true; + + } + + return x + y, false; + + } else { + + result: Tr = ---; + saturated: bool = ---; + + + ADD_SIGNED_ASM :: #string DONE + #asm { + mov result, -1; // Pre-set result with signed maximum (set all bits... + shr.SIZE result, 1; // ...then, clear MSB). + bt x, SIGN_BIT; // Test sign bit (affect CF). + adc result, 0; // Overflow signed maximum to signed minimum if CF is set. + + add.SIZE x, y; // Add values (affect OF). + seto saturated; // Set saturated flag if OF. + cmovno result, x; // Move add-result to result if NOT OF. + } + DONE + + #if Tr == s8 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); + #if Tr == s16 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); + #if Tr == s32 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); + #if Tr == s64 + #insert #run replace(replace(ADD_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); + + + ADD_UNSIGNED_ASM :: #string DONE + #asm { + mov result, -1; // Pre-set result with unsigned maximum. + add.SIZE x, y; // Add values (affect CF). + setc saturated; // Set saturated flag if CF. + cmovnc result, x; // Move add-result to result if NOT CF. + } + DONE + + #if Tr == u8 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".b"); + #if Tr == u16 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(ADD_UNSIGNED_ASM, ".SIZE", ".q"); + + + return result, saturated; + + } +} + +sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if (y < 0 && x > MAX + y) then return MAX, true; + if (y > 0 && x < MIN + y) then return MIN, true; + + } else { + + if (y > x) then return 0, true; + + } + + return x - y, false; + + } else { + + result: Tr = ---; + saturated: bool = ---; + + + SUB_SIGNED_ASM :: #string DONE + #asm { + mov result, -1; // Pre-set result with signed maximum (set all bits... + shr.SIZE result, 1; // ...then, clear MSB). + bt x, SIGN_BIT; // Test signal bit (affect CF). + adc result, 0; // Overflow signed maximum to signed minimum if CF is set. + + sub.SIZE x, y; // Subtract values (affect OF). + seto saturated; // Set saturated flag if OF. + cmovno result, x; // Move subtract-result to result if NOT OF. + } + DONE + + #if Tr == s8 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); + #if Tr == s16 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); + #if Tr == s32 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); + #if Tr == s64 + #insert #run replace(replace(SUB_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); + + + SUB_UNSIGNED_ASM :: #string DONE + #asm { + xor result, result; // Pre-set result with usigned minimum (zero). + sub.SIZE x, y; // Subtract values (affect CF). + setc saturated; // Set saturated flag if CF. + cmovnc result, x; // Move subtract-result to result if NOT CF. + } + DONE + + #if Tr == u8 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".b"); + #if Tr == u16 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(SUB_UNSIGNED_ASM, ".SIZE", ".q"); + + + return result, saturated; + + } + +} + +mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if x == 0 || y == 0 then return 0, false; + if x > 0 && y > 0 && x > MAX / y then return MAX, true; + if x < 0 && y < 0 && x < MAX / y then return MAX, true; + if (y < 0 && x > 0 && y < MIN / x) || (x < 0 && y > 0 && x < MIN / y) then return MIN, true; + + } else { + + #if Tr == u8 { MAX :: U8_MAX; } + #if Tr == u16 { MAX :: U16_MAX; } + #if Tr == u32 { MAX :: U32_MAX; } + #if Tr == u64 { MAX :: U64_MAX; } + + if x == 0 || y == 0 then return 0, false; + if x > MAX / y then return MAX, true; + + } + + return x * y, false; + + } else { + + result: Tr = ---; + saturated: bool = ---; + + MUL_SIGNED_ASM :: #string DONE + #asm { + // Using two copies of the x value (x_, sign) seems to be a bit faster (not sure why). + mov x_: gpr === a, x; // Pin copy of x value to register A. + + mov result, -1; // Pre-set result with signed maximum (set all bits... + shr.SIZE result, 1; // ...then, clear MSB). + mov sign:, x; // Use copy of x value. + xor sign, y; // Calculate result signal bit using xor. + bt sign, SIGN_BIT; // Test signal bit (affect CF). + adc result, 0; // Overflow signed maximum to signed minimum if CF is set. + + imul.SIZE x_, y; // Multiply values (affect OF). + seto saturated; // Set saturated flag if OF. + cmovno result, x_; // Move multiply-result to result if NOT OF. + } + DONE + + #if Tr == s8 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".b"), "SIGN_BIT", "7"); + #if Tr == s16 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".w"), "SIGN_BIT", "15"); + #if Tr == s32 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".d"), "SIGN_BIT", "31"); + #if Tr == s64 + #insert #run replace(replace(MUL_SIGNED_ASM, ".SIZE", ".q"), "SIGN_BIT", "63"); + + + MUL_UNSIGNED_ASM :: #string DONE + #asm { + result === a; // Pin result to register A. + + mov result, x; // Move value x to result. + mul.SIZE reg_d:, result, y; // Multiply values (affect CF). + setc saturated; // Set saturated flag if CF. + sbb mask:, mask; // If CF: mask = -1 (all bits set); else: mask = 0. + or result, mask; // If CF was set, then result will be set to unsigned maximum (all bits set). + } + DONE + + #if Tr == u8 + #insert #run replace(replace(MUL_UNSIGNED_ASM, ".SIZE", ".b"), "reg_d:,", ""); // For 8bits mul, we do not need D register. + #if Tr == u16 + #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(MUL_UNSIGNED_ASM, ".SIZE", ".q"); + + + return result, saturated; + + } +} + +div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +{ + + #if USE_GENERIC || CPU != .X64 { + + #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { + + #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } + #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } + #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } + #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } + + if x == MIN && y == -1 then return MAX, -1, true; + + } + + result := x / y; + remainder := x - (y * result); + return result, remainder, false; + + } else { + + result: Tr = ---; + remainder: Tr = ---; + saturated: bool = ---; + + DIV_SIGNED_ASM :: #string DONE + #asm { + result === a; // Pin result to register A (to be used as dividend on idiv). + remainder === d; // Pin remainder to register D. + + xor saturated, saturated; // Clear saturated. + + // Detect div(MIN/-1) and flag it on ZF. + mov t_dividend:, -1; // Pre-set t_dividend with signed minimum (set all bits... + shr.SIZE t_dividend, 1; // ...then, clear MSB... + not t_dividend; // ...then, negate to obtain MSB set and all other bits cleared). + // + mov limit:, t_dividend; // Keep copy of signed minimum on limit. + add limit, 1; // Set limit as signed minimum + 1. + // + xor.SIZE t_dividend, x; // Clear dividend if x value is equal to signed minimum. + // + mov t_divisor:, -1; // Pre-set test_divisor with -1. + xor.SIZE t_divisor, y; // Clear test_divisor if y value is equal to -1. + // + or.SIZE t_dividend, t_divisor; // Or t_dividend with t_divisor (affect ZF). + + setz saturated; // Set saturated flag if ZF. + mov result, x; // Copy x value to result (dividend). + cmovz result, limit; // If ZF: copy limit (signed minimum + 1) to result (dividend). + + DIVIDE_PLACEHOLDER + + sub.SIZE remainder, saturated; // If saturated: remainder = 0 - 1; otherwise: remainder = x - 0. + } + DONE + + DIV_SIGNED_CALC_8BITS :: #string DONE + cbw result; // Prepare dividend high bits (sign-extend). + idiv.SIZE result, y; // Divide values. + mov remainder, result; // Extract remainder from result's high bits. + sar remainder, 8; // Shift remainder from high to low bits. + DONE + + DIV_SIGNED_CALC_16BITS :: #string DONE + cwd remainder, result; // Prepare dividend high bits (sign-extend). + idiv.SIZE remainder, result, y; // Divide values. + DONE + + DIV_SIGNED_CALC_32BITS :: #string DONE + cdq remainder, result; // Prepare dividend high bits (sign-extend). + idiv.SIZE remainder, result, y; // Divide values. + DONE + + DIV_SIGNED_CALC_64BITS :: #string DONE + cqo remainder, result; // Prepare dividend high bits (sign-extend). + idiv.SIZE remainder, result, y; // Divide values. + DONE + + #if Tr == s8 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_8BITS), ".SIZE", ".b"); + #if Tr == s16 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_16BITS), ".SIZE", ".w"); + #if Tr == s32 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_32BITS), ".SIZE", ".d"); + #if Tr == s64 + #insert #run replace(replace(DIV_SIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_SIGNED_CALC_64BITS), ".SIZE", ".q"); + + + DIV_UNSIGNED_ASM :: #string DONE + #asm { + result === a; // Pin result to register A. + remainder === d; // Pin remainder to register D. + + xor result, result; // Clear result. + xor remainder, remainder; // Clear remainder (required when used as dividend's high bits). + xor saturated, saturated; // Clear saturated (unsigned division never saturates). + mov result, x; // Copy x value to result. + + DIVIDE_PLACEHOLDER + } + DONE + + DIV_UNSIGNED_CALC_8BITS :: #string DONE + div.SIZE result, y; // Divide values. + mov remainder, result; // Extract remainder from result's high bits. + sar remainder, 8; // Shift remainder from high to low bits. + DONE + + DIV_UNSIGNED_CALC :: #string DONE + div.SIZE remainder, result, y; // Divide values. + DONE + + #if Tr == u8 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC_8BITS), ".SIZE", ".b"); + #if Tr == u16 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".w"); + #if Tr == u32 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".d"); + #if Tr == u64 + #insert #run replace(replace(DIV_UNSIGNED_ASM, "DIVIDE_PLACEHOLDER", DIV_UNSIGNED_CALC), ".SIZE", ".q"); + + + return result, remainder, saturated; + + } +} diff --git a/modules/Saturation/tests.jai b/modules/Saturation/tests.jai new file mode 100644 index 0000000..372d66d --- /dev/null +++ b/modules/Saturation/tests.jai @@ -0,0 +1,304 @@ +// Tests for integer saturating arighmetic procedures. + +#import "Basic"; +#import "Compiler"; +#import "Math"; +#import "String"; +#import "Saturation"; + +main :: () { + + write_strings( + "#=======================#\n", + "# Basic tests #\n" + ); + + test_op :: (operation: string, x: $Tx, y: $Ty, result: $Tr, type: Type, saturated: bool, remainder: Tr = 0) -> errors_found: int #expand { + + print_test_call :: (operation: string) -> string { + str: string = ---; + if operation != "div" { + TEST_CALL :: #string DONE + t_result, t_saturated := OP(cast(Tx)x, cast(Ty)y); + if result != t_result print("%_%(%, %) = %0%0\n", operation, type, x, y, result, ifx saturated then " : saturated"); + DONE + str = replace(TEST_CALL, "OP", operation); + } else { + TEST_CALL :: #string DONE + t_result, t_remainder, t_saturated := OP(cast(Tx)x, cast(Ty)y); + if result != t_result print("%_%(%, %) = % + %0%0\n", operation, type, x, y, result, remainder, ifx saturated then " : saturated"); + DONE + str = replace(TEST_CALL, "OP", operation); + } + return str; + } + + #insert #run print_test_call(operation); + + errors := 0; + if result != t_result { errors += 1; print(" > incorrect result value: got % expected %\n", t_result, result); }; + if type != type_of(t_result) { errors += 1; print(" > incorrect result type: got % expected %\n", type_of(t_result), type); }; + if saturated != t_saturated { errors += 1; print(" > incorrect saturated flag: got % expected %\n", t_saturated, saturated); }; + #if operation == "div" { + if remainder != t_remainder { errors += 1; print(" > incorrect remainder value: got % expected %\n", t_remainder, remainder); }; + } + return errors; + } + + errors := 0; + + // Test signed add. + errors += test_op("add", cast( s8) S8_MAX, cast( s8)1, S8_MAX, s8, true); + errors += test_op("add", cast(s16)S16_MAX, cast( u8)1, S16_MAX, s16, true); + errors += test_op("add", cast(s32)S32_MAX, cast(s32)1, S32_MAX, s32, true); + errors += test_op("add", cast(s64)S64_MAX, cast(u32)1, S64_MAX, s64, true); + + errors += test_op("add", cast( s8) S8_MAX, cast( s8) S8_MIN, -1, s8, false); + errors += test_op("add", cast(s16)S16_MAX, cast(s16)S16_MIN, -1, s16, false); + errors += test_op("add", cast(s32)S32_MAX, cast(s32)S32_MIN, -1, s32, false); + errors += test_op("add", cast(s64)S64_MAX, cast(s64)S64_MIN, -1, s64, false); + + // Test unsigned add. + errors += test_op("add", cast( u8) U8_MAX, cast( u8)1, U8_MAX, u8, true); + errors += test_op("add", cast(u16)U16_MAX, cast(u16)1, U16_MAX, u16, true); + errors += test_op("add", cast(u32)U32_MAX, cast(u32)1, U32_MAX, u32, true); + errors += test_op("add", cast(u64)U64_MAX, cast(u64)1, U64_MAX, u64, true); + + errors += test_op("add", cast( u8) U8_MAX, cast( u8)0, U8_MAX, u8, false); + errors += test_op("add", cast(u16)U16_MAX, cast(u16)0, U16_MAX, u16, false); + errors += test_op("add", cast(u32)U32_MAX, cast(u32)0, U32_MAX, u32, false); + errors += test_op("add", cast(u64)U64_MAX, cast(u64)0, U64_MAX, u64, false); + + // Test signed sub. + errors += test_op("sub", cast( s8) S8_MIN, cast( s8)1, S8_MIN, s8, true); + errors += test_op("sub", cast(s16)S16_MIN, cast( u8)1, S16_MIN, s16, true); + errors += test_op("sub", cast(s32)S32_MIN, cast(s32)1, S32_MIN, s32, true); + errors += test_op("sub", cast(s64)S64_MIN, cast(u32)1, S64_MIN, s64, true); + + errors += test_op("sub", cast( s8)-1, cast( s8) S8_MAX, S8_MIN, s8, false); + errors += test_op("sub", cast(s16)-1, cast(s16)S16_MAX, S16_MIN, s16, false); + errors += test_op("sub", cast(s32)-1, cast(s32)S32_MAX, S32_MIN, s32, false); + errors += test_op("sub", cast(s64)-1, cast(s64)S64_MAX, S64_MIN, s64, false); + + // Test unsigned sub. + errors += test_op("sub", cast( u8)1, cast( u8) U8_MAX, 0, u8, true); + errors += test_op("sub", cast( u8)1, cast(u16)U16_MAX, 0, u16, true); + errors += test_op("sub", cast(u32)1, cast(u32)U32_MAX, 0, u32, true); + errors += test_op("sub", cast(u32)1, cast(u64)U64_MAX, 0, u64, true); + + errors += test_op("sub", cast( u8) U8_MAX, cast( u8)0, U8_MAX, u8, false); + errors += test_op("sub", cast(u16)U16_MAX, cast( u8)0, U16_MAX, u16, false); + errors += test_op("sub", cast(u32)U32_MAX, cast(u32)0, U32_MAX, u32, false); + errors += test_op("sub", cast(u64)U64_MAX, cast(u32)0, U64_MAX, u64, false); + + // Test signed mul. + errors += test_op("mul", cast( s8) S8_MIN, cast( s8)-1, S8_MAX, s8, true); + errors += test_op("mul", cast(s16)S16_MIN, cast( s8)-1, S16_MAX, s16, true); + errors += test_op("mul", cast(s32)S32_MIN, cast(s32)-1, S32_MAX, s32, true); + errors += test_op("mul", cast(s64)S64_MIN, cast(s32)-1, S64_MAX, s64, true); + + errors += test_op("mul", cast( s8) S8_MAX, cast( s8)-2, S8_MIN, s8, true); + errors += test_op("mul", cast(s16)S16_MAX, cast( s8)-2, S16_MIN, s16, true); + errors += test_op("mul", cast(s32)S32_MAX, cast(s32)-2, S32_MIN, s32, true); + errors += test_op("mul", cast(s64)S64_MAX, cast(s32)-2, S64_MIN, s64, true); + + errors += test_op("mul", cast( s8)-2, cast( s8) S8_MAX, S8_MIN, s8, true); + errors += test_op("mul", cast( s8)-2, cast(s16)S16_MAX, S16_MIN, s16, true); + errors += test_op("mul", cast(s32)-2, cast(s32)S32_MAX, S32_MIN, s32, true); + errors += test_op("mul", cast(s32)-2, cast(s64)S64_MAX, S64_MIN, s64, true); + + errors += test_op("mul", cast( s8) S8_MAX, cast( s8)2, S8_MAX, s8, true); + errors += test_op("mul", cast(s16)S16_MAX, cast( s8)2, S16_MAX, s16, true); + errors += test_op("mul", cast(s32)S32_MAX, cast(s32)2, S32_MAX, s32, true); + errors += test_op("mul", cast(s64)S64_MAX, cast(s32)2, S64_MAX, s64, true); + + errors += test_op("mul", cast( s8) S8_MAX, cast( s8)-1, -S8_MAX, s8, false); + errors += test_op("mul", cast(s16)S16_MAX, cast( s8)-1, -S16_MAX, s16, false); + errors += test_op("mul", cast(s32)S32_MAX, cast(s32)-1, -S32_MAX, s32, false); + errors += test_op("mul", cast(s64)S64_MAX, cast(s32)-1, -S64_MAX, s64, false); + + errors += test_op("mul", cast( s8) S8_MAX, cast( s8)0, 0, s8, false); + errors += test_op("mul", cast(s16)S16_MAX, cast( u8)0, 0, s16, false); + errors += test_op("mul", cast(s32)S32_MAX, cast(s32)0, 0, s32, false); + errors += test_op("mul", cast(s64)S64_MAX, cast(u32)0, 0, s64, false); + + // Test unsigned mul. + errors += test_op("mul", cast( u8) U8_MAX, cast( u8)1, U8_MAX, u8, false); + errors += test_op("mul", cast(u16)U16_MAX, cast( u8)1, U16_MAX, u16, false); + errors += test_op("mul", cast(u32)U32_MAX, cast(u32)1, U32_MAX, u32, false); + errors += test_op("mul", cast(u64)U64_MAX, cast(u32)1, U64_MAX, u64, false); + + errors += test_op("mul", cast( u8) U8_MAX, cast( u8)2, U8_MAX, u8, true); + errors += test_op("mul", cast(u16)U16_MAX, cast( u8)2, U16_MAX, u16, true); + errors += test_op("mul", cast(u32)U32_MAX, cast(u32)2, U32_MAX, u32, true); + errors += test_op("mul", cast(u64)U64_MAX, cast(u32)2, U64_MAX, u64, true); + + // Test signed div. + errors += test_op("div", cast( s8) S8_MIN, cast( s8)-1, S8_MAX, s8, true, -1); + errors += test_op("div", cast(s16)S16_MIN, cast( s8)-1, S16_MAX, s16, true, -1); + errors += test_op("div", cast(s32)S32_MIN, cast(s32)-1, S32_MAX, s32, true, -1); + errors += test_op("div", cast(s64)S64_MIN, cast(s32)-1, S64_MAX, s64, true, -1); + + errors += test_op("div", cast( s8) S8_MAX, cast( s8)-2, - S8_MAX/2, s8, false, 1); + errors += test_op("div", cast(s16)S16_MAX, cast( s8)-2, -S16_MAX/2, s16, false, 1); + errors += test_op("div", cast(s32)S32_MAX, cast(s32)-2, -S32_MAX/2, s32, false, 1); + errors += test_op("div", cast(s64)S64_MAX, cast(s32)-2, -S64_MAX/2, s64, false, 1); + + errors += test_op("div", cast( s8)15, cast( s8)5, 3, s8, false, 0); + errors += test_op("div", cast( u8)15, cast(s16)7, 2, s16, false, 1); + errors += test_op("div", cast(s16)15, cast(s32)13, 1, s32, false, 2); + errors += test_op("div", cast(u16)100, cast(s64)3, 33, s64, false, 1); + + // Test unsigned div. + errors += test_op("div", cast( u8) U8_MAX, cast( u8)2, U8_MAX/2, u8, false, 1); + errors += test_op("div", cast(u16)U16_MAX, cast( u8)2, U16_MAX/2, u16, false, 1); + errors += test_op("div", cast(u32)U32_MAX, cast(u32)2, U32_MAX/2, u32, false, 1); + errors += test_op("div", cast(u64)U64_MAX, cast(u32)2, U64_MAX/2, u64, false, 1); + + if errors > 0 print("# Found % %!\n", errors, ifx errors == 1 then "error" else "errors"); else print(" No errors found.\n"); + + write_strings( + "#=======================#\n", + "# Benchmarks #\n" + ); + + #import "Random"; + + performance_test :: ($operation: string, $type: Type, print_result: bool = true) -> ops_per_us_gen: float, ops_per_us_asm: float { + + #if type == u8 { MIN :: 0; MAX :: U8_MAX; } + #if type == u16 { MIN :: 0; MAX :: U16_MAX; } + #if type == u32 { MIN :: 0; MAX :: U32_MAX; } + #if type == u64 { MIN :: 0; MAX :: U64_MAX; } + #if type == s8 { MIN :: S8_MIN; MAX :: S8_MAX; } + #if type == s16 { MIN :: S16_MIN; MAX :: S16_MAX; } + #if type == s32 { MIN :: S32_MIN; MAX :: S32_MAX; } + #if type == s64 { MIN :: S64_MIN; MAX :: S64_MAX; } + + NUM_TESTS :: 50000; + DATA_SIZE_BITS :: 64*1024*8; + #if type == s8 || type == u8 then + DATA_SIZE :: DATA_SIZE_BITS/8; + else #if type == s16 || type == u16 then + DATA_SIZE :: DATA_SIZE_BITS/16; + else #if type == s32 || type == u32 then + DATA_SIZE :: DATA_SIZE_BITS/32; + else #if type == s64 || type == u64 then + DATA_SIZE :: DATA_SIZE_BITS/64; + + best_gen := 0.0; + best_asm := 0.0; + numbers_x: [..] type; + numbers_y: [..] type; + array_reserve(*numbers_x, DATA_SIZE); + array_reserve(*numbers_y, DATA_SIZE); + + // Comment the line bellow to use the same "random" values. + random_seed(cast(u64)to_nanoseconds(current_time_monotonic())); + + for 0..DATA_SIZE-1 { + x := cast(type) random_get_within_range(xx MIN, xx MAX); + y := cast(type) random_get_within_range(xx MIN, xx MAX); + if y == 0 && operation == "div" { + y = 1; + } + array_add(*numbers_x, x); + array_add(*numbers_y, y); + } + + for 0..NUM_TESTS-1 { + + r_gen: type = 0; + r_asm: type = 0; + + time_gen := current_time_monotonic(); + for idx: 0..DATA_SIZE-1 #insert #run replace("r_gen ^= OP(numbers_x[idx], numbers_y[idx], true);", "OP", operation); + time_gen = current_time_monotonic() - time_gen; + + time_asm := current_time_monotonic(); + for idx: 0..DATA_SIZE-1 #insert #run replace("r_asm ^= OP(numbers_x[idx], numbers_y[idx]);", "OP", operation); + time_asm = current_time_monotonic() - time_asm; + + assert(r_gen == r_asm); + + perf_gen := cast(float)DATA_SIZE/cast(float)to_nanoseconds(time_gen); + perf_asm := cast(float)DATA_SIZE/cast(float)to_nanoseconds(time_asm); + best_gen = max(best_gen, perf_gen); + best_asm = max(best_asm, perf_asm); + } + + tmp_context := context; + push_context tmp_context { + ff := *context.print_style.default_format_float; + ff.zero_removal = .NO; + ff.width = 7; + ff.trailing_width = 2; + + fi := *context.print_style.default_format_int; + fi.minimum_digits = 3; + + if print_result { + if type == s8 || type == u8 write_string(" "); + print("% | % | % | %\n", type, best_gen, best_asm, cast(int)(100*best_asm/best_gen)); + } + } + return best_gen, best_asm; + } + + write_strings( + " | (ops / nsec) |\n", + " T | generic | x64 asm | %\n" + ); + + write_strings( + "--- | ----------------- | ---\n", + " | add |\n" + ); + performance_test("add", u8); + performance_test("add", u16); + performance_test("add", u32); + performance_test("add", u64); + performance_test("add", s8); + performance_test("add", s16); + performance_test("add", s32); + performance_test("add", s64); + + write_strings( + "--- | ----------------- | ---\n", + " | sub |\n" + ); + performance_test("sub", u8); + performance_test("sub", u16); + performance_test("sub", u32); + performance_test("sub", u64); + performance_test("sub", s8); + performance_test("sub", s16); + performance_test("sub", s32); + performance_test("sub", s64); + + write_strings( + "--- | ----------------- | ---\n", + " | mul |\n" + ); + performance_test("mul", u8); + performance_test("mul", u16); + performance_test("mul", u32); + performance_test("mul", u64); + performance_test("mul", s8); + performance_test("mul", s16); + performance_test("mul", s32); + performance_test("mul", s64); + + write_strings( + "--- | ----------------- | ---\n", + " | div |\n" + ); + performance_test("div", u8); + performance_test("div", u16); + performance_test("div", u32); + performance_test("div", u64); + performance_test("div", s8); + performance_test("div", s16); + performance_test("div", s32); + performance_test("div", s64); +} diff --git a/modules/TUI/key_map.jai b/modules/TUI/key_map.jai index 7a074b2..f0754a7 100644 --- a/modules/TUI/key_map.jai +++ b/modules/TUI/key_map.jai @@ -8,10 +8,11 @@ setup_key_map :: () { /* This table was created/tested using the following terminals: - - g: gnome (terminal) + - g: gnome terminal - i: kitty - k: konsole - - l: linux (console) + - l: linux console + - w: windows terminal - x: xterm To signal modifier keys, a letter is appended after a + (plus sign): @@ -33,16 +34,16 @@ setup_key_map :: () { "#f1+Z" -> F1 + Shift + Alt + Ctrl + Super */ - // Up // g i k l x - table_set(*key_map, "\e[A", to_key("#up")); // + + + + + + // Up // g i k l w x + table_set(*key_map, "\e[A", to_key("#up")); // + + + + + + table_set(*key_map, "\e[1;1A", to_key("#up")); // - table_set(*key_map, "\e[1;2A", to_key("#up+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[1;8A", to_key("#up+W")); // + + + + + table_set(*key_map, "\e[1;2A", to_key("#up+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[1;8A", to_key("#up+W")); // + + + + + table_set(*key_map, "\e[1;9A", to_key("#up+s")); // + table_set(*key_map, "\e[1;10A", to_key("#up+S")); // + table_set(*key_map, "\e[1;11A", to_key("#up+x")); // + @@ -52,16 +53,16 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15A", to_key("#up+z")); // + table_set(*key_map, "\e[1;16A", to_key("#up+Z")); // + - // Down // g i k l x - table_set(*key_map, "\e[B", to_key("#down")); // + + + + + + // Down // g i k l w x + table_set(*key_map, "\e[B", to_key("#down")); // + + + + + + table_set(*key_map, "\e[1;1B", to_key("#down")); // - table_set(*key_map, "\e[1;2B", to_key("#down+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[1;8B", to_key("#down+W")); // + + + + + table_set(*key_map, "\e[1;2B", to_key("#down+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[1;8B", to_key("#down+W")); // + + + + + table_set(*key_map, "\e[1;9B", to_key("#down+s")); // + table_set(*key_map, "\e[1;10B", to_key("#down+S")); // + table_set(*key_map, "\e[1;11B", to_key("#down+x")); // + @@ -71,16 +72,16 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15B", to_key("#down+z")); // + table_set(*key_map, "\e[1;16B", to_key("#down+Z")); // + - // Right // g i k l x - table_set(*key_map, "\e[C", to_key("#right")); // + + + + + + // Right // g i k l w x + table_set(*key_map, "\e[C", to_key("#right")); // + + + + + + table_set(*key_map, "\e[1;1C", to_key("#right")); // - table_set(*key_map, "\e[1;2C", to_key("#right+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[1;8C", to_key("#right+W")); // + + + + + table_set(*key_map, "\e[1;2C", to_key("#right+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[1;8C", to_key("#right+W")); // + + + + + table_set(*key_map, "\e[1;9C", to_key("#right+s")); // + table_set(*key_map, "\e[1;10C", to_key("#right+S")); // + table_set(*key_map, "\e[1;11C", to_key("#right+x")); // + @@ -90,16 +91,16 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15C", to_key("#right+z")); // + table_set(*key_map, "\e[1;16C", to_key("#right+Z")); // + - // Left // g i k l x - table_set(*key_map, "\e[D", to_key("#left")); // + + + + + + // Left // g i k l w x + table_set(*key_map, "\e[D", to_key("#left")); // + + + + + + table_set(*key_map, "\e[1;1D", to_key("#left")); // - table_set(*key_map, "\e[1;2D", to_key("#left+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[1;8D", to_key("#left+W")); // + + + + + table_set(*key_map, "\e[1;2D", to_key("#left+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[1;8D", to_key("#left+W")); // + + + + + table_set(*key_map, "\e[1;9D", to_key("#left+s")); // + table_set(*key_map, "\e[1;10D", to_key("#left+S")); // + table_set(*key_map, "\e[1;11D", to_key("#left+x")); // + @@ -109,17 +110,17 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15D", to_key("#left+z")); // + table_set(*key_map, "\e[1;16D", to_key("#left+Z")); // + - // Home // g i k l x + // Home // g i k l w x table_set(*key_map, "\e[1~", to_key("#home")); // + - table_set(*key_map, "\e[H", to_key("#home")); // + + + + + table_set(*key_map, "\e[H", to_key("#home")); // + + + + + table_set(*key_map, "\e[1;1H", to_key("#home")); // - table_set(*key_map, "\e[1;2H", to_key("#home+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[1;8H", to_key("#home+W")); // + + + + + table_set(*key_map, "\e[1;2H", to_key("#home+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[1;8H", to_key("#home+W")); // + + + + + table_set(*key_map, "\e[1;9H", to_key("#home+s")); // + table_set(*key_map, "\e[1;10H", to_key("#home+S")); // + table_set(*key_map, "\e[1;11H", to_key("#home+x")); // + @@ -129,17 +130,17 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15H", to_key("#home+z")); // + table_set(*key_map, "\e[1;16H", to_key("#home+Z")); // + - // End // g i k l x + // End // g i k l w x table_set(*key_map, "\e[4~", to_key("#end")); // + - table_set(*key_map, "\e[F", to_key("#end")); // + + + + + table_set(*key_map, "\e[F", to_key("#end")); // + + + + + table_set(*key_map, "\e[1;1F", to_key("#end")); // - table_set(*key_map, "\e[1;2F", to_key("#end+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[1;8F", to_key("#end+W")); // + + + + + table_set(*key_map, "\e[1;2F", to_key("#end+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[1;8F", to_key("#end+W")); // + + + + + table_set(*key_map, "\e[1;9F", to_key("#end+s")); // + table_set(*key_map, "\e[1;10F", to_key("#end+S")); // + table_set(*key_map, "\e[1;11F", to_key("#end+x")); // + @@ -149,16 +150,16 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15F", to_key("#end+z")); // + table_set(*key_map, "\e[1;16F", to_key("#end+Z")); // + - // Insert // g i k l x - table_set(*key_map, "\e[2~", to_key("#ins")); // + + + + + + // Insert // g i k l w x + table_set(*key_map, "\e[2~", to_key("#ins")); // + + + + + + table_set(*key_map, "\e[2;1~", to_key("#ins")); // - table_set(*key_map, "\e[2;2~", to_key("#ins+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[2;8~", to_key("#ins+W")); // + + + + + table_set(*key_map, "\e[2;2~", to_key("#ins+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[2;8~", to_key("#ins+W")); // + + + + + table_set(*key_map, "\e[2;9~", to_key("#ins+s")); // + table_set(*key_map, "\e[2;10~", to_key("#ins+S")); // + table_set(*key_map, "\e[2;11~", to_key("#ins+x")); // + @@ -168,16 +169,16 @@ setup_key_map :: () { table_set(*key_map, "\e[2;15~", to_key("#ins+z")); // + table_set(*key_map, "\e[2;16~", to_key("#ins+Z")); // + - // Delete // g i k l x - table_set(*key_map, "\e[3~", to_key("#del")); // + + + + + + // Delete // g i k l w x + table_set(*key_map, "\e[3~", to_key("#del")); // + + + + + + table_set(*key_map, "\e[3;1~", to_key("#del")); // - table_set(*key_map, "\e[3;2~", to_key("#del+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[3;8~", to_key("#del+W")); // + + + + + table_set(*key_map, "\e[3;2~", to_key("#del+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[3;8~", to_key("#del+W")); // + + + + + table_set(*key_map, "\e[3;9~", to_key("#del+s")); // + table_set(*key_map, "\e[3;10~", to_key("#del+S")); // + table_set(*key_map, "\e[3;11~", to_key("#del+x")); // + @@ -187,16 +188,16 @@ setup_key_map :: () { table_set(*key_map, "\e[3;15~", to_key("#del+z")); // + table_set(*key_map, "\e[3;16~", to_key("#del+Z")); // + - // Page Up // g i k l x - table_set(*key_map, "\e[5~", to_key("#pup")); // + + + + + + // Page Up // g i k l w x + table_set(*key_map, "\e[5~", to_key("#pup")); // + + + + + + table_set(*key_map, "\e[5;1~", to_key("#pup")); // - table_set(*key_map, "\e[5;2~", to_key("#pup+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[5;8~", to_key("#pup+W")); // + + + + + table_set(*key_map, "\e[5;2~", to_key("#pup+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[5;8~", to_key("#pup+W")); // + + + + + table_set(*key_map, "\e[5;9~", to_key("#pup+s")); // + table_set(*key_map, "\e[5;10~", to_key("#pup+S")); // + table_set(*key_map, "\e[5;11~", to_key("#pup+x")); // + @@ -206,16 +207,16 @@ setup_key_map :: () { table_set(*key_map, "\e[5;15~", to_key("#pup+z")); // + table_set(*key_map, "\e[5;16~", to_key("#pup+Z")); // + - // Page Down // g i k l x - table_set(*key_map, "\e[6~", to_key("#pdown")); // + + + + + + // Page Down // g i k l w x + table_set(*key_map, "\e[6~", to_key("#pdown")); // + + + + + + table_set(*key_map, "\e[6;1~", to_key("#pdown")); // - table_set(*key_map, "\e[6;2~", to_key("#pdown+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[6;8~", to_key("#pdown+W")); // + + + + + table_set(*key_map, "\e[6;2~", to_key("#pdown+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[6;8~", to_key("#pdown+W")); // + + + + + table_set(*key_map, "\e[6;9~", to_key("#pdown+s")); // + table_set(*key_map, "\e[6;10~", to_key("#pdown+S")); // + table_set(*key_map, "\e[6;11~", to_key("#pdown+x")); // + @@ -225,10 +226,10 @@ setup_key_map :: () { table_set(*key_map, "\e[6;15~", to_key("#pdown+z")); // + table_set(*key_map, "\e[6;16~", to_key("#pdown+Z")); // + - // F1 // g i k l x + // F1 // g i k l w x table_set(*key_map, "\e[[A", to_key("#f1")); // + table_set(*key_map, "\e[25~", to_key("#f1+$")); // + - table_set(*key_map, "\eOP", to_key("#f1")); // + + + + + table_set(*key_map, "\eOP", to_key("#f1")); // + + + + + table_set(*key_map, "\eO1P", to_key("#f1+s")); // + table_set(*key_map, "\eO2P", to_key("#f1+$")); // + table_set(*key_map, "\eO3P", to_key("#f1+a")); // + @@ -239,13 +240,13 @@ setup_key_map :: () { table_set(*key_map, "\eO8P", to_key("#f1+W")); // + table_set(*key_map, "\e[1P", to_key("#f1")); // table_set(*key_map, "\e[1;1P", to_key("#f1")); // - table_set(*key_map, "\e[1;2P", to_key("#f1+$")); // + + + - 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+w")); // + + + - table_set(*key_map, "\e[1;8P", to_key("#f1+W")); // + + + + table_set(*key_map, "\e[1;2P", to_key("#f1+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8P", to_key("#f1+W")); // + + + + table_set(*key_map, "\e[1;9P", to_key("#f1+s")); // + table_set(*key_map, "\e[1;10P", to_key("#f1+S")); // + table_set(*key_map, "\e[1;11P", to_key("#f1+x")); // + @@ -255,10 +256,10 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15P", to_key("#f1+z")); // + table_set(*key_map, "\e[1;16P", to_key("#f1+Z")); // + - // F2 // g i k l x + // F2 // g i k l w x table_set(*key_map, "\e[[B", to_key("#f2")); // + table_set(*key_map, "\e[26~", to_key("#f2+$")); // + - table_set(*key_map, "\eOQ", to_key("#f2")); // + + + + + table_set(*key_map, "\eOQ", to_key("#f2")); // + + + + + table_set(*key_map, "\eO1Q", to_key("#f2+s")); // + table_set(*key_map, "\eO2Q", to_key("#f2+$")); // + table_set(*key_map, "\eO3Q", to_key("#f2+a")); // + @@ -269,13 +270,13 @@ setup_key_map :: () { table_set(*key_map, "\eO8Q", to_key("#f2+W")); // + table_set(*key_map, "\e[1Q", to_key("#f2")); // table_set(*key_map, "\e[1;1Q", to_key("#f2")); // - table_set(*key_map, "\e[1;2Q", to_key("#f2+$")); // + + + - 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+w")); // + + + - table_set(*key_map, "\e[1;8Q", to_key("#f2+W")); // + + + + table_set(*key_map, "\e[1;2Q", to_key("#f2+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8Q", to_key("#f2+W")); // + + + + table_set(*key_map, "\e[1;9Q", to_key("#f2+s")); // + table_set(*key_map, "\e[1;10Q", to_key("#f2+S")); // + table_set(*key_map, "\e[1;11Q", to_key("#f2+x")); // + @@ -285,10 +286,10 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15Q", to_key("#f2+z")); // + table_set(*key_map, "\e[1;16Q", to_key("#f2+Z")); // + - // F3 // g i k l x + // F3 // g i k l w x table_set(*key_map, "\e[[C", to_key("#f3")); // + table_set(*key_map, "\e[28~", to_key("#f3+$")); // + - table_set(*key_map, "\eOR", to_key("#f3")); // + + + + + table_set(*key_map, "\eOR", to_key("#f3")); // + + + + + table_set(*key_map, "\eO1R", to_key("#f3+s")); // + table_set(*key_map, "\eO2R", to_key("#f3+$")); // + table_set(*key_map, "\eO3R", to_key("#f3+a")); // + @@ -299,13 +300,13 @@ setup_key_map :: () { table_set(*key_map, "\eO8R", to_key("#f3+W")); // + table_set(*key_map, "\e[1R", to_key("#f3")); // table_set(*key_map, "\e[1;1R", to_key("#f3")); // - table_set(*key_map, "\e[1;2R", to_key("#f3+$")); // + + + - 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+w")); // + + + - table_set(*key_map, "\e[1;8R", to_key("#f3+W")); // + + + + table_set(*key_map, "\e[1;2R", to_key("#f3+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8R", to_key("#f3+W")); // + + + + table_set(*key_map, "\e[1;9R", to_key("#f3+s")); // + table_set(*key_map, "\e[1;10R", to_key("#f3+S")); // + table_set(*key_map, "\e[1;11R", to_key("#f3+x")); // + @@ -315,10 +316,10 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15R", to_key("#f3+z")); // + table_set(*key_map, "\e[1;16R", to_key("#f3+Z")); // + - // F4 // g i k l x + // F4 // g i k l w x table_set(*key_map, "\e[[D", to_key("#f4")); // + table_set(*key_map, "\e[29~", to_key("#f4+$")); // + - table_set(*key_map, "\eOS", to_key("#f4")); // + + + + + table_set(*key_map, "\eOS", to_key("#f4")); // + + + + + table_set(*key_map, "\eO1S", to_key("#f4+s")); // + table_set(*key_map, "\eO2S", to_key("#f4+$")); // + table_set(*key_map, "\eO3S", to_key("#f4+a")); // + @@ -329,13 +330,13 @@ setup_key_map :: () { table_set(*key_map, "\eO8S", to_key("#f4+W")); // + table_set(*key_map, "\e[1S", to_key("#f4")); // table_set(*key_map, "\e[1;1S", to_key("#f4")); // - table_set(*key_map, "\e[1;2S", to_key("#f4+$")); // + + + - 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+w")); // + + + - table_set(*key_map, "\e[1;8S", to_key("#f4+W")); // + + + + table_set(*key_map, "\e[1;2S", to_key("#f4+$")); // + + + + + 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+w")); // + + + + + table_set(*key_map, "\e[1;8S", to_key("#f4+W")); // + + + + table_set(*key_map, "\e[1;9S", to_key("#f4+s")); // + table_set(*key_map, "\e[1;10S", to_key("#f4+S")); // + table_set(*key_map, "\e[1;11S", to_key("#f4+x")); // + @@ -345,18 +346,18 @@ setup_key_map :: () { table_set(*key_map, "\e[1;15S", to_key("#f4+z")); // + table_set(*key_map, "\e[1;16S", to_key("#f4+Z")); // + - // F5 // g i k l x + // F5 // g i k l w x table_set(*key_map, "\e[[E", to_key("#f5")); // + table_set(*key_map, "\e[31~", to_key("#f5+$")); // + - table_set(*key_map, "\e[15~", to_key("#f5")); // + + + + + table_set(*key_map, "\e[15~", to_key("#f5")); // + + + + + table_set(*key_map, "\e[15;1~", to_key("#f5")); // - table_set(*key_map, "\e[15;2~", to_key("#f5+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[15;8~", to_key("#f5+W")); // + + + + + table_set(*key_map, "\e[15;2~", to_key("#f5+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[15;8~", to_key("#f5+W")); // + + + + + table_set(*key_map, "\e[15;9~", to_key("#f5+s")); // + table_set(*key_map, "\e[15;10~",to_key("#f5+S")); // + table_set(*key_map, "\e[15;11~",to_key("#f5+x")); // + @@ -366,17 +367,17 @@ setup_key_map :: () { table_set(*key_map, "\e[15;15~",to_key("#f5+z")); // + table_set(*key_map, "\e[15;16~",to_key("#f5+Z")); // + - // F6 // g i k l x + // F6 // g i k l w x table_set(*key_map, "\e[32~", to_key("#f6+$")); // + - table_set(*key_map, "\e[17~", to_key("#f6")); // + + + + + + table_set(*key_map, "\e[17~", to_key("#f6")); // + + + + + + table_set(*key_map, "\e[17;1~", to_key("#f6")); // - table_set(*key_map, "\e[17;2~", to_key("#f6+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[17;8~", to_key("#f6+W")); // + + + + + table_set(*key_map, "\e[17;2~", to_key("#f6+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[17;8~", to_key("#f6+W")); // + + + + + table_set(*key_map, "\e[17;9~", to_key("#f6+s")); // + table_set(*key_map, "\e[17;10~",to_key("#f6+S")); // + table_set(*key_map, "\e[17;11~",to_key("#f6+x")); // + @@ -386,17 +387,17 @@ setup_key_map :: () { table_set(*key_map, "\e[17;15~",to_key("#f6+z")); // + table_set(*key_map, "\e[17;16~",to_key("#f6+Z")); // + - // F7 // g i k l x + // F7 // g i k l w x table_set(*key_map, "\e[33~", to_key("#f7+$")); // + - table_set(*key_map, "\e[18~", to_key("#f7")); // + + + + + + table_set(*key_map, "\e[18~", to_key("#f7")); // + + + + + + table_set(*key_map, "\e[18;1~", to_key("#f7")); // - table_set(*key_map, "\e[18;2~", to_key("#f7+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[18;8~", to_key("#f7+W")); // + + + + + table_set(*key_map, "\e[18;2~", to_key("#f7+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[18;8~", to_key("#f7+W")); // + + + + + table_set(*key_map, "\e[18;9~", to_key("#f7+s")); // + table_set(*key_map, "\e[18;10~",to_key("#f7+S")); // + table_set(*key_map, "\e[18;11~",to_key("#f7+x")); // + @@ -406,17 +407,17 @@ setup_key_map :: () { table_set(*key_map, "\e[18;15~",to_key("#f7+z")); // + table_set(*key_map, "\e[18;16~",to_key("#f7+Z")); // + - // F8 // g i k l x + // F8 // g i k l w x table_set(*key_map, "\e[34~", to_key("#f8+$")); // + - table_set(*key_map, "\e[19~", to_key("#f8")); // + + + + + + table_set(*key_map, "\e[19~", to_key("#f8")); // + + + + + + table_set(*key_map, "\e[19;1~", to_key("#f8")); // - table_set(*key_map, "\e[19;2~", to_key("#f8+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[19;8~", to_key("#f8+W")); // + + + + + table_set(*key_map, "\e[19;2~", to_key("#f8+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[19;8~", to_key("#f8+W")); // + + + + + table_set(*key_map, "\e[19;9~", to_key("#f8+s")); // + table_set(*key_map, "\e[19;10~",to_key("#f8+S")); // + table_set(*key_map, "\e[19;11~",to_key("#f8+x")); // + @@ -426,16 +427,16 @@ setup_key_map :: () { table_set(*key_map, "\e[19;15~",to_key("#f8+z")); // + table_set(*key_map, "\e[19;16~",to_key("#f8+Z")); // + - // F9 // g i k l x - table_set(*key_map, "\e[20~", to_key("#f9")); // + + + + + + // F9 // g i k l w x + table_set(*key_map, "\e[20~", to_key("#f9")); // + + + + + + table_set(*key_map, "\e[20;1~", to_key("#f9")); // - table_set(*key_map, "\e[20;2~", to_key("#f9+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[20;8~", to_key("#f9+W")); // + + + + + table_set(*key_map, "\e[20;2~", to_key("#f9+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[20;8~", to_key("#f9+W")); // + + + + + table_set(*key_map, "\e[20;9~", to_key("#f9+s")); // + table_set(*key_map, "\e[20;10~",to_key("#f9+S")); // + table_set(*key_map, "\e[20;11~",to_key("#f9+x")); // + @@ -445,16 +446,16 @@ setup_key_map :: () { table_set(*key_map, "\e[20;15~",to_key("#f9+z")); // + table_set(*key_map, "\e[20;16~",to_key("#f9+Z")); // + - // F10 // g i k l x - table_set(*key_map, "\e[21~", to_key("#f10")); // + + + + + + // F10 // g i k l w x + table_set(*key_map, "\e[21~", to_key("#f10")); // + + + + + + table_set(*key_map, "\e[21;1~", to_key("#f10")); // - table_set(*key_map, "\e[21;2~", to_key("#f10+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[21;8~", to_key("#f10+W")); // + + + + + table_set(*key_map, "\e[21;2~", to_key("#f10+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[21;8~", to_key("#f10+W")); // + + + + + table_set(*key_map, "\e[21;9~", to_key("#f10+s")); // + table_set(*key_map, "\e[21;10~",to_key("#f10+S")); // + table_set(*key_map, "\e[21;11~",to_key("#f10+x")); // + @@ -464,16 +465,16 @@ setup_key_map :: () { table_set(*key_map, "\e[21;15~",to_key("#f10+z")); // + table_set(*key_map, "\e[21;16~",to_key("#f10+Z")); // + - // F11 // g i k l x - table_set(*key_map, "\e[23~", to_key("#f11")); // + + + + + + // F11 // g i k l w x + table_set(*key_map, "\e[23~", to_key("#f11")); // + + + + + + table_set(*key_map, "\e[23;1~", to_key("#f11")); // - table_set(*key_map, "\e[23;2~", to_key("#f11+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[23;8~", to_key("#f11+W")); // + + + + + table_set(*key_map, "\e[23;2~", to_key("#f11+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[23;8~", to_key("#f11+W")); // + + + + + table_set(*key_map, "\e[23;9~", to_key("#f11+s")); // + table_set(*key_map, "\e[23;10~",to_key("#f11+S")); // + table_set(*key_map, "\e[23;11~",to_key("#f11+x")); // + @@ -483,16 +484,16 @@ setup_key_map :: () { table_set(*key_map, "\e[23;15~",to_key("#f11+z")); // + table_set(*key_map, "\e[23;16~",to_key("#f11+Z")); // + - // F12 // g i k l x - table_set(*key_map, "\e[24~", to_key("#f12")); // + + + + + + // F12 // g i k l w x + table_set(*key_map, "\e[24~", to_key("#f12")); // + + + + + + table_set(*key_map, "\e[24;1~", to_key("#f12")); // - table_set(*key_map, "\e[24;2~", to_key("#f12+$")); // + + + + - 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+w")); // + + + + - table_set(*key_map, "\e[24;8~", to_key("#f12+W")); // + + + + + table_set(*key_map, "\e[24;2~", to_key("#f12+$")); // + + + + + + 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+w")); // + + + + + + table_set(*key_map, "\e[24;8~", to_key("#f12+W")); // + + + + + table_set(*key_map, "\e[24;9~", to_key("#f12+s")); // + table_set(*key_map, "\e[24;10~",to_key("#f12+S")); // + table_set(*key_map, "\e[24;11~",to_key("#f12+x")); // + diff --git a/modules/TUI/tests.jai b/modules/TUI/tests.jai index fe213cb..a740e6b 100644 --- a/modules/TUI/tests.jai +++ b/modules/TUI/tests.jai @@ -31,21 +31,6 @@ main :: () { 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(); @@ -121,7 +106,7 @@ main :: () { 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"; + key: TUI.Key = TUI.Keys.None; last_none_char := "X"; width, height := TUI.get_terminal_size(); @@ -135,7 +120,7 @@ main :: () { 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)"); + write_strings(last_none_char, " (press: q to exit, c to clear, and any other key to print it's value)"); } case TUI.Keys.Resize; #through; diff --git a/modules/UTF8.jai b/modules/UTF8.jai deleted file mode 100644 index 72d3d75..0000000 --- a/modules/UTF8.jai +++ /dev/null @@ -1,128 +0,0 @@ -// Some procedures to help working with UTF8 strings. -// https://en.wikipedia.org/wiki/UTF-8 - -// Returns true if argument is a continuation byte. -is_continuation_byte :: inline (byte: u8) -> bool { - // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte - return (byte & 0xC0) == 0x80; -} - -// Given a leading_byte, returns the number of bytes on the character. -count_character_bytes :: inline (leading_byte: u8) -> int { - // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte - if (leading_byte & 0xE0) == 0xC0 return 1+1; - - // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte - if (leading_byte & 0xF0) == 0xE0 return 1+2; - - // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte - if (leading_byte & 0xF8) == 0xF0 return 1+3; - - return 1; -} - -// Returns the string (using same str.data) truncated to the provided length. -truncate :: (str: string, length: int) -> string { - - if str.data == null then return ""; - - if str.count < length then return .{str.count, str.data}; - - data := str.data; - count := str.count; - - // Find index of first continuation byte. - idx := length; - while (idx > 0 && is_continuation_byte(data[idx - 1])) { - idx -= 1; - } - continuation_bytes := length - idx; - - // If string starts with continuation bytes, it's an invalid UTF8 string. - if (idx == 0 && continuation_bytes > 0) { - length = 0; - } - // If length truncates some continuation bytes, remove incomplete UTF8 character. - else if (idx > 0 // string is not empty - // continuation bytes are not complete - && !(continuation_bytes == 0 && (data[idx - 1] & 0x80) == 0x00) - && !(continuation_bytes == 1 && (data[idx - 1] & 0xE0) == 0xC0) - && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) - && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) - ) { - length -= (continuation_bytes + 1); // Remove start byte, hence '+ 1'. - } - - return .{length, str.data}; -} - -// Returns true when the string is empty or consists of space characters. -is_empty :: (str: string) -> bool { - for 0..str.count-1 { - if str[it] == { - case #char "\0"; #through; - case #char "\t"; #through; // horizontal tab - case #char "\n"; #through; // line feed - case #char "\x0B"; #through; // vertical tabulation - case #char "\x0C"; #through; // form feed - case #char "\r"; #through; // carriage return - case #char " "; - continue; - case; - return false; - } - } - return true; -} - -// Counts the number of characters. -count_characters :: (str: string, $is_null_terminated := false) -> int { - characters := 0; - idx := 0; - - #if is_null_terminated { - while idx < str.count && str[idx] != 0 { - idx += count_character_bytes(str[idx]); - characters += 1; - } - } - else { - while idx < str.count { - idx += count_character_bytes(str[idx]); - characters += 1; - } - } - - return characters; -} - -// Deletes character by it's index, and moves tail data to take its place. -delete_character :: (str: *string, character_idx: int) -> success := true { - buffer_idx := get_byte_index(str.*, character_idx); - - if buffer_idx < 0 return false; - - bytes_to_delete := count_character_bytes(str.data[buffer_idx]); - - for buffer_idx..str.count-1-bytes_to_delete { - str.data[it] = str.data[it+bytes_to_delete]; - } - for str.count-bytes_to_delete..str.count-1 { - str.data[it] = 0; - } - - str.count -= bytes_to_delete; - return; -} - -// Searches for the given character index and returns its byte index on the string. -get_byte_index :: (str: string, character_index: int) -> buffer_index: int, success := true { - buff_idx := 0; - char_idx := 0; - while buff_idx < str.count { - if char_idx == character_index return buff_idx; - buff_idx += count_character_bytes(str[buff_idx]); - char_idx += 1; - } - return -1, false; -} diff --git a/modules/UTF8/module.jai b/modules/UTF8/module.jai new file mode 100644 index 0000000..72d3d75 --- /dev/null +++ b/modules/UTF8/module.jai @@ -0,0 +1,128 @@ +// Some procedures to help working with UTF8 strings. +// https://en.wikipedia.org/wiki/UTF-8 + +// Returns true if argument is a continuation byte. +is_continuation_byte :: inline (byte: u8) -> bool { + // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte + return (byte & 0xC0) == 0x80; +} + +// Given a leading_byte, returns the number of bytes on the character. +count_character_bytes :: inline (leading_byte: u8) -> int { + // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte + if (leading_byte & 0xE0) == 0xC0 return 1+1; + + // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte + if (leading_byte & 0xF0) == 0xE0 return 1+2; + + // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte + if (leading_byte & 0xF8) == 0xF0 return 1+3; + + return 1; +} + +// Returns the string (using same str.data) truncated to the provided length. +truncate :: (str: string, length: int) -> string { + + if str.data == null then return ""; + + if str.count < length then return .{str.count, str.data}; + + data := str.data; + count := str.count; + + // Find index of first continuation byte. + idx := length; + while (idx > 0 && is_continuation_byte(data[idx - 1])) { + idx -= 1; + } + continuation_bytes := length - idx; + + // If string starts with continuation bytes, it's an invalid UTF8 string. + if (idx == 0 && continuation_bytes > 0) { + length = 0; + } + // If length truncates some continuation bytes, remove incomplete UTF8 character. + else if (idx > 0 // string is not empty + // continuation bytes are not complete + && !(continuation_bytes == 0 && (data[idx - 1] & 0x80) == 0x00) + && !(continuation_bytes == 1 && (data[idx - 1] & 0xE0) == 0xC0) + && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) + && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) + ) { + length -= (continuation_bytes + 1); // Remove start byte, hence '+ 1'. + } + + return .{length, str.data}; +} + +// Returns true when the string is empty or consists of space characters. +is_empty :: (str: string) -> bool { + for 0..str.count-1 { + if str[it] == { + case #char "\0"; #through; + case #char "\t"; #through; // horizontal tab + case #char "\n"; #through; // line feed + case #char "\x0B"; #through; // vertical tabulation + case #char "\x0C"; #through; // form feed + case #char "\r"; #through; // carriage return + case #char " "; + continue; + case; + return false; + } + } + return true; +} + +// Counts the number of characters. +count_characters :: (str: string, $is_null_terminated := false) -> int { + characters := 0; + idx := 0; + + #if is_null_terminated { + while idx < str.count && str[idx] != 0 { + idx += count_character_bytes(str[idx]); + characters += 1; + } + } + else { + while idx < str.count { + idx += count_character_bytes(str[idx]); + characters += 1; + } + } + + return characters; +} + +// Deletes character by it's index, and moves tail data to take its place. +delete_character :: (str: *string, character_idx: int) -> success := true { + buffer_idx := get_byte_index(str.*, character_idx); + + if buffer_idx < 0 return false; + + bytes_to_delete := count_character_bytes(str.data[buffer_idx]); + + for buffer_idx..str.count-1-bytes_to_delete { + str.data[it] = str.data[it+bytes_to_delete]; + } + for str.count-bytes_to_delete..str.count-1 { + str.data[it] = 0; + } + + str.count -= bytes_to_delete; + return; +} + +// Searches for the given character index and returns its byte index on the string. +get_byte_index :: (str: string, character_index: int) -> buffer_index: int, success := true { + buff_idx := 0; + char_idx := 0; + while buff_idx < str.count { + if char_idx == character_index return buff_idx; + buff_idx += count_character_bytes(str[buff_idx]); + char_idx += 1; + } + return -1, false; +} diff --git a/modules/UTF8/tests.jai b/modules/UTF8/tests.jai new file mode 100644 index 0000000..aff1d36 --- /dev/null +++ b/modules/UTF8/tests.jai @@ -0,0 +1,5 @@ +#import "Basic"; + +main :: () { + assert(false, "TODO"); // TODO +} diff --git a/sizeof.c b/sizeof.c deleted file mode 100644 index c260c31..0000000 --- a/sizeof.c +++ /dev/null @@ -1,50 +0,0 @@ -// compile with : gcc sizeof.c -lncurses - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char **argv) { - // initscr(); - - fprintf(stderr, "sizeof char: %d\n", sizeof(char)); - fprintf(stderr, "sizeof short: %d\n", sizeof(short)); - fprintf(stderr, "sizeof int: %d\n", sizeof(int)); - fprintf(stderr, "sizeof long: %d\n", sizeof(long)); - fprintf(stderr, "sizeof unsigned: %d\n", sizeof(unsigned)); - fprintf(stderr, "sizeof chtype: %d\n", sizeof(chtype)); - // int w_size_x, w_size_y; - // getmaxyx(stdscr, w_size_y, w_size_x); - // char str[64]; - // memset(str, 0, 64); - // sprintf(str, "x,y : %dx%d\n", w_size_x, w_size_y); - // mvaddstr(2, 2, str); - // sprintf(str, "resize:%d\n", KEY_RESIZE); - // mvaddstr(3, 2, str); - - // unsigned m = ACS_DIAMOND; - fprintf(stderr, "sizeof ACS %d\n", sizeof(ACS_DIAMOND)); - // - // if (ACS_DIAMOND != 0 || ACS_URCORNER != 0){ - // fprintf(stderr, "BAZINGA\n"); - // } -// fprintf(stderr, ">%d<\n", strlen(ACS_DIAMOND)); - - // mvaddch(0, 0, m); - // getch(); - // endwin(); -} - diff --git a/ttt.jai b/ttt.jai index bb395fa..b0d60d0 100644 --- a/ttt.jai +++ b/ttt.jai @@ -7,7 +7,7 @@ DEBUG :: false; #import "File"; #import "File_Utilities"; #import "String"; -#import "Integer_Saturating_Arithmetic"; +#import "Saturation"; #import "UTF8"; TUI :: #import "TUI"(COLOR_MODE_BITS=4); -- cgit v1.2.3 From af1317ed93f7d5fc16baceaadb0da17aa17591be Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 18 May 2024 01:30:01 +0100 Subject: Updated modules. --- modules/Saturation/module.jai | 26 +-- modules/Saturation/tests.jai | 397 +++++++++++++++++++++++++++++++++++++++--- modules/TUI/module.jai | 17 +- modules/TUI/snake.jai | 165 ++++++++++++++++++ modules/UTF8/module.jai | 29 ++- modules/UTF8/tests.jai | 159 ++++++++++++++++- snake.jai | 165 ------------------ ttt.jai | 2 +- 8 files changed, 748 insertions(+), 212 deletions(-) create mode 100644 modules/TUI/snake.jai delete mode 100644 snake.jai (limited to 'modules') diff --git a/modules/Saturation/module.jai b/modules/Saturation/module.jai index 74643e0..50c9b3c 100644 --- a/modules/Saturation/module.jai +++ b/modules/Saturation/module.jai @@ -1,5 +1,7 @@ // Integer saturating arighmetic (with assembly branch-free procedures for x64 - expecting signed values in two's complement). +#module_parameters(PREFER_BRANCH_FREE_CODE := false); + #import "Basic"; #import "Math"; #import "String"; @@ -37,11 +39,11 @@ INTEGER_ARITHMETIC_TYPES_CHECK :: #string DONE return true; DONE -add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +add :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { + + #if !(prefer_branch_free_code && CPU == .X64) { - #if USE_GENERIC || CPU != .X64 { - #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } @@ -66,7 +68,7 @@ add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b return x + y, false; } else { - + result: Tr = ---; saturated: bool = ---; @@ -118,10 +120,10 @@ add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b } } -sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +sub :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { - #if USE_GENERIC || CPU != .X64 { + #if !(prefer_branch_free_code && CPU == .X64) { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { @@ -195,10 +197,10 @@ sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b } -mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +mul :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { - #if USE_GENERIC || CPU != .X64 { + #if !(prefer_branch_free_code && CPU == .X64) { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { @@ -286,10 +288,10 @@ mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b } } -div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +div :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { - #if USE_GENERIC || CPU != .X64 { + #if !(prefer_branch_free_code && CPU == .X64) { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { @@ -380,11 +382,11 @@ div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: T #asm { result === a; // Pin result to register A. remainder === d; // Pin remainder to register D. - + xor result, result; // Clear result. xor remainder, remainder; // Clear remainder (required when used as dividend's high bits). xor saturated, saturated; // Clear saturated (unsigned division never saturates). - mov result, x; // Copy x value to result. + mov.SIZE result, x; // Copy x value to result. DIVIDE_PLACEHOLDER } diff --git a/modules/Saturation/tests.jai b/modules/Saturation/tests.jai index 372d66d..2a82300 100644 --- a/modules/Saturation/tests.jai +++ b/modules/Saturation/tests.jai @@ -1,5 +1,7 @@ // Tests for integer saturating arighmetic procedures. +AVOID_INFINITE_FOR_LOOP :: true; + #import "Basic"; #import "Compiler"; #import "Math"; @@ -12,36 +14,41 @@ main :: () { "#=======================#\n", "# Basic tests #\n" ); + + + test_op :: ($operation: string, x: $Tx, y: $Ty, result: $Tr, type: Type, saturated: bool, remainder: Tr = 0) -> errors_found: int #expand { + + #insert #run () -> string { + // Build test call. + builder: String_Builder; + call := ifx operation == "div" + then "t_result, t_remainder, t_saturated := %(cast(Tx)x, cast(Ty)y);" + else "t_result, t_saturated := %(cast(Tx)x, cast(Ty)y);"; + + print(*builder, call, operation); - test_op :: (operation: string, x: $Tx, y: $Ty, result: $Tr, type: Type, saturated: bool, remainder: Tr = 0) -> errors_found: int #expand { - - print_test_call :: (operation: string) -> string { - str: string = ---; - if operation != "div" { - TEST_CALL :: #string DONE - t_result, t_saturated := OP(cast(Tx)x, cast(Ty)y); - if result != t_result print("%_%(%, %) = %0%0\n", operation, type, x, y, result, ifx saturated then " : saturated"); - DONE - str = replace(TEST_CALL, "OP", operation); - } else { - TEST_CALL :: #string DONE - t_result, t_remainder, t_saturated := OP(cast(Tx)x, cast(Ty)y); - if result != t_result print("%_%(%, %) = % + %0%0\n", operation, type, x, y, result, remainder, ifx saturated then " : saturated"); - DONE - str = replace(TEST_CALL, "OP", operation); - } - return str; - } - - #insert #run print_test_call(operation); + return builder_to_string(*builder); + }(); errors := 0; - if result != t_result { errors += 1; print(" > incorrect result value: got % expected %\n", t_result, result); }; - if type != type_of(t_result) { errors += 1; print(" > incorrect result type: got % expected %\n", type_of(t_result), type); }; - if saturated != t_saturated { errors += 1; print(" > incorrect saturated flag: got % expected %\n", t_saturated, saturated); }; + log: String_Builder; + if result != t_result { errors += 1; print(*log, " > incorrect result value: got % expected %\n", t_result, result); }; + if type != type_of(t_result) { errors += 1; print(*log, " > incorrect result type: got % expected %\n", type_of(t_result), type); }; + if saturated != t_saturated { errors += 1; print(*log, " > incorrect saturated flag: got % expected %\n", t_saturated, saturated); }; #if operation == "div" { - if remainder != t_remainder { errors += 1; print(" > incorrect remainder value: got % expected %\n", t_remainder, remainder); }; + if remainder != t_remainder { errors += 1; print(*log, " > incorrect remainder value: got % expected %\n", t_remainder, remainder); }; } + + if errors > 0 { + #if operation == "div" { + print("%_%(%, %) = % + %0%0\n", operation, type, x, y, result, remainder, ifx saturated then " : saturated"); + } + else { + print("%_%(%, %) = %0%0\n", operation, type, x, y, result, ifx saturated then " : saturated"); + } + write_builder(*log); + } + return errors; } @@ -157,6 +164,342 @@ main :: () { if errors > 0 print("# Found % %!\n", errors, ifx errors == 1 then "error" else "errors"); else print(" No errors found.\n"); + + // Test generic agains branch-free alternative. + write_strings( + "#=======================#\n", + "# generic == x64 asm ? #\n" + ); + + + full_test :: ($type: Type, test: (a: type, b: type)) { + #if type == { + case u8; + min :u8 = 0; + max :u8 = U8_MAX; + + case u16; + min :u16 = 0; + max :u16 = U16_MAX; + + case s8; + min :s8 = S8_MIN; + max :s8 = S8_MAX; + + case s16; + min :s16 = S16_MIN; + max :s16 = S16_MAX; + + case; + assert(false, "This will take way too long."); + } + + #if !AVOID_INFINITE_FOR_LOOP { + for a : min..max { + for b : min..max { + test(a, b); + } + } + } + else { + a :type = min; + b :type = min; + while loop_a := true { + while loop_b := true { + test(a, b); + if b == max then break loop_b; else b += 1; + } + if a == max then break loop_a; else a += 1; + } + } + } + + partial_test :: ($type: Type, test: (a: type, b: type)) { + min, max: type; + #if type == { + case u8; + min = 0; + max = U8_MAX; + + case u16; + min = 0; + max = U16_MAX; + + case u32; + min = 0; + max = U32_MAX; + + case s8; + min = S8_MIN; + max = S8_MAX; + + case s16; + min = S16_MIN; + max = S16_MAX; + + case s32; + min = S32_MIN; + max = S32_MAX; + + case; + assert(false, "This will take way too long."); + } + + #if !AVOID_INFINITE_FOR_LOOP { + for a: min..max { + b := a; + c := max - a + min; + test(a, b); + test(a, c); + } + } + else { + a := min; + while loop := true { + b := a; + c := max - a + min; + test(a, b); + test(a, c); + if a == max then break loop; else a += 1; + } + } + } + + minimal_test :: ($type: Type, test: (a: type, b: type)) { + #if type == { + case u32; + min :u32 = 0; + mid :u32 = U32_MAX / 2; + max :u32 = U32_MAX; + range :u32 = cast(u32)U16_MAX * 2048; + + case u64; + min :u64 = 0; + mid :u64 = U64_MAX / 2; + max :u64 = U64_MAX; + range :u64 = cast(u64)U16_MAX * 2048; + + case s32; + min :s32 = S32_MIN; + mid :s32 = (S32_MIN / 2) + (S32_MAX / 2); + max :s32 = S32_MAX; + range :s32 = cast(s32)S16_MAX * 2048; + + case s64; + min :s64 = S64_MIN; + mid :s64 = (S64_MIN / 2) + (S64_MAX / 2); + max :s64 = S64_MAX; + range :s64 = cast(s64)S16_MAX * 2048; + + case; + assert(false, "Invalid type % given.", type); + } + + #if !AVOID_INFINITE_FOR_LOOP { + start, end : type; + + start = min; + end = min+range; + for a: start..end { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + } + + start = mid-range; + end = mid+range; + for a: start..end { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + } + + start = max-range; + end = max; + for a: start..end { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + } + } + else { + start, end, a : type; + + start = min; + end = min + range; + a = start; + while loop := true { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + if a == end then break loop; else a += 1; + } + + start = mid - range; + end = mid + range; + a = start; + while loop := true { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + if a == end then break loop; else a += 1; + } + + start = max - range; + end = max; + a = start; + while loop := true { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + if a == end then break loop; else a += 1; + } + } + } + + + // add + + ADD_TEST_TEMPLATE :: (a: $T, b: T) { + rT, sT := add(a, b, true); + rF, sF := add(a, b, false); + assert(rT == rF && sT == sF, "> add(%1, %2, true) = %3,%4 != add(%1, %2, false) = %5,%6\n", a, b, rT, sT, rF, sF); + } + + write_string("# testing add,u8 #\n"); + full_test(u8, ADD_TEST_TEMPLATE); + + write_string("# testing add,u16 #\n"); + full_test(u16, ADD_TEST_TEMPLATE); + + write_string("# testing add,u32 #\n"); + partial_test(u32, ADD_TEST_TEMPLATE); + + write_string("# testing add,u64 #\n"); + minimal_test(u64, ADD_TEST_TEMPLATE); + + write_string("# testing add,s8 #\n"); + full_test(s8, ADD_TEST_TEMPLATE); + + write_string("# testing add,s16 #\n"); + full_test(s16, ADD_TEST_TEMPLATE); + + write_string("# testing add,s32 #\n"); + partial_test(s32, ADD_TEST_TEMPLATE); + + write_string("# testing add,s64 #\n"); + minimal_test(s64, ADD_TEST_TEMPLATE); + + + // sub + + SUB_TEST_TEMPLATE :: (a: $T, b: T) { + rT, sT := sub(a, b, true); + rF, sF := sub(a, b, false); + assert(rT == rF && sT == sF, "> sub(%1, %2, true) = %3,%4 != sub(%1, %2, false) = %5,%6\n", a, b, rT, sT, rF, sF); + } + + write_string("# testing sub,u8 #\n"); + full_test(u8, SUB_TEST_TEMPLATE); + + write_string("# testing sub,u16 #\n"); + full_test(u16, SUB_TEST_TEMPLATE); + + write_string("# testing sub,u32 #\n"); + partial_test(u32, SUB_TEST_TEMPLATE); + + write_string("# testing sub,u64 #\n"); + minimal_test(u64, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s8 #\n"); + full_test(s8, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s16 #\n"); + full_test(s16, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s32 #\n"); + partial_test(s32, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s64 #\n"); + minimal_test(s64, SUB_TEST_TEMPLATE); + + + // mul + + MUL_TEST_TEMPLATE :: (a: $T, b: T) { + rT, sT := mul(a, b, true); + rF, sF := mul(a, b, false); + assert(rT == rF && sT == sF, "> mul(%1, %2, true) = %3,%4 != mul(%1, %2, false) = %5,%6\n", a, b, rT, sT, rF, sF); + } + + write_string("# testing mul,u8 #\n"); + full_test(u8, MUL_TEST_TEMPLATE); + + write_string("# testing mul,u16 #\n"); + full_test(u16, MUL_TEST_TEMPLATE); + + write_string("# testing mul,u32 #\n"); + partial_test(u32, MUL_TEST_TEMPLATE); + + write_string("# testing mul,u64 #\n"); + minimal_test(u64, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s8 #\n"); + full_test(s8, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s16 #\n"); + full_test(s16, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s32 #\n"); + partial_test(s32, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s64 #\n"); + minimal_test(s64, MUL_TEST_TEMPLATE); + + + // div + + DIV_TEST_TEMPLATE :: (a: $T, b: T) { + if b == 0 then return; + rT, remT, sT := div(a, b, true); + rF, remF, sF := div(a, b, false); + assert(rT == rF && sT == sF, "> mul(%1, %2, true) = %3,%4,%5 != mul(%1, %2, false) = %6,%7,%8\n", a, b, rT, remT, sT, rF, remF, sF); + } + + write_string("# testing div,u8 #\n"); + full_test(u8, DIV_TEST_TEMPLATE); + + write_string("# testing div,u16 #\n"); + full_test(u16, DIV_TEST_TEMPLATE); + + write_string("# testing div,u32 #\n"); + partial_test(u32, DIV_TEST_TEMPLATE); + + write_string("# testing div,u64 #\n"); + minimal_test(u64, DIV_TEST_TEMPLATE); + + write_string("# testing div,s8 #\n"); + full_test(s8, DIV_TEST_TEMPLATE); + + write_string("# testing div,s16 #\n"); + full_test(s16, DIV_TEST_TEMPLATE); + + write_string("# testing div,s32 #\n"); + partial_test(s32, DIV_TEST_TEMPLATE); + + write_string("# testing div,s64 #\n"); + minimal_test(s64, DIV_TEST_TEMPLATE); + + + write_string(" No errors found.\n"); + + write_strings( "#=======================#\n", "# Benchmarks #\n" @@ -212,11 +555,11 @@ main :: () { r_asm: type = 0; time_gen := current_time_monotonic(); - for idx: 0..DATA_SIZE-1 #insert #run replace("r_gen ^= OP(numbers_x[idx], numbers_y[idx], true);", "OP", operation); + for idx: 0..DATA_SIZE-1 #insert #run replace("r_gen ^= OP(numbers_x[idx], numbers_y[idx], false);", "OP", operation); time_gen = current_time_monotonic() - time_gen; time_asm := current_time_monotonic(); - for idx: 0..DATA_SIZE-1 #insert #run replace("r_asm ^= OP(numbers_x[idx], numbers_y[idx]);", "OP", operation); + for idx: 0..DATA_SIZE-1 #insert #run replace("r_asm ^= OP(numbers_x[idx], numbers_y[idx], true);", "OP", operation); time_asm = current_time_monotonic() - time_asm; assert(r_gen == r_asm); diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index fb742da..d0373f9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,3 +1,16 @@ +/* + A simple terminal user interface module that provides basic functionalities similar to the [ncurses library](https://en.wikipedia.org/wiki/Ncurses). + Usefull for creating simple terminal-based apps that require user input. + View `snake.jai` for an example. + It has been tested on the following terminal emulators: + - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) + - [kitty](https://en.wikipedia.org/wiki/Kitty_(terminal_emulator)) + - [Konsole](https://en.wikipedia.org/wiki/Konsole) + - [Linux console](https://en.wikipedia.org/wiki/Linux_console) + - [xterm](https://en.wikipedia.org/wiki/Xterm) + - [Windows Terminal](https://en.wikipedia.org/wiki/Windows_Terminal) +*/ + #module_parameters(COLOR_MODE_BITS := 24); @@ -32,11 +45,11 @@ #import "UTF8"; #load "key_map.jai"; -#add_context tui_style : Style; // This contains the last style applied by the module. +#add_context tui_style : Style; // This contains the last style applied by the module. #add_context tui_output_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. KEY_SIZE :: #run type_info(Key).runtime_size; -#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. active := false; input_override : Key; diff --git a/modules/TUI/snake.jai b/modules/TUI/snake.jai new file mode 100644 index 0000000..b35c0fb --- /dev/null +++ b/modules/TUI/snake.jai @@ -0,0 +1,165 @@ +#import "Basic"; +#import "Random"; +TUI :: #import "TUI"(COLOR_MODE_BITS = 8); + +Vec2D :: struct { + x: int; + y: int; +} + +operator == :: (a: Vec2D, b: Vec2D) -> bool { + return a.x == b.x && a.y == b.y; +} + +screen_size_x: int = ---; +screen_size_y: int = ---; +player_name: string = ---; + +main :: () { + + game_loop :: () { + + LOOP_PERIOD_MS :: 30; + + score := 0; + dir := Vec2D.{1, 0}; + food := Vec2D.{5, 5}; + + snake_parts: [..] Vec2D; + for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); + snake_parts[0].x += 1; + + TUI.flush_input(); + TUI.set_next_key(TUI.Keys.Resize); + timer := current_time_monotonic(); + while main_loop := true { + + timestamp := current_time_monotonic(); + key := TUI.get_key(LOOP_PERIOD_MS); + + if key == { + case TUI.Keys.Resize; + TUI.clear_terminal(); + screen_size_x, screen_size_y = TUI.get_terminal_size(); + TUI.draw_box(1, 1, screen_size_x, screen_size_y); + TUI.set_cursor_position(3, screen_size_y); + write_strings(" ", player_name, " "); + + case #char "q"; #through; + case #char "Q"; #through; + case TUI.Keys.Escape; + break main_loop; + + case TUI.Keys.Up; + if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; + + case TUI.Keys.Down; + if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; + + case TUI.Keys.Left; + if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; + + case TUI.Keys.Right; + if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; + } + + if screen_size_x < 15 || screen_size_y < 15 { + TUI.clear_terminal(); + TUI.set_cursor_position(1,1); + write_string("~ paused : increase window size ~"); + continue; + } + + last_pos := snake_parts[snake_parts.count-1]; + + // Update position. + for < snake_parts.count-1..1 { + if snake_parts[it] != snake_parts[it-1] { + snake_parts[it] = snake_parts[it-1]; + } + } + snake_parts[0].x += dir.x; + snake_parts[0].y += dir.y; + + // Teleport on borders. + if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; + if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; + if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; + if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; + food.x = clamp(food.x, 2, screen_size_x-1); + food.y = clamp(food.y, 2, screen_size_y-1); + + // Check for game-over. + for 1..snake_parts.count-1 { + if snake_parts[it] == snake_parts[0] { + break main_loop; + } + } + + // Check for food. + if snake_parts[0] == food { + score += 1; + array_add(*snake_parts, snake_parts[snake_parts.count-1]); + food = Vec2D.{ + cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), + cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) + }; + } + + // Wait to match game loop time. + delta := to_milliseconds(current_time_monotonic() - timestamp); + if delta < LOOP_PERIOD_MS { + sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); + } + + // Draw snake. + write_string(TUI.Commands.DrawingMode); + TUI.set_cursor_position(last_pos.x, last_pos.y); + write_string(TUI.Drawings.Blank); + for snake_parts { + TUI.set_cursor_position(it.x, it.y); + write_string(TUI.Drawings.Checkerboard); + } + // Draw food. + { + TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); + TUI.set_cursor_position(food.x, food.y); + write_string(TUI.Drawings.Diamond); + } + write_string(TUI.Commands.TextMode); + + // Set score + TUI.set_cursor_position(3, 1); + print(" % ", score); + } + } + + GAME_OVER_TEXT :: "~ game over ~"; + INSTRUCTIONS_TEXT :: "(esc to exit)"; + + seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. + random_seed(seed); + + assert(TUI.setup_terminal(), "Failed to setup TUI."); + TUI.set_cursor_position(1, 1); + + write_string("Please enter player name: "); + player_name = TUI.read_input_line(64); + + while true { + game_loop(); + + // Game over screen. + box_size := Vec2D.{19, 4}; + TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1); + write_string(GAME_OVER_TEXT); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2); + write_string(INSTRUCTIONS_TEXT); + sleep_milliseconds(100); + TUI.flush_input(); + if TUI.get_key() == TUI.Keys.Escape then break; + } + + assert(TUI.reset_terminal(), "Failed to reset TUI."); +} diff --git a/modules/UTF8/module.jai b/modules/UTF8/module.jai index 72d3d75..5e6fd65 100644 --- a/modules/UTF8/module.jai +++ b/modules/UTF8/module.jai @@ -8,15 +8,15 @@ is_continuation_byte :: inline (byte: u8) -> bool { } // Given a leading_byte, returns the number of bytes on the character. -count_character_bytes :: inline (leading_byte: u8) -> int { +count_character_bytes :: inline (character_leading_byte: u8) -> int { // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte - if (leading_byte & 0xE0) == 0xC0 return 1+1; + if (character_leading_byte & 0xE0) == 0xC0 return 1+1; // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte - if (leading_byte & 0xF0) == 0xE0 return 1+2; + if (character_leading_byte & 0xF0) == 0xE0 return 1+2; // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte - if (leading_byte & 0xF8) == 0xF0 return 1+3; + if (character_leading_byte & 0xF8) == 0xF0 return 1+3; return 1; } @@ -126,3 +126,24 @@ get_byte_index :: (str: string, character_index: int) -> buffer_index: int, succ } return -1, false; } + +// Scans the string for UTF8 encoding errors. +is_valid :: (str: string) -> is_valid := true, error_index: int = -1 { + idx := 0; + remainig_bytes := 0; + while idx < str.count { + defer idx += 1; + + is_continuation := is_continuation_byte(str[idx]); + + if (is_continuation && remainig_bytes == 0) || (!is_continuation && remainig_bytes > 0) then return false, idx; + + if is_continuation { + remainig_bytes -= 1; + continue; + } + + remainig_bytes = count_character_bytes(str[idx]) - 1; + } + return; +} diff --git a/modules/UTF8/tests.jai b/modules/UTF8/tests.jai index aff1d36..b7e3579 100644 --- a/modules/UTF8/tests.jai +++ b/modules/UTF8/tests.jai @@ -1,5 +1,162 @@ #import "Basic"; +#import "String"; +#import "UTF8"; main :: () { - assert(false, "TODO"); // TODO + write_strings( + "#=======================#\n", + "# Basic tests #\n" + ); + + tmp_str: string; + tmp_bool: bool; + tmp_int: int; + + assert(is_continuation_byte("0€1"[0]) == false); + assert(is_continuation_byte("0€1"[1]) == false); + assert(is_continuation_byte("0€1"[2]) == true); + assert(is_continuation_byte("0€1"[3]) == true); + assert(is_continuation_byte("0€1"[4]) == false); + + + write_strings("# count_character_bytes #\n"); + + assert(count_character_bytes("0£€𐍈1"[0]) == 1); + assert(count_character_bytes("0£€𐍈1"[1]) == 2); + assert(count_character_bytes("0£€𐍈1"[2]) == 1); + assert(count_character_bytes("0£€𐍈1"[3]) == 3); + assert(count_character_bytes("0£€𐍈1"[4]) == 1); + assert(count_character_bytes("0£€𐍈1"[5]) == 1); + assert(count_character_bytes("0£€𐍈1"[6]) == 4); + assert(count_character_bytes("0£€𐍈1"[7]) == 1); + assert(count_character_bytes("0£€𐍈1"[8]) == 1); + assert(count_character_bytes("0£€𐍈1"[9]) == 1); + assert(count_character_bytes("0£€𐍈1"[10]) == 1); + + + write_strings("# truncate #\n"); + + assert(compare(truncate("0£€𐍈1", 0), "") == 0); + assert(compare(truncate("0£€𐍈1", 1), "0") == 0); + assert(compare(truncate("0£€𐍈1", 2), "0") == 0); + assert(compare(truncate("0£€𐍈1", 3), "0£") == 0); + assert(compare(truncate("0£€𐍈1", 4), "0£") == 0); + assert(compare(truncate("0£€𐍈1", 5), "0£") == 0); + assert(compare(truncate("0£€𐍈1", 6), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 7), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 8), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 9), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 10), "0£€𐍈") == 0); + assert(compare(truncate("0£€𐍈1", 11), "0£€𐍈1") == 0); + assert(compare(truncate("0£€𐍈1", 12), "0£€𐍈1") == 0); + + + write_strings("# is_empty #\n"); + + assert(is_empty("")); + assert(is_empty("\0")); + assert(is_empty("\0\t")); + assert(is_empty("\0\t\n")); + assert(is_empty("\0\t\n\x0B")); + assert(is_empty("\0\t\n\x0B\x0C")); + assert(is_empty("\0\t\n\x0B\x0C\r")); + assert(is_empty("\0\t\n\x0B\x0C\r ")); + assert(is_empty("\0\t\n\x0B\x0C\r .") == false); + assert(is_empty("| B A Z € N G A |") == false); + + + write_strings("# delete_character #\n"); + + tmp_str = copy_string("",, temporary_allocator); + assert(delete_character(*tmp_str, 0) == false); + + tmp_str = copy_string("12£45€78𐍈",, temporary_allocator); + assert(delete_character(*tmp_str, -1) == false); + assert(delete_character(*tmp_str, 99999) == false); + assert(delete_character(*tmp_str, 7) == true); + assert(compare(tmp_str, "12£45€7𐍈") == 0); + assert(delete_character(*tmp_str, 2) == true); + assert(compare(tmp_str, "1245€7𐍈") == 0); + assert(delete_character(*tmp_str, 4) == true); + assert(compare(tmp_str, "12457𐍈") == 0); + assert(delete_character(*tmp_str, 3) == true); + assert(compare(tmp_str, "1247𐍈") == 0); + + + write_strings("# get_byte_index #\n"); + + tmp_str = copy_string("12£45€78𐍈X",, temporary_allocator); + + tmp_int, tmp_bool = get_byte_index("", 0); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, -1); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, -99999); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 99999); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + + tmp_int, tmp_bool = get_byte_index(tmp_str, 0); + assert(tmp_int == 0 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 1); + assert(tmp_int == 1 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 2); + assert(tmp_int == 2 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 3); + assert(tmp_int == 4 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 4); + assert(tmp_int == 5 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 5); + assert(tmp_int == 6 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 6); + assert(tmp_int == 9 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 7); + assert(tmp_int == 10 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 8); + assert(tmp_int == 11 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 9); + assert(tmp_int == 15 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + + + write_strings("# count_characters #\n"); + + assert(count_characters("") == 0); + assert(count_characters("0") == 1); + assert(count_characters("0£") == 2); + assert(count_characters("0£€") == 3); + assert(count_characters("0£€𐍈") == 4); + assert(count_characters("0£€𐍈1") == 5); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[6] = 0; + assert(count_characters(tmp_str) == 10); + assert(count_characters(tmp_str, true) == 4); + + + write_strings("# is_valid #\n"); + + assert(is_valid("")); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[6] = 0; + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == true && tmp_int == -1, "(%, %)", tmp_bool, tmp_int); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[3] = 0; // Cut € at start. + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == false && tmp_int == 4, "(%, %)", tmp_bool, tmp_int); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[4] = 0; // Cut € at middle. + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == false && tmp_int == 4, "(%, %)", tmp_bool, tmp_int); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[5] = 0; // Cut € at end. + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == false && tmp_int == 5, "(%, %)", tmp_bool, tmp_int); + + + write_strings(" No errors found.\n"); } diff --git a/snake.jai b/snake.jai deleted file mode 100644 index b35c0fb..0000000 --- a/snake.jai +++ /dev/null @@ -1,165 +0,0 @@ -#import "Basic"; -#import "Random"; -TUI :: #import "TUI"(COLOR_MODE_BITS = 8); - -Vec2D :: struct { - x: int; - y: int; -} - -operator == :: (a: Vec2D, b: Vec2D) -> bool { - return a.x == b.x && a.y == b.y; -} - -screen_size_x: int = ---; -screen_size_y: int = ---; -player_name: string = ---; - -main :: () { - - game_loop :: () { - - LOOP_PERIOD_MS :: 30; - - score := 0; - dir := Vec2D.{1, 0}; - food := Vec2D.{5, 5}; - - snake_parts: [..] Vec2D; - for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); - snake_parts[0].x += 1; - - TUI.flush_input(); - TUI.set_next_key(TUI.Keys.Resize); - timer := current_time_monotonic(); - while main_loop := true { - - timestamp := current_time_monotonic(); - key := TUI.get_key(LOOP_PERIOD_MS); - - if key == { - case TUI.Keys.Resize; - TUI.clear_terminal(); - screen_size_x, screen_size_y = TUI.get_terminal_size(); - TUI.draw_box(1, 1, screen_size_x, screen_size_y); - TUI.set_cursor_position(3, screen_size_y); - write_strings(" ", player_name, " "); - - case #char "q"; #through; - case #char "Q"; #through; - case TUI.Keys.Escape; - break main_loop; - - case TUI.Keys.Up; - if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; - - case TUI.Keys.Down; - if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; - - case TUI.Keys.Left; - if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; - - case TUI.Keys.Right; - if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; - } - - if screen_size_x < 15 || screen_size_y < 15 { - TUI.clear_terminal(); - TUI.set_cursor_position(1,1); - write_string("~ paused : increase window size ~"); - continue; - } - - last_pos := snake_parts[snake_parts.count-1]; - - // Update position. - for < snake_parts.count-1..1 { - if snake_parts[it] != snake_parts[it-1] { - snake_parts[it] = snake_parts[it-1]; - } - } - snake_parts[0].x += dir.x; - snake_parts[0].y += dir.y; - - // Teleport on borders. - if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; - if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; - if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; - if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; - food.x = clamp(food.x, 2, screen_size_x-1); - food.y = clamp(food.y, 2, screen_size_y-1); - - // Check for game-over. - for 1..snake_parts.count-1 { - if snake_parts[it] == snake_parts[0] { - break main_loop; - } - } - - // Check for food. - if snake_parts[0] == food { - score += 1; - array_add(*snake_parts, snake_parts[snake_parts.count-1]); - food = Vec2D.{ - cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), - cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) - }; - } - - // Wait to match game loop time. - delta := to_milliseconds(current_time_monotonic() - timestamp); - if delta < LOOP_PERIOD_MS { - sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); - } - - // Draw snake. - write_string(TUI.Commands.DrawingMode); - TUI.set_cursor_position(last_pos.x, last_pos.y); - write_string(TUI.Drawings.Blank); - for snake_parts { - TUI.set_cursor_position(it.x, it.y); - write_string(TUI.Drawings.Checkerboard); - } - // Draw food. - { - TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); - TUI.set_cursor_position(food.x, food.y); - write_string(TUI.Drawings.Diamond); - } - write_string(TUI.Commands.TextMode); - - // Set score - TUI.set_cursor_position(3, 1); - print(" % ", score); - } - } - - GAME_OVER_TEXT :: "~ game over ~"; - INSTRUCTIONS_TEXT :: "(esc to exit)"; - - seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. - random_seed(seed); - - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_cursor_position(1, 1); - - write_string("Please enter player name: "); - player_name = TUI.read_input_line(64); - - while true { - game_loop(); - - // Game over screen. - box_size := Vec2D.{19, 4}; - TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1); - write_string(GAME_OVER_TEXT); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2); - write_string(INSTRUCTIONS_TEXT); - sleep_milliseconds(100); - TUI.flush_input(); - if TUI.get_key() == TUI.Keys.Escape then break; - } - - assert(TUI.reset_terminal(), "Failed to reset TUI."); -} diff --git a/ttt.jai b/ttt.jai index b0d60d0..d5d6e4e 100644 --- a/ttt.jai +++ b/ttt.jai @@ -7,7 +7,7 @@ DEBUG :: false; #import "File"; #import "File_Utilities"; #import "String"; -#import "Saturation"; +#import "Saturation"(PREFER_BRANCH_FREE_CODE=true); #import "UTF8"; TUI :: #import "TUI"(COLOR_MODE_BITS=4); -- cgit v1.2.3 From 22e3b95ce23808ee08a5fa3b0481c17c2e5506d6 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 20 May 2024 23:19:55 +0100 Subject: Updated TUI module to clear inside error message box. --- modules/TUI/module.jai | 26 ++++++-- modules/TUI/snake.jai | 165 ------------------------------------------------- ttt.jai | 2 +- 3 files changed, 21 insertions(+), 172 deletions(-) delete mode 100644 modules/TUI/snake.jai (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d0373f9..2fb1233 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -125,10 +125,21 @@ Commands :: struct #type_info_none { // Draw/text. DrawingMode :: "\e(0"; TextMode :: "\e(B"; - ClearScreen :: "\e[2J"; - ClearLine :: "\e[2K"; + ClearToEndOfScreen :: "\e[0J"; // From current cursor position (inclusive) to end of screen. + ClearFromStartOfScreen :: "\e[1J"; // From start of screen to current cursor position. + ClearScreen :: "\e[2J"; // Leaves cursor in top left corner position. ClearScrollBack :: "\e[3J"; + ClearToEndOfLine :: "\e[0K"; // From current cursor position (inclusive) to end of line. + ClearFromStartOfLine :: "\e[1K"; // From start of line to current cursor position. + ClearLine :: "\e[2K"; SetGraphicsRendition :: "\e[%m"; + + // Text Modification. + InsertCharacters :: "\e[%@"; // Insert % spaces at curret cursor position (shifts existing text to the right). + DeleteCharacters :: "\e[%P"; // Delete % characters at the current cursor position (inserts space characters from the right). + EraseCharacters :: "\e[%X"; // Erase % characters from the current cursor position by overwriting them with space characters. + InsertLines :: "\e[%L"; // Insert % lines into the buffer at the current cursor position. + DeleteLines :: "\e[%M"; // Deletes % lines from the buffer, starting with the row the cursor is on. // Character encoding. EncodingIEC2022 :: "\e%@"; @@ -536,12 +547,12 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if is_visible { print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, str); - for chars_count..count_limit-1 append(*builder, " "); + if count_limit > chars_count then print_to_builder(*builder, Commands.EraseCharacters, count_limit-chars_count); } else { print_to_builder(*builder, Commands.SetCursorPosition, y, x); for 1..chars_count append(*builder, "*"); - for chars_count..count_limit-1 append(*builder, " "); + if count_limit > chars_count print_to_builder(*builder, Commands.EraseCharacters, count_limit-chars_count); } print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); write_builder(*builder); @@ -615,7 +626,7 @@ flush_input :: () { input_string.count = 0; } -draw_box :: (x: int, y: int, width: int, height: int) { +draw_box :: (x: int, y: int, width: int, height: int, clear_inside := false) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); @@ -641,10 +652,13 @@ draw_box :: (x: int, y: int, width: int, height: int) { for idx: y+1..y+height-2 { print_to_builder(builder, Commands.SetCursorPosition, idx, x); append(builder, Drawings.LineV); + if clear_inside { + print_to_builder(builder, Commands.EraseCharacters, width-2); + } print_to_builder(builder, Commands.SetCursorPosition, idx, x+width-1); append(builder, Drawings.LineV); } - + // Draw bottom line. print_to_builder(builder, Commands.SetCursorPosition, y+height-1, x); append(builder, Drawings.CornerBL); diff --git a/modules/TUI/snake.jai b/modules/TUI/snake.jai deleted file mode 100644 index b35c0fb..0000000 --- a/modules/TUI/snake.jai +++ /dev/null @@ -1,165 +0,0 @@ -#import "Basic"; -#import "Random"; -TUI :: #import "TUI"(COLOR_MODE_BITS = 8); - -Vec2D :: struct { - x: int; - y: int; -} - -operator == :: (a: Vec2D, b: Vec2D) -> bool { - return a.x == b.x && a.y == b.y; -} - -screen_size_x: int = ---; -screen_size_y: int = ---; -player_name: string = ---; - -main :: () { - - game_loop :: () { - - LOOP_PERIOD_MS :: 30; - - score := 0; - dir := Vec2D.{1, 0}; - food := Vec2D.{5, 5}; - - snake_parts: [..] Vec2D; - for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); - snake_parts[0].x += 1; - - TUI.flush_input(); - TUI.set_next_key(TUI.Keys.Resize); - timer := current_time_monotonic(); - while main_loop := true { - - timestamp := current_time_monotonic(); - key := TUI.get_key(LOOP_PERIOD_MS); - - if key == { - case TUI.Keys.Resize; - TUI.clear_terminal(); - screen_size_x, screen_size_y = TUI.get_terminal_size(); - TUI.draw_box(1, 1, screen_size_x, screen_size_y); - TUI.set_cursor_position(3, screen_size_y); - write_strings(" ", player_name, " "); - - case #char "q"; #through; - case #char "Q"; #through; - case TUI.Keys.Escape; - break main_loop; - - case TUI.Keys.Up; - if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; - - case TUI.Keys.Down; - if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; - - case TUI.Keys.Left; - if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; - - case TUI.Keys.Right; - if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; - } - - if screen_size_x < 15 || screen_size_y < 15 { - TUI.clear_terminal(); - TUI.set_cursor_position(1,1); - write_string("~ paused : increase window size ~"); - continue; - } - - last_pos := snake_parts[snake_parts.count-1]; - - // Update position. - for < snake_parts.count-1..1 { - if snake_parts[it] != snake_parts[it-1] { - snake_parts[it] = snake_parts[it-1]; - } - } - snake_parts[0].x += dir.x; - snake_parts[0].y += dir.y; - - // Teleport on borders. - if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; - if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; - if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; - if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; - food.x = clamp(food.x, 2, screen_size_x-1); - food.y = clamp(food.y, 2, screen_size_y-1); - - // Check for game-over. - for 1..snake_parts.count-1 { - if snake_parts[it] == snake_parts[0] { - break main_loop; - } - } - - // Check for food. - if snake_parts[0] == food { - score += 1; - array_add(*snake_parts, snake_parts[snake_parts.count-1]); - food = Vec2D.{ - cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), - cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) - }; - } - - // Wait to match game loop time. - delta := to_milliseconds(current_time_monotonic() - timestamp); - if delta < LOOP_PERIOD_MS { - sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); - } - - // Draw snake. - write_string(TUI.Commands.DrawingMode); - TUI.set_cursor_position(last_pos.x, last_pos.y); - write_string(TUI.Drawings.Blank); - for snake_parts { - TUI.set_cursor_position(it.x, it.y); - write_string(TUI.Drawings.Checkerboard); - } - // Draw food. - { - TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); - TUI.set_cursor_position(food.x, food.y); - write_string(TUI.Drawings.Diamond); - } - write_string(TUI.Commands.TextMode); - - // Set score - TUI.set_cursor_position(3, 1); - print(" % ", score); - } - } - - GAME_OVER_TEXT :: "~ game over ~"; - INSTRUCTIONS_TEXT :: "(esc to exit)"; - - seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. - random_seed(seed); - - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_cursor_position(1, 1); - - write_string("Please enter player name: "); - player_name = TUI.read_input_line(64); - - while true { - game_loop(); - - // Game over screen. - box_size := Vec2D.{19, 4}; - TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1); - write_string(GAME_OVER_TEXT); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2); - write_string(INSTRUCTIONS_TEXT); - sleep_milliseconds(100); - TUI.flush_input(); - if TUI.get_key() == TUI.Keys.Escape then break; - } - - assert(TUI.reset_terminal(), "Failed to reset TUI."); -} diff --git a/ttt.jai b/ttt.jai index d5d6e4e..1bfabd0 100644 --- a/ttt.jai +++ b/ttt.jai @@ -141,7 +141,7 @@ draw_error_window :: () { pos_y := 1 + (size_y - w_size_y) / 2; TUI.using_style(style_error); - TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y); + TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y, true); TUI.set_cursor_position(pos_x + 1, pos_y); write_string(" Error "); TUI.set_cursor_position(pos_x + 1, pos_y + 1); -- cgit v1.2.3 From 4468717c64d0fcac52f00326cff9e6b0b15917fc Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 26 May 2024 01:26:52 +0100 Subject: Patched TUI module. --- modules/TUI/module.jai | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 2fb1233..3c40ee1 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -342,6 +342,7 @@ is_active :: inline () -> bool { return active; } +// Prepares the terminal to be used by the module. setup_terminal :: () -> success := true #must { if active == true return; @@ -367,6 +368,7 @@ setup_terminal :: () -> success := true #must { return; } +// Restores the initial terminal settings. reset_terminal :: () -> success := true #must { if active == false return; @@ -390,6 +392,12 @@ set_next_key :: inline (key: Key) { input_override = key; } +// Returns, with the following priority: +// - last key passed to set_next_key; +// - Keys.Resize if terminal was resized; +// - key pressed by user; +// - Keys.None if everything else fails after the given timeout. +// If timeout is set to -1, it will wait indefinitely by the user input. get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); @@ -689,7 +697,7 @@ get_terminal_size :: () -> width: int, height: int { write_string(Commands.QueryWindowSizeInChars); rows, columns: int = ---; - if OS_wait_for_input(0) { + if OS_wait_for_input(1) { // Expected response format: \e[8;;t // where is the number of rows and of columns. @@ -718,16 +726,17 @@ get_terminal_size :: () -> width: int, height: int { // In such cases, measure it indirectly by the maximum possible cursor position. // (e.g.: allowWindowOps/disallowedWindowOps properties in xterm) else { - x, y := get_cursor_position(); - defer set_cursor_position(x, y); - - set_cursor_position(0xFFFF, 0xFFFF); - columns, rows = get_cursor_position(); + write_string(Commands.SaveCursorPosition); + defer write_string(Commands.RestoreCursorPosition); + + set_cursor_position(0xFFFF, 0xFFFF,, tui_output_builder = null); + columns, rows = get_cursor_position(); } return columns, rows; } +// Range between 1 and terminal size. set_cursor_position :: inline (x: int, y: int) { assert_is_active(); if context.tui_output_builder == null { @@ -738,6 +747,7 @@ set_cursor_position :: inline (x: int, y: int) { } } +// Range between 1 and terminal size. get_cursor_position :: () -> x: int, y: int { assert_is_active(); @@ -777,12 +787,14 @@ set_terminal_title :: inline (title: string) { print(Commands.SetWindowTitle, title); } +// Set the module's context string builder in the current scope context. using_builder_as_output :: (builder: *String_Builder) #expand { __builder := context.tui_output_builder; context.tui_output_builder = builder; `defer context.tui_output_builder = __builder; } +// Helper to use the module's context string builder. tui_print :: inline (format_string: string, args: .. Any) { if context.tui_output_builder == null { print(format_string, ..args, to_standard_error = false); @@ -792,6 +804,7 @@ tui_print :: inline (format_string: string, args: .. Any) { } } +// Helper to use the module's context string builder. tui_write_string :: inline (s: string) { if context.tui_output_builder == null { write_string(s, to_standard_error = false); -- cgit v1.2.3 From 7f8a90efed56db9f49e9d7e5167372ddd7aa1df2 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 26 May 2024 11:54:44 +0100 Subject: Patched TUI module. --- modules/TUI/module.jai | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) (limited to 'modules') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3c40ee1..124b906 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -693,10 +693,12 @@ get_terminal_size :: () -> width: int, height: int { auto_release_temp(); + rows, columns: int = ---; + flush_input(); write_string(Commands.QueryWindowSizeInChars); - - rows, columns: int = ---; + + // Wait a bit for a response to QueryWindowSizeInChars... if OS_wait_for_input(1) { // Expected response format: \e[8;;t @@ -704,15 +706,9 @@ get_terminal_size :: () -> width: int, height: int { FORMAT :: "\e[8;;t"; input := read_input(64, #char "t",, allocator = 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; - } + // Discard head noise, assuming that the response is at the end of the input (because we used a terminator character during read). + start_idx := find_index_from_right(input, #char "\e"); + if start_idx > 0 then advance(*input, start_idx); assert(input.count >= 3 && input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], @@ -761,20 +757,14 @@ get_cursor_position :: () -> x: int, y: int { FORMAT :: "\e[;R"; input := read_input(64, #char "R",, allocator = temporary_allocator); - // 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; - } + // Discard head noise, assuming that the response is at the end of the input (because we used a terminator character during read). + start_idx := find_index_from_right(input, #char "\e"); + if start_idx > 0 then advance(*input, start_idx); assert(input.count >= 2 && input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[input.count-1] == FORMAT[FORMAT.count-1], "Failed to query cursor position: invalid response."); - + advance(*input, 2); parts := split(input, ";",, allocator = temporary_allocator); row := parse_int(*parts[0]); -- cgit v1.2.3 From ec706533ca26d49670adb97617df0d565528e395 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 29 May 2024 12:49:49 +0100 Subject: Updated documentation and licenses for release. --- LICENSE-GPL-3.0-or-later | 674 +++++++++++++++++++++++++++++++++++++++++ LICENSE-ISC | 15 - LICENSE-MIT | 21 -- README.md | 202 +++++++----- modules/LICENSE-ISC | 15 + modules/LICENSE-MIT | 21 ++ modules/README.md | 34 +++ modules/TUI/examples/snake.jai | 188 ++++++++++++ ttt.jai | 3 + 9 files changed, 1058 insertions(+), 115 deletions(-) create mode 100644 LICENSE-GPL-3.0-or-later delete mode 100644 LICENSE-ISC delete mode 100644 LICENSE-MIT create mode 100644 modules/LICENSE-ISC create mode 100644 modules/LICENSE-MIT create mode 100644 modules/README.md create mode 100644 modules/TUI/examples/snake.jai (limited to 'modules') diff --git a/LICENSE-GPL-3.0-or-later b/LICENSE-GPL-3.0-or-later new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE-GPL-3.0-or-later @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE-ISC b/LICENSE-ISC deleted file mode 100644 index 3ca0ef1..0000000 --- a/LICENSE-ISC +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2024 Daniel Almeida Martins - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 1632077..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Daniel Almeida Martins - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 57a2544..6167659 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,128 @@ Task Time Tracker ================= +A tool to keep track of the time spent on tasks. + +# Why use it? + +Why not? Besides, you'll be able to create, duplicate, move, rename, +archive, restore and delete tasks at the distance of one (or two) key +stroke(s). You'll also be able to edit the time spent on a task by +adding, subtracting or setting the time you want. But that's not all! + +With the Track'n'Close technology, you won't need to keep the app always +open. Once you start tracking time for a task, the app will keep track +of it, even if you close it or turn off your computer. + +Count seconds, minutes, hours, days, and even years... all the way up to +infinity\* thanks to the not-so-new technology of 64 bit integers. + +Be amazed by the compact interface that automatically adapts the time +representations while maximizing the displayed precision. + +Cleanup the workspace by moving your finished tasks to the archive. Want +to bring back some archived tasks? No problem, just switch into the +archive view and restore them. + +Ever felt like your data is being held hostage? Not anymore! Import and +export your tasks using the widely supported CSV text file format. + +Want to be part of the last frontier? Grab the bleeding edge version 2 +which brings: sorting capabilities; better text input with UTF8 +support; possibility to archive and reset all current tasks with a +single command; and allows to merge tasks with the same name. As an +extra , you'll need to export and re-import your tasks due to some +database incompatibility (oh, the joy). + +And if you don't like what you're seeing, this is your lucky day! +Because you have access to the source code, you can adapt it to your +needs! + +**Task Time Tracker - *You may not need it, but I enjoyed making it!*** + +*\* Although the app cannot count to infinity, it surely displays it (∞) +if the time goes to 9999.5 years or above. Also, it will display a minus +(-) if you force a time to have negative values.* + +# Why create such tool? + +I like to keep track of the time spent on my daily job's tasks. It helps +me be aware of time pits, and improve my time estimates. This motivated +me to search for a simple app that allowed to take measurements without +too much effort. + +After skimming through all cloud-based and too-complex apps, I +eventually landed on some text-based user interface (TUI) apps. The one +that almost convinced me was [worklog](https://github.com/atsb/worklog), +but I just couldn't come to terms with its interface. + +So, after spending more time than I'd like to admit searching for a task +time tracker, I decided to take matters on my own hands. Maybe I just +needed to improve my search-fu. Maybe it was just an excuse to write +some code. Either way, I wasn't turning back. + +# Why use C and ncurses? + +I've been looking for an excuse to revisit C, which I haven't used for +more than a decade, and this seemed like a good opportunity: a simple +app with a small set of features. But how would I build the user +interface? I didn't want to learn an advanced toolkit just for this +small project, and since the TUI apps had somehow resonated with me, I +decided to try out the ncurses library which has been surviving the test +of time. + +Because this was an hobby project focused in exploring C and its +standard library, I allowed myself to obsess with whatever details I +wanted to. This serves to justify the lousy code, and explains the fun I +had. + +Overall, it was a satisfying experience with occasional moments of +frustration whenever "string" manipulations were required. And now that +the first part is completed the best part begins: let's try to implement +it in Jai and get some hands-on experience of how the two compare. See +you on the other side. 🖖 + +# What is Jai? + +Jai is a temporary name used for the programming language being +developed at Thekla, Inc. + +# Why port it to Jai? + +Because I love to explore, and I needed an excuse to try out the Jai +compiler I got access in the meantime. This allowed me to experiment and +compare this new programing language against the original C +implementation. + +During the initial pass to port the code, I made some small adaptations +due to syntax differences, and improved some data types (finally, I +could replace all those `*u8` with proper `string`). The initial pass +was easy, so I decided to add some features I missed while using the app +on my daily job. + +Still, the ncurses dependency was bothering me. Although the language is +well prepared to interact with C/C++ libraries, and I was able to +quickly setup the necessary bindings to use ncurses, it didn’t fell +right… this dependency was blocking me from building this app for +different operating systems (OS). Could I replace ncurses with something +native to this language? Yes… but that require much reading and coding, +and that’s what I did. + +After surfing an uncountable number of websites and manuals about linux, +terminals, escape codes, and whatnot, I ended up creating +[TUI](https://github.com/gudinoff/jai-modules), a simple terminal user +interface module that provides basic functionalities similar to the +[ncurses library](https://en.wikipedia.org/wiki/Ncurses), written in Jai +to allow portability between OSs. + +Working with this new language was a joyful experience. Most of the time +it felt like I was simply cleaning up (simplifying) the code, with very +little friction. There was no hiccups setting up the project or adding +new modules, it all just worked. I really hope this language gets to +spread its wings. + # License -Licensed under MIT or ISC. - -SPDX-License-Identifier: MIT OR ISC - -# Know-how - -- [ncurses colors](https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/color.html#COLORBASICS) -- [pprintf](https://cplusplus.com/reference/cstdio/printf/) -- [intmax_t](https://wiki.sei.cmu.edu/confluence/plugins/servlet/mobile?contentId=87152366#content/view/87152366) -- [hexed.it](https://hexed.it/) - -# To-do list -- [x] Include check on number of char bits; -- [x] Decide once for all if I'll be using uint8_t or char for strings: use char. -- [x] maybe rename to task-time-tracker? -- [x] Remove hash stuff; -- [x] Tasks should have a `modified_on` timestamp field; -- [x] Change capacity to size_t. -- [x] Change active_task to active_task_ptrdiff. -- [x] Use selected_task_ptrdiff? -- [x] Make sure task names don't include commas ','; -- [x] Format time being displayed. -- [x] Replace max_capacity by its true value; -- [x] Replace intmax_t by int64_t; -- [x] Adapt input cycle to work with `database_t *db` to allow pointing to database/archive. -- [x] Show a symbol to let the user know when we're seeing the archive. -- [x] Status of task will allow to keep counting time even when the process gets terminated forcefully; -- [x] Review code: char !uint8_t; -- [x] Make sure that only one task is running at each time; -- [x] Mouse selection is broken due to entire TUI update: No, it was fixed by using `erase()` on the `draw_tui` instead of `clear()`; -- [x] I bet the headers are no longer being used all on a single cycle. Let's separate them and include "header_title_archive"; -- [x] Rename layout members: title_header, archive_header, total_header, days_headers, column_widths, column_alignments, headers_paddings. -- [x] Using the archive header, we can remove the top-left-corner diamond on the archive. -- [x] Allow to cancel a rename_task operation: you can do it by leaving it blank. -- [x] Make sure we are not using `strcat` and `strcpy`... or that we are using them wisely (famous last words). -- [x] Make archive be stored in CSV format: takes less space and allows to quickly archive by appending to end of file; -- [x] Implement `append_to_csv(task_t *task, char *path_name)` and use it in archive function; -- [x] At startup, check for required files and create them if not present. -- [x] Allow to archive task using keys: `a` and `A`; -- [x] By default, store files on `~/.config/task_time_tracker/` or `~/.local/share/task_time_tracker` and allow to store elsewhere if passed by argument `--config`. -- [x] Allow usage of `ttt: ./ttt --dpath ./` to change the app folder: To changes app data path change the environment variable HOME (USERPROFILE for windows users). -- [x] Clone (replicate) task; If task is active, mark newly created task as inactive; -- [x] Check if next/previous is safe against overflows/underflows using https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html -- [x] Confirm delete_task operation by show confirmation message on selected line (horizontally centered). -- [x] Check totals update speedup using https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html - - For 1M entries, generic C code runs in 12.0ms while special approaches using builtins or SIMD takes around 9.5ms. - - Used optimization described [here](https://stackoverflow.com/questions/17580118/signed-saturated-add-of-64-bit-ints). -- [x] Allow to jump to specific task by index number using key `g` and `G`; -- [x] Move task to (using task_t tmp_task + memcpy) using key `m` and `M`; -- [x] Rethink keys; - - [x] Create task using keys: `n` and `N`; - - [x] Delete task using key: delete; - - [x] Change task name using keys: `F2`; - - [x] Duplicate task using keys: `d` and `D`; -- [x] Add/remove time using keys: `F3`; -- [x] Add/remove time for any day of week; -- [x] Total times may saturate, but before that the user will see the infinite symbol. Solution: Provide user with possibility to refresh totals. -- [x] Decide on a INVALID_WINDOW_MESSAGE. -- [x] Use backspace to clear all timers for current task. -- [x] Move `store_database_partial` to misc and save only when leaving or after 15 seconds of inactivity and having dirty flag set. -- [x] Register kill signals to exit gracefully. -- [x] Check if string_buffer needs to be cleared. We may be leaking info on the string_buffer. -- [x] Replaced `sprintf` by `snprintf`; -- [x] Make sure that string_buffer bounds are respected; -- [x] Rename `MAX_TASK_NAME` to something more informative; -- [x] Compress code: - - [x] Re-do sprint_time5_utf8: -12 delta LOC; - - [x] Re-do truncate_string_utf8: 0 delta LOC; - - [x] Implement `read_input_to_string_buffer`: -24 delta LOC; - - [x] Wrap malloc (and maybe others) in a function with error checking; - - [x] Move database actions into functions; -- [x] Fix bug: archiving/unarchiving task introduces " ," at end of name and increases the number of spaces before comma; -- [x] Check if draw_tui may be simplified by drawing entire lines of tasks at once and draw columns separators after; - - By having each column-print job decoulpled, we avoid havint to measure and compensate lengths of UTF8 strings; -- [x] Review all code for bugs related to auto-cast on ptrdiff_t (signed/unsigned); -- [x] Review all code for bugs related to auto-cast on size_t (signed/unsigned); -- [x] Go over all to-do items; -- [x] Hide stderr messages from app screen. -- [x] Improve error detection/messages. +Licensed under GPL-3.0-or-later. + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/modules/LICENSE-ISC b/modules/LICENSE-ISC new file mode 100644 index 0000000..3ca0ef1 --- /dev/null +++ b/modules/LICENSE-ISC @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2024 Daniel Almeida Martins + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/modules/LICENSE-MIT b/modules/LICENSE-MIT new file mode 100644 index 0000000..1632077 --- /dev/null +++ b/modules/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Daniel Almeida Martins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 0000000..d9b5839 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,34 @@ +jai-modules +=========== + +Modules for the language being developed by Thekla, Inc. + +# Saturation + +This module provides basic integer [saturation arithmetic](https://en.wikipedia.org/wiki/Saturation_arithmetic) procedures: `add`, `sub`, `mul`, and `div`. +These procedures accept any of the built-in integer types and adjust the output accordingly, e.g., adding an `u8` with an `s16` results in an `s16`; +All procedures return a flag signaling if the result is saturated and, additionally, the division procedure returns the remainder; +Branch-free procedures are included for the x64 architecture. These may be used by setting the `PREFER_BRANCH_FREE_CODE` module argument, or by setting `prefer_branch_free_code` on each function call. These should speed things up, specially when using signed values. Some benchmarks are included on the tests file. + +# TUI + +A simple terminal user interface module that provides basic functionalities similar to the [ncurses library](https://en.wikipedia.org/wiki/Ncurses). +Usefull for creating simple terminal-based apps that require user input. +View `snake.jai` for an example. +It has been tested on the following terminal emulators: +- [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) +- [kitty](https://en.wikipedia.org/wiki/Kitty_(terminal_emulator)) +- [Konsole](https://en.wikipedia.org/wiki/Konsole) +- [Linux console](https://en.wikipedia.org/wiki/Linux_console) +- [xterm](https://en.wikipedia.org/wiki/Xterm) +- [Windows Terminal](https://en.wikipedia.org/wiki/Windows_Terminal) + +# UTF8 + +Basic operations over UTF8 encoded strings. + +# License + +Licensed under MIT or ISC. + +SPDX-License-Identifier: MIT OR ISC diff --git a/modules/TUI/examples/snake.jai b/modules/TUI/examples/snake.jai new file mode 100644 index 0000000..b62136c --- /dev/null +++ b/modules/TUI/examples/snake.jai @@ -0,0 +1,188 @@ +#import "Basic"; +#import "Math"; +#import "Random"; +TUI :: #import "TUI"(COLOR_MODE_BITS = 4); + +screen_size_x: int = ---; +screen_size_y: int = ---; +player_name: string = ---; + +main :: () { + // Randomize initial random state. + seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. + random_seed(seed); + + assert(TUI.setup_terminal(), "Failed to setup TUI."); + + // Ask for the player name, and keep it limited to 64 bytes. + TUI.set_cursor_position(1, 1); + write_string("Please enter player name: "); + player_name = TUI.read_input_line(64); + + while true { + + game_loop(); + + // Draw the game over screen. + BOX_SIZE_X :: 20; + BOX_SIZE_Y :: 4; + GAME_OVER_TEXT :: "~ game over ~"; #assert(GAME_OVER_TEXT.count < BOX_SIZE_X-2); + INSTRUCTIONS_TEXT :: "(esc to exit)"; #assert(INSTRUCTIONS_TEXT.count < BOX_SIZE_X-2); + + TUI.draw_box((screen_size_x-BOX_SIZE_X)/2, (screen_size_y-BOX_SIZE_Y)/2, BOX_SIZE_X, BOX_SIZE_Y, true); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-BOX_SIZE_Y)/2 + 1); + write_string(GAME_OVER_TEXT); + TUI.set_cursor_position((screen_size_x-INSTRUCTIONS_TEXT.count)/2, (screen_size_y-BOX_SIZE_Y)/2 + 2); + write_string(INSTRUCTIONS_TEXT); + sleep_milliseconds(100); // Avoid any sudden player input. + + // Wait for user input, and exit if the user presses Escape. + TUI.flush_input(); + if TUI.get_key() == TUI.Keys.Escape then break; + } + + assert(TUI.reset_terminal(), "Failed to reset TUI."); +} + +game_loop :: () { + + Vec2D :: struct { + x: int; + y: int; + } + + operator == :: (a: Vec2D, b: Vec2D) -> bool { + return a.x == b.x && a.y == b.y; + } + + LOOP_PERIOD_MS :: 66; + + // Setup game state. + score := 0; + dir := Vec2D.{1, 0}; + food := Vec2D.{5, 5}; + snake_parts: [..] Vec2D; + for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); + snake_parts[0].x += 1; + + // Use the default foreground and background colors. + TUI.set_style(.{ use_default_background_color = true, use_default_foreground_color = true }); + + // Force to draw the game UI by simulating a terminal resize. + TUI.flush_input(); + TUI.set_next_key(TUI.Keys.Resize); + + while main_loop := true { + + // Setup the module's context string builder to buffer the output on temporary memory and print everything at once. + auto_release_temp(); + temp_builder := String_Builder.{ allocator = temporary_allocator }; + TUI.using_builder_as_output(*temp_builder); + defer write_builder(*temp_builder); + + // Redirect text output to TUI functions to make use of module's context string builder. + print :: TUI.tui_print; + write_string :: TUI.tui_write_string; + + timestamp := current_time_monotonic(); + key := TUI.get_key(LOOP_PERIOD_MS); + + if key == { + case TUI.Keys.Resize; + // Draw game UI. + TUI.clear_terminal(); + screen_size_x, screen_size_y = TUI.get_terminal_size(); + TUI.draw_box(1, 1, screen_size_x, screen_size_y); + TUI.set_cursor_position(3, screen_size_y); + print(" % ", player_name); + + case TUI.Keys.Escape; + break main_loop; + + case TUI.Keys.Up; + if dir != .{0, 1} then dir = .{0, -1}; + + case TUI.Keys.Down; + if dir != .{0, -1} then dir = .{0, 1}; + + case TUI.Keys.Left; + if dir != .{1, 0} then dir = .{-1, 0}; + + case TUI.Keys.Right; + if dir != .{-1, 0} then dir = .{1, 0}; + } + + // Pause game if screen is too small. + if screen_size_x < 15 || screen_size_y < 15 { + TUI.clear_terminal(); + TUI.set_cursor_position(1,1); + write_string("~ paused : increase window size ~"); + continue; + } + + // Keep snake's last position so we can clear it from screen. + last_pos := snake_parts[snake_parts.count-1]; + + // Update snake position. + for < snake_parts.count-1..1 { + if snake_parts[it] != snake_parts[it-1] { + snake_parts[it] = snake_parts[it-1]; + } + } + snake_parts[0].x += dir.x; + snake_parts[0].y += dir.y; + + // Teleport on borders. + if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; + if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; + if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; + if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; + food.x = clamp(food.x, 2, screen_size_x-1); + food.y = clamp(food.y, 2, screen_size_y-1); + + // Check for game-over. + for 1..snake_parts.count-1 { + if snake_parts[it] == snake_parts[0] { + break main_loop; + } + } + + // Check for food. + if snake_parts[0] == food { + score += 1; + array_add(*snake_parts, snake_parts[snake_parts.count-1]); + food = Vec2D.{ + cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), + cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) + }; + } + + // Wait to match game loop time. + delta := to_milliseconds(current_time_monotonic() - timestamp); + if delta < LOOP_PERIOD_MS { + sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); + } + + // Draw snake. + { + write_string(TUI.Commands.DrawingMode); + TUI.set_cursor_position(last_pos.x, last_pos.y); + write_string(TUI.Drawings.Blank); + for snake_parts { + TUI.set_cursor_position(it.x, it.y); + write_string(TUI.Drawings.Checkerboard); + } + } + // Draw food. + { + TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, use_default_background_color = true }); + TUI.set_cursor_position(food.x, food.y); + write_string(TUI.Drawings.Diamond); + } + write_string(TUI.Commands.TextMode); + + // Draw score. + TUI.set_cursor_position(3, 1); + print(" % ", score); + } +} diff --git a/ttt.jai b/ttt.jai index 1bfabd0..67d6e18 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1,3 +1,6 @@ +// Copyright 2024 Daniel Almeida Martins +// License GPL-3.0-or-later + DEBUG :: false; #import "Basic"()(MEMORY_DEBUGGER=DEBUG); // Enabling memory debug adds ~30MB of RAM. -- cgit v1.2.3