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

source code of gcc/text-art/style.cc