x86: Add a test for PR rtl-optimization/111673
[official-gcc.git] / gcc / text-art / styled-string.cc
blobe13278b40d8556741470e45f5f8366cff16c4eed
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
10 version.
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
15 for more details.
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/>. */
21 #include "config.h"
22 #define INCLUDE_VECTOR
23 #include "system.h"
24 #include "coretypes.h"
25 #include "make-unique.h"
26 #include "pretty-print.h"
27 #include "intl.h"
28 #include "diagnostic.h"
29 #include "selftest.h"
30 #include "text-art/selftests.h"
31 #include "text-art/types.h"
32 #include "color-macros.h"
34 using namespace text_art;
36 namespace {
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
44 public:
45 escape_code_parser (style_manager &sm,
46 std::vector<styled_unichar> &out)
47 : m_sm (sm),
48 m_out (out),
49 m_cur_style_obj (),
50 m_cur_style_id (style::id_plain),
51 m_state (state::START)
55 void on_char (cppchar_t ch)
57 switch (m_state)
59 default:
60 gcc_unreachable ();
61 case state::START:
62 if (ch == '\033')
64 /* The start of an escape sequence. */
65 m_state = state::AFTER_ESC;
66 return;
68 break;
69 case state::AFTER_ESC:
70 if (ch == '[')
72 /* ESC [ is a Control Sequence Introducer. */
73 m_state = state::CS_PARAMETER_BYTES;
74 return;
76 else if (ch == ']')
78 /* ESC ] is an Operating System Command. */
79 m_state = state::WITHIN_OSC;
80 return;
82 break;
83 case state::CS_PARAMETER_BYTES:
84 if (parameter_byte_p (ch))
86 m_parameter_bytes.push_back ((char)ch);
87 return;
89 else if (intermediate_byte_p (ch))
91 m_intermediate_bytes.push_back ((char)ch);
92 m_state = state::CS_INTERMEDIATE_BYTES;
93 return;
95 else if (final_byte_p (ch))
97 on_final_csi_char (ch);
98 return;
100 break;
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);
106 return;
108 else if (final_byte_p (ch))
110 on_final_csi_char (ch);
111 return;
113 break;
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"). */
118 if (ch == '\\'
119 && m_osc_string.size () > 0
120 && m_osc_string.back () == '\033')
122 m_osc_string.pop_back ();
123 on_final_osc_char ();
124 return;
126 else if (ch == '\a')
128 // BEL
129 on_final_osc_char ();
130 return;
132 m_osc_string.push_back (ch);
133 return;
135 break;
138 /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
139 variation for the previous character. */
140 if (ch == 0xFE0F)
142 if (m_out.size () > 0)
143 m_out.back ().set_emoji_variant ();
144 return;
147 if (cpp_is_combining_char (ch))
149 if (m_out.size () > 0)
151 m_out.back ().add_combining_char (ch);
152 return;
155 /* By default, add the char. */
156 m_out.push_back (styled_unichar (ch, false, m_cur_style_id));
159 private:
160 void on_final_csi_char (cppchar_t ch)
162 switch (ch)
164 default:
165 /* Unrecognized. */
166 break;
167 case 'm':
169 /* SGR control sequence. */
170 if (m_parameter_bytes.empty ())
171 reset_style ();
172 std::vector<int> params (params_from_decimal ());
173 for (auto iter = params.begin (); iter != params.end (); )
175 const int param = *iter;
176 switch (param)
178 default:
179 /* Unrecognized SGR parameter. */
180 break;
181 case 0:
182 reset_style ();
183 break;
184 case 1:
185 set_style_bold ();
186 break;
187 case 4:
188 set_style_underscore ();
189 break;
190 case 5:
191 set_style_blink ();
192 break;
194 /* Named foreground colors. */
195 case 30:
196 set_style_fg_color (style::named_color::BLACK);
197 break;
198 case 31:
199 set_style_fg_color (style::named_color::RED);
200 break;
201 case 32:
202 set_style_fg_color (style::named_color::GREEN);
203 break;
204 case 33:
205 set_style_fg_color (style::named_color::YELLOW);
206 break;
207 case 34:
208 set_style_fg_color (style::named_color::BLUE);
209 break;
210 case 35:
211 set_style_fg_color (style::named_color::MAGENTA);
212 break;
213 case 36:
214 set_style_fg_color (style::named_color::CYAN);
215 break;
216 case 37:
217 set_style_fg_color (style::named_color::WHITE);
218 break;
220 /* 8-bit and 24-bit color */
221 case 38:
222 case 48:
224 const bool fg = (param == 38);
225 iter++;
226 if (iter != params.end ())
227 switch (*(iter++))
229 default:
230 break;
231 case 5:
232 /* 8-bit color. */
233 if (iter != params.end ())
235 const uint8_t col = *(iter++);
236 if (fg)
237 set_style_fg_color (style::color (col));
238 else
239 set_style_bg_color (style::color (col));
241 continue;
242 case 2:
243 /* 24-bit color. */
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++);
253 if (fg)
254 set_style_fg_color (style::color (r,
256 b));
257 else
258 set_style_bg_color (style::color (r,
260 b));
264 continue;
266 continue;
268 break;
270 /* Named background colors. */
271 case 40:
272 set_style_bg_color (style::named_color::BLACK);
273 break;
274 case 41:
275 set_style_bg_color (style::named_color::RED);
276 break;
277 case 42:
278 set_style_bg_color (style::named_color::GREEN);
279 break;
280 case 43:
281 set_style_bg_color (style::named_color::YELLOW);
282 break;
283 case 44:
284 set_style_bg_color (style::named_color::BLUE);
285 break;
286 case 45:
287 set_style_bg_color (style::named_color::MAGENTA);
288 break;
289 case 46:
290 set_style_bg_color (style::named_color::CYAN);
291 break;
292 case 47:
293 set_style_bg_color (style::named_color::WHITE);
294 break;
296 /* Named foreground colors, bright. */
297 case 90:
298 set_style_fg_color (style::color (style::named_color::BLACK,
299 true));
300 break;
301 case 91:
302 set_style_fg_color (style::color (style::named_color::RED,
303 true));
304 break;
305 case 92:
306 set_style_fg_color (style::color (style::named_color::GREEN,
307 true));
308 break;
309 case 93:
310 set_style_fg_color (style::color (style::named_color::YELLOW,
311 true));
312 break;
313 case 94:
314 set_style_fg_color (style::color (style::named_color::BLUE,
315 true));
316 break;
317 case 95:
318 set_style_fg_color (style::color (style::named_color::MAGENTA,
319 true));
320 break;
321 case 96:
322 set_style_fg_color (style::color (style::named_color::CYAN,
323 true));
324 break;
325 case 97:
326 set_style_fg_color (style::color (style::named_color::WHITE,
327 true));
328 break;
330 /* Named foreground colors, bright. */
331 case 100:
332 set_style_bg_color (style::color (style::named_color::BLACK,
333 true));
334 break;
335 case 101:
336 set_style_bg_color (style::color (style::named_color::RED,
337 true));
338 break;
339 case 102:
340 set_style_bg_color (style::color (style::named_color::GREEN,
341 true));
342 break;
343 case 103:
344 set_style_bg_color (style::color (style::named_color::YELLOW,
345 true));
346 break;
347 case 104:
348 set_style_bg_color (style::color (style::named_color::BLUE,
349 true));
350 break;
351 case 105:
352 set_style_bg_color (style::color (style::named_color::MAGENTA,
353 true));
354 break;
355 case 106:
356 set_style_bg_color (style::color (style::named_color::CYAN,
357 true));
358 break;
359 case 107:
360 set_style_bg_color (style::color (style::named_color::WHITE,
361 true));
362 break;
364 ++iter;
367 break;
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])
380 default:
381 break;
382 case '8':
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 ());
395 break;
398 m_osc_string.clear ();
399 m_state = state::START;
402 std::vector<int> params_from_decimal () const
404 std::vector<int> result;
406 int curr_int = -1;
407 for (auto param_ch : m_parameter_bytes)
409 if (param_ch >= '0' && param_ch <= '9')
411 if (curr_int == -1)
412 curr_int = 0;
413 else
414 curr_int *= 10;
415 curr_int += param_ch - '0';
417 else
419 if (curr_int != -1)
421 result.push_back (curr_int);
422 curr_int = -1;
426 if (curr_int != -1)
427 result.push_back (curr_int);
428 return result;
431 void refresh_style_id ()
433 m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj);
435 void reset_style ()
437 m_cur_style_obj = style ();
438 refresh_style_id ();
440 void set_style_bold ()
442 m_cur_style_obj.m_bold = true;
443 refresh_style_id ();
445 void set_style_underscore ()
447 m_cur_style_obj.m_underscore = true;
448 refresh_style_id ();
450 void set_style_blink ()
452 m_cur_style_obj.m_blink = true;
453 refresh_style_id ();
455 void set_style_fg_color (style::color color)
457 m_cur_style_obj.m_fg_color = color;
458 refresh_style_id ();
460 void set_style_bg_color (style::color color)
462 m_cur_style_obj.m_bg_color = color;
463 refresh_style_id ();
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);
470 refresh_style_id ();
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;
488 style_manager &m_sm;
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. */
495 enum class state
497 START,
499 /* After ESC, expecting '['. */
500 AFTER_ESC,
502 /* Expecting zero or more parameter bytes, an
503 intermediate byte, or a final byte. */
504 CS_PARAMETER_BYTES,
506 /* Expecting zero or more intermediate bytes, or a final byte. */
507 CS_INTERMEDIATE_BYTES,
509 /* Within OSC. */
510 WITHIN_OSC
512 } m_state;
513 std::vector<char> m_parameter_bytes;
514 std::vector<char> m_intermediate_bytes;
515 std::vector<cppchar_t> m_osc_string;
518 } // anon namespace
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)
531 : m_chars ()
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);
539 while (!dw.done ())
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. */
546 continue;
548 /* Decode SGR formatting. */
549 cppchar_t ch = decoded_char.m_ch;
550 parser.on_char (ch);
554 styled_string::styled_string (cppchar_t cppchar, bool emoji)
556 m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain));
559 styled_string
560 styled_string::from_fmt_va (style_manager &sm,
561 printer_fn format_decoder,
562 const char *fmt,
563 va_list *args)
565 text_info text (fmt, args, errno);
566 pretty_printer pp;
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));
573 return result;
576 styled_string
577 styled_string::from_fmt (style_manager &sm,
578 printer_fn format_decoder,
579 const char *fmt, ...)
581 va_list ap;
582 va_start (ap, fmt);
583 styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap);
584 va_end (ap);
585 return result;
589 styled_string::calc_canvas_width () const
591 int result = 0;
592 for (auto ch : m_chars)
593 result += ch.get_canvas_width ();
594 return result;
597 void
598 styled_string::append (const styled_string &suffix)
600 m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (),
601 suffix.begin (),
602 suffix.end ());
605 void
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);
617 #if CHECKING_P
619 namespace selftest {
621 static void
622 test_combining_chars ()
624 /* This really ought to be in libcpp, but we don't have
625 selftests there. */
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));
639 static void
640 test_empty ()
642 style_manager sm;
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. */
650 static void
651 test_simple ()
653 const char *c_str = "hello world!";
654 style_manager sm;
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. */
667 static void
668 test_pi_from_utf8 ()
670 /* U+03C0 "GREEK SMALL LETTER PI". */
671 const char * const pi_utf8 = "\xCF\x80";
673 style_manager sm;
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. */
685 static void
686 test_emoji_from_utf8 ()
688 /* U+1F642 "SLIGHTLY SMILING FACE". */
689 const char * const emoji_utf8 = "\xF0\x9F\x99\x82";
691 style_manager sm;
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. */
703 static void
704 test_emoji_variant_from_utf8 ()
706 const char * const emoji_utf8
707 = (/* U+26A0 WARNING SIGN. */
708 "\xE2\x9A\xA0"
709 /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */
710 "\xEF\xB8\x8F");
712 style_manager sm;
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);
722 static void
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);
733 static void
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
747 = ("before "
749 /* U+6587 CJK UNIFIED IDEOGRAPH-6587
750 UTF-8: 0xE6 0x96 0x87
751 C octal escaped UTF-8: \346\226\207. */
752 "\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. */
757 "\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. */
762 "\345\214\226"
764 /* U+3051 HIRAGANA LETTER KE
765 UTF-8: 0xE3 0x81 0x91
766 C octal escaped UTF-8: \343\201\221. */
767 "\343\201\221"
769 " after");
771 style_manager sm;
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);
802 static void
803 assert_style_urleq (const location &loc,
804 const style &s,
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))
815 static void
816 test_url ()
818 // URL_FORMAT_ST
820 style_manager sm;
821 styled_string s
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");
835 // URL_FORMAT_BEL
837 style_manager sm;
838 styled_string s
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");
853 static void
854 test_from_fmt ()
856 style_manager sm;
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);
868 static void
869 test_from_fmt_qs ()
871 auto_fix_quotes fix_quotes;
872 open_quote = "\xe2\x80\x98";
873 close_quote = "\xe2\x80\x99";
875 style_manager sm;
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.
893 static void
894 test_from_str_with_bold ()
896 style_manager sm;
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);
914 static void
915 test_from_str_with_underscore ()
917 style_manager sm;
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);
924 static void
925 test_from_str_with_blink ()
927 style_manager sm;
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.
936 static void
937 test_from_str_with_color ()
939 style_manager sm;
941 styled_string s (sm,
942 ("0"
943 SGR_SEQ (COLOR_FG_RED)
945 SGR_RESET
947 SGR_SEQ (COLOR_FG_GREEN)
949 SGR_RESET
950 "4"));
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);
967 static void
968 test_from_str_with_named_color ()
970 style_manager sm;
971 styled_string s (sm,
972 ("F"
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');
1014 static void
1015 test_from_str_with_8_bit_color ()
1018 style_manager sm;
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));
1027 style_manager sm;
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));
1037 static void
1038 test_from_str_with_24_bit_color ()
1041 style_manager sm;
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));
1050 style_manager sm;
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));
1060 static void
1061 test_from_str_combining_characters ()
1063 style_manager sm;
1064 styled_string s (sm,
1065 /* CYRILLIC CAPITAL LETTER U (U+0423). */
1066 "\xD0\xA3"
1067 /* COMBINING BREVE (U+0306). */
1068 "\xCC\x86");
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. */
1077 void
1078 text_art_styled_string_cc_tests ()
1080 test_combining_chars ();
1081 test_empty ();
1082 test_simple ();
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 ();
1088 test_url ();
1089 test_from_fmt ();
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 */