Logo

index : 0x2lib

Library extension for Jai

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/0x2lib.git/html/0x2_Termcolors.jai blob: b9fb31be60a954e14d0f900442f946c184aa14d0 [raw] [clear marker]

        
0/*
1
2 --------------------
3 --- [ Reminder ] ---
4 --------------------
5
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.
8
9 The builtin lib:
10
11 - prints the characters directly to stdout/stderr, instead of returning them
12 - uses a fixed default color palette, instead of supporting arbitrary values
13
14 If you don't need the features provided by this lib, consider using the builtin variant.
15
16
17 ----------------------------------
18 --- [ paint() / General Info ] ---
19 ----------------------------------
20
21 paint() uses the 4bit terminal color palette. The structure of the proc signature
22 through every API is the same:
23
24 `text, font style, foreground color, background color`
25
26 Only `text` is mandatory. Other params can be omitted since they default to `.NONE`.
27
28
29 [Note]: You can overload each proc for more flexibility.
30
31
32 This prints text without any formatting.
33 log(paint("Foo Bar"));
34
35 Applies a font weight, foreground and background colors
36 log(paint("Foo Bar", .BOLD, .BLACK, .WHITE));
37
38 Applies only foreground and background colors
39 log(paint("Foo Bar", .RESET, .BLACK, .WHITE));
40
41 Applies multiple text decorations
42 log(paint("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .BLACK, .WHITE));
43
44
45 --- [ Buffering Text ]
46
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.
49
50 When you're done with your string buffer, you can call the proc `paint_reset()`, which
51 returns the reset code.
52
53
54 --- [ Visual Width / Real Width ]
55
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
58 width.
59
60 Because of that, every API returns additionally an integer, which describes the count of
61 used characters by the terminal codes.
62
63 ```
64 my_str, vcount := paint("foo", fg = .RED);
65 ```
66
67 Using `vcount` you can now account for the shift in your application.
68
69
70 ------------------
71 --- [ Memory ] ---
72 ------------------
73
74 All strings returned must be freed by the caller. Internal procs are using the TS as
75 scratch buffer.
76
77
78 ----------------------
79 --- [ paint_ex() ] ---
80 ----------------------
81
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));
84
85 It also allows multiple font styles.
86 log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .GREEN_DARK, .ORANGE_LIGHT));
87
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));
91
92 You could even create a own color palette. Just create a enum with this signature:
93 My_Colors :: enum #specified {
94 NONE :: -1;
95 COL1 :: 84;
96 COL2 :: 124;
97 }
98
99 And use `paint_ex_custom()` like this:
100 log(paint_ex_custom("Foo Bar", .UNDERLINE, My_Colors.COL1, My_Colors.COL2));
101
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.
104
105
106 -----------------------
107 --- [ paint_rgb() ] ---
108 -----------------------
109
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 }));
113
114
115 Consult the enums below for more colors.
116
117
118 ---------------------------------------------------
119 --- [ Using String Literals as Terminal Codes ] ---
120 ---------------------------------------------------
121
122 If you need "raw" access because you want to build more complex stuff:
123 paint_raw();
124
125 Example:
126 log(paint_raw("1;3;32;45", "Foo Bar"));
127
128
129 --------------------------------
130 --- [ Notes on Performance ] ---
131 --------------------------------
132
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.
136
137 To bypass this, you can just use this proc:
138
139 - paint_raw()
140
141 Which basically only fprints this string: `"\u001b[%m%\u001b[0m"`.
142*/
143
144
145
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;";
151
152
153Term_Rgb :: struct {
154 r, g, b: u8 = 255, 255, 255;
155}
156
157Term_Text_Style :: enum #specified {
158 RESET :: 0;
159 BOLD :: 1;
160 FAINT :: 2; // not widely supported
161 ITALIC :: 3; // not widely supported
162 UNDERLINE :: 4;
163 SLOW_BLINK :: 5; // less than 150 bpm
164 RAPID_BLINK :: 6; // not widely supported
165 SWAP_FG_BG :: 7;
166 CONCEAL :: 8; // not widely supported
167 CROSSED_OUT :: 9; // not widely supported
168 PRIMARY_FONT :: 10;
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;
174 UNDERLINE_OFF :: 24;
175 BLINK_OFF :: 25;
176 // Code 26 does nothing (https://vt100.net/docs/vt510-rm/SGR.html)
177 INVERSE_OFF :: 27;
178 CONCEAL_OFF :: 28;
179 CROSSED_OUT_OFF :: 29;
180 FRAMED :: 51;
181 ENCIRCLED :: 52;
182 OVERLINED :: 53;
183 ENCIRCLED_OFF_FRAMED_OFF :: 54;
184 OVERLINED_OFF :: 55;
185 // 60 - 65 Ideograms hardly ever supported
186 // 90 - 107 Bright fg and bg color is non standard
187}
188
189Term_Color_Foreground :: enum #specified {
190 NONE :: -1;
191
192 BLACK :: 30;
193 RED :: 31;
194 GREEN :: 32;
195 YELLOW :: 33;
196 BLUE :: 34;
197 MAGENTA :: 35;
198 CYAN :: 36;
199 WHITE :: 37;
200 EXTEND :: 38; // 5;<Term_Color_Table> OR 2;<r>;<g>;<b>
201 DEFAULT :: 39;
202}
203
204Term_Color_Background :: enum #specified {
205 NONE :: -1;
206
207 BLACK :: 40;
208 RED :: 41;
209 GREEN :: 42;
210 YELLOW :: 43;
211 BLUE :: 44;
212 MAGENTA :: 45;
213 CYAN :: 46;
214 WHITE :: 47;
215 EXTEND :: 48; // 5;<Term_Color_Table> OR 2;<r>;<g>;<b>
216 DEFAULT :: 49;
217}
218
219Term_Color_Table :: enum #specified {
220 NONE :: -1;
221
222 // Standard
223 ST_BLACK :: 0;
224 ST_RED :: 1;
225 ST_GREEN :: 2;
226 ST_YELLOW :: 3;
227 ST_BLUE :: 4;
228 ST_PURPLE :: 5;
229 ST_TEAL :: 6;
230 ST_GRAY :: 7;
231
232 // High Intensity
233 HI_GRAY :: 8;
234 HI_RED :: 9;
235 HI_GREEN :: 10;
236 HI_YELLOW :: 11;
237 HI_BLUE :: 12;
238 HI_PURPLE :: 13;
239 HI_TEAL :: 14;
240 HI_WHITE :: 15;
241
242 // Selected Subset
243 BLACK :: 16;
244 WHITE :: 231;
245
246 GRAY_DARK :: 234;
247 GRAY_MID :: 243;
248 GRAY_LIGHT :: 250;
249
250 BLUE_DARK :: 17;
251 BLUE_MID :: 21;
252 BLUE_LIGHT :: 45;
253
254 GREEN_DARK :: 22;
255 GREEN_MID :: 34;
256 GREEN_LIGHT :: 46;
257
258 RED_DARK :: 52;
259 RED_MID :: 124;
260 RED_LIGHT :: 196;
261
262 ROSE_DARK :: 163;
263 ROSE_MID :: 201;
264 ROSE_LIGHT :: 213;
265
266 MINT_DARK :: 35;
267 MINT_MID :: 78;
268 MINT_LIGHT :: 84;
269
270 VIOLET_DARK :: 53;
271 VIOLET_MID :: 93;
272 VIOLET_LIGHT :: 141;
273
274 ORANGE_DARK :: 166;
275 ORANGE_MID :: 202;
276 ORANGE_LIGHT :: 214;
277
278 YELLOW_DARK :: 220;
279 YELLOW_MID :: 226;
280 YELLOW_LIGHT :: 228;
281}
282
283
284paint :: (
285 str: string,
286 style: Term_Text_Style = .RESET,
287 fg: Term_Color_Foreground = .NONE,
288 bg: Term_Color_Background = .NONE,
289 no_termination := false
290)
291 -> string, int
292{
293 a, b := base_paint(str, .[style], fg, bg, "", "", no_termination);
294 return a, b;
295}
296
297paint :: (
298 str: string,
299 style: []Term_Text_Style = .[],
300 fg: Term_Color_Foreground = .NONE,
301 bg: Term_Color_Background = .NONE,
302 no_termination := false
303)
304 -> string, int
305{
306 a, b := base_paint(str, style, fg, bg, "", "", no_termination);
307 return a, b;
308}
309
310paint_ex :: (
311 str: string,
312 style: Term_Text_Style = .RESET,
313 fg_color: Term_Color_Table = .NONE,
314 bg_color: Term_Color_Table = .NONE,
315 no_termination := false
316)
317 -> string, int
318{
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,
322 no_termination
323 );
324 return a, b;
325}
326
327paint_ex :: (
328 str: string,
329 style: Term_Text_Style = .RESET,
330 fg_color: int = -1,
331 bg_color: int = -1,
332 no_termination := false
333)
334 -> string, int
335{
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,
339 no_termination
340 );
341 return a, b;
342}
343
344paint_ex :: (
345 str: string,
346 style: []Term_Text_Style = .[],
347 fg_color: int = -1,
348 bg_color: int = -1,
349 no_termination := false
350)
351 -> string, int
352{
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,
356 no_termination
357 );
358 return a, b;
359}
360
361paint_ex :: (
362 str: string,
363 style: []Term_Text_Style = .[],
364 fg_color: Term_Color_Table = .NONE,
365 bg_color: Term_Color_Table = .NONE,
366 no_termination := false
367)
368 -> string, int
369{
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,
373 no_termination
374 );
375 return a, b;
376}
377
378
379paint_ex_custom :: (
380 str: string,
381 style: Term_Text_Style = .RESET,
382 fg_color: $A, // TODO: type??
383 bg_color: $B, // TODO: type??
384 no_termination := false
385)
386 -> string, int
387{
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,
391 no_termination
392 );
393 return a, b;
394}
395
396
397paint_rgb :: (
398 str: string,
399 style: Term_Text_Style = .RESET,
400 fg_rgb: Term_Rgb,
401 bg_rgb: Term_Rgb,
402 no_termination := false
403)
404 -> string, int
405{
406 a, b := base_paint_rgb(str, .[style], fg_rgb, bg_rgb, no_termination);
407 return a, b;
408}
409
410paint_rgb :: (
411 str: string,
412 style: []Term_Text_Style = .[],
413 fg_rgb: Term_Rgb,
414 bg_rgb: Term_Rgb,
415 no_termination := false
416)
417 -> string, int
418{
419 a, b := base_paint_rgb(str, style, fg_rgb, bg_rgb, no_termination);
420 return a, b;
421}
422
423paint_raw :: (codes: string, str: string, no_termination: bool) -> string, int {
424 out: string;
425
426 if no_termination {
427 out = sprint(
428 TERM_ESCAPE_START,
429 codes,
430 str
431 );
432 } else {
433 out = sprint(
434 #run -> string { return tprint("%%", TERM_ESCAPE_START, TERM_ESCPAE_RESET); },
435 codes,
436 str
437 );
438 }
439
440 return out, abs(out.count - str.count);
441}
442
443paint_reset :: () -> string, int {
444 out := TERM_ESCPAE_RESET;
445 return out, out.count;
446}
447
448
449#scope_file;
450
451
452build_style_str :: (style: []Term_Text_Style) -> string {
453 buf_style: [..]string;
454
455 for style array_add(*buf_style, sprint("%", cast(int)it));
456 s_style := join(.. buf_style, ";");
457 return trim_right(s_style, ";");
458}
459
460to_term_code_args :: () -> string, int #expand {
461 s := join(.. `buf, ";");
462 s = trim_right(s, ";");
463
464 a, b := paint_raw(s, `str, `no_termination);
465 return a, b;
466}
467
468buffer_add_term_codes :: (color_type: string, term_color_code: int) #expand {
469 `buf[`count] = sprint("%0%", color_type, term_color_code); `count += 1;
470}
471
472buffer_add_style :: () #expand {
473 if `style.count > 0 {
474 s_style := build_style_str(`style);
475 `buf[`count] = s_style;
476 `count += 1;
477 }
478}
479
480base_paint :: (
481 str: string,
482 style: []Term_Text_Style,
483 fg: int,
484 bg: int,
485 fg_code: string,
486 bg_code: string,
487 no_termination := false
488)
489 -> string, int
490{
491 push_allocator(temp);
492
493 buf: [3]string;
494 count: int;
495
496 buffer_add_style();
497
498 if fg != -1 { buffer_add_term_codes(fg_code, fg); }
499 if bg != -1 { buffer_add_term_codes(bg_code, bg); }
500
501 if count == 0 {
502 a, b := paint_raw("", str, no_termination);
503 return copy_string(a,, context.default_allocator), b;
504 }
505
506 a, b := to_term_code_args();
507 return copy_string(a,, context.default_allocator), b;
508}
509
510base_paint :: (
511 str: string,
512 style: []Term_Text_Style,
513 fg: $A, // TODO(adam, 5): This is garbage!
514 bg: $B, // TODO(adam, 5): This is garbage!
515 fg_code: string,
516 bg_code: string,
517 no_termination := false
518)
519 -> string, int
520{
521 push_allocator(temp);
522
523 buf: [3]string;
524 count: int;
525
526 buffer_add_style();
527
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); }
530
531 if count == 0 {
532 a, b := paint_raw("", str, no_termination);
533 return copy_string(a,, context.default_allocator), b;
534 }
535
536 a, b := to_term_code_args();
537 return copy_string(a,, context.default_allocator), b;
538}
539
540base_paint_rgb :: (
541 str: string,
542 style: []Term_Text_Style,
543 fg_rgb: Term_Rgb,
544 bg_rgb: Term_Rgb,
545 no_termination := false
546)
547 -> string, int
548{
549 push_allocator(temp);
550
551 buf: [3]string;
552
553 if style.count > 0 {
554 buf[0] = build_style_str(style);
555 }
556
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);
559
560 s := join(.. buf, ";");
561 s = trim_right(s, ";");
562
563 a, b := paint_raw(s, str, no_termination);
564 return copy_string(a,, context.default_allocator), b;
565}
566
567
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";
571
572
573/*
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
593SOFTWARE.
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------------------------------------------------------------------------------
613*/
614
615
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit