1/* Implementation of text_art::styled_string.
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_MEMORY
23#define INCLUDE_VECTOR
24#include "system.h"
25#include "coretypes.h"
26#include "make-unique.h"
27#include "pretty-print.h"
28#include "intl.h"
29#include "diagnostic.h"
30#include "selftest.h"
31#include "text-art/selftests.h"
32#include "text-art/types.h"
33#include "color-macros.h"
34
35using namespace text_art;
36
37namespace {
38
39/* Support class for parsing text containing escape codes.
40 See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code
41 We only support the codes that pretty-print.cc can generate. */
42
43class escape_code_parser
44{
45public:
46 escape_code_parser (style_manager &sm,
47 std::vector<styled_unichar> &out)
48 : m_sm (sm),
49 m_out (out),
50 m_cur_style_obj (),
51 m_cur_style_id (style::id_plain),
52 m_state (state::START)
53 {
54 }
55
56 void on_char (cppchar_t ch)
57 {
58 switch (m_state)
59 {
60 default:
61 gcc_unreachable ();
62 case state::START:
63 if (ch == '\033')
64 {
65 /* The start of an escape sequence. */
66 m_state = state::AFTER_ESC;
67 return;
68 }
69 break;
70 case state::AFTER_ESC:
71 if (ch == '[')
72 {
73 /* ESC [ is a Control Sequence Introducer. */
74 m_state = state::CS_PARAMETER_BYTES;
75 return;
76 }
77 else if (ch == ']')
78 {
79 /* ESC ] is an Operating System Command. */
80 m_state = state::WITHIN_OSC;
81 return;
82 }
83 break;
84 case state::CS_PARAMETER_BYTES:
85 if (parameter_byte_p (ch))
86 {
87 m_parameter_bytes.push_back (x: (char)ch);
88 return;
89 }
90 else if (intermediate_byte_p (ch))
91 {
92 m_intermediate_bytes.push_back (x: (char)ch);
93 m_state = state::CS_INTERMEDIATE_BYTES;
94 return;
95 }
96 else if (final_byte_p (ch))
97 {
98 on_final_csi_char (ch);
99 return;
100 }
101 break;
102 case state::CS_INTERMEDIATE_BYTES:
103 /* Expect zero or more intermediate bytes. */
104 if (intermediate_byte_p (ch))
105 {
106 m_intermediate_bytes.push_back (x: (char)ch);
107 return;
108 }
109 else if (final_byte_p (ch))
110 {
111 on_final_csi_char (ch);
112 return;
113 }
114 break;
115 case state::WITHIN_OSC:
116 /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */
117 {
118 /* Check for ESC \, the String Terminator (aka "ST"). */
119 if (ch == '\\'
120 && m_osc_string.size () > 0
121 && m_osc_string.back () == '\033')
122 {
123 m_osc_string.pop_back ();
124 on_final_osc_char ();
125 return;
126 }
127 else if (ch == '\a')
128 {
129 // BEL
130 on_final_osc_char ();
131 return;
132 }
133 m_osc_string.push_back (x: ch);
134 return;
135 }
136 break;
137 }
138
139 /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
140 variation for the previous character. */
141 if (ch == 0xFE0F)
142 {
143 if (m_out.size () > 0)
144 m_out.back ().set_emoji_variant ();
145 return;
146 }
147
148 if (cpp_is_combining_char (c: ch))
149 {
150 if (m_out.size () > 0)
151 {
152 m_out.back ().add_combining_char (ch);
153 return;
154 }
155 }
156 /* By default, add the char. */
157 m_out.push_back (x: styled_unichar (ch, false, m_cur_style_id));
158 }
159
160private:
161 void on_final_csi_char (cppchar_t ch)
162 {
163 switch (ch)
164 {
165 default:
166 /* Unrecognized. */
167 break;
168 case 'm':
169 {
170 /* SGR control sequence. */
171 if (m_parameter_bytes.empty ())
172 reset_style ();
173 std::vector<int> params (params_from_decimal ());
174 for (auto iter = params.begin (); iter != params.end (); )
175 {
176 const int param = *iter;
177 switch (param)
178 {
179 default:
180 /* Unrecognized SGR parameter. */
181 break;
182 case 0:
183 reset_style ();
184 break;
185 case 1:
186 set_style_bold ();
187 break;
188 case 4:
189 set_style_underscore ();
190 break;
191 case 5:
192 set_style_blink ();
193 break;
194
195 /* Named foreground colors. */
196 case 30:
197 set_style_fg_color (style::named_color::BLACK);
198 break;
199 case 31:
200 set_style_fg_color (style::named_color::RED);
201 break;
202 case 32:
203 set_style_fg_color (style::named_color::GREEN);
204 break;
205 case 33:
206 set_style_fg_color (style::named_color::YELLOW);
207 break;
208 case 34:
209 set_style_fg_color (style::named_color::BLUE);
210 break;
211 case 35:
212 set_style_fg_color (style::named_color::MAGENTA);
213 break;
214 case 36:
215 set_style_fg_color (style::named_color::CYAN);
216 break;
217 case 37:
218 set_style_fg_color (style::named_color::WHITE);
219 break;
220
221 /* 8-bit and 24-bit color */
222 case 38:
223 case 48:
224 {
225 const bool fg = (param == 38);
226 iter++;
227 if (iter != params.end ())
228 switch (*(iter++))
229 {
230 default:
231 break;
232 case 5:
233 /* 8-bit color. */
234 if (iter != params.end ())
235 {
236 const uint8_t col = *(iter++);
237 if (fg)
238 set_style_fg_color (style::color (col));
239 else
240 set_style_bg_color (style::color (col));
241 }
242 continue;
243 case 2:
244 /* 24-bit color. */
245 if (iter != params.end ())
246 {
247 const uint8_t r = *(iter++);
248 if (iter != params.end ())
249 {
250 const uint8_t g = *(iter++);
251 if (iter != params.end ())
252 {
253 const uint8_t b = *(iter++);
254 if (fg)
255 set_style_fg_color (style::color (r,
256 g,
257 b));
258 else
259 set_style_bg_color (style::color (r,
260 g,
261 b));
262 }
263 }
264 }
265 continue;
266 }
267 continue;
268 }
269 break;
270
271 /* Named background colors. */
272 case 40:
273 set_style_bg_color (style::named_color::BLACK);
274 break;
275 case 41:
276 set_style_bg_color (style::named_color::RED);
277 break;
278 case 42:
279 set_style_bg_color (style::named_color::GREEN);
280 break;
281 case 43:
282 set_style_bg_color (style::named_color::YELLOW);
283 break;
284 case 44:
285 set_style_bg_color (style::named_color::BLUE);
286 break;
287 case 45:
288 set_style_bg_color (style::named_color::MAGENTA);
289 break;
290 case 46:
291 set_style_bg_color (style::named_color::CYAN);
292 break;
293 case 47:
294 set_style_bg_color (style::named_color::WHITE);
295 break;
296
297 /* Named foreground colors, bright. */
298 case 90:
299 set_style_fg_color (style::color (style::named_color::BLACK,
300 true));
301 break;
302 case 91:
303 set_style_fg_color (style::color (style::named_color::RED,
304 true));
305 break;
306 case 92:
307 set_style_fg_color (style::color (style::named_color::GREEN,
308 true));
309 break;
310 case 93:
311 set_style_fg_color (style::color (style::named_color::YELLOW,
312 true));
313 break;
314 case 94:
315 set_style_fg_color (style::color (style::named_color::BLUE,
316 true));
317 break;
318 case 95:
319 set_style_fg_color (style::color (style::named_color::MAGENTA,
320 true));
321 break;
322 case 96:
323 set_style_fg_color (style::color (style::named_color::CYAN,
324 true));
325 break;
326 case 97:
327 set_style_fg_color (style::color (style::named_color::WHITE,
328 true));
329 break;
330
331 /* Named foreground colors, bright. */
332 case 100:
333 set_style_bg_color (style::color (style::named_color::BLACK,
334 true));
335 break;
336 case 101:
337 set_style_bg_color (style::color (style::named_color::RED,
338 true));
339 break;
340 case 102:
341 set_style_bg_color (style::color (style::named_color::GREEN,
342 true));
343 break;
344 case 103:
345 set_style_bg_color (style::color (style::named_color::YELLOW,
346 true));
347 break;
348 case 104:
349 set_style_bg_color (style::color (style::named_color::BLUE,
350 true));
351 break;
352 case 105:
353 set_style_bg_color (style::color (style::named_color::MAGENTA,
354 true));
355 break;
356 case 106:
357 set_style_bg_color (style::color (style::named_color::CYAN,
358 true));
359 break;
360 case 107:
361 set_style_bg_color (style::color (style::named_color::WHITE,
362 true));
363 break;
364 }
365 ++iter;
366 }
367 }
368 break;
369 }
370 m_parameter_bytes.clear ();
371 m_intermediate_bytes.clear ();
372 m_state = state::START;
373 }
374
375 void on_final_osc_char ()
376 {
377 if (!m_osc_string.empty ())
378 {
379 switch (m_osc_string[0])
380 {
381 default:
382 break;
383 case '8':
384 /* Hyperlink support; see:
385 https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
386 We don't support params, so we expect either:
387 (a) "8;;URL" to begin a url (see pp_begin_url), or
388 (b) "8;;" to end a URL (see pp_end_url). */
389 if (m_osc_string.size () >= 3
390 && m_osc_string[1] == ';'
391 && m_osc_string[2] == ';')
392 {
393 set_style_url (begin: m_osc_string.begin () + 3,
394 end: m_osc_string.end ());
395 }
396 break;
397 }
398 }
399 m_osc_string.clear ();
400 m_state = state::START;
401 }
402
403 std::vector<int> params_from_decimal () const
404 {
405 std::vector<int> result;
406
407 int curr_int = -1;
408 for (auto param_ch : m_parameter_bytes)
409 {
410 if (param_ch >= '0' && param_ch <= '9')
411 {
412 if (curr_int == -1)
413 curr_int = 0;
414 else
415 curr_int *= 10;
416 curr_int += param_ch - '0';
417 }
418 else
419 {
420 if (curr_int != -1)
421 {
422 result.push_back (x: curr_int);
423 curr_int = -1;
424 }
425 }
426 }
427 if (curr_int != -1)
428 result.push_back (x: curr_int);
429 return result;
430 }
431
432 void refresh_style_id ()
433 {
434 m_cur_style_id = m_sm.get_or_create_id (style: m_cur_style_obj);
435 }
436 void reset_style ()
437 {
438 m_cur_style_obj = style ();
439 refresh_style_id ();
440 }
441 void set_style_bold ()
442 {
443 m_cur_style_obj.m_bold = true;
444 refresh_style_id ();
445 }
446 void set_style_underscore ()
447 {
448 m_cur_style_obj.m_underscore = true;
449 refresh_style_id ();
450 }
451 void set_style_blink ()
452 {
453 m_cur_style_obj.m_blink = true;
454 refresh_style_id ();
455 }
456 void set_style_fg_color (style::color color)
457 {
458 m_cur_style_obj.m_fg_color = color;
459 refresh_style_id ();
460 }
461 void set_style_bg_color (style::color color)
462 {
463 m_cur_style_obj.m_bg_color = color;
464 refresh_style_id ();
465 }
466 void set_style_url (std::vector<cppchar_t>::iterator begin,
467 std::vector<cppchar_t>::iterator end)
468 {
469 // The empty string means "no URL"
470 m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end);
471 refresh_style_id ();
472 }
473
474 static bool parameter_byte_p (cppchar_t ch)
475 {
476 return ch >= 0x30 && ch <= 0x3F;
477 }
478
479 static bool intermediate_byte_p (cppchar_t ch)
480 {
481 return ch >= 0x20 && ch <= 0x2F;
482 }
483
484 static bool final_byte_p (cppchar_t ch)
485 {
486 return ch >= 0x40 && ch <= 0x7E;
487 }
488
489 style_manager &m_sm;
490 std::vector<styled_unichar> &m_out;
491
492 style m_cur_style_obj;
493 style::id_t m_cur_style_id;
494
495 /* Handling of control sequences. */
496 enum class state
497 {
498 START,
499
500 /* After ESC, expecting '['. */
501 AFTER_ESC,
502
503 /* Expecting zero or more parameter bytes, an
504 intermediate byte, or a final byte. */
505 CS_PARAMETER_BYTES,
506
507 /* Expecting zero or more intermediate bytes, or a final byte. */
508 CS_INTERMEDIATE_BYTES,
509
510 /* Within OSC. */
511 WITHIN_OSC
512
513 } m_state;
514 std::vector<char> m_parameter_bytes;
515 std::vector<char> m_intermediate_bytes;
516 std::vector<cppchar_t> m_osc_string;
517};
518
519} // anon namespace
520
521/* class text_art::styled_string. */
522
523/* Construct a styled_string from STR.
524 STR is assumed to be UTF-8 encoded and 0-terminated.
525
526 Parse SGR formatting chars from being in-band (within in the sequence
527 of chars) to being out-of-band, as style elements.
528 We only support parsing the subset of SGR chars that can be emitted
529 by pretty-print.cc */
530
531styled_string::styled_string (style_manager &sm, const char *str)
532: m_chars ()
533{
534 escape_code_parser parser (sm, m_chars);
535
536 /* We don't actually want the display widths here, but
537 it's an easy way to decode UTF-8. */
538 cpp_char_column_policy policy (8, cpp_wcwidth);
539 cpp_display_width_computation dw (str, strlen (s: str), policy);
540 while (!dw.done ())
541 {
542 cpp_decoded_char decoded_char;
543 dw.process_next_codepoint (out: &decoded_char);
544
545 if (!decoded_char.m_valid_ch)
546 /* Skip bytes that aren't valid UTF-8. */
547 continue;
548
549 /* Decode SGR formatting. */
550 cppchar_t ch = decoded_char.m_ch;
551 parser.on_char (ch);
552 }
553}
554
555styled_string::styled_string (cppchar_t cppchar, bool emoji)
556{
557 m_chars.push_back (x: styled_unichar (cppchar, emoji, style::id_plain));
558}
559
560styled_string
561styled_string::from_fmt_va (style_manager &sm,
562 printer_fn format_decoder,
563 const char *fmt,
564 va_list *args)
565{
566 text_info text (fmt, args, errno);
567 pretty_printer pp;
568 pp_show_color (&pp) = true;
569 pp.url_format = URL_FORMAT_DEFAULT;
570 pp_format_decoder (&pp) = format_decoder;
571 pp_format (&pp, &text);
572 pp_output_formatted_text (&pp);
573 styled_string result (sm, pp_formatted_text (&pp));
574 return result;
575}
576
577styled_string
578styled_string::from_fmt (style_manager &sm,
579 printer_fn format_decoder,
580 const char *fmt, ...)
581{
582 va_list ap;
583 va_start (ap, fmt);
584 styled_string result = from_fmt_va (sm, format_decoder, fmt, args: &ap);
585 va_end (ap);
586 return result;
587}
588
589int
590styled_string::calc_canvas_width () const
591{
592 int result = 0;
593 for (auto ch : m_chars)
594 result += ch.get_canvas_width ();
595 return result;
596}
597
598void
599styled_string::append (const styled_string &suffix)
600{
601 m_chars.insert<std::vector<styled_unichar>::const_iterator> (position: m_chars.end (),
602 first: suffix.begin (),
603 last: suffix.end ());
604}
605
606void
607styled_string::set_url (style_manager &sm, const char *url)
608{
609 for (auto& ch : m_chars)
610 {
611 const style &existing_style = sm.get_style (id: ch.get_style_id ());
612 style with_url (existing_style);
613 with_url.set_style_url (url);
614 ch.m_style_id = sm.get_or_create_id (style: with_url);
615 }
616}
617
618#if CHECKING_P
619
620namespace selftest {
621
622static void
623test_combining_chars ()
624{
625 /* This really ought to be in libcpp, but we don't have
626 selftests there. */
627 ASSERT_FALSE (cpp_is_combining_char (0));
628 ASSERT_FALSE (cpp_is_combining_char ('a'));
629
630 /* COMBINING BREVE (U+0306). */
631 ASSERT_TRUE (cpp_is_combining_char (0x0306));
632
633 /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */
634 ASSERT_FALSE (cpp_is_combining_char (0x5B57));
635
636 /* U+FE0F VARIATION SELECTOR-16. */
637 ASSERT_FALSE (cpp_is_combining_char (0xFE0F));
638}
639
640static void
641test_empty ()
642{
643 style_manager sm;
644 styled_string s (sm, "");
645 ASSERT_EQ (s.size (), 0);
646 ASSERT_EQ (s.calc_canvas_width (), 0);
647}
648
649/* Test of a pure ASCII string with no escape codes. */
650
651static void
652test_simple ()
653{
654 const char *c_str = "hello world!";
655 style_manager sm;
656 styled_string s (sm, c_str);
657 ASSERT_EQ (s.size (), strlen (c_str));
658 ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str));
659 for (size_t i = 0; i < strlen (s: c_str); i++)
660 {
661 ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]);
662 ASSERT_EQ (s[i].get_style_id (), 0);
663 }
664}
665
666/* Test of decoding UTF-8. */
667
668static void
669test_pi_from_utf8 ()
670{
671 /* U+03C0 "GREEK SMALL LETTER PI". */
672 const char * const pi_utf8 = "\xCF\x80";
673
674 style_manager sm;
675 styled_string s (sm, pi_utf8);
676 ASSERT_EQ (s.size (), 1);
677 ASSERT_EQ (s.calc_canvas_width (), 1);
678 ASSERT_EQ (s[0].get_code (), 0x03c0);
679 ASSERT_EQ (s[0].emoji_variant_p (), false);
680 ASSERT_EQ (s[0].double_width_p (), false);
681 ASSERT_EQ (s[0].get_style_id (), 0);
682}
683
684/* Test of double-width character. */
685
686static void
687test_emoji_from_utf8 ()
688{
689 /* U+1F642 "SLIGHTLY SMILING FACE". */
690 const char * const emoji_utf8 = "\xF0\x9F\x99\x82";
691
692 style_manager sm;
693 styled_string s (sm, emoji_utf8);
694 ASSERT_EQ (s.size (), 1);
695 ASSERT_EQ (s.calc_canvas_width (), 2);
696 ASSERT_EQ (s[0].get_code (), 0x1f642);
697 ASSERT_EQ (s[0].double_width_p (), true);
698 ASSERT_EQ (s[0].get_style_id (), 0);
699}
700
701/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
702 variation for the previous character. */
703
704static void
705test_emoji_variant_from_utf8 ()
706{
707 const char * const emoji_utf8
708 = (/* U+26A0 WARNING SIGN. */
709 "\xE2\x9A\xA0"
710 /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */
711 "\xEF\xB8\x8F");
712
713 style_manager sm;
714 styled_string s (sm, emoji_utf8);
715 ASSERT_EQ (s.size (), 1);
716 ASSERT_EQ (s.calc_canvas_width (), 1);
717 ASSERT_EQ (s[0].get_code (), 0x26a0);
718 ASSERT_EQ (s[0].emoji_variant_p (), true);
719 ASSERT_EQ (s[0].double_width_p (), false);
720 ASSERT_EQ (s[0].get_style_id (), 0);
721}
722
723static void
724test_emoji_from_codepoint ()
725{
726 styled_string s ((cppchar_t)0x1f642);
727 ASSERT_EQ (s.size (), 1);
728 ASSERT_EQ (s.calc_canvas_width (), 2);
729 ASSERT_EQ (s[0].get_code (), 0x1f642);
730 ASSERT_EQ (s[0].double_width_p (), true);
731 ASSERT_EQ (s[0].get_style_id (), 0);
732}
733
734static void
735test_from_mixed_width_utf8 ()
736{
737 /* This UTF-8 string literal is of the form
738 before mojibake after
739 where the Japanese word "mojibake" is written as the following
740 four unicode code points:
741 U+6587 CJK UNIFIED IDEOGRAPH-6587
742 U+5B57 CJK UNIFIED IDEOGRAPH-5B57
743 U+5316 CJK UNIFIED IDEOGRAPH-5316
744 U+3051 HIRAGANA LETTER KE.
745 Each of these is 3 bytes wide when encoded in UTF-8, whereas the
746 "before" and "after" are 1 byte per unicode character. */
747 const char * const mixed_width_utf8
748 = ("before "
749
750 /* U+6587 CJK UNIFIED IDEOGRAPH-6587
751 UTF-8: 0xE6 0x96 0x87
752 C octal escaped UTF-8: \346\226\207. */
753 "\346\226\207"
754
755 /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57
756 UTF-8: 0xE5 0xAD 0x97
757 C octal escaped UTF-8: \345\255\227. */
758 "\345\255\227"
759
760 /* U+5316 CJK UNIFIED IDEOGRAPH-5316
761 UTF-8: 0xE5 0x8C 0x96
762 C octal escaped UTF-8: \345\214\226. */
763 "\345\214\226"
764
765 /* U+3051 HIRAGANA LETTER KE
766 UTF-8: 0xE3 0x81 0x91
767 C octal escaped UTF-8: \343\201\221. */
768 "\343\201\221"
769
770 " after");
771
772 style_manager sm;
773 styled_string s (sm, mixed_width_utf8);
774 ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5);
775 ASSERT_EQ (sm.get_num_styles (), 1);
776
777 // We expect the Japanese characters to be double width.
778 ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5);
779
780 ASSERT_EQ (s[0].get_code (), 'b');
781 ASSERT_EQ (s[0].double_width_p (), false);
782 ASSERT_EQ (s[1].get_code (), 'e');
783 ASSERT_EQ (s[2].get_code (), 'f');
784 ASSERT_EQ (s[3].get_code (), 'o');
785 ASSERT_EQ (s[4].get_code (), 'r');
786 ASSERT_EQ (s[5].get_code (), 'e');
787 ASSERT_EQ (s[6].get_code (), ' ');
788 ASSERT_EQ (s[7].get_code (), 0x6587);
789 ASSERT_EQ (s[7].double_width_p (), true);
790 ASSERT_EQ (s[8].get_code (), 0x5B57);
791 ASSERT_EQ (s[9].get_code (), 0x5316);
792 ASSERT_EQ (s[10].get_code (), 0x3051);
793 ASSERT_EQ (s[11].get_code (), ' ');
794 ASSERT_EQ (s[12].get_code (), 'a');
795 ASSERT_EQ (s[13].get_code (), 'f');
796 ASSERT_EQ (s[14].get_code (), 't');
797 ASSERT_EQ (s[15].get_code (), 'e');
798 ASSERT_EQ (s[16].get_code (), 'r');
799
800 ASSERT_EQ (s[0].get_style_id (), 0);
801}
802
803static void
804assert_style_urleq (const location &loc,
805 const style &s,
806 const char *expected_str)
807{
808 ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str));
809 for (size_t i = 0; i < s.m_url.size (); i++)
810 ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]);
811}
812
813#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \
814 assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR))
815
816static void
817test_url ()
818{
819 // URL_FORMAT_ST
820 {
821 style_manager sm;
822 styled_string s
823 (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\");
824 const char *expected = "This is a link";
825 ASSERT_EQ (s.size (), strlen (expected));
826 ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
827 ASSERT_EQ (sm.get_num_styles (), 2);
828 for (size_t i = 0; i < strlen (s: expected); i++)
829 {
830 ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
831 ASSERT_EQ (s[i].get_style_id (), 1);
832 }
833 ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
834 }
835
836 // URL_FORMAT_BEL
837 {
838 style_manager sm;
839 styled_string s
840 (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a");
841 const char *expected = "This is a link";
842 ASSERT_EQ (s.size (), strlen (expected));
843 ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
844 ASSERT_EQ (sm.get_num_styles (), 2);
845 for (size_t i = 0; i < strlen (s: expected); i++)
846 {
847 ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
848 ASSERT_EQ (s[i].get_style_id (), 1);
849 }
850 ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
851 }
852}
853
854static void
855test_from_fmt ()
856{
857 style_manager sm;
858 styled_string s (styled_string::from_fmt (sm, NULL, fmt: "%%i: %i", 42));
859 ASSERT_EQ (s[0].get_code (), '%');
860 ASSERT_EQ (s[1].get_code (), 'i');
861 ASSERT_EQ (s[2].get_code (), ':');
862 ASSERT_EQ (s[3].get_code (), ' ');
863 ASSERT_EQ (s[4].get_code (), '4');
864 ASSERT_EQ (s[5].get_code (), '2');
865 ASSERT_EQ (s.size (), 6);
866 ASSERT_EQ (s.calc_canvas_width (), 6);
867}
868
869static void
870test_from_fmt_qs ()
871{
872 auto_fix_quotes fix_quotes;
873 open_quote = "\xe2\x80\x98";
874 close_quote = "\xe2\x80\x99";
875
876 style_manager sm;
877 styled_string s (styled_string::from_fmt (sm, NULL, fmt: "%qs", "msg"));
878 ASSERT_EQ (sm.get_num_styles (), 2);
879 ASSERT_EQ (s[0].get_code (), 0x2018);
880 ASSERT_EQ (s[0].get_style_id (), 0);
881 ASSERT_EQ (s[1].get_code (), 'm');
882 ASSERT_EQ (s[1].get_style_id (), 1);
883 ASSERT_EQ (s[2].get_code (), 's');
884 ASSERT_EQ (s[2].get_style_id (), 1);
885 ASSERT_EQ (s[3].get_code (), 'g');
886 ASSERT_EQ (s[3].get_style_id (), 1);
887 ASSERT_EQ (s[4].get_code (), 0x2019);
888 ASSERT_EQ (s[4].get_style_id (), 0);
889 ASSERT_EQ (s.size (), 5);
890}
891
892// Test of parsing SGR codes.
893
894static void
895test_from_str_with_bold ()
896{
897 style_manager sm;
898 /* This is the result of pp_printf (pp, "%qs", "foo")
899 with auto_fix_quotes. */
900 styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'");
901 ASSERT_EQ (s[0].get_code (), '`');
902 ASSERT_EQ (s[0].get_style_id (), 0);
903 ASSERT_EQ (s[1].get_code (), 'f');
904 ASSERT_EQ (s[1].get_style_id (), 1);
905 ASSERT_EQ (s[2].get_code (), 'o');
906 ASSERT_EQ (s[2].get_style_id (), 1);
907 ASSERT_EQ (s[3].get_code (), 'o');
908 ASSERT_EQ (s[3].get_style_id (), 1);
909 ASSERT_EQ (s[4].get_code (), '\'');
910 ASSERT_EQ (s[4].get_style_id (), 0);
911 ASSERT_EQ (s.size (), 5);
912 ASSERT_TRUE (sm.get_style (1).m_bold);
913}
914
915static void
916test_from_str_with_underscore ()
917{
918 style_manager sm;
919 styled_string s (sm, "\33[04m\33[KA");
920 ASSERT_EQ (s[0].get_code (), 'A');
921 ASSERT_EQ (s[0].get_style_id (), 1);
922 ASSERT_TRUE (sm.get_style (1).m_underscore);
923}
924
925static void
926test_from_str_with_blink ()
927{
928 style_manager sm;
929 styled_string s (sm, "\33[05m\33[KA");
930 ASSERT_EQ (s[0].get_code (), 'A');
931 ASSERT_EQ (s[0].get_style_id (), 1);
932 ASSERT_TRUE (sm.get_style (1).m_blink);
933}
934
935// Test of parsing SGR codes.
936
937static void
938test_from_str_with_color ()
939{
940 style_manager sm;
941
942 styled_string s (sm,
943 ("0"
944 SGR_SEQ (COLOR_FG_RED)
945 "R"
946 SGR_RESET
947 "2"
948 SGR_SEQ (COLOR_FG_GREEN)
949 "G"
950 SGR_RESET
951 "4"));
952 ASSERT_EQ (s.size (), 5);
953 ASSERT_EQ (sm.get_num_styles (), 3);
954 ASSERT_EQ (s[0].get_code (), '0');
955 ASSERT_EQ (s[0].get_style_id (), 0);
956 ASSERT_EQ (s[1].get_code (), 'R');
957 ASSERT_EQ (s[1].get_style_id (), 1);
958 ASSERT_EQ (s[2].get_code (), '2');
959 ASSERT_EQ (s[2].get_style_id (), 0);
960 ASSERT_EQ (s[3].get_code (), 'G');
961 ASSERT_EQ (s[3].get_style_id (), 2);
962 ASSERT_EQ (s[4].get_code (), '4');
963 ASSERT_EQ (s[4].get_style_id (), 0);
964 ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED);
965 ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN);
966}
967
968static void
969test_from_str_with_named_color ()
970{
971 style_manager sm;
972 styled_string s (sm,
973 ("F"
974 SGR_SEQ (COLOR_FG_BLACK) "F"
975 SGR_SEQ (COLOR_FG_RED) "F"
976 SGR_SEQ (COLOR_FG_GREEN) "F"
977 SGR_SEQ (COLOR_FG_YELLOW) "F"
978 SGR_SEQ (COLOR_FG_BLUE) "F"
979 SGR_SEQ (COLOR_FG_MAGENTA) "F"
980 SGR_SEQ (COLOR_FG_CYAN) "F"
981 SGR_SEQ (COLOR_FG_WHITE) "F"
982 SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F"
983 SGR_SEQ (COLOR_FG_BRIGHT_RED) "F"
984 SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F"
985 SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F"
986 SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F"
987 SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F"
988 SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F"
989 SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F"
990 SGR_SEQ (COLOR_BG_BLACK) "B"
991 SGR_SEQ (COLOR_BG_RED) "B"
992 SGR_SEQ (COLOR_BG_GREEN) "B"
993 SGR_SEQ (COLOR_BG_YELLOW) "B"
994 SGR_SEQ (COLOR_BG_BLUE) "B"
995 SGR_SEQ (COLOR_BG_MAGENTA) "B"
996 SGR_SEQ (COLOR_BG_CYAN) "B"
997 SGR_SEQ (COLOR_BG_WHITE) "B"
998 SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B"
999 SGR_SEQ (COLOR_BG_BRIGHT_RED) "B"
1000 SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B"
1001 SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B"
1002 SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B"
1003 SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B"
1004 SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B"
1005 SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B"));
1006 ASSERT_EQ (s.size (), 33);
1007 for (size_t i = 0; i < s.size (); i++)
1008 ASSERT_EQ (s[i].get_style_id (), i);
1009 for (size_t i = 0; i < 17; i++)
1010 ASSERT_EQ (s[i].get_code (), 'F');
1011 for (size_t i = 17; i < 33; i++)
1012 ASSERT_EQ (s[i].get_code (), 'B');
1013}
1014
1015static void
1016test_from_str_with_8_bit_color ()
1017{
1018 {
1019 style_manager sm;
1020 styled_string s (sm,
1021 ("F"));
1022 ASSERT_EQ (s.size (), 1);
1023 ASSERT_EQ (s[0].get_code (), 'F');
1024 ASSERT_EQ (s[0].get_style_id (), 1);
1025 ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232));
1026 }
1027 {
1028 style_manager sm;
1029 styled_string s (sm,
1030 ("B"));
1031 ASSERT_EQ (s.size (), 1);
1032 ASSERT_EQ (s[0].get_code (), 'B');
1033 ASSERT_EQ (s[0].get_style_id (), 1);
1034 ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231));
1035 }
1036}
1037
1038static void
1039test_from_str_with_24_bit_color ()
1040{
1041 {
1042 style_manager sm;
1043 styled_string s (sm,
1044 ("F"));
1045 ASSERT_EQ (s.size (), 1);
1046 ASSERT_EQ (s[0].get_code (), 'F');
1047 ASSERT_EQ (s[0].get_style_id (), 1);
1048 ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242));
1049 }
1050 {
1051 style_manager sm;
1052 styled_string s (sm,
1053 ("B"));
1054 ASSERT_EQ (s.size (), 1);
1055 ASSERT_EQ (s[0].get_code (), 'B');
1056 ASSERT_EQ (s[0].get_style_id (), 1);
1057 ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231));
1058 }
1059}
1060
1061static void
1062test_from_str_combining_characters ()
1063{
1064 style_manager sm;
1065 styled_string s (sm,
1066 /* CYRILLIC CAPITAL LETTER U (U+0423). */
1067 "\xD0\xA3"
1068 /* COMBINING BREVE (U+0306). */
1069 "\xCC\x86");
1070 ASSERT_EQ (s.size (), 1);
1071 ASSERT_EQ (s[0].get_code (), 0x423);
1072 ASSERT_EQ (s[0].get_combining_chars ().size (), 1);
1073 ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306);
1074}
1075
1076/* Run all selftests in this file. */
1077
1078void
1079text_art_styled_string_cc_tests ()
1080{
1081 test_combining_chars ();
1082 test_empty ();
1083 test_simple ();
1084 test_pi_from_utf8 ();
1085 test_emoji_from_utf8 ();
1086 test_emoji_variant_from_utf8 ();
1087 test_emoji_from_codepoint ();
1088 test_from_mixed_width_utf8 ();
1089 test_url ();
1090 test_from_fmt ();
1091 test_from_fmt_qs ();
1092 test_from_str_with_bold ();
1093 test_from_str_with_underscore ();
1094 test_from_str_with_blink ();
1095 test_from_str_with_color ();
1096 test_from_str_with_named_color ();
1097 test_from_str_with_8_bit_color ();
1098 test_from_str_with_24_bit_color ();
1099 test_from_str_combining_characters ();
1100}
1101
1102} // namespace selftest
1103
1104
1105#endif /* #if CHECKING_P */
1106

source code of gcc/text-art/styled-string.cc