1 /* Implementation of text_art::styled_string.
2 Copyright (C) 2023-2025 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_VECTOR
24 #include "coretypes.h"
25 #include "make-unique.h"
26 #include "pretty-print.h"
28 #include "diagnostic.h"
30 #include "text-art/selftests.h"
31 #include "text-art/types.h"
32 #include "color-macros.h"
34 using namespace text_art
;
38 /* Support class for parsing text containing escape codes.
39 See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code
40 We only support the codes that pretty-print.cc can generate. */
42 class escape_code_parser
45 escape_code_parser (style_manager
&sm
,
46 std::vector
<styled_unichar
> &out
)
50 m_cur_style_id (style::id_plain
),
51 m_state (state::START
)
55 void on_char (cppchar_t ch
)
64 /* The start of an escape sequence. */
65 m_state
= state::AFTER_ESC
;
69 case state::AFTER_ESC
:
72 /* ESC [ is a Control Sequence Introducer. */
73 m_state
= state::CS_PARAMETER_BYTES
;
78 /* ESC ] is an Operating System Command. */
79 m_state
= state::WITHIN_OSC
;
83 case state::CS_PARAMETER_BYTES
:
84 if (parameter_byte_p (ch
))
86 m_parameter_bytes
.push_back ((char)ch
);
89 else if (intermediate_byte_p (ch
))
91 m_intermediate_bytes
.push_back ((char)ch
);
92 m_state
= state::CS_INTERMEDIATE_BYTES
;
95 else if (final_byte_p (ch
))
97 on_final_csi_char (ch
);
101 case state::CS_INTERMEDIATE_BYTES
:
102 /* Expect zero or more intermediate bytes. */
103 if (intermediate_byte_p (ch
))
105 m_intermediate_bytes
.push_back ((char)ch
);
108 else if (final_byte_p (ch
))
110 on_final_csi_char (ch
);
114 case state::WITHIN_OSC
:
115 /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */
117 /* Check for ESC \, the String Terminator (aka "ST"). */
119 && m_osc_string
.size () > 0
120 && m_osc_string
.back () == '\033')
122 m_osc_string
.pop_back ();
123 on_final_osc_char ();
129 on_final_osc_char ();
132 m_osc_string
.push_back (ch
);
138 /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
139 variation for the previous character. */
142 if (m_out
.size () > 0)
143 m_out
.back ().set_emoji_variant ();
147 if (cpp_is_combining_char (ch
))
149 if (m_out
.size () > 0)
151 m_out
.back ().add_combining_char (ch
);
155 /* By default, add the char. */
156 m_out
.push_back (styled_unichar (ch
, false, m_cur_style_id
));
160 void on_final_csi_char (cppchar_t ch
)
169 /* SGR control sequence. */
170 if (m_parameter_bytes
.empty ())
172 std::vector
<int> params (params_from_decimal ());
173 for (auto iter
= params
.begin (); iter
!= params
.end (); )
175 const int param
= *iter
;
179 /* Unrecognized SGR parameter. */
188 set_style_underscore ();
194 /* Named foreground colors. */
196 set_style_fg_color (style::named_color::BLACK
);
199 set_style_fg_color (style::named_color::RED
);
202 set_style_fg_color (style::named_color::GREEN
);
205 set_style_fg_color (style::named_color::YELLOW
);
208 set_style_fg_color (style::named_color::BLUE
);
211 set_style_fg_color (style::named_color::MAGENTA
);
214 set_style_fg_color (style::named_color::CYAN
);
217 set_style_fg_color (style::named_color::WHITE
);
220 /* 8-bit and 24-bit color */
224 const bool fg
= (param
== 38);
226 if (iter
!= params
.end ())
233 if (iter
!= params
.end ())
235 const uint8_t col
= *(iter
++);
237 set_style_fg_color (style::color (col
));
239 set_style_bg_color (style::color (col
));
244 if (iter
!= params
.end ())
246 const uint8_t r
= *(iter
++);
247 if (iter
!= params
.end ())
249 const uint8_t g
= *(iter
++);
250 if (iter
!= params
.end ())
252 const uint8_t b
= *(iter
++);
254 set_style_fg_color (style::color (r
,
258 set_style_bg_color (style::color (r
,
270 /* Named background colors. */
272 set_style_bg_color (style::named_color::BLACK
);
275 set_style_bg_color (style::named_color::RED
);
278 set_style_bg_color (style::named_color::GREEN
);
281 set_style_bg_color (style::named_color::YELLOW
);
284 set_style_bg_color (style::named_color::BLUE
);
287 set_style_bg_color (style::named_color::MAGENTA
);
290 set_style_bg_color (style::named_color::CYAN
);
293 set_style_bg_color (style::named_color::WHITE
);
296 /* Named foreground colors, bright. */
298 set_style_fg_color (style::color (style::named_color::BLACK
,
302 set_style_fg_color (style::color (style::named_color::RED
,
306 set_style_fg_color (style::color (style::named_color::GREEN
,
310 set_style_fg_color (style::color (style::named_color::YELLOW
,
314 set_style_fg_color (style::color (style::named_color::BLUE
,
318 set_style_fg_color (style::color (style::named_color::MAGENTA
,
322 set_style_fg_color (style::color (style::named_color::CYAN
,
326 set_style_fg_color (style::color (style::named_color::WHITE
,
330 /* Named foreground colors, bright. */
332 set_style_bg_color (style::color (style::named_color::BLACK
,
336 set_style_bg_color (style::color (style::named_color::RED
,
340 set_style_bg_color (style::color (style::named_color::GREEN
,
344 set_style_bg_color (style::color (style::named_color::YELLOW
,
348 set_style_bg_color (style::color (style::named_color::BLUE
,
352 set_style_bg_color (style::color (style::named_color::MAGENTA
,
356 set_style_bg_color (style::color (style::named_color::CYAN
,
360 set_style_bg_color (style::color (style::named_color::WHITE
,
369 m_parameter_bytes
.clear ();
370 m_intermediate_bytes
.clear ();
371 m_state
= state::START
;
374 void on_final_osc_char ()
376 if (!m_osc_string
.empty ())
378 switch (m_osc_string
[0])
383 /* Hyperlink support; see:
384 https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
385 We don't support params, so we expect either:
386 (a) "8;;URL" to begin a url (see pp_begin_url), or
387 (b) "8;;" to end a URL (see pp_end_url). */
388 if (m_osc_string
.size () >= 3
389 && m_osc_string
[1] == ';'
390 && m_osc_string
[2] == ';')
392 set_style_url (m_osc_string
.begin () + 3,
393 m_osc_string
.end ());
398 m_osc_string
.clear ();
399 m_state
= state::START
;
402 std::vector
<int> params_from_decimal () const
404 std::vector
<int> result
;
407 for (auto param_ch
: m_parameter_bytes
)
409 if (param_ch
>= '0' && param_ch
<= '9')
415 curr_int
+= param_ch
- '0';
421 result
.push_back (curr_int
);
427 result
.push_back (curr_int
);
431 void refresh_style_id ()
433 m_cur_style_id
= m_sm
.get_or_create_id (m_cur_style_obj
);
437 m_cur_style_obj
= style ();
440 void set_style_bold ()
442 m_cur_style_obj
.m_bold
= true;
445 void set_style_underscore ()
447 m_cur_style_obj
.m_underscore
= true;
450 void set_style_blink ()
452 m_cur_style_obj
.m_blink
= true;
455 void set_style_fg_color (style::color color
)
457 m_cur_style_obj
.m_fg_color
= color
;
460 void set_style_bg_color (style::color color
)
462 m_cur_style_obj
.m_bg_color
= color
;
465 void set_style_url (std::vector
<cppchar_t
>::iterator begin
,
466 std::vector
<cppchar_t
>::iterator end
)
468 // The empty string means "no URL"
469 m_cur_style_obj
.m_url
= std::vector
<cppchar_t
> (begin
, end
);
473 static bool parameter_byte_p (cppchar_t ch
)
475 return ch
>= 0x30 && ch
<= 0x3F;
478 static bool intermediate_byte_p (cppchar_t ch
)
480 return ch
>= 0x20 && ch
<= 0x2F;
483 static bool final_byte_p (cppchar_t ch
)
485 return ch
>= 0x40 && ch
<= 0x7E;
489 std::vector
<styled_unichar
> &m_out
;
491 style m_cur_style_obj
;
492 style::id_t m_cur_style_id
;
494 /* Handling of control sequences. */
499 /* After ESC, expecting '['. */
502 /* Expecting zero or more parameter bytes, an
503 intermediate byte, or a final byte. */
506 /* Expecting zero or more intermediate bytes, or a final byte. */
507 CS_INTERMEDIATE_BYTES
,
513 std::vector
<char> m_parameter_bytes
;
514 std::vector
<char> m_intermediate_bytes
;
515 std::vector
<cppchar_t
> m_osc_string
;
520 /* class text_art::styled_string. */
522 /* Construct a styled_string from STR.
523 STR is assumed to be UTF-8 encoded and 0-terminated.
525 Parse SGR formatting chars from being in-band (within in the sequence
526 of chars) to being out-of-band, as style elements.
527 We only support parsing the subset of SGR chars that can be emitted
528 by pretty-print.cc */
530 styled_string::styled_string (style_manager
&sm
, const char *str
)
533 escape_code_parser
parser (sm
, m_chars
);
535 /* We don't actually want the display widths here, but
536 it's an easy way to decode UTF-8. */
537 cpp_char_column_policy
policy (8, cpp_wcwidth
);
538 cpp_display_width_computation
dw (str
, strlen (str
), policy
);
541 cpp_decoded_char decoded_char
;
542 dw
.process_next_codepoint (&decoded_char
);
544 if (!decoded_char
.m_valid_ch
)
545 /* Skip bytes that aren't valid UTF-8. */
548 /* Decode SGR formatting. */
549 cppchar_t ch
= decoded_char
.m_ch
;
554 styled_string::styled_string (cppchar_t cppchar
, bool emoji
)
556 m_chars
.push_back (styled_unichar (cppchar
, emoji
, style::id_plain
));
560 styled_string::from_fmt_va (style_manager
&sm
,
561 printer_fn format_decoder
,
565 text_info
text (fmt
, args
, errno
);
567 pp_show_color (&pp
) = true;
568 pp
.set_url_format (URL_FORMAT_DEFAULT
);
569 pp_format_decoder (&pp
) = format_decoder
;
570 pp_format (&pp
, &text
);
571 pp_output_formatted_text (&pp
);
572 styled_string
result (sm
, pp_formatted_text (&pp
));
577 styled_string::from_fmt (style_manager
&sm
,
578 printer_fn format_decoder
,
579 const char *fmt
, ...)
583 styled_string result
= from_fmt_va (sm
, format_decoder
, fmt
, &ap
);
589 styled_string::calc_canvas_width () const
592 for (auto ch
: m_chars
)
593 result
+= ch
.get_canvas_width ();
598 styled_string::append (const styled_string
&suffix
)
600 m_chars
.insert
<std::vector
<styled_unichar
>::const_iterator
> (m_chars
.end (),
606 styled_string::set_url (style_manager
&sm
, const char *url
)
608 for (auto& ch
: m_chars
)
610 const style
&existing_style
= sm
.get_style (ch
.get_style_id ());
611 style
with_url (existing_style
);
612 with_url
.set_style_url (url
);
613 ch
.m_style_id
= sm
.get_or_create_id (with_url
);
622 test_combining_chars ()
624 /* This really ought to be in libcpp, but we don't have
626 ASSERT_FALSE (cpp_is_combining_char (0));
627 ASSERT_FALSE (cpp_is_combining_char ('a'));
629 /* COMBINING BREVE (U+0306). */
630 ASSERT_TRUE (cpp_is_combining_char (0x0306));
632 /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */
633 ASSERT_FALSE (cpp_is_combining_char (0x5B57));
635 /* U+FE0F VARIATION SELECTOR-16. */
636 ASSERT_FALSE (cpp_is_combining_char (0xFE0F));
643 styled_string
s (sm
, "");
644 ASSERT_EQ (s
.size (), 0);
645 ASSERT_EQ (s
.calc_canvas_width (), 0);
648 /* Test of a pure ASCII string with no escape codes. */
653 const char *c_str
= "hello world!";
655 styled_string
s (sm
, c_str
);
656 ASSERT_EQ (s
.size (), strlen (c_str
));
657 ASSERT_EQ (s
.calc_canvas_width (), (int)strlen (c_str
));
658 for (size_t i
= 0; i
< strlen (c_str
); i
++)
660 ASSERT_EQ (s
[i
].get_code (), (cppchar_t
)c_str
[i
]);
661 ASSERT_EQ (s
[i
].get_style_id (), 0);
665 /* Test of decoding UTF-8. */
670 /* U+03C0 "GREEK SMALL LETTER PI". */
671 const char * const pi_utf8
= "\xCF\x80";
674 styled_string
s (sm
, pi_utf8
);
675 ASSERT_EQ (s
.size (), 1);
676 ASSERT_EQ (s
.calc_canvas_width (), 1);
677 ASSERT_EQ (s
[0].get_code (), 0x03c0);
678 ASSERT_EQ (s
[0].emoji_variant_p (), false);
679 ASSERT_EQ (s
[0].double_width_p (), false);
680 ASSERT_EQ (s
[0].get_style_id (), 0);
683 /* Test of double-width character. */
686 test_emoji_from_utf8 ()
688 /* U+1F642 "SLIGHTLY SMILING FACE". */
689 const char * const emoji_utf8
= "\xF0\x9F\x99\x82";
692 styled_string
s (sm
, emoji_utf8
);
693 ASSERT_EQ (s
.size (), 1);
694 ASSERT_EQ (s
.calc_canvas_width (), 2);
695 ASSERT_EQ (s
[0].get_code (), 0x1f642);
696 ASSERT_EQ (s
[0].double_width_p (), true);
697 ASSERT_EQ (s
[0].get_style_id (), 0);
700 /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
701 variation for the previous character. */
704 test_emoji_variant_from_utf8 ()
706 const char * const emoji_utf8
707 = (/* U+26A0 WARNING SIGN. */
709 /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */
713 styled_string
s (sm
, emoji_utf8
);
714 ASSERT_EQ (s
.size (), 1);
715 ASSERT_EQ (s
.calc_canvas_width (), 1);
716 ASSERT_EQ (s
[0].get_code (), 0x26a0);
717 ASSERT_EQ (s
[0].emoji_variant_p (), true);
718 ASSERT_EQ (s
[0].double_width_p (), false);
719 ASSERT_EQ (s
[0].get_style_id (), 0);
723 test_emoji_from_codepoint ()
725 styled_string
s ((cppchar_t
)0x1f642);
726 ASSERT_EQ (s
.size (), 1);
727 ASSERT_EQ (s
.calc_canvas_width (), 2);
728 ASSERT_EQ (s
[0].get_code (), 0x1f642);
729 ASSERT_EQ (s
[0].double_width_p (), true);
730 ASSERT_EQ (s
[0].get_style_id (), 0);
734 test_from_mixed_width_utf8 ()
736 /* This UTF-8 string literal is of the form
737 before mojibake after
738 where the Japanese word "mojibake" is written as the following
739 four unicode code points:
740 U+6587 CJK UNIFIED IDEOGRAPH-6587
741 U+5B57 CJK UNIFIED IDEOGRAPH-5B57
742 U+5316 CJK UNIFIED IDEOGRAPH-5316
743 U+3051 HIRAGANA LETTER KE.
744 Each of these is 3 bytes wide when encoded in UTF-8, whereas the
745 "before" and "after" are 1 byte per unicode character. */
746 const char * const mixed_width_utf8
749 /* U+6587 CJK UNIFIED IDEOGRAPH-6587
750 UTF-8: 0xE6 0x96 0x87
751 C octal escaped UTF-8: \346\226\207. */
754 /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57
755 UTF-8: 0xE5 0xAD 0x97
756 C octal escaped UTF-8: \345\255\227. */
759 /* U+5316 CJK UNIFIED IDEOGRAPH-5316
760 UTF-8: 0xE5 0x8C 0x96
761 C octal escaped UTF-8: \345\214\226. */
764 /* U+3051 HIRAGANA LETTER KE
765 UTF-8: 0xE3 0x81 0x91
766 C octal escaped UTF-8: \343\201\221. */
772 styled_string
s (sm
, mixed_width_utf8
);
773 ASSERT_EQ (s
.size (), 6 + 1 + 4 + 1 + 5);
774 ASSERT_EQ (sm
.get_num_styles (), 1);
776 // We expect the Japanese characters to be double width.
777 ASSERT_EQ (s
.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5);
779 ASSERT_EQ (s
[0].get_code (), 'b');
780 ASSERT_EQ (s
[0].double_width_p (), false);
781 ASSERT_EQ (s
[1].get_code (), 'e');
782 ASSERT_EQ (s
[2].get_code (), 'f');
783 ASSERT_EQ (s
[3].get_code (), 'o');
784 ASSERT_EQ (s
[4].get_code (), 'r');
785 ASSERT_EQ (s
[5].get_code (), 'e');
786 ASSERT_EQ (s
[6].get_code (), ' ');
787 ASSERT_EQ (s
[7].get_code (), 0x6587);
788 ASSERT_EQ (s
[7].double_width_p (), true);
789 ASSERT_EQ (s
[8].get_code (), 0x5B57);
790 ASSERT_EQ (s
[9].get_code (), 0x5316);
791 ASSERT_EQ (s
[10].get_code (), 0x3051);
792 ASSERT_EQ (s
[11].get_code (), ' ');
793 ASSERT_EQ (s
[12].get_code (), 'a');
794 ASSERT_EQ (s
[13].get_code (), 'f');
795 ASSERT_EQ (s
[14].get_code (), 't');
796 ASSERT_EQ (s
[15].get_code (), 'e');
797 ASSERT_EQ (s
[16].get_code (), 'r');
799 ASSERT_EQ (s
[0].get_style_id (), 0);
803 assert_style_urleq (const location
&loc
,
805 const char *expected_str
)
807 ASSERT_EQ_AT (loc
, s
.m_url
.size (), strlen (expected_str
));
808 for (size_t i
= 0; i
< s
.m_url
.size (); i
++)
809 ASSERT_EQ_AT (loc
, s
.m_url
[i
], (cppchar_t
)expected_str
[i
]);
812 #define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \
813 assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR))
822 (sm
, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\");
823 const char *expected
= "This is a link";
824 ASSERT_EQ (s
.size (), strlen (expected
));
825 ASSERT_EQ (s
.calc_canvas_width (), (int)strlen (expected
));
826 ASSERT_EQ (sm
.get_num_styles (), 2);
827 for (size_t i
= 0; i
< strlen (expected
); i
++)
829 ASSERT_EQ (s
[i
].get_code (), (cppchar_t
)expected
[i
]);
830 ASSERT_EQ (s
[i
].get_style_id (), 1);
832 ASSERT_STYLE_URLEQ (sm
.get_style (1), "http://example.com");
839 (sm
, "\33]8;;http://example.com\aThis is a link\33]8;;\a");
840 const char *expected
= "This is a link";
841 ASSERT_EQ (s
.size (), strlen (expected
));
842 ASSERT_EQ (s
.calc_canvas_width (), (int)strlen (expected
));
843 ASSERT_EQ (sm
.get_num_styles (), 2);
844 for (size_t i
= 0; i
< strlen (expected
); i
++)
846 ASSERT_EQ (s
[i
].get_code (), (cppchar_t
)expected
[i
]);
847 ASSERT_EQ (s
[i
].get_style_id (), 1);
849 ASSERT_STYLE_URLEQ (sm
.get_style (1), "http://example.com");
857 styled_string
s (styled_string::from_fmt (sm
, NULL
, "%%i: %i", 42));
858 ASSERT_EQ (s
[0].get_code (), '%');
859 ASSERT_EQ (s
[1].get_code (), 'i');
860 ASSERT_EQ (s
[2].get_code (), ':');
861 ASSERT_EQ (s
[3].get_code (), ' ');
862 ASSERT_EQ (s
[4].get_code (), '4');
863 ASSERT_EQ (s
[5].get_code (), '2');
864 ASSERT_EQ (s
.size (), 6);
865 ASSERT_EQ (s
.calc_canvas_width (), 6);
871 auto_fix_quotes fix_quotes
;
872 open_quote
= "\xe2\x80\x98";
873 close_quote
= "\xe2\x80\x99";
876 styled_string
s (styled_string::from_fmt (sm
, NULL
, "%qs", "msg"));
877 ASSERT_EQ (sm
.get_num_styles (), 2);
878 ASSERT_EQ (s
[0].get_code (), 0x2018);
879 ASSERT_EQ (s
[0].get_style_id (), 0);
880 ASSERT_EQ (s
[1].get_code (), 'm');
881 ASSERT_EQ (s
[1].get_style_id (), 1);
882 ASSERT_EQ (s
[2].get_code (), 's');
883 ASSERT_EQ (s
[2].get_style_id (), 1);
884 ASSERT_EQ (s
[3].get_code (), 'g');
885 ASSERT_EQ (s
[3].get_style_id (), 1);
886 ASSERT_EQ (s
[4].get_code (), 0x2019);
887 ASSERT_EQ (s
[4].get_style_id (), 0);
888 ASSERT_EQ (s
.size (), 5);
891 // Test of parsing SGR codes.
894 test_from_str_with_bold ()
897 /* This is the result of pp_printf (pp, "%qs", "foo")
898 with auto_fix_quotes. */
899 styled_string
s (sm
, "`\33[01m\33[Kfoo\33[m\33[K'");
900 ASSERT_EQ (s
[0].get_code (), '`');
901 ASSERT_EQ (s
[0].get_style_id (), 0);
902 ASSERT_EQ (s
[1].get_code (), 'f');
903 ASSERT_EQ (s
[1].get_style_id (), 1);
904 ASSERT_EQ (s
[2].get_code (), 'o');
905 ASSERT_EQ (s
[2].get_style_id (), 1);
906 ASSERT_EQ (s
[3].get_code (), 'o');
907 ASSERT_EQ (s
[3].get_style_id (), 1);
908 ASSERT_EQ (s
[4].get_code (), '\'');
909 ASSERT_EQ (s
[4].get_style_id (), 0);
910 ASSERT_EQ (s
.size (), 5);
911 ASSERT_TRUE (sm
.get_style (1).m_bold
);
915 test_from_str_with_underscore ()
918 styled_string
s (sm
, "\33[04m\33[KA");
919 ASSERT_EQ (s
[0].get_code (), 'A');
920 ASSERT_EQ (s
[0].get_style_id (), 1);
921 ASSERT_TRUE (sm
.get_style (1).m_underscore
);
925 test_from_str_with_blink ()
928 styled_string
s (sm
, "\33[05m\33[KA");
929 ASSERT_EQ (s
[0].get_code (), 'A');
930 ASSERT_EQ (s
[0].get_style_id (), 1);
931 ASSERT_TRUE (sm
.get_style (1).m_blink
);
934 // Test of parsing SGR codes.
937 test_from_str_with_color ()
943 SGR_SEQ (COLOR_FG_RED
)
947 SGR_SEQ (COLOR_FG_GREEN
)
951 ASSERT_EQ (s
.size (), 5);
952 ASSERT_EQ (sm
.get_num_styles (), 3);
953 ASSERT_EQ (s
[0].get_code (), '0');
954 ASSERT_EQ (s
[0].get_style_id (), 0);
955 ASSERT_EQ (s
[1].get_code (), 'R');
956 ASSERT_EQ (s
[1].get_style_id (), 1);
957 ASSERT_EQ (s
[2].get_code (), '2');
958 ASSERT_EQ (s
[2].get_style_id (), 0);
959 ASSERT_EQ (s
[3].get_code (), 'G');
960 ASSERT_EQ (s
[3].get_style_id (), 2);
961 ASSERT_EQ (s
[4].get_code (), '4');
962 ASSERT_EQ (s
[4].get_style_id (), 0);
963 ASSERT_EQ (sm
.get_style (1).m_fg_color
, style::named_color::RED
);
964 ASSERT_EQ (sm
.get_style (2).m_fg_color
, style::named_color::GREEN
);
968 test_from_str_with_named_color ()
973 SGR_SEQ (COLOR_FG_BLACK
) "F"
974 SGR_SEQ (COLOR_FG_RED
) "F"
975 SGR_SEQ (COLOR_FG_GREEN
) "F"
976 SGR_SEQ (COLOR_FG_YELLOW
) "F"
977 SGR_SEQ (COLOR_FG_BLUE
) "F"
978 SGR_SEQ (COLOR_FG_MAGENTA
) "F"
979 SGR_SEQ (COLOR_FG_CYAN
) "F"
980 SGR_SEQ (COLOR_FG_WHITE
) "F"
981 SGR_SEQ (COLOR_FG_BRIGHT_BLACK
) "F"
982 SGR_SEQ (COLOR_FG_BRIGHT_RED
) "F"
983 SGR_SEQ (COLOR_FG_BRIGHT_GREEN
) "F"
984 SGR_SEQ (COLOR_FG_BRIGHT_YELLOW
) "F"
985 SGR_SEQ (COLOR_FG_BRIGHT_BLUE
) "F"
986 SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA
) "F"
987 SGR_SEQ (COLOR_FG_BRIGHT_CYAN
) "F"
988 SGR_SEQ (COLOR_FG_BRIGHT_WHITE
) "F"
989 SGR_SEQ (COLOR_BG_BLACK
) "B"
990 SGR_SEQ (COLOR_BG_RED
) "B"
991 SGR_SEQ (COLOR_BG_GREEN
) "B"
992 SGR_SEQ (COLOR_BG_YELLOW
) "B"
993 SGR_SEQ (COLOR_BG_BLUE
) "B"
994 SGR_SEQ (COLOR_BG_MAGENTA
) "B"
995 SGR_SEQ (COLOR_BG_CYAN
) "B"
996 SGR_SEQ (COLOR_BG_WHITE
) "B"
997 SGR_SEQ (COLOR_BG_BRIGHT_BLACK
) "B"
998 SGR_SEQ (COLOR_BG_BRIGHT_RED
) "B"
999 SGR_SEQ (COLOR_BG_BRIGHT_GREEN
) "B"
1000 SGR_SEQ (COLOR_BG_BRIGHT_YELLOW
) "B"
1001 SGR_SEQ (COLOR_BG_BRIGHT_BLUE
) "B"
1002 SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA
) "B"
1003 SGR_SEQ (COLOR_BG_BRIGHT_CYAN
) "B"
1004 SGR_SEQ (COLOR_BG_BRIGHT_WHITE
) "B"));
1005 ASSERT_EQ (s
.size (), 33);
1006 for (size_t i
= 0; i
< s
.size (); i
++)
1007 ASSERT_EQ (s
[i
].get_style_id (), i
);
1008 for (size_t i
= 0; i
< 17; i
++)
1009 ASSERT_EQ (s
[i
].get_code (), 'F');
1010 for (size_t i
= 17; i
< 33; i
++)
1011 ASSERT_EQ (s
[i
].get_code (), 'B');
1015 test_from_str_with_8_bit_color ()
1019 styled_string
s (sm
,
1020 ("\e[38;5;232m\e[KF"));
1021 ASSERT_EQ (s
.size (), 1);
1022 ASSERT_EQ (s
[0].get_code (), 'F');
1023 ASSERT_EQ (s
[0].get_style_id (), 1);
1024 ASSERT_EQ (sm
.get_style (1).m_fg_color
, style::color (232));
1028 styled_string
s (sm
,
1029 ("\e[48;5;231m\e[KB"));
1030 ASSERT_EQ (s
.size (), 1);
1031 ASSERT_EQ (s
[0].get_code (), 'B');
1032 ASSERT_EQ (s
[0].get_style_id (), 1);
1033 ASSERT_EQ (sm
.get_style (1).m_bg_color
, style::color (231));
1038 test_from_str_with_24_bit_color ()
1042 styled_string
s (sm
,
1043 ("\e[38;2;243;250;242m\e[KF"));
1044 ASSERT_EQ (s
.size (), 1);
1045 ASSERT_EQ (s
[0].get_code (), 'F');
1046 ASSERT_EQ (s
[0].get_style_id (), 1);
1047 ASSERT_EQ (sm
.get_style (1).m_fg_color
, style::color (243, 250, 242));
1051 styled_string
s (sm
,
1052 ("\e[48;2;253;247;231m\e[KB"));
1053 ASSERT_EQ (s
.size (), 1);
1054 ASSERT_EQ (s
[0].get_code (), 'B');
1055 ASSERT_EQ (s
[0].get_style_id (), 1);
1056 ASSERT_EQ (sm
.get_style (1).m_bg_color
, style::color (253, 247, 231));
1061 test_from_str_combining_characters ()
1064 styled_string
s (sm
,
1065 /* CYRILLIC CAPITAL LETTER U (U+0423). */
1067 /* COMBINING BREVE (U+0306). */
1069 ASSERT_EQ (s
.size (), 1);
1070 ASSERT_EQ (s
[0].get_code (), 0x423);
1071 ASSERT_EQ (s
[0].get_combining_chars ().size (), 1);
1072 ASSERT_EQ (s
[0].get_combining_chars ()[0], 0x306);
1075 /* Run all selftests in this file. */
1078 text_art_styled_string_cc_tests ()
1080 test_combining_chars ();
1083 test_pi_from_utf8 ();
1084 test_emoji_from_utf8 ();
1085 test_emoji_variant_from_utf8 ();
1086 test_emoji_from_codepoint ();
1087 test_from_mixed_width_utf8 ();
1090 test_from_fmt_qs ();
1091 test_from_str_with_bold ();
1092 test_from_str_with_underscore ();
1093 test_from_str_with_blink ();
1094 test_from_str_with_color ();
1095 test_from_str_with_named_color ();
1096 test_from_str_with_8_bit_color ();
1097 test_from_str_with_24_bit_color ();
1098 test_from_str_combining_characters ();
1101 } // namespace selftest
1104 #endif /* #if CHECKING_P */