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>.
5 This file is part of GCC.
7 GCC is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 3, or (at your option) any later
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
22 #define INCLUDE_ALGORITHM
23 #define INCLUDE_MEMORY
24 #define INCLUDE_VECTOR
26 #include "coretypes.h"
27 #include "make-unique.h"
28 #include "pretty-print.h"
31 #include "text-art/selftests.h"
32 #include "text-art/types.h"
33 #include "color-macros.h"
34 #include "diagnostic-color.h"
36 using namespace text_art
;
38 /* class text_art::style. */
41 style::set_style_url (const char *url
)
45 m_url
.push_back (*(url
++));
49 /* class text_art::style::color. */
52 style::color::operator== (const style::color
&other
) const
54 if (m_kind
!= other
.m_kind
)
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
);
64 return u
.m_8bit
== other
.u
.m_8bit
;
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
);
73 ensure_separator (pretty_printer
*pp
, bool &need_separator
)
76 pp_string (pp
, COLOR_SEPARATOR
);
77 need_separator
= true;
81 style::color::print_sgr (pretty_printer
*pp
,
83 bool &need_separator
) const
91 static const char * const fg_normal
[] = {"", // reset, for DEFAULT
100 static const char * const fg_bright
[] = {"", // reset, for DEFAULT
101 COLOR_FG_BRIGHT_BLACK
,
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
118 static const char * const bg_bright
[] = {"", // reset, for DEFAULT
119 COLOR_BG_BRIGHT_BLACK
,
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
;
133 arr
= u
.m_named
.m_bright
? fg_bright
: fg_normal
;
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 (str
) > 0)
139 ensure_separator (pp
, need_separator
);
146 ensure_separator (pp
, need_separator
);
148 pp_string (pp
, "38");
150 pp_string (pp
, "48");
151 pp_printf (pp
, ";5;%i", (int)u
.m_8bit
);
156 ensure_separator (pp
, need_separator
);
158 pp_string (pp
, "38");
160 pp_string (pp
, "48");
161 pp_printf (pp
, ";2;%i;%i;%i",
170 /* class text_art::style. */
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. */
180 style::print_changes (pretty_printer
*pp
,
181 const style
&old_style
,
182 const style
&new_style
)
184 if (pp_show_color (pp
))
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
));
193 bool emit_reset
= (old_style
.m_bold
195 || old_style
.m_underscore
196 || new_style
.m_underscore
198 || new_style
.m_blink
);
199 bool need_separator
= false;
201 pp_string (pp
, SGR_START
);
204 pp_string (pp
, COLOR_NONE
);
205 need_separator
= true;
207 if (new_style
.m_bold
)
209 gcc_assert (emit_reset
);
210 ensure_separator (pp
, need_separator
);
211 pp_string (pp
, COLOR_BOLD
);
213 if (new_style
.m_underscore
)
215 gcc_assert (emit_reset
);
216 ensure_separator (pp
, need_separator
);
217 pp_string (pp
, COLOR_UNDERSCORE
);
219 if (new_style
.m_blink
)
221 gcc_assert (emit_reset
);
222 ensure_separator (pp
, need_separator
);
223 pp_string (pp
, COLOR_BLINK
);
225 new_style
.m_fg_color
.print_sgr (pp
, true, need_separator
);
226 new_style
.m_bg_color
.print_sgr (pp
, false, need_separator
);
227 pp_string (pp
, SGR_END
);
231 if (old_style
.m_url
!= new_style
.m_url
)
233 if (!old_style
.m_url
.empty ())
235 if (pp
->supports_urls_p ()
236 && !new_style
.m_url
.empty ())
238 /* Adapted from pp_begin_url, but encoding the
239 chars to UTF-8 on the fly, rather than converting
241 pp_string (pp
, "\33]8;;");
242 for (auto ch
: new_style
.m_url
)
243 pp_unicode_character (pp
, ch
);
244 switch (pp
->get_url_format ())
247 case URL_FORMAT_NONE
:
250 pp_string (pp
, "\33\\");
253 pp_string (pp
, "\a");
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. */
265 text_art::get_style_from_color_cap_name (const char *name
)
267 const char *sgr_codes
= colorize_start (true, name
);
268 gcc_assert (sgr_codes
);
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. */
273 styled_string
styled_str (sm
, sgr_codes
);
274 return sm
.get_style (sm
.get_num_styles () - 1);
277 /* class text_art::style_manager. */
279 style_manager::style_manager ()
281 // index 0 will be the default style
282 m_styles
.push_back (style ());
286 style_manager::get_or_create_id (const style
&s
)
288 // For now, linear search
289 std::vector
<style
>::iterator existing
290 (std::find (m_styles
.begin (), m_styles
.end (), s
));
292 /* If found, return index of slot. */
293 if (existing
!= m_styles
.end ())
294 return std::distance (m_styles
.begin (), existing
);
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)
304 m_styles
.push_back (s
);
305 return m_styles
.size () - 1;
309 style_manager::print_any_style_changes (pretty_printer
*pp
,
311 style::id_t new_id
) const
314 if (old_id
== new_id
)
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
);
328 assert_style_change_streq (const location
&loc
,
329 const style
&old_style
,
330 const style
&new_style
,
331 const char *expected_str
)
334 pp_show_color (&pp
) = true;
335 style::print_changes (&pp
, old_style
, new_style
);
336 ASSERT_STREQ_AT (loc
, pp_formatted_text (&pp
), expected_str
);
339 #define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
340 SELFTEST_BEGIN_STMT \
341 assert_style_change_streq ((SELFTEST_LOCATION), \
351 ASSERT_EQ (sm
.get_num_styles (), 1);
354 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
355 ASSERT_EQ (sm
.get_num_styles (), 1);
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);
365 ASSERT_STYLE_CHANGE_STREQ (plain
, bold
, "\33[00;01m\33[K");
366 ASSERT_STYLE_CHANGE_STREQ (bold
, plain
, "\33[00m\33[K");
373 ASSERT_EQ (sm
.get_num_styles (), 1);
376 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
377 ASSERT_EQ (sm
.get_num_styles (), 1);
380 underscore
.m_underscore
= true;
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);
387 ASSERT_STYLE_CHANGE_STREQ (plain
, underscore
, "\33[00;04m\33[K");
388 ASSERT_STYLE_CHANGE_STREQ (underscore
, plain
, "\33[00m\33[K");
395 ASSERT_EQ (sm
.get_num_styles (), 1);
398 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
399 ASSERT_EQ (sm
.get_num_styles (), 1);
402 blink
.m_blink
= true;
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);
409 ASSERT_STYLE_CHANGE_STREQ (plain
, blink
, "\33[00;05m\33[K");
410 ASSERT_STYLE_CHANGE_STREQ (blink
, plain
, "\33[00m\33[K");
413 #define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
414 SELFTEST_BEGIN_STMT \
419 s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
421 s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
422 assert_style_change_streq ((SELFTEST_LOCATION), \
432 /* Foreground colors. */
434 const bool fg
= true;
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
,
440 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
442 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
444 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
446 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
448 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
450 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
452 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
456 const bool bright
= true;
457 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT
, fg
, bright
,
459 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK
, fg
, bright
,
461 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
463 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
465 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
467 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
469 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
471 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
473 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
478 /* Background colors. */
480 const bool fg
= false;
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
,
486 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
488 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
490 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
492 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
494 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
496 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
498 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
502 const bool bright
= true;
503 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT
, fg
, bright
,
505 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK
, fg
, bright
,
507 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
509 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
511 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
513 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
515 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
517 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
519 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
525 #define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
526 SELFTEST_BEGIN_STMT \
531 s.m_fg_color = style::color (COL_VAL); \
533 s.m_bg_color = style::color (COL_VAL); \
534 assert_style_change_streq ((SELFTEST_LOCATION), \
544 /* Foreground colors. */
546 const bool fg
= true;
547 /* 0-15: standard and high-intensity standard colors. */
548 ASSERT_8_BIT_COL_STREQ (0, fg
, "\e[38;5;0m\e[K");
549 ASSERT_8_BIT_COL_STREQ (15, fg
, "\e[38;5;15m\e[K");
550 /* 16-231: 6x6x6 color cube. */
551 ASSERT_8_BIT_COL_STREQ (16, fg
, "\e[38;5;16m\e[K");
552 ASSERT_8_BIT_COL_STREQ (231, fg
, "\e[38;5;231m\e[K");
553 /* 232-255: grayscale. */
554 ASSERT_8_BIT_COL_STREQ (232, fg
, "\e[38;5;232m\e[K");
555 ASSERT_8_BIT_COL_STREQ (255, fg
, "\e[38;5;255m\e[K");
557 /* Background colors. */
559 const bool fg
= false;
560 /* 0-15: standard and high-intensity standard colors. */
561 ASSERT_8_BIT_COL_STREQ (0, fg
, "\e[48;5;0m\e[K");
562 ASSERT_8_BIT_COL_STREQ (15, fg
, "\e[48;5;15m\e[K");
563 /* 16-231: 6x6x6 color cube. */
564 ASSERT_8_BIT_COL_STREQ (16, fg
, "\e[48;5;16m\e[K");
565 ASSERT_8_BIT_COL_STREQ (231, fg
, "\e[48;5;231m\e[K");
566 /* 232-255: grayscale. */
567 ASSERT_8_BIT_COL_STREQ (232, fg
, "\e[48;5;232m\e[K");
568 ASSERT_8_BIT_COL_STREQ (255, fg
, "\e[48;5;255m\e[K");
572 #define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
573 SELFTEST_BEGIN_STMT \
578 s.m_fg_color = style::color ((R), (G), (B)); \
580 s.m_bg_color = style::color ((R), (G), (B)); \
581 assert_style_change_streq ((SELFTEST_LOCATION), \
589 test_24_bit_colors ()
591 /* Foreground colors. */
593 const bool fg
= true;
595 ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg
,
596 "\e[38;2;243;250;242m\e[K");
598 /* Background colors. */
600 const bool fg
= false;
602 ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg
,
603 "\e[48;2;253;247;231m\e[K");
608 test_style_combinations ()
611 ASSERT_EQ (sm
.get_num_styles (), 1);
614 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
615 ASSERT_EQ (sm
.get_num_styles (), 1);
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);
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);
634 /* Run all selftests in this file. */
637 text_art_style_cc_tests ()
642 test_named_colors ();
643 test_8_bit_colors ();
644 test_24_bit_colors ();
645 test_style_combinations ();
648 } // namespace selftest
651 #endif /* #if CHECKING_P */