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