Logo

index : termcolors-jai

---

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/termcolors-jai.git/html/termcolors/lib.jai blob: 3437393121d045f893c1ce9adab7bafb73ab96e7 [raw] [clear marker]

        
0/*
1 * MIT License
2 *
3 * Copyright (c) 2025 dev@ptrace.dev
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 *
23 */
24
25/*
26 +Version: 2.0.0
27
28
29 -------------------
30 ---- [ Setup ] ----
31 -------------------
32
33 Initialize this lib via `termcolors_init_allocator_and_context()`.
34 It creates a arena for this lib which you can reset with `termcolors_reset_pool()`.
35
36 Everything is thrown into the arena, so you don't have to free individual strings.
37
38 The lib context is also exposed as: `termcolors_context`.
39 The arena is exposed as: `termcolors_pool`.
40
41
42 ----------------------------------
43 --- [ paint() / General Info ] ---
44 ----------------------------------
45
46 paint() uses the 4bit terminal color palette. The structure of the proc signature
47 through every API is the same:
48
49 `text, font style, foreground color, background color`
50
51 Only `text` is mandatory. Other params can be omitted since they default to `.NONE`.
52
53
54 [Note]: You can overload each proc for more flexibility.
55
56
57 This prints text without any formatting.
58 log(paint("Foo Bar"));
59
60 Applies a font weight, foreground and background colors
61 log(paint("Foo Bar", .BOLD, .BLACK, .WHITE));
62
63 Applies only foreground and background colors
64 log(paint("Foo Bar", .RESET, .BLACK, .WHITE));
65
66 Applies multiple text decorations
67 log(paint("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .BLACK, .WHITE));
68
69
70 --- [ Buffering Text ]
71
72 If you want to buffer a lot of text, you can provide `no_termination = true` to the
73 APIs. Then they won't append the "reset" terminal code.
74
75 When you're done with your string buffer, you can call the proc `paint_reset()`, which
76 returns the reset code.
77
78
79 --- [ Visual Width / Real Width ]
80
81 If you do `paint("foo", fg = .RED).count`, it will return way more then three characters.
82 This can be quite annoying if you want to build an CLI program, that is aware of its
83 width.
84
85 Because of that, every API returns additionally an integer, which describes the count of
86 used characters by the terminal codes.
87
88 ```
89 my_str, vcount := paint("foo", fg = .RED);
90 ```
91
92 Using `vcount` you can now account for the shift in your application.
93
94
95 ----------------------
96 --- [ paint_ex() ] ---
97 ----------------------
98
99 paint_ex() uses the 256bit color palette. There are some predefined colors you can use.
100 log(paint_ex("Foo Bar", .UNDERLINE, .GREEN_DARK, .ORANGE_LIGHT));
101
102 It also allows multiple font styles.
103 log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .GREEN_DARK, .ORANGE_LIGHT));
104
105 If you want to have more control over the colors, you can use integers.
106 log(paint_ex("Foo Bar", .UNDERLINE, 84, 124));
107 log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], 84, 124));
108
109 You could even create a own color palette. Just create a enum with this signature:
110 My_Colors :: enum #specified {
111 NONE :: -1;
112 COL1 :: 84;
113 COL2 :: 124;
114 }
115
116 And use `paint_ex_custom()` like this:
117 log(paint_ex_custom("Foo Bar", .UNDERLINE, My_Colors.COL1, My_Colors.COL2));
118
119 The downside is, you cannot omit the fore- and background color. If you want that
120 feature, you have to wrap this proc in a custom proc.
121
122
123 -----------------------
124 --- [ paint_rgb() ] ---
125 -----------------------
126
127 If you want to use RGB values you can to it like that:
128 log(paint_rgb("Foo Bar", .UNDERLINE, .{ 255, 0, 0 }, .{ 0, 0, 255 }));
129 log(paint_rgb("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .{ 255, 0, 0 }, .{ 0, 0, 255 }));
130
131
132 Consult the enums below for more colors.
133
134
135 ---------------------------------------------------
136 --- [ Using String Literals as Terminal Codes ] ---
137 ---------------------------------------------------
138
139 If you need "raw" access because you want to build more complex stuff:
140 paint_raw();
141
142 Example:
143 log(paint_raw("1;3;32;45", "Foo Bar"));
144
145
146 --------------------------------
147 --- [ Notes on Performance ] ---
148 --------------------------------
149
150 Since those APIs providing flexibility, they have to branch a few times.
151 Which won't be a negative hit on most programs. But if you're developing
152 something hyper-fast, those APIs could be a perf hit.
153
154 To bypass this, you can just use this proc:
155
156 - paint_raw()
157
158 Which basically only fprints this string: `"\u001b[%m%\u001b[0m"`.
159*/
160
161#import "Basic";
162#import "String";
163#import "Math";
164#import "Flat_Pool";
165
166
167termcolors_context: #Context;
168termcolors_pool: Flat_Pool;
169
170
171// leaving those constants public, maybe someone wants to use them
172TERM_ESCAPE_START :: "\e[%m%";
173TERM_ESCPAE_RESET :: "\e[0m";
174FOREGROUND_COLOR_FROM_EXT_TABLE :: "38;5;";
175BACKGROUND_COLOR_FROM_EXT_TABLE :: "48;5;";
176
177
178Term_Rgb :: struct {
179 r, g, b: u8 = 255, 255, 255;
180}
181
182Text_Style :: enum #specified {
183 RESET :: 0;
184 BOLD :: 1;
185 FAINT :: 2; // not widely supported
186 ITALIC :: 3; // not widely supported
187 UNDERLINE :: 4;
188 SLOW_BLINK :: 5; // less than 150 bpm
189 RAPID_BLINK :: 6; // not widely supported
190 SWAP_FG_BG :: 7;
191 CONCEAL :: 8; // not widely supported
192 CROSSED_OUT :: 9; // not widely supported
193 PRIMARY_FONT :: 10;
194 // omitting alternate fonts (11-19) since they aren't really supported anymore
195 FRAKTUR :: 20; // not widely supported
196 BOLD_OFF_OR_DOUBLE_UNDERLINE :: 21; // not widely supported
197 NORMAL_COLOR_OR_INTENSITY :: 22;
198 ITALIC_OFF_FRAKTUR_OFF :: 23;
199 UNDERLINE_OFF :: 24;
200 BLINK_OFF :: 25;
201 // Code 26 does nothing (https://vt100.net/docs/vt510-rm/SGR.html)
202 INVERSE_OFF :: 27;
203 CONCEAL_OFF :: 28;
204 CROSSED_OUT_OFF :: 29;
205 FRAMED :: 51;
206 ENCIRCLED :: 52;
207 OVERLINED :: 53;
208 ENCIRCLED_OFF_FRAMED_OFF :: 54;
209 OVERLINED_OFF :: 55;
210 // 60 - 65 Ideograms hardly ever supported
211 // 90 - 107 Bright fg and bg color is non standard
212}
213
214Color_Foreground :: enum #specified {
215 NONE :: -1;
216
217 BLACK :: 30;
218 RED :: 31;
219 GREEN :: 32;
220 YELLOW :: 33;
221 BLUE :: 34;
222 MAGENTA :: 35;
223 CYAN :: 36;
224 WHITE :: 37;
225 EXTEND :: 38; // 5;<Color_Table> OR 2;<r>;<g>;<b>
226 DEFAULT :: 39;
227}
228
229Color_Background :: enum #specified {
230 NONE :: -1;
231
232 BLACK :: 40;
233 RED :: 41;
234 GREEN :: 42;
235 YELLOW :: 43;
236 BLUE :: 44;
237 MAGENTA :: 45;
238 CYAN :: 46;
239 WHITE :: 47;
240 EXTEND :: 48; // 5;<Color_Table> OR 2;<r>;<g>;<b>
241 DEFAULT :: 49;
242}
243
244Color_Table :: enum #specified {
245 NONE :: -1;
246
247 // Standard
248 ST_BLACK :: 0;
249 ST_RED :: 1;
250 ST_GREEN :: 2;
251 ST_YELLOW :: 3;
252 ST_BLUE :: 4;
253 ST_PURPLE :: 5;
254 ST_TEAL :: 6;
255 ST_GRAY :: 7;
256
257 // High Intensity
258 HI_GRAY :: 8;
259 HI_RED :: 9;
260 HI_GREEN :: 10;
261 HI_YELLOW :: 11;
262 HI_BLUE :: 12;
263 HI_PURPLE :: 13;
264 HI_TEAL :: 14;
265 HI_WHITE :: 15;
266
267 // Selected Subset
268 BLACK :: 16;
269 WHITE :: 231;
270
271 GRAY_DARK :: 234;
272 GRAY_MID :: 243;
273 GRAY_LIGHT :: 250;
274
275 BLUE_DARK :: 17;
276 BLUE_MID :: 21;
277 BLUE_LIGHT :: 45;
278
279 GREEN_DARK :: 22;
280 GREEN_MID :: 34;
281 GREEN_LIGHT :: 46;
282
283 RED_DARK :: 52;
284 RED_MID :: 124;
285 RED_LIGHT :: 196;
286
287 ROSE_DARK :: 163;
288 ROSE_MID :: 201;
289 ROSE_LIGHT :: 213;
290
291 MINT_DARK :: 35;
292 MINT_MID :: 78;
293 MINT_LIGHT :: 84;
294
295 VIOLET_DARK :: 53;
296 VIOLET_MID :: 93;
297 VIOLET_LIGHT :: 141;
298
299 ORANGE_DARK :: 166;
300 ORANGE_MID :: 202;
301 ORANGE_LIGHT :: 214;
302
303 YELLOW_DARK :: 220;
304 YELLOW_MID :: 226;
305 YELLOW_LIGHT :: 228;
306}
307
308
309termcolors_init_allocator_and_context :: () {
310 allocator: Allocator;
311 allocator.proc = flat_pool_allocator_proc;
312 allocator.data = *termcolors_pool;
313
314 termcolors_context.allocator = allocator;
315}
316
317termcolors_reset_pool :: (overwrite_memory: bool) {
318 reset(*termcolors_pool, overwrite_memory);
319}
320
321
322paint :: (
323 str: string,
324 style: Text_Style = .RESET,
325 fg: Color_Foreground = .NONE,
326 bg: Color_Background = .NONE,
327 no_termination := false
328) -> string, int
329{
330 a, b := __paint(str, .[style], fg, bg, "", "", no_termination);
331 return a, b;
332}
333
334paint :: (
335 str: string,
336 style: []Text_Style = .[],
337 fg: Color_Foreground = .NONE,
338 bg: Color_Background = .NONE,
339 no_termination := false
340) -> string, int
341{
342 a, b := __paint(str, style, fg, bg, "", "", no_termination);
343 return a, b;
344}
345
346paint_ex :: (
347 str: string,
348 style: Text_Style = .RESET,
349 fg_color: Color_Table = .NONE,
350 bg_color: Color_Table = .NONE,
351 no_termination := false
352) -> string, int
353{
354 a, b := __paint(str, .[style], fg_color, bg_color,
355 FOREGROUND_COLOR_FROM_EXT_TABLE,
356 BACKGROUND_COLOR_FROM_EXT_TABLE,
357 no_termination
358 );
359 return a, b;
360}
361
362paint_ex :: (
363 str: string,
364 style: Text_Style = .RESET,
365 fg_color: int = -1,
366 bg_color: int = -1,
367 no_termination := false
368) -> string, int
369{
370 a, b := __paint(str, .[style], fg_color, bg_color,
371 FOREGROUND_COLOR_FROM_EXT_TABLE,
372 BACKGROUND_COLOR_FROM_EXT_TABLE,
373 no_termination
374 );
375 return a, b;
376}
377
378paint_ex :: (
379 str: string,
380 style: []Text_Style = .[],
381 fg_color: int = -1,
382 bg_color: int = -1,
383 no_termination := false
384) -> string, int
385{
386 a, b := __paint(str, style, fg_color, bg_color,
387 FOREGROUND_COLOR_FROM_EXT_TABLE,
388 BACKGROUND_COLOR_FROM_EXT_TABLE,
389 no_termination
390 );
391 return a, b;
392}
393
394paint_ex :: (
395 str: string,
396 style: []Text_Style = .[],
397 fg_color: Color_Table = .NONE,
398 bg_color: Color_Table = .NONE,
399 no_termination := false
400) -> string, int
401{
402 a, b := __paint(str, style, fg_color, bg_color,
403 FOREGROUND_COLOR_FROM_EXT_TABLE,
404 BACKGROUND_COLOR_FROM_EXT_TABLE,
405 no_termination
406 );
407 return a, b;
408}
409
410
411paint_ex_custom :: (
412 str: string,
413 style: Text_Style = .RESET,
414 fg_color: $A,
415 bg_color: $B,
416 no_termination := false
417) -> string, int
418{
419 a, b := __paint(str, .[style], fg_color, bg_color,
420 FOREGROUND_COLOR_FROM_EXT_TABLE,
421 BACKGROUND_COLOR_FROM_EXT_TABLE,
422 no_termination
423 );
424 return a, b;
425}
426
427
428paint_rgb :: (
429 str: string,
430 style: Text_Style = .RESET,
431 fg_rgb: Term_Rgb,
432 bg_rgb: Term_Rgb,
433 no_termination := false
434) -> string, int
435{
436 a, b := __paint_rgb(str, .[style], fg_rgb, bg_rgb, no_termination);
437 return a, b;
438}
439
440paint_rgb :: (
441 str: string,
442 style: []Text_Style = .[],
443 fg_rgb: Term_Rgb,
444 bg_rgb: Term_Rgb,
445 no_termination := false
446) -> string, int
447{
448 a, b := __paint_rgb(str, style, fg_rgb, bg_rgb, no_termination);
449 return a, b;
450}
451
452paint_raw :: (codes: string, str: string, no_termination: bool) -> string, int {
453 push_context termcolors_context {
454 out: string;
455
456 if no_termination {
457 out = sprint(
458 TERM_ESCAPE_START,
459 codes,
460 str
461 );
462 } else {
463 out = sprint(
464 #run -> string { return tprint("%%", TERM_ESCAPE_START, TERM_ESCPAE_RESET); },
465 codes,
466 str
467 );
468 }
469
470 return out, abs(out.count - str.count);
471 }
472}
473
474paint_reset :: () -> string, int {
475 out := TERM_ESCPAE_RESET;
476 return out, out.count;
477}
478
479
480#scope_file;
481
482
483// ----------------------------------------
484// Internal Paint API
485// ------------------
486
487__paint__build_style_str :: (style: []Text_Style) -> string {
488 buf_style: [..]string;
489
490 for style array_add(*buf_style, sprint("%", cast(int)it));
491 s_style := join(.. buf_style, ";");
492 return trim_right(s_style, ";");
493}
494
495__paint__to_term_code_args :: () -> string, int #expand {
496 s := join(.. `buf, ";");
497 s = trim_right(s, ";");
498
499 a, b := paint_raw(s, `str, `no_termination);
500 return a, b;
501}
502
503__paint__buffer_add_term_codes :: (color_type: string, term_color_code: int) #expand {
504 `buf[`count] = sprint("%0%", color_type, term_color_code); `count += 1;
505}
506
507__paint__buffer_add_style :: () #expand {
508 if `style.count > 0 {
509 s_style := __paint__build_style_str(`style);
510 `buf[`count] = s_style;
511 `count += 1;
512 }
513}
514
515__paint :: (
516 str: string,
517 style: []Text_Style,
518 fg: int,
519 bg: int,
520 fg_code: string,
521 bg_code: string,
522 no_termination := false
523) -> string, int
524{
525 push_context termcolors_context {
526 buf: [3]string;
527 count: int;
528
529 __paint__buffer_add_style();
530
531 if fg != -1 { __paint__buffer_add_term_codes(fg_code, fg); }
532 if bg != -1 { __paint__buffer_add_term_codes(bg_code, bg); }
533
534 if count == 0 {
535 a, b := paint_raw("", str, no_termination);
536 return a, b;
537 }
538
539 a, b := __paint__to_term_code_args();
540 return a, b,;
541 }
542}
543
544__paint :: (
545 str: string,
546 style: []Text_Style,
547 fg: $A,
548 bg: $B,
549 fg_code: string,
550 bg_code: string,
551 no_termination := false
552) -> string, int
553{
554 push_context termcolors_context {
555 buf: [3]string;
556 count: int;
557
558 __paint__buffer_add_style();
559
560 if fg != .NONE { __paint__buffer_add_term_codes(fg_code, cast(int)fg); }
561 if bg != .NONE { __paint__buffer_add_term_codes(bg_code, cast(int)bg); }
562
563 if count == 0 {
564 a, b := paint_raw("", str, no_termination);
565 return a, b;
566 }
567
568 a, b := __paint__to_term_code_args();
569 return a, b,;
570 }
571}
572
573__paint_rgb :: (
574 str: string,
575 style: []Text_Style,
576 fg_rgb: Term_Rgb,
577 bg_rgb: Term_Rgb,
578 no_termination := false
579) -> string, int
580{
581 push_context termcolors_context {
582 buf: [3]string;
583
584 if style.count > 0 {
585 buf[0] = __paint__build_style_str(style);
586 }
587
588 buf[1] = sprint("38;2;%;%;%", fg_rgb.r, fg_rgb.g, fg_rgb.b);
589 buf[2] = sprint("48;2;%;%;%", bg_rgb.r, bg_rgb.g, bg_rgb.b);
590
591 s := join(.. buf, ";");
592 s = trim_right(s, ";");
593
594 a, b := paint_raw(s, str, no_termination);
595 return a, b;
596 }
597}
598
599
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit