1 /* Classes for styling text cells (color, URLs).
2 Copyright (C) 2023 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"
35 using namespace text_art
;
37 /* class text_art::style. */
40 style::set_style_url (const char *url
)
44 m_url
.push_back (*(url
++));
48 /* class text_art::style::color. */
51 style::color::operator== (const style::color
&other
) const
53 if (m_kind
!= other
.m_kind
)
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
);
63 return u
.m_8bit
== other
.u
.m_8bit
;
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
);
72 ensure_separator (pretty_printer
*pp
, bool &need_separator
)
75 pp_string (pp
, COLOR_SEPARATOR
);
76 need_separator
= true;
80 style::color::print_sgr (pretty_printer
*pp
,
82 bool &need_separator
) const
90 static const char * const fg_normal
[] = {"", // reset, for DEFAULT
99 static const char * const fg_bright
[] = {"", // reset, for DEFAULT
100 COLOR_FG_BRIGHT_BLACK
,
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
117 static const char * const bg_bright
[] = {"", // reset, for DEFAULT
118 COLOR_BG_BRIGHT_BLACK
,
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
;
132 arr
= u
.m_named
.m_bright
? fg_bright
: fg_normal
;
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)
138 ensure_separator (pp
, need_separator
);
145 ensure_separator (pp
, need_separator
);
147 pp_string (pp
, "38");
149 pp_string (pp
, "48");
150 pp_printf (pp
, ";5;%i", (int)u
.m_8bit
);
155 ensure_separator (pp
, need_separator
);
157 pp_string (pp
, "38");
159 pp_string (pp
, "48");
160 pp_printf (pp
, ";2;%i;%i;%i",
169 /* class text_art::style. */
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. */
179 style::print_changes (pretty_printer
*pp
,
180 const style
&old_style
,
181 const style
&new_style
)
183 if (pp_show_color (pp
))
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
));
192 bool emit_reset
= (old_style
.m_bold
194 || old_style
.m_underscore
195 || new_style
.m_underscore
197 || new_style
.m_blink
);
198 bool need_separator
= false;
200 pp_string (pp
, SGR_START
);
203 pp_string (pp
, COLOR_NONE
);
204 need_separator
= true;
206 if (new_style
.m_bold
)
208 gcc_assert (emit_reset
);
209 ensure_separator (pp
, need_separator
);
210 pp_string (pp
, COLOR_BOLD
);
212 if (new_style
.m_underscore
)
214 gcc_assert (emit_reset
);
215 ensure_separator (pp
, need_separator
);
216 pp_string (pp
, COLOR_UNDERSCORE
);
218 if (new_style
.m_blink
)
220 gcc_assert (emit_reset
);
221 ensure_separator (pp
, need_separator
);
222 pp_string (pp
, COLOR_BLINK
);
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
);
230 if (old_style
.m_url
!= new_style
.m_url
)
232 if (!old_style
.m_url
.empty ())
234 if (pp
->url_format
!= URL_FORMAT_NONE
235 && !new_style
.m_url
.empty ())
237 /* Adapted from pp_begin_url, but encoding the
238 chars to UTF-8 on the fly, rather than converting
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
)
246 case URL_FORMAT_NONE
:
249 pp_string (pp
, "\33\\");
252 pp_string (pp
, "\a");
259 /* class text_art::style_manager. */
261 style_manager::style_manager ()
263 // index 0 will be the default style
264 m_styles
.push_back (style ());
268 style_manager::get_or_create_id (const style
&s
)
270 // For now, linear search
271 std::vector
<style
>::iterator existing
272 (std::find (m_styles
.begin (), m_styles
.end (), s
));
274 /* If found, return index of slot. */
275 if (existing
!= m_styles
.end ())
276 return std::distance (m_styles
.begin (), existing
);
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)
286 m_styles
.push_back (s
);
287 return m_styles
.size () - 1;
291 style_manager::print_any_style_changes (pretty_printer
*pp
,
293 style::id_t new_id
) const
296 if (old_id
== new_id
)
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
);
310 assert_style_change_streq (const location
&loc
,
311 const style
&old_style
,
312 const style
&new_style
,
313 const char *expected_str
)
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
);
321 #define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
322 SELFTEST_BEGIN_STMT \
323 assert_style_change_streq ((SELFTEST_LOCATION), \
333 ASSERT_EQ (sm
.get_num_styles (), 1);
336 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
337 ASSERT_EQ (sm
.get_num_styles (), 1);
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);
347 ASSERT_STYLE_CHANGE_STREQ (plain
, bold
, "\33[00;01m\33[K");
348 ASSERT_STYLE_CHANGE_STREQ (bold
, plain
, "\33[00m\33[K");
355 ASSERT_EQ (sm
.get_num_styles (), 1);
358 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
359 ASSERT_EQ (sm
.get_num_styles (), 1);
362 underscore
.m_underscore
= true;
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);
369 ASSERT_STYLE_CHANGE_STREQ (plain
, underscore
, "\33[00;04m\33[K");
370 ASSERT_STYLE_CHANGE_STREQ (underscore
, plain
, "\33[00m\33[K");
377 ASSERT_EQ (sm
.get_num_styles (), 1);
380 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
381 ASSERT_EQ (sm
.get_num_styles (), 1);
384 blink
.m_blink
= true;
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);
391 ASSERT_STYLE_CHANGE_STREQ (plain
, blink
, "\33[00;05m\33[K");
392 ASSERT_STYLE_CHANGE_STREQ (blink
, plain
, "\33[00m\33[K");
395 #define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
396 SELFTEST_BEGIN_STMT \
401 s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
403 s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
404 assert_style_change_streq ((SELFTEST_LOCATION), \
414 /* Foreground colors. */
416 const bool fg
= true;
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
,
422 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
424 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
426 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
428 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
430 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
432 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
434 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
438 const bool bright
= true;
439 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT
, fg
, bright
,
441 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK
, fg
, bright
,
443 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
445 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
447 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
449 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
451 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
453 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
455 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
460 /* Background colors. */
462 const bool fg
= false;
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
,
468 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
470 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
472 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
474 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
476 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
478 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
480 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
484 const bool bright
= true;
485 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT
, fg
, bright
,
487 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK
, fg
, bright
,
489 ASSERT_NAMED_COL_STREQ (style::named_color::RED
, fg
, bright
,
491 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN
, fg
, bright
,
493 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW
, fg
, bright
,
495 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE
, fg
, bright
,
497 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA
, fg
, bright
,
499 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN
, fg
, bright
,
501 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE
, fg
, bright
,
507 #define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
508 SELFTEST_BEGIN_STMT \
513 s.m_fg_color = style::color (COL_VAL); \
515 s.m_bg_color = style::color (COL_VAL); \
516 assert_style_change_streq ((SELFTEST_LOCATION), \
526 /* Foreground colors. */
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");
539 /* Background colors. */
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");
554 #define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
555 SELFTEST_BEGIN_STMT \
560 s.m_fg_color = style::color ((R), (G), (B)); \
562 s.m_bg_color = style::color ((R), (G), (B)); \
563 assert_style_change_streq ((SELFTEST_LOCATION), \
571 test_24_bit_colors ()
573 /* Foreground colors. */
575 const bool fg
= true;
577 ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg
,
578 "\e[38;2;243;250;242m\e[K");
580 /* Background colors. */
582 const bool fg
= false;
584 ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg
,
585 "\e[48;2;253;247;231m\e[K");
590 test_style_combinations ()
593 ASSERT_EQ (sm
.get_num_styles (), 1);
596 ASSERT_EQ (sm
.get_or_create_id (plain
), 0);
597 ASSERT_EQ (sm
.get_num_styles (), 1);
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);
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);
616 /* Run all selftests in this file. */
619 text_art_style_cc_tests ()
624 test_named_colors ();
625 test_8_bit_colors ();
626 test_24_bit_colors ();
627 test_style_combinations ();
630 } // namespace selftest
633 #endif /* #if CHECKING_P */