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