Author:ptrace Comitter:ptrace Date:2026-01-16 01:15:34 UTC

Breaking changes; Incorporated separate context and arena; Expanded the APIs: return additionally how many characters where used for the terminal colors; Allowing to reset the text state manually

diff --git a/README.md b/README.md index 04fe40c..74c159c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,28 @@ # Terminal Colors for Jai This is a small declarative library which allows you to create colorful terminal messages. Note, that Jai already provides a module for printing colors: `modules/Print_Color`.   The differences are, that the builtin module prints the characters directly, where  this library returns strings.   APIs of this library also returning the amount of characters, which were used  for representing the colors. Which makes it useful for CLI programs, that are aware  of the terminal window width.   The last thing: The builtin library uses a default color palette.  Where this library extends it a bit more and allows you to use RGB values.   ### Windows I don't develop for Windows, so I'm not sure if this library will work on it.   But I don't mind supporting Windows, you can provide me more information or a patch. ## Usage Put the directory `termcolors` in your projects `modules` directory, or link it while building.   ### Example ```plain @@ -11,7 +30,10 @@ Put the directory `termcolors` in your projects `modules` directory, or link it  #import "termcolors"; main :: () {     context.allocator = temp;     termcolors_init_allocator_and_context();     // If you want, you can reset the library pool with this proc     defer termcolors_reset_pool();                         font style     background color diff --git a/termcolors/lib.jai b/termcolors/lib.jai index 02f921b..3437393 100644 --- a/termcolors/lib.jai +++ b/termcolors/lib.jai @@ -24,12 +24,26 @@  */ /*     +Version: 1.0.1     +Version: 2.0.0     -------------------     --- [ paint() ] ---     ---- [ Setup ] ----     -------------------     Initialize this lib via `termcolors_init_allocator_and_context()`.     It creates a arena for this lib which you can reset with `termcolors_reset_pool()`.     Everything is thrown into the arena, so you don't have to free individual strings.     The lib context is also exposed as: `termcolors_context`.     The arena is exposed as: `termcolors_pool`.     ----------------------------------     --- [ paint() / General Info ] ---     ----------------------------------     paint() uses the 4bit terminal color palette. The structure of the proc signature     through every API is the same: @@ -54,6 +68,31 @@         log(paint("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .BLACK, .WHITE));     --- [ Buffering Text ]     If you want to buffer a lot of text, you can provide `no_termination = true` to the     APIs. Then they won't append the "reset" terminal code.     When you're done with your string buffer, you can call the proc `paint_reset()`, which     returns the reset code.     --- [ Visual Width / Real Width ]     If you do `paint("foo", fg = .RED).count`, it will return way more then three characters.     This can be quite annoying if you want to build an CLI program, that is aware of its     width.     Because of that, every API returns additionally an integer, which describes the count of     used characters by the terminal codes.     ```     my_str, vcount := paint("foo", fg = .RED);     ```     Using `vcount` you can now account for the shift in your application.     ----------------------     --- [ paint_ex() ] ---     ---------------------- @@ -118,149 +157,29 @@         - paint_raw()     Which basically only fprints this string: `"\u001b[%m%\u001b[0m"`.     --------------------------------------     --- [ Notes on Memory Allocation ] ---     --------------------------------------     All APIs returning a string, which you have to manage.     Depending on your use case, you might be fine with pushing it to     the temporary storage via comma-comma:         log(pain("Foo Baar", .BOLD, .BLACK, .WHITE,, temp));     or         context.allocator = temp;         log(pain("Foo Baar"));         log(pain("Foo Baar", .BOLD, .BLACK, .WHITE)); */ #import "Basic"; #import "String"; #import "Math"; #import "Flat_Pool"; paint :: (     str: string,     style: Text_Style = .RESET,     fg: Color_Foreground = .NONE,     bg: Color_Background = .NONE ) -> string {     return __paint(str, .[style], fg, bg, "", ""); } paint :: (     str: string,     style: []Text_Style = .[],     fg: Color_Foreground = .NONE,     bg: Color_Background = .NONE ) -> string {     return __paint(str, style, fg, bg, "", ""); } paint_ex :: (     str: string,     style: Text_Style = .RESET,     fg_color: Color_Table = .NONE,     bg_color: Color_Table = .NONE ) -> string {     return __paint(str, .[style], fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE     ); } paint_ex :: (     str: string,     style: Text_Style = .RESET,     fg_color: int = -1,     bg_color: int = -1 ) -> string {     return __paint(str, .[style], fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE     ); } paint_ex :: (     str: string,     style: []Text_Style = .[],     fg_color: int = -1,     bg_color: int = -1 ) -> string {     return __paint(str, style, fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE     ); } paint_ex :: (     str: string,     style: []Text_Style = .[],     fg_color: Color_Table = .NONE,     bg_color: Color_Table = .NONE ) -> string {     return __paint(str, style, fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE     ); } termcolors_context: #Context; termcolors_pool: Flat_Pool; paint_ex_custom :: (     str: string,     style: Text_Style = .RESET,     fg_color: $A,     bg_color: $B ) -> string {     return __paint(str, .[style], fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE     ); } paint_rgb :: (     str: string,     style: Text_Style = .RESET,     fg_rgb: Term_Rgb,     bg_rgb: Term_Rgb ) -> string {     return __paint_rgb(str, .[style], fg_rgb, bg_rgb); } paint_rgb :: (     str: string,     style: []Text_Style = .[],     fg_rgb: Term_Rgb,     bg_rgb: Term_Rgb ) -> string {     return __paint_rgb(str, style, fg_rgb, bg_rgb); } paint_raw :: (codes: string, str: string) -> string {     return sprint(#insert TERM_ESCAPE_CODE, codes, str); } // leaving those constants public, maybe someone wants to use them TERM_ESCAPE_START :: "\e[%m%"; TERM_ESCPAE_RESET :: "\e[0m"; FOREGROUND_COLOR_FROM_EXT_TABLE :: "38;5;"; BACKGROUND_COLOR_FROM_EXT_TABLE :: "48;5;"; Term_Rgb :: struct {     r, g, b: u8 = 255, 255, 255; } Text_Style :: enum #specified {     RESET               :: 0;     BOLD                :: 1; @@ -388,35 +307,202 @@ Color_Table :: enum #specified { } // leaving those constants public, maybe someone wants to use them TERM_ESCAPE_CODE :: #code "\u001b[%m%\u001b[0m"; FOREGROUND_COLOR_FROM_EXT_TABLE :: "38;5;"; BACKGROUND_COLOR_FROM_EXT_TABLE :: "48;5;"; termcolors_init_allocator_and_context :: () {     allocator: Allocator;     allocator.proc = flat_pool_allocator_proc;     allocator.data = *termcolors_pool;     termcolors_context.allocator = allocator; } termcolors_reset_pool :: (overwrite_memory: bool) {     reset(*termcolors_pool, overwrite_memory); } paint :: (     str: string,     style: Text_Style = .RESET,     fg: Color_Foreground = .NONE,     bg: Color_Background = .NONE,     no_termination := false ) -> string, int {     a, b := __paint(str, .[style], fg, bg, "", "", no_termination);     return a, b; } paint :: (     str: string,     style: []Text_Style = .[],     fg: Color_Foreground = .NONE,     bg: Color_Background = .NONE,     no_termination := false ) -> string, int {     a, b := __paint(str, style, fg, bg, "", "", no_termination);     return a, b; } paint_ex :: (     str: string,     style: Text_Style = .RESET,     fg_color: Color_Table = .NONE,     bg_color: Color_Table = .NONE,     no_termination := false ) -> string, int {     a, b := __paint(str, .[style], fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE,         no_termination     );     return a, b; } paint_ex :: (     str: string,     style: Text_Style = .RESET,     fg_color: int = -1,     bg_color: int = -1,     no_termination := false ) -> string, int {     a, b := __paint(str, .[style], fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE,         no_termination     );     return a, b; } paint_ex :: (     str: string,     style: []Text_Style = .[],     fg_color: int = -1,     bg_color: int = -1,     no_termination := false ) -> string, int {     a, b := __paint(str, style, fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE,         no_termination     );     return a, b; } paint_ex :: (     str: string,     style: []Text_Style = .[],     fg_color: Color_Table = .NONE,     bg_color: Color_Table = .NONE,     no_termination := false ) -> string, int {     a, b := __paint(str, style, fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE,         no_termination     );     return a, b; } paint_ex_custom :: (     str: string,     style: Text_Style = .RESET,     fg_color: $A,     bg_color: $B,     no_termination := false ) -> string, int {     a, b := __paint(str, .[style], fg_color, bg_color,         FOREGROUND_COLOR_FROM_EXT_TABLE,         BACKGROUND_COLOR_FROM_EXT_TABLE,         no_termination     );     return a, b; } paint_rgb :: (     str: string,     style: Text_Style = .RESET,     fg_rgb: Term_Rgb,     bg_rgb: Term_Rgb,     no_termination := false ) -> string, int {     a, b := __paint_rgb(str, .[style], fg_rgb, bg_rgb, no_termination);     return a, b; } paint_rgb :: (     str: string,     style: []Text_Style = .[],     fg_rgb: Term_Rgb,     bg_rgb: Term_Rgb,     no_termination := false ) -> string, int {     a, b := __paint_rgb(str, style, fg_rgb, bg_rgb, no_termination);     return a, b; } paint_raw :: (codes: string, str: string, no_termination: bool) -> string, int {     push_context termcolors_context {         out: string;         if no_termination {             out = sprint(                 TERM_ESCAPE_START,                 codes,                 str             );         } else {             out = sprint(                 #run -> string { return tprint("%%", TERM_ESCAPE_START, TERM_ESCPAE_RESET); },                 codes,                 str             );         }         return out, abs(out.count - str.count);     } } paint_reset :: () -> string, int {     out := TERM_ESCPAE_RESET;     return out, out.count; } #scope_file; // ---------------------------------------- // Internal Paint API // ------------------ __paint__build_style_str :: (style: []Text_Style) -> string {     buf_style: [..]string;     buf_style.allocator = temp;     for style  array_add(*buf_style, tprint("%", cast(int)it));     s_style := join(.. buf_style, ";",, temp);     return trim_right(s_style, ";",, temp);     for style  array_add(*buf_style, sprint("%", cast(int)it));     s_style := join(.. buf_style, ";");     return trim_right(s_style, ";"); } __paint__to_term_code_args :: () -> string #expand {     s := join(.. `buf, ";",, temp);     s = trim_right(s, ";",, temp);     return paint_raw(s, `str); __paint__to_term_code_args :: () -> string, int #expand {     s := join(.. `buf, ";");     s = trim_right(s, ";");     a, b := paint_raw(s, `str, `no_termination);     return a, b; } __paint__buffer_add_term_codes :: (color_type: string, term_color_code: int) #expand {     `buf[`count] = tprint("%0%", color_type, term_color_code); `count += 1;     `buf[`count] = sprint("%0%", color_type, term_color_code); `count += 1; } __paint__buffer_add_style :: () #expand { @@ -433,22 +519,27 @@ __paint :: (     fg: int,     bg: int,     fg_code: string,     bg_code: string ) -> string     bg_code: string,     no_termination := false ) -> string, int {     buf: [3]string;     count: int;     push_context termcolors_context {         buf: [3]string;         count: int;     __paint__buffer_add_style();         __paint__buffer_add_style();     if fg != -1 { __paint__buffer_add_term_codes(fg_code, fg); }     if bg != -1 { __paint__buffer_add_term_codes(bg_code, bg); }         if fg != -1 { __paint__buffer_add_term_codes(fg_code, fg); }         if bg != -1 { __paint__buffer_add_term_codes(bg_code, bg); }     if count == 0 {         return paint_raw("", str);     }         if count == 0 {             a, b := paint_raw("", str, no_termination);             return a, b;         }     return __paint__to_term_code_args();         a, b := __paint__to_term_code_args();         return a, b,;     } } __paint :: ( @@ -457,43 +548,52 @@ __paint :: (     fg: $A,     bg: $B,     fg_code: string,     bg_code: string ) -> string     bg_code: string,     no_termination := false ) -> string, int {     buf: [3]string;     count: int;     push_context termcolors_context {         buf: [3]string;         count: int;     __paint__buffer_add_style();         __paint__buffer_add_style();     if fg != .NONE { __paint__buffer_add_term_codes(fg_code, cast(int)fg); }     if bg != .NONE { __paint__buffer_add_term_codes(bg_code, cast(int)bg); }         if fg != .NONE { __paint__buffer_add_term_codes(fg_code, cast(int)fg); }         if bg != .NONE { __paint__buffer_add_term_codes(bg_code, cast(int)bg); }     if count == 0 {         return paint_raw("", str);     }         if count == 0 {             a, b := paint_raw("", str, no_termination);             return a, b;         }     return __paint__to_term_code_args();         a, b := __paint__to_term_code_args();         return a, b,;     } } __paint_rgb :: (     str: string,     style: []Text_Style,     fg_rgb: Term_Rgb,     bg_rgb: Term_Rgb ) -> string     bg_rgb: Term_Rgb,     no_termination := false ) -> string, int {     buf: [3]string;     push_context termcolors_context {         buf: [3]string;     if style.count > 0 {         buf[0] = __paint__build_style_str(style);     }         if style.count > 0 {             buf[0] = __paint__build_style_str(style);         }     buf[1] = tprint("38;2;%;%;%", fg_rgb.r, fg_rgb.g, fg_rgb.b);     buf[2] = tprint("48;2;%;%;%", bg_rgb.r, bg_rgb.g, bg_rgb.b);         buf[1] = sprint("38;2;%;%;%", fg_rgb.r, fg_rgb.g, fg_rgb.b);         buf[2] = sprint("48;2;%;%;%", bg_rgb.r, bg_rgb.g, bg_rgb.b);     s := join(.. buf, ";",, temp);     s = trim_right(s, ";",, temp);         s := join(.. buf, ";");         s = trim_right(s, ";");     return paint_raw(s, str);         a, b := paint_raw(s, str, no_termination);         return a, b;     } } diff --git a/test/test.jai b/test/test.jai index e2c61de..aeb138b 100644 --- a/test/test.jai +++ b/test/test.jai @@ -10,7 +10,11 @@ MEMORY_DEBUGGER :: false; main :: () {     #if MEMORY_DEBUGGER defer report_memory_leaks();     context.allocator = temp;     termcolors_init_allocator_and_context();     // Technically you don't need do reset the pool for this example.     // But I keep it here, so you're aware of this procedure.     defer termcolors_reset_pool(overwrite_memory = true);     log("--- paint() -----------------------------");     log(paint("1 Plain")); @@ -61,8 +65,8 @@ main :: () {     log(paint_rgb("13 Bold, Underline, Italic, fg rgb(255,0,0), bg rgb(0,0,255)", .[.BOLD, .UNDERLINE, .ITALIC], .{ 255, 0, 0 }, .{ 0, 0, 255 }));     print("\n\n");     log("--- paint_raw_temp() -----------------------------");     log(paint_raw("1;3;32;45", "14 tbd tbd tbd"));     log("--- paint_raw() -----------------------------");     log(paint_raw("1;3;32;45", "14 tbd tbd tbd", no_termination = false));     print("\n\n"); }