]> git.ipfire.org Git - thirdparty/gcc.git/blame - gcc/text-art/style.cc
Update copyright years.
[thirdparty/gcc.git] / gcc / text-art / style.cc
CommitLineData
4f01ae37 1/* Classes for styling text cells (color, URLs).
a945c346 2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
4f01ae37
DM
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along with GCC; see the file COPYING3. If not see
19<http://www.gnu.org/licenses/>. */
20
21#include "config.h"
22#define INCLUDE_ALGORITHM
23#define INCLUDE_MEMORY
b2e075a5 24#define INCLUDE_VECTOR
4f01ae37
DM
25#include "system.h"
26#include "coretypes.h"
27#include "make-unique.h"
28#include "pretty-print.h"
29#include "intl.h"
30#include "selftest.h"
31#include "text-art/selftests.h"
32#include "text-art/types.h"
33#include "color-macros.h"
34
35using namespace text_art;
36
37/* class text_art::style. */
38
39style &
40style::set_style_url (const char *url)
41{
42 m_url.clear ();
43 while (*url)
44 m_url.push_back (*(url++));
45 return *this;
46}
47
48/* class text_art::style::color. */
49
50bool
51style::color::operator== (const style::color &other) const
52{
53 if (m_kind != other.m_kind)
54 return false;
55 switch (m_kind)
56 {
57 default:
58 gcc_unreachable ();
59 case kind::NAMED:
60 return (u.m_named.m_name == other.u.m_named.m_name
61 && u.m_named.m_bright == other.u.m_named.m_bright);
62 case kind::BITS_8:
63 return u.m_8bit == other.u.m_8bit;
64 case kind::BITS_24:
65 return (u.m_24bit.r == other.u.m_24bit.r
66 && u.m_24bit.g == other.u.m_24bit.g
67 && u.m_24bit.b == other.u.m_24bit.b);
68 }
69}
70
71static void
72ensure_separator (pretty_printer *pp, bool &need_separator)
73{
74 if (need_separator)
75 pp_string (pp, COLOR_SEPARATOR);
76 need_separator = true;
77}
78
79void
80style::color::print_sgr (pretty_printer *pp,
81 bool fg,
82 bool &need_separator) const
83{
84 switch (m_kind)
85 {
86 default:
87 gcc_unreachable ();
88 case kind::NAMED:
89 {
90 static const char * const fg_normal[] = {"", // reset, for DEFAULT
91 COLOR_FG_BLACK,
92 COLOR_FG_RED,
93 COLOR_FG_GREEN,
94 COLOR_FG_YELLOW,
95 COLOR_FG_BLUE,
96 COLOR_FG_MAGENTA,
97 COLOR_FG_CYAN,
98 COLOR_FG_WHITE};
99 static const char * const fg_bright[] = {"", // reset, for DEFAULT
100 COLOR_FG_BRIGHT_BLACK,
101 COLOR_FG_BRIGHT_RED,
102 COLOR_FG_BRIGHT_GREEN,
103 COLOR_FG_BRIGHT_YELLOW,
104 COLOR_FG_BRIGHT_BLUE,
105 COLOR_FG_BRIGHT_MAGENTA,
106 COLOR_FG_BRIGHT_CYAN,
107 COLOR_FG_BRIGHT_WHITE};
108 static const char * const bg_normal[] = {"", // reset, for DEFAULT
109 COLOR_BG_BLACK,
110 COLOR_BG_RED,
111 COLOR_BG_GREEN,
112 COLOR_BG_YELLOW,
113 COLOR_BG_BLUE,
114 COLOR_BG_MAGENTA,
115 COLOR_BG_CYAN,
116 COLOR_BG_WHITE};
117 static const char * const bg_bright[] = {"", // reset, for DEFAULT
118 COLOR_BG_BRIGHT_BLACK,
119 COLOR_BG_BRIGHT_RED,
120 COLOR_BG_BRIGHT_GREEN,
121 COLOR_BG_BRIGHT_YELLOW,
122 COLOR_BG_BRIGHT_BLUE,
123 COLOR_BG_BRIGHT_MAGENTA,
124 COLOR_BG_BRIGHT_CYAN,
125 COLOR_BG_BRIGHT_WHITE};
126 STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright));
127 STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal));
128 STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright));
129 gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal));
130 const char *const *arr;
131 if (fg)
132 arr = u.m_named.m_bright ? fg_bright : fg_normal;
133 else
134 arr = u.m_named.m_bright ? bg_bright : bg_normal;
135 const char *str = arr[(size_t)u.m_named.m_name];
136 if (strlen (str) > 0)
137 {
138 ensure_separator (pp, need_separator);
139 pp_string (pp, str);
140 }
141 }
142 break;
143 case kind::BITS_8:
144 {
145 ensure_separator (pp, need_separator);
146 if (fg)
147 pp_string (pp, "38");
148 else
149 pp_string (pp, "48");
150 pp_printf (pp, ";5;%i", (int)u.m_8bit);
151 }
152 break;
153 case kind::BITS_24:
154 {
155 ensure_separator (pp, need_separator);
156 if (fg)
157 pp_string (pp, "38");
158 else
159 pp_string (pp, "48");
160 pp_printf (pp, ";2;%i;%i;%i",
161 (int)u.m_24bit.r,
162 (int)u.m_24bit.g,
163 (int)u.m_24bit.b);
164 }
165 break;
166 }
167}
168
169/* class text_art::style. */
170
171/* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
172 GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or
173 "CUMULATIVE", which affects whether we need to respecify all attributes
174 at each SGR, or can accumulate them. Looks like we can't rely on the value
175 of this, so we have to emit a single SGR for all changes, with a "0" reset
176 at the front, forcing it to be effectively replacing. */
177
178void
179style::print_changes (pretty_printer *pp,
180 const style &old_style,
181 const style &new_style)
182{
183 if (pp_show_color (pp))
184 {
185 bool needs_sgr = ((old_style.m_bold != new_style.m_bold)
186 || (old_style.m_underscore != new_style.m_underscore)
187 || (old_style.m_blink != new_style.m_blink)
188 || (old_style.m_fg_color != new_style.m_fg_color)
189 || (old_style.m_bg_color != new_style.m_bg_color));
190 if (needs_sgr)
191 {
192 bool emit_reset = (old_style.m_bold
193 || new_style.m_bold
194 || old_style.m_underscore
195 || new_style.m_underscore
196 || old_style.m_blink
197 || new_style.m_blink);
198 bool need_separator = false;
199
200 pp_string (pp, SGR_START);
201 if (emit_reset)
202 {
203 pp_string (pp, COLOR_NONE);
204 need_separator = true;
205 }
206 if (new_style.m_bold)
207 {
208 gcc_assert (emit_reset);
209 ensure_separator (pp, need_separator);
210 pp_string (pp, COLOR_BOLD);
211 }
212 if (new_style.m_underscore)
213 {
214 gcc_assert (emit_reset);
215 ensure_separator (pp, need_separator);
216 pp_string (pp, COLOR_UNDERSCORE);
217 }
218 if (new_style.m_blink)
219 {
220 gcc_assert (emit_reset);
221 ensure_separator (pp, need_separator);
222 pp_string (pp, COLOR_BLINK);
223 }
224 new_style.m_fg_color.print_sgr (pp, true, need_separator);
225 new_style.m_bg_color.print_sgr (pp, false, need_separator);
226 pp_string (pp, SGR_END);
227 }
228 }
229
230 if (old_style.m_url != new_style.m_url)
231 {
232 if (!old_style.m_url.empty ())
233 pp_end_url (pp);
234 if (pp->url_format != URL_FORMAT_NONE
235 && !new_style.m_url.empty ())
236 {
237 /* Adapted from pp_begin_url, but encoding the
238 chars to UTF-8 on the fly, rather than converting
239 to a buffer. */
240 pp_string (pp, "\33]8;;");
241 for (auto ch : new_style.m_url)
242 pp_unicode_character (pp, ch);
243 switch (pp->url_format)
244 {
245 default:
246 case URL_FORMAT_NONE:
247 gcc_unreachable ();
248 case URL_FORMAT_ST:
249 pp_string (pp, "\33\\");
250 break;
251 case URL_FORMAT_BEL:
252 pp_string (pp, "\a");
253 break;
254 }
255 }
256 }
257}
258
259/* class text_art::style_manager. */
260
261style_manager::style_manager ()
262{
263 // index 0 will be the default style
264 m_styles.push_back (style ());
265}
266
267style::id_t
268style_manager::get_or_create_id (const style &s)
269{
270 // For now, linear search
271 std::vector<style>::iterator existing
272 (std::find (m_styles.begin (), m_styles.end (), s));
273
274 /* If found, return index of slot. */
275 if (existing != m_styles.end ())
276 return std::distance (m_styles.begin (), existing);
277
278 /* Not found. */
279
280 /* styled_str uses 7 bits for style information, so we can only support
281 up to 128 different style combinations.
282 Gracefully fail by turning off styling when this limit is reached. */
283 if (m_styles.size () >= 127)
284 return 0;
285
286 m_styles.push_back (s);
287 return m_styles.size () - 1;
288}
289
290void
291style_manager::print_any_style_changes (pretty_printer *pp,
292 style::id_t old_id,
293 style::id_t new_id) const
294{
295 gcc_assert (pp);
296 if (old_id == new_id)
297 return;
298
299 const style &old_style = m_styles[old_id];
300 const style &new_style = m_styles[new_id];
301 gcc_assert (!(old_style == new_style));
302 style::print_changes (pp, old_style, new_style);
303}
304
305#if CHECKING_P
306
307namespace selftest {
308
309void
310assert_style_change_streq (const location &loc,
311 const style &old_style,
312 const style &new_style,
313 const char *expected_str)
314{
315 pretty_printer pp;
316 pp_show_color (&pp) = true;
317 style::print_changes (&pp, old_style, new_style);
318 ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str);
319}
320
321#define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
322 SELFTEST_BEGIN_STMT \
323 assert_style_change_streq ((SELFTEST_LOCATION), \
324 (OLD_STYLE), \
325 (NEW_STYLE), \
326 (EXPECTED_STR)); \
327 SELFTEST_END_STMT
328
329static void
330test_bold ()
331{
332 style_manager sm;
333 ASSERT_EQ (sm.get_num_styles (), 1);
334
335 style plain;
336 ASSERT_EQ (sm.get_or_create_id (plain), 0);
337 ASSERT_EQ (sm.get_num_styles (), 1);
338
339 style bold;
340 bold.m_bold = true;
341
342 ASSERT_EQ (sm.get_or_create_id (bold), 1);
343 ASSERT_EQ (sm.get_num_styles (), 2);
344 ASSERT_EQ (sm.get_or_create_id (bold), 1);
345 ASSERT_EQ (sm.get_num_styles (), 2);
346
347 ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K");
348 ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K");
349}
350
351static void
352test_underscore ()
353{
354 style_manager sm;
355 ASSERT_EQ (sm.get_num_styles (), 1);
356
357 style plain;
358 ASSERT_EQ (sm.get_or_create_id (plain), 0);
359 ASSERT_EQ (sm.get_num_styles (), 1);
360
361 style underscore;
362 underscore.m_underscore = true;
363
364 ASSERT_EQ (sm.get_or_create_id (underscore), 1);
365 ASSERT_EQ (sm.get_num_styles (), 2);
366 ASSERT_EQ (sm.get_or_create_id (underscore), 1);
367 ASSERT_EQ (sm.get_num_styles (), 2);
368
369 ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K");
370 ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K");
371}
372
373static void
374test_blink ()
375{
376 style_manager sm;
377 ASSERT_EQ (sm.get_num_styles (), 1);
378
379 style plain;
380 ASSERT_EQ (sm.get_or_create_id (plain), 0);
381 ASSERT_EQ (sm.get_num_styles (), 1);
382
383 style blink;
384 blink.m_blink = true;
385
386 ASSERT_EQ (sm.get_or_create_id (blink), 1);
387 ASSERT_EQ (sm.get_num_styles (), 2);
388 ASSERT_EQ (sm.get_or_create_id (blink), 1);
389 ASSERT_EQ (sm.get_num_styles (), 2);
390
391 ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K");
392 ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K");
393}
394
395#define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
396 SELFTEST_BEGIN_STMT \
397 { \
398 style plain; \
399 style s; \
400 if (FG) \
401 s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
402 else \
403 s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
404 assert_style_change_streq ((SELFTEST_LOCATION), \
405 plain, \
406 s, \
407 (EXPECTED_STR)); \
408 } \
409 SELFTEST_END_STMT
410
411static void
412test_named_colors ()
413{
414 /* Foreground colors. */
415 {
416 const bool fg = true;
417 {
418 const bool bright = false;
419 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
420 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
421 "\e[30m\e[K");
422 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
423 "\e[31m\e[K");
424 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
425 "\e[32m\e[K");
426 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
427 "\e[33m\e[K");
428 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
429 "\e[34m\e[K");
430 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
431 "\e[35m\e[K");
432 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
433 "\e[36m\e[K");
434 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
435 "\e[37m\e[K");
436 }
437 {
438 const bool bright = true;
439 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
440 "\e[m\e[K");
441 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
442 "\e[90m\e[K");
443 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
444 "\e[91m\e[K");
445 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
446 "\e[92m\e[K");
447 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
448 "\e[93m\e[K");
449 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
450 "\e[94m\e[K");
451 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
452 "\e[95m\e[K");
453 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
454 "\e[96m\e[K");
455 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
456 "\e[97m\e[K");
457 }
458 }
459
460 /* Background colors. */
461 {
462 const bool fg = false;
463 {
464 const bool bright = false;
465 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
466 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
467 "\e[40m\e[K");
468 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
469 "\e[41m\e[K");
470 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
471 "\e[42m\e[K");
472 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
473 "\e[43m\e[K");
474 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
475 "\e[44m\e[K");
476 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
477 "\e[45m\e[K");
478 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
479 "\e[46m\e[K");
480 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
481 "\e[47m\e[K");
482 }
483 {
484 const bool bright = true;
485 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
486 "\e[m\e[K");
487 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
488 "\e[100m\e[K");
489 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
490 "\e[101m\e[K");
491 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
492 "\e[102m\e[K");
493 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
494 "\e[103m\e[K");
495 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
496 "\e[104m\e[K");
497 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
498 "\e[105m\e[K");
499 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
500 "\e[106m\e[K");
501 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
502 "\e[107m\e[K");
503 }
504 }
505}
506
507#define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
508 SELFTEST_BEGIN_STMT \
509 { \
510 style plain; \
511 style s; \
512 if (FG) \
513 s.m_fg_color = style::color (COL_VAL); \
514 else \
515 s.m_bg_color = style::color (COL_VAL); \
516 assert_style_change_streq ((SELFTEST_LOCATION), \
517 plain, \
518 s, \
519 (EXPECTED_STR)); \
520 } \
521 SELFTEST_END_STMT
522
523static void
524test_8_bit_colors ()
525{
526 /* Foreground colors. */
527 {
528 const bool fg = true;
529 /* 0-15: standard and high-intensity standard colors. */
530 ASSERT_8_BIT_COL_STREQ (0, fg, "\e[38;5;0m\e[K");
531 ASSERT_8_BIT_COL_STREQ (15, fg, "\e[38;5;15m\e[K");
532 /* 16-231: 6x6x6 color cube. */
533 ASSERT_8_BIT_COL_STREQ (16, fg, "\e[38;5;16m\e[K");
534 ASSERT_8_BIT_COL_STREQ (231, fg, "\e[38;5;231m\e[K");
535 /* 232-255: grayscale. */
536 ASSERT_8_BIT_COL_STREQ (232, fg, "\e[38;5;232m\e[K");
537 ASSERT_8_BIT_COL_STREQ (255, fg, "\e[38;5;255m\e[K");
538 }
539 /* Background colors. */
540 {
541 const bool fg = false;
542 /* 0-15: standard and high-intensity standard colors. */
543 ASSERT_8_BIT_COL_STREQ (0, fg, "\e[48;5;0m\e[K");
544 ASSERT_8_BIT_COL_STREQ (15, fg, "\e[48;5;15m\e[K");
545 /* 16-231: 6x6x6 color cube. */
546 ASSERT_8_BIT_COL_STREQ (16, fg, "\e[48;5;16m\e[K");
547 ASSERT_8_BIT_COL_STREQ (231, fg, "\e[48;5;231m\e[K");
548 /* 232-255: grayscale. */
549 ASSERT_8_BIT_COL_STREQ (232, fg, "\e[48;5;232m\e[K");
550 ASSERT_8_BIT_COL_STREQ (255, fg, "\e[48;5;255m\e[K");
551 }
552}
553
554#define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
555 SELFTEST_BEGIN_STMT \
556 { \
557 style plain; \
558 style s; \
559 if (FG) \
560 s.m_fg_color = style::color ((R), (G), (B)); \
561 else \
562 s.m_bg_color = style::color ((R), (G), (B)); \
563 assert_style_change_streq ((SELFTEST_LOCATION), \
564 plain, \
565 s, \
566 (EXPECTED_STR)); \
567 } \
568 SELFTEST_END_STMT
569
570static void
571test_24_bit_colors ()
572{
573 /* Foreground colors. */
574 {
575 const bool fg = true;
576 // #F3FAF2:
577 ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg,
578 "\e[38;2;243;250;242m\e[K");
579 }
580 /* Background colors. */
581 {
582 const bool fg = false;
583 // #FDF7E7
584 ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg,
585 "\e[48;2;253;247;231m\e[K");
586 }
587}
588
589static void
590test_style_combinations ()
591{
592 style_manager sm;
593 ASSERT_EQ (sm.get_num_styles (), 1);
594
595 style plain;
596 ASSERT_EQ (sm.get_or_create_id (plain), 0);
597 ASSERT_EQ (sm.get_num_styles (), 1);
598
599 style bold;
600 bold.m_bold = true;
601
602 ASSERT_EQ (sm.get_or_create_id (bold), 1);
603 ASSERT_EQ (sm.get_num_styles (), 2);
604 ASSERT_EQ (sm.get_or_create_id (bold), 1);
605 ASSERT_EQ (sm.get_num_styles (), 2);
606
607 style magenta_on_blue;
608 magenta_on_blue.m_fg_color = style::named_color::MAGENTA;
609 magenta_on_blue.m_bg_color = style::named_color::BLUE;
610 ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
611 ASSERT_EQ (sm.get_num_styles (), 3);
612 ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
613 ASSERT_EQ (sm.get_num_styles (), 3);
614}
615
616/* Run all selftests in this file. */
617
618void
619text_art_style_cc_tests ()
620{
621 test_bold ();
622 test_underscore ();
623 test_blink ();
624 test_named_colors ();
625 test_8_bit_colors ();
626 test_24_bit_colors ();
627 test_style_combinations ();
628}
629
630} // namespace selftest
631
632
633#endif /* #if CHECKING_P */