<<
path:
root/public/0x2lib.git/html/0x2_Termcolors.jai
blob: b9fb31be60a954e14d0f900442f946c184aa14d0
[raw]
[clear marker]
6 Jai already provides a module for console color codes: `modules/Print_Color.jai`.
7 There are small differences between the builtin lib and this lib.
11 - prints the characters directly to stdout/stderr, instead of returning them
12 - uses a fixed default color palette, instead of supporting arbitrary values
14 If you don't need the features provided by this lib, consider using the builtin variant.
17 ----------------------------------
18 --- [ paint() / General Info ] ---
19 ----------------------------------
21 paint() uses the 4bit terminal color palette. The structure of the proc signature
22 through every API is the same:
24 `text, font style, foreground color, background color`
26 Only `text` is mandatory. Other params can be omitted since they default to `.NONE`.
29 [Note]: You can overload each proc for more flexibility.
32 This prints text without any formatting.
33 log(paint("Foo Bar"));
35 Applies a font weight, foreground and background colors
36 log(paint("Foo Bar", .BOLD, .BLACK, .WHITE));
38 Applies only foreground and background colors
39 log(paint("Foo Bar", .RESET, .BLACK, .WHITE));
41 Applies multiple text decorations
42 log(paint("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .BLACK, .WHITE));
45 --- [ Buffering Text ]
47 If you want to buffer a lot of text, you can provide `no_termination = true` to the
48 APIs. Then they won't append the "reset" terminal code.
50 When you're done with your string buffer, you can call the proc `paint_reset()`, which
51 returns the reset code.
54 --- [ Visual Width / Real Width ]
56 If you do `paint("foo", fg = .RED).count`, it will return way more then three characters.
57 This can be quite annoying if you want to build an CLI program, that is aware of its
60 Because of that, every API returns additionally an integer, which describes the count of
61 used characters by the terminal codes.
64 my_str, vcount := paint("foo", fg = .RED);
67 Using `vcount` you can now account for the shift in your application.
74 All strings returned must be freed by the caller. Internal procs are using the TS as
78 ----------------------
79 --- [ paint_ex() ] ---
80 ----------------------
82 paint_ex() uses the 256bit color palette. There are some predefined colors you can use.
83 log(paint_ex("Foo Bar", .UNDERLINE, .GREEN_DARK, .ORANGE_LIGHT));
85 It also allows multiple font styles.
86 log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .GREEN_DARK, .ORANGE_LIGHT));
88 If you want to have more control over the colors, you can use integers.
89 log(paint_ex("Foo Bar", .UNDERLINE, 84, 124));
90 log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], 84, 124));
92 You could even create a own color palette. Just create a enum with this signature:
93 My_Colors :: enum #specified {
99 And use `paint_ex_custom()` like this:
100 log(paint_ex_custom("Foo Bar", .UNDERLINE, My_Colors.COL1, My_Colors.COL2));
102 The downside is, you cannot omit the fore- and background color. If you want that
103 feature, you have to wrap this proc in a custom proc.
106 -----------------------
107 --- [ paint_rgb() ] ---
108 -----------------------
110 If you want to use RGB values you can to it like that:
111 log(paint_rgb("Foo Bar", .UNDERLINE, .{ 255, 0, 0 }, .{ 0, 0, 255 }));
112 log(paint_rgb("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .{ 255, 0, 0 }, .{ 0, 0, 255 }));
115 Consult the enums below for more colors.
118 ---------------------------------------------------
119 --- [ Using String Literals as Terminal Codes ] ---
120 ---------------------------------------------------
122 If you need "raw" access because you want to build more complex stuff:
126 log(paint_raw("1;3;32;45", "Foo Bar"));
129 --------------------------------
130 --- [ Notes on Performance ] ---
131 --------------------------------
133 Since those APIs providing flexibility, they have to branch a few times.
134 Which won't be a negative hit on most programs. But if you're developing
135 something hyper-fast, those APIs could be a perf hit.
137 To bypass this, you can just use this proc:
141 Which basically only fprints this string: `"\u001b[%m%\u001b[0m"`.
146// leaving those constants public, maybe someone wants to use them
147TERM_ESCAPE_START :: "\e[%m%";
148TERM_ESCPAE_RESET :: "\e[0m";
149TERM_FOREGROUND_COLOR_FROM_EXT_TABLE :: "38;5;";
150TERM_BACKGROUND_COLOR_FROM_EXT_TABLE :: "48;5;";
154 r, g, b: u8 = 255, 255, 255;
157Term_Text_Style :: enum #specified {
160 FAINT :: 2; // not widely supported
161 ITALIC :: 3; // not widely supported
163 SLOW_BLINK :: 5; // less than 150 bpm
164 RAPID_BLINK :: 6; // not widely supported
166 CONCEAL :: 8; // not widely supported
167 CROSSED_OUT :: 9; // not widely supported
169 // omitting alternate fonts (11-19) since they aren't really supported anymore
170 FRAKTUR :: 20; // not widely supported
171 BOLD_OFF_OR_DOUBLE_UNDERLINE :: 21; // not widely supported
172 NORMAL_COLOR_OR_INTENSITY :: 22;
173 ITALIC_OFF_FRAKTUR_OFF :: 23;
176 // Code 26 does nothing (https://vt100.net/docs/vt510-rm/SGR.html)
179 CROSSED_OUT_OFF :: 29;
183 ENCIRCLED_OFF_FRAMED_OFF :: 54;
185 // 60 - 65 Ideograms hardly ever supported
186 // 90 - 107 Bright fg and bg color is non standard
189Term_Color_Foreground :: enum #specified {
200 EXTEND :: 38; // 5;<Term_Color_Table> OR 2;<r>;<g>;<b>
204Term_Color_Background :: enum #specified {
215 EXTEND :: 48; // 5;<Term_Color_Table> OR 2;<r>;<g>;<b>
219Term_Color_Table :: enum #specified {
286 style: Term_Text_Style = .RESET,
287 fg: Term_Color_Foreground = .NONE,
288 bg: Term_Color_Background = .NONE,
289 no_termination := false
293 a, b := base_paint(str, .[style], fg, bg, "", "", no_termination);
299 style: []Term_Text_Style = .[],
300 fg: Term_Color_Foreground = .NONE,
301 bg: Term_Color_Background = .NONE,
302 no_termination := false
306 a, b := base_paint(str, style, fg, bg, "", "", no_termination);
312 style: Term_Text_Style = .RESET,
313 fg_color: Term_Color_Table = .NONE,
314 bg_color: Term_Color_Table = .NONE,
315 no_termination := false
319 a, b := base_paint(str, .[style], fg_color, bg_color,
320 TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
321 TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
329 style: Term_Text_Style = .RESET,
332 no_termination := false
336 a, b := base_paint(str, .[style], fg_color, bg_color,
337 TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
338 TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
346 style: []Term_Text_Style = .[],
349 no_termination := false
353 a, b := base_paint(str, style, fg_color, bg_color,
354 TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
355 TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
363 style: []Term_Text_Style = .[],
364 fg_color: Term_Color_Table = .NONE,
365 bg_color: Term_Color_Table = .NONE,
366 no_termination := false
370 a, b := base_paint(str, style, fg_color, bg_color,
371 TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
372 TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
381 style: Term_Text_Style = .RESET,
382 fg_color: $A, // TODO: type??
383 bg_color: $B, // TODO: type??
384 no_termination := false
388 a, b := base_paint(str, .[style], fg_color, bg_color,
389 TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
390 TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
399 style: Term_Text_Style = .RESET,
402 no_termination := false
406 a, b := base_paint_rgb(str, .[style], fg_rgb, bg_rgb, no_termination);
412 style: []Term_Text_Style = .[],
415 no_termination := false
419 a, b := base_paint_rgb(str, style, fg_rgb, bg_rgb, no_termination);
423paint_raw :: (codes: string, str: string, no_termination: bool) -> string, int {
434 #run -> string { return tprint("%%", TERM_ESCAPE_START, TERM_ESCPAE_RESET); },
440 return out, abs(out.count - str.count);
443paint_reset :: () -> string, int {
444 out := TERM_ESCPAE_RESET;
445 return out, out.count;
452build_style_str :: (style: []Term_Text_Style) -> string {
453 buf_style: [..]string;
455 for style array_add(*buf_style, sprint("%", cast(int)it));
456 s_style := join(.. buf_style, ";");
457 return trim_right(s_style, ";");
460to_term_code_args :: () -> string, int #expand {
461 s := join(.. `buf, ";");
462 s = trim_right(s, ";");
464 a, b := paint_raw(s, `str, `no_termination);
468buffer_add_term_codes :: (color_type: string, term_color_code: int) #expand {
469 `buf[`count] = sprint("%0%", color_type, term_color_code); `count += 1;
472buffer_add_style :: () #expand {
473 if `style.count > 0 {
474 s_style := build_style_str(`style);
475 `buf[`count] = s_style;
482 style: []Term_Text_Style,
487 no_termination := false
491 push_allocator(temp);
498 if fg != -1 { buffer_add_term_codes(fg_code, fg); }
499 if bg != -1 { buffer_add_term_codes(bg_code, bg); }
502 a, b := paint_raw("", str, no_termination);
503 return copy_string(a,, context.default_allocator), b;
506 a, b := to_term_code_args();
507 return copy_string(a,, context.default_allocator), b;
512 style: []Term_Text_Style,
513 fg: $A, // TODO(adam, 5): This is garbage!
514 bg: $B, // TODO(adam, 5): This is garbage!
517 no_termination := false
521 push_allocator(temp);
528 if fg != .NONE { buffer_add_term_codes(fg_code, cast(int)fg); }
529 if bg != .NONE { buffer_add_term_codes(bg_code, cast(int)bg); }
532 a, b := paint_raw("", str, no_termination);
533 return copy_string(a,, context.default_allocator), b;
536 a, b := to_term_code_args();
537 return copy_string(a,, context.default_allocator), b;
542 style: []Term_Text_Style,
545 no_termination := false
549 push_allocator(temp);
554 buf[0] = build_style_str(style);
557 buf[1] = sprint("38;2;%;%;%", fg_rgb.r, fg_rgb.g, fg_rgb.b);
558 buf[2] = sprint("48;2;%;%;%", bg_rgb.r, bg_rgb.g, bg_rgb.b);
560 s := join(.. buf, ";");
561 s = trim_right(s, ";");
563 a, b := paint_raw(s, str, no_termination);
564 return copy_string(a,, context.default_allocator), b;
568using,only(array_add, copy_string, sprint, tprint, temp, push_allocator) Basic :: #import "Basic";
569using,only(join, trim_right) String :: #import "String";
570using,only(abs) Math :: #import "Math";
574------------------------------------------------------------------------------
575This software is available under 2 licenses -- choose whichever you prefer.
576------------------------------------------------------------------------------
577ALTERNATIVE A - MIT License
578Copyright (c) 2026 Adam Blazeowsky
579Permission is hereby granted, free of charge, to any person obtaining a copy of
580this software and associated documentation files (the "Software"), to deal in
581the Software without restriction, including without limitation the rights to
582use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
583of the Software, and to permit persons to whom the Software is furnished to do
584so, subject to the following conditions:
585The above copyright notice and this permission notice shall be included in all
586copies or substantial portions of the Software.
587THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
588IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
589FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
590AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
591LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
592OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
594------------------------------------------------------------------------------
595ALTERNATIVE B - Public Domain (www.unlicense.org)
596This is free and unencumbered software released into the public domain.
597Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
598software, either in source code form or as a compiled binary, for any purpose,
599commercial or non-commercial, and by any means.
600In jurisdictions that recognize copyright laws, the author or authors of this
601software dedicate any and all copyright interest in the software to the public
602domain. We make this dedication for the benefit of the public at large and to
603the detriment of our heirs and successors. We intend this dedication to be an
604overt act of relinquishment in perpetuity of all present and future rights to
605this software under copyright law.
606THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
607IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
608FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
609AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
610ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
611WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
612------------------------------------------------------------------------------