diff options
Diffstat (limited to 'gcc/text-art/styled-string.cc')
-rw-r--r-- | gcc/text-art/styled-string.cc | 1108 |
1 files changed, 1108 insertions, 0 deletions
diff --git a/gcc/text-art/styled-string.cc b/gcc/text-art/styled-string.cc new file mode 100644 index 0000000..a0cc187 --- /dev/null +++ b/gcc/text-art/styled-string.cc @@ -0,0 +1,1108 @@ +/* Implementation of text_art::styled_string. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_MEMORY +#define INCLUDE_VECTOR +#include "system.h" +#include "coretypes.h" +#include "make-unique.h" +#include "pretty-print.h" +#include "intl.h" +#include "diagnostic.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/types.h" +#include "color-macros.h" + +using namespace text_art; + +namespace { + +/* Support class for parsing text containing escape codes. + See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code + We only support the codes that pretty-print.cc can generate. */ + +class escape_code_parser +{ +public: + escape_code_parser (style_manager &sm, + std::vector<styled_unichar> &out) + : m_sm (sm), + m_out (out), + m_cur_style_obj (), + m_cur_style_id (style::id_plain), + m_state (state::START) + { + } + + void on_char (cppchar_t ch) + { + switch (m_state) + { + default: + gcc_unreachable (); + case state::START: + if (ch == '\033') + { + /* The start of an escape sequence. */ + m_state = state::AFTER_ESC; + return; + } + break; + case state::AFTER_ESC: + if (ch == '[') + { + /* ESC [ is a Control Sequence Introducer. */ + m_state = state::CS_PARAMETER_BYTES; + return; + } + else if (ch == ']') + { + /* ESC ] is an Operating System Command. */ + m_state = state::WITHIN_OSC; + return; + } + break; + case state::CS_PARAMETER_BYTES: + if (parameter_byte_p (ch)) + { + m_parameter_bytes.push_back ((char)ch); + return; + } + else if (intermediate_byte_p (ch)) + { + m_intermediate_bytes.push_back ((char)ch); + m_state = state::CS_INTERMEDIATE_BYTES; + return; + } + else if (final_byte_p (ch)) + { + on_final_csi_char (ch); + return; + } + break; + case state::CS_INTERMEDIATE_BYTES: + /* Expect zero or more intermediate bytes. */ + if (intermediate_byte_p (ch)) + { + m_intermediate_bytes.push_back ((char)ch); + return; + } + else if (final_byte_p (ch)) + { + on_final_csi_char (ch); + return; + } + break; + case state::WITHIN_OSC: + /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */ + { + /* Check for ESC \, the String Terminator (aka "ST"). */ + if (ch == '\\' + && m_osc_string.size () > 0 + && m_osc_string.back () == '\033') + { + m_osc_string.pop_back (); + on_final_osc_char (); + return; + } + else if (ch == '\a') + { + // BEL + on_final_osc_char (); + return; + } + m_osc_string.push_back (ch); + return; + } + break; + } + + /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji + variation for the previous character. */ + if (ch == 0xFE0F) + { + if (m_out.size () > 0) + m_out.back ().set_emoji_variant (); + return; + } + + if (cpp_is_combining_char (ch)) + { + if (m_out.size () > 0) + { + m_out.back ().add_combining_char (ch); + return; + } + } + /* By default, add the char. */ + m_out.push_back (styled_unichar (ch, false, m_cur_style_id)); + } + +private: + void on_final_csi_char (cppchar_t ch) + { + switch (ch) + { + default: + /* Unrecognized. */ + break; + case 'm': + { + /* SGR control sequence. */ + if (m_parameter_bytes.empty ()) + reset_style (); + std::vector<int> params (params_from_decimal ()); + for (auto iter = params.begin (); iter != params.end (); ) + { + const int param = *iter; + switch (param) + { + default: + /* Unrecognized SGR parameter. */ + break; + case 0: + reset_style (); + break; + case 1: + set_style_bold (); + break; + case 4: + set_style_underscore (); + break; + case 5: + set_style_blink (); + break; + + /* Named foreground colors. */ + case 30: + set_style_fg_color (style::named_color::BLACK); + break; + case 31: + set_style_fg_color (style::named_color::RED); + break; + case 32: + set_style_fg_color (style::named_color::GREEN); + break; + case 33: + set_style_fg_color (style::named_color::YELLOW); + break; + case 34: + set_style_fg_color (style::named_color::BLUE); + break; + case 35: + set_style_fg_color (style::named_color::MAGENTA); + break; + case 36: + set_style_fg_color (style::named_color::CYAN); + break; + case 37: + set_style_fg_color (style::named_color::WHITE); + break; + + /* 8-bit and 24-bit color */ + case 38: + case 48: + { + const bool fg = (param == 38); + iter++; + if (iter != params.end ()) + switch (*(iter++)) + { + default: + break; + case 5: + /* 8-bit color. */ + if (iter != params.end ()) + { + const uint8_t col = *(iter++); + if (fg) + set_style_fg_color (style::color (col)); + else + set_style_bg_color (style::color (col)); + } + continue; + case 2: + /* 24-bit color. */ + if (iter != params.end ()) + { + const uint8_t r = *(iter++); + if (iter != params.end ()) + { + const uint8_t g = *(iter++); + if (iter != params.end ()) + { + const uint8_t b = *(iter++); + if (fg) + set_style_fg_color (style::color (r, + g, + b)); + else + set_style_bg_color (style::color (r, + g, + b)); + } + } + } + continue; + } + continue; + } + break; + + /* Named background colors. */ + case 40: + set_style_bg_color (style::named_color::BLACK); + break; + case 41: + set_style_bg_color (style::named_color::RED); + break; + case 42: + set_style_bg_color (style::named_color::GREEN); + break; + case 43: + set_style_bg_color (style::named_color::YELLOW); + break; + case 44: + set_style_bg_color (style::named_color::BLUE); + break; + case 45: + set_style_bg_color (style::named_color::MAGENTA); + break; + case 46: + set_style_bg_color (style::named_color::CYAN); + break; + case 47: + set_style_bg_color (style::named_color::WHITE); + break; + + /* Named foreground colors, bright. */ + case 90: + set_style_fg_color (style::color (style::named_color::BLACK, + true)); + break; + case 91: + set_style_fg_color (style::color (style::named_color::RED, + true)); + break; + case 92: + set_style_fg_color (style::color (style::named_color::GREEN, + true)); + break; + case 93: + set_style_fg_color (style::color (style::named_color::YELLOW, + true)); + break; + case 94: + set_style_fg_color (style::color (style::named_color::BLUE, + true)); + break; + case 95: + set_style_fg_color (style::color (style::named_color::MAGENTA, + true)); + break; + case 96: + set_style_fg_color (style::color (style::named_color::CYAN, + true)); + break; + case 97: + set_style_fg_color (style::color (style::named_color::WHITE, + true)); + break; + + /* Named foreground colors, bright. */ + case 100: + set_style_bg_color (style::color (style::named_color::BLACK, + true)); + break; + case 101: + set_style_bg_color (style::color (style::named_color::RED, + true)); + break; + case 102: + set_style_bg_color (style::color (style::named_color::GREEN, + true)); + break; + case 103: + set_style_bg_color (style::color (style::named_color::YELLOW, + true)); + break; + case 104: + set_style_bg_color (style::color (style::named_color::BLUE, + true)); + break; + case 105: + set_style_bg_color (style::color (style::named_color::MAGENTA, + true)); + break; + case 106: + set_style_bg_color (style::color (style::named_color::CYAN, + true)); + break; + case 107: + set_style_bg_color (style::color (style::named_color::WHITE, + true)); + break; + } + ++iter; + } + } + break; + } + m_parameter_bytes.clear (); + m_intermediate_bytes.clear (); + m_state = state::START; + } + + void on_final_osc_char () + { + if (!m_osc_string.empty ()) + { + switch (m_osc_string[0]) + { + default: + break; + case '8': + /* Hyperlink support; see: + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + We don't support params, so we expect either: + (a) "8;;URL" to begin a url (see pp_begin_url), or + (b) "8;;" to end a URL (see pp_end_url). */ + if (m_osc_string.size () >= 3 + && m_osc_string[1] == ';' + && m_osc_string[2] == ';') + { + set_style_url (m_osc_string.begin () + 3, + m_osc_string.end ()); + } + break; + } + } + m_osc_string.clear (); + m_state = state::START; + } + + std::vector<int> params_from_decimal () const + { + std::vector<int> result; + + int curr_int = -1; + for (auto param_ch : m_parameter_bytes) + { + if (param_ch >= '0' && param_ch <= '9') + { + if (curr_int == -1) + curr_int = 0; + else + curr_int *= 10; + curr_int += param_ch - '0'; + } + else + { + if (curr_int != -1) + { + result.push_back (curr_int); + curr_int = -1; + } + } + } + if (curr_int != -1) + result.push_back (curr_int); + return result; + } + + void refresh_style_id () + { + m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj); + } + void reset_style () + { + m_cur_style_obj = style (); + refresh_style_id (); + } + void set_style_bold () + { + m_cur_style_obj.m_bold = true; + refresh_style_id (); + } + void set_style_underscore () + { + m_cur_style_obj.m_underscore = true; + refresh_style_id (); + } + void set_style_blink () + { + m_cur_style_obj.m_blink = true; + refresh_style_id (); + } + void set_style_fg_color (style::color color) + { + m_cur_style_obj.m_fg_color = color; + refresh_style_id (); + } + void set_style_bg_color (style::color color) + { + m_cur_style_obj.m_bg_color = color; + refresh_style_id (); + } + void set_style_url (std::vector<cppchar_t>::iterator begin, + std::vector<cppchar_t>::iterator end) + { + // The empty string means "no URL" + m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end); + refresh_style_id (); + } + + static bool parameter_byte_p (cppchar_t ch) + { + return ch >= 0x30 && ch <= 0x3F; + } + + static bool intermediate_byte_p (cppchar_t ch) + { + return ch >= 0x20 && ch <= 0x2F; + } + + static bool final_byte_p (cppchar_t ch) + { + return ch >= 0x40 && ch <= 0x7E; + } + + style_manager &m_sm; + std::vector<styled_unichar> &m_out; + + style m_cur_style_obj; + style::id_t m_cur_style_id; + + /* Handling of control sequences. */ + enum class state + { + START, + + /* After ESC, expecting '['. */ + AFTER_ESC, + + /* Expecting zero or more parameter bytes, an + intermediate byte, or a final byte. */ + CS_PARAMETER_BYTES, + + /* Expecting zero or more intermediate bytes, or a final byte. */ + CS_INTERMEDIATE_BYTES, + + /* Within OSC. */ + WITHIN_OSC + + } m_state; + std::vector<char> m_parameter_bytes; + std::vector<char> m_intermediate_bytes; + std::vector<cppchar_t> m_osc_string; +}; + +} // anon namespace + +/* class text_art::styled_string. */ + +/* Construct a styled_string from STR. + STR is assumed to be UTF-8 encoded and 0-terminated. + + Parse SGR formatting chars from being in-band (within in the sequence + of chars) to being out-of-band, as style elements. + We only support parsing the subset of SGR chars that can be emitted + by pretty-print.cc */ + +styled_string::styled_string (style_manager &sm, const char *str) +: m_chars () +{ + escape_code_parser parser (sm, m_chars); + + /* We don't actually want the display widths here, but + it's an easy way to decode UTF-8. */ + cpp_char_column_policy policy (8, cpp_wcwidth); + cpp_display_width_computation dw (str, strlen (str), policy); + while (!dw.done ()) + { + cpp_decoded_char decoded_char; + dw.process_next_codepoint (&decoded_char); + + if (!decoded_char.m_valid_ch) + /* Skip bytes that aren't valid UTF-8. */ + continue; + + /* Decode SGR formatting. */ + cppchar_t ch = decoded_char.m_ch; + parser.on_char (ch); + } +} + +styled_string::styled_string (cppchar_t cppchar, bool emoji) +{ + m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain)); +} + +styled_string +styled_string::from_fmt_va (style_manager &sm, + printer_fn format_decoder, + const char *fmt, + va_list *args) +{ + text_info text; + text.err_no = errno; + text.args_ptr = args; + text.format_spec = fmt; + pretty_printer pp; + pp_show_color (&pp) = true; + pp.url_format = URL_FORMAT_DEFAULT; + pp_format_decoder (&pp) = format_decoder; + pp_format (&pp, &text); + pp_output_formatted_text (&pp); + styled_string result (sm, pp_formatted_text (&pp)); + return result; +} + +styled_string +styled_string::from_fmt (style_manager &sm, + printer_fn format_decoder, + const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap); + va_end (ap); + return result; +} + +int +styled_string::calc_canvas_width () const +{ + int result = 0; + for (auto ch : m_chars) + result += ch.get_canvas_width (); + return result; +} + +void +styled_string::append (const styled_string &suffix) +{ + m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (), + suffix.begin (), + suffix.end ()); +} + +void +styled_string::set_url (style_manager &sm, const char *url) +{ + for (auto& ch : m_chars) + { + const style &existing_style = sm.get_style (ch.get_style_id ()); + style with_url (existing_style); + with_url.set_style_url (url); + ch.m_style_id = sm.get_or_create_id (with_url); + } +} + +#if CHECKING_P + +namespace selftest { + +static void +test_combining_chars () +{ + /* This really ought to be in libcpp, but we don't have + selftests there. */ + ASSERT_FALSE (cpp_is_combining_char (0)); + ASSERT_FALSE (cpp_is_combining_char ('a')); + + /* COMBINING BREVE (U+0306). */ + ASSERT_TRUE (cpp_is_combining_char (0x0306)); + + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */ + ASSERT_FALSE (cpp_is_combining_char (0x5B57)); + + /* U+FE0F VARIATION SELECTOR-16. */ + ASSERT_FALSE (cpp_is_combining_char (0xFE0F)); +} + +static void +test_empty () +{ + style_manager sm; + styled_string s (sm, ""); + ASSERT_EQ (s.size (), 0); + ASSERT_EQ (s.calc_canvas_width (), 0); +} + +/* Test of a pure ASCII string with no escape codes. */ + +static void +test_simple () +{ + const char *c_str = "hello world!"; + style_manager sm; + styled_string s (sm, c_str); + ASSERT_EQ (s.size (), strlen (c_str)); + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str)); + for (size_t i = 0; i < strlen (c_str); i++) + { + ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]); + ASSERT_EQ (s[i].get_style_id (), 0); + } +} + +/* Test of decoding UTF-8. */ + +static void +test_pi_from_utf8 () +{ + /* U+03C0 "GREEK SMALL LETTER PI". */ + const char * const pi_utf8 = "\xCF\x80"; + + style_manager sm; + styled_string s (sm, pi_utf8); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 1); + ASSERT_EQ (s[0].get_code (), 0x03c0); + ASSERT_EQ (s[0].emoji_variant_p (), false); + ASSERT_EQ (s[0].double_width_p (), false); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +/* Test of double-width character. */ + +static void +test_emoji_from_utf8 () +{ + /* U+1F642 "SLIGHTLY SMILING FACE". */ + const char * const emoji_utf8 = "\xF0\x9F\x99\x82"; + + style_manager sm; + styled_string s (sm, emoji_utf8); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 2); + ASSERT_EQ (s[0].get_code (), 0x1f642); + ASSERT_EQ (s[0].double_width_p (), true); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji + variation for the previous character. */ + +static void +test_emoji_variant_from_utf8 () +{ + const char * const emoji_utf8 + = (/* U+26A0 WARNING SIGN. */ + "\xE2\x9A\xA0" + /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */ + "\xEF\xB8\x8F"); + + style_manager sm; + styled_string s (sm, emoji_utf8); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 1); + ASSERT_EQ (s[0].get_code (), 0x26a0); + ASSERT_EQ (s[0].emoji_variant_p (), true); + ASSERT_EQ (s[0].double_width_p (), false); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +static void +test_emoji_from_codepoint () +{ + styled_string s ((cppchar_t)0x1f642); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 2); + ASSERT_EQ (s[0].get_code (), 0x1f642); + ASSERT_EQ (s[0].double_width_p (), true); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +static void +test_from_mixed_width_utf8 () +{ + /* This UTF-8 string literal is of the form + before mojibake after + where the Japanese word "mojibake" is written as the following + four unicode code points: + U+6587 CJK UNIFIED IDEOGRAPH-6587 + U+5B57 CJK UNIFIED IDEOGRAPH-5B57 + U+5316 CJK UNIFIED IDEOGRAPH-5316 + U+3051 HIRAGANA LETTER KE. + Each of these is 3 bytes wide when encoded in UTF-8, whereas the + "before" and "after" are 1 byte per unicode character. */ + const char * const mixed_width_utf8 + = ("before " + + /* U+6587 CJK UNIFIED IDEOGRAPH-6587 + UTF-8: 0xE6 0x96 0x87 + C octal escaped UTF-8: \346\226\207. */ + "\346\226\207" + + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57 + UTF-8: 0xE5 0xAD 0x97 + C octal escaped UTF-8: \345\255\227. */ + "\345\255\227" + + /* U+5316 CJK UNIFIED IDEOGRAPH-5316 + UTF-8: 0xE5 0x8C 0x96 + C octal escaped UTF-8: \345\214\226. */ + "\345\214\226" + + /* U+3051 HIRAGANA LETTER KE + UTF-8: 0xE3 0x81 0x91 + C octal escaped UTF-8: \343\201\221. */ + "\343\201\221" + + " after"); + + style_manager sm; + styled_string s (sm, mixed_width_utf8); + ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5); + ASSERT_EQ (sm.get_num_styles (), 1); + + // We expect the Japanese characters to be double width. + ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5); + + ASSERT_EQ (s[0].get_code (), 'b'); + ASSERT_EQ (s[0].double_width_p (), false); + ASSERT_EQ (s[1].get_code (), 'e'); + ASSERT_EQ (s[2].get_code (), 'f'); + ASSERT_EQ (s[3].get_code (), 'o'); + ASSERT_EQ (s[4].get_code (), 'r'); + ASSERT_EQ (s[5].get_code (), 'e'); + ASSERT_EQ (s[6].get_code (), ' '); + ASSERT_EQ (s[7].get_code (), 0x6587); + ASSERT_EQ (s[7].double_width_p (), true); + ASSERT_EQ (s[8].get_code (), 0x5B57); + ASSERT_EQ (s[9].get_code (), 0x5316); + ASSERT_EQ (s[10].get_code (), 0x3051); + ASSERT_EQ (s[11].get_code (), ' '); + ASSERT_EQ (s[12].get_code (), 'a'); + ASSERT_EQ (s[13].get_code (), 'f'); + ASSERT_EQ (s[14].get_code (), 't'); + ASSERT_EQ (s[15].get_code (), 'e'); + ASSERT_EQ (s[16].get_code (), 'r'); + + ASSERT_EQ (s[0].get_style_id (), 0); +} + +static void +assert_style_urleq (const location &loc, + const style &s, + const char *expected_str) +{ + ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str)); + for (size_t i = 0; i < s.m_url.size (); i++) + ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]); +} + +#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \ + assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR)) + +static void +test_url () +{ + // URL_FORMAT_ST + { + style_manager sm; + styled_string s + (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\"); + const char *expected = "This is a link"; + ASSERT_EQ (s.size (), strlen (expected)); + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected)); + ASSERT_EQ (sm.get_num_styles (), 2); + for (size_t i = 0; i < strlen (expected); i++) + { + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]); + ASSERT_EQ (s[i].get_style_id (), 1); + } + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com"); + } + + // URL_FORMAT_BEL + { + style_manager sm; + styled_string s + (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a"); + const char *expected = "This is a link"; + ASSERT_EQ (s.size (), strlen (expected)); + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected)); + ASSERT_EQ (sm.get_num_styles (), 2); + for (size_t i = 0; i < strlen (expected); i++) + { + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]); + ASSERT_EQ (s[i].get_style_id (), 1); + } + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com"); + } +} + +static void +test_from_fmt () +{ + style_manager sm; + styled_string s (styled_string::from_fmt (sm, NULL, "%%i: %i", 42)); + ASSERT_EQ (s[0].get_code (), '%'); + ASSERT_EQ (s[1].get_code (), 'i'); + ASSERT_EQ (s[2].get_code (), ':'); + ASSERT_EQ (s[3].get_code (), ' '); + ASSERT_EQ (s[4].get_code (), '4'); + ASSERT_EQ (s[5].get_code (), '2'); + ASSERT_EQ (s.size (), 6); + ASSERT_EQ (s.calc_canvas_width (), 6); +} + +static void +test_from_fmt_qs () +{ + auto_fix_quotes fix_quotes; + open_quote = "\xe2\x80\x98"; + close_quote = "\xe2\x80\x99"; + + style_manager sm; + styled_string s (styled_string::from_fmt (sm, NULL, "%qs", "msg")); + ASSERT_EQ (sm.get_num_styles (), 2); + ASSERT_EQ (s[0].get_code (), 0x2018); + ASSERT_EQ (s[0].get_style_id (), 0); + ASSERT_EQ (s[1].get_code (), 'm'); + ASSERT_EQ (s[1].get_style_id (), 1); + ASSERT_EQ (s[2].get_code (), 's'); + ASSERT_EQ (s[2].get_style_id (), 1); + ASSERT_EQ (s[3].get_code (), 'g'); + ASSERT_EQ (s[3].get_style_id (), 1); + ASSERT_EQ (s[4].get_code (), 0x2019); + ASSERT_EQ (s[4].get_style_id (), 0); + ASSERT_EQ (s.size (), 5); +} + +// Test of parsing SGR codes. + +static void +test_from_str_with_bold () +{ + style_manager sm; + /* This is the result of pp_printf (pp, "%qs", "foo") + with auto_fix_quotes. */ + styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'"); + ASSERT_EQ (s[0].get_code (), '`'); + ASSERT_EQ (s[0].get_style_id (), 0); + ASSERT_EQ (s[1].get_code (), 'f'); + ASSERT_EQ (s[1].get_style_id (), 1); + ASSERT_EQ (s[2].get_code (), 'o'); + ASSERT_EQ (s[2].get_style_id (), 1); + ASSERT_EQ (s[3].get_code (), 'o'); + ASSERT_EQ (s[3].get_style_id (), 1); + ASSERT_EQ (s[4].get_code (), '\''); + ASSERT_EQ (s[4].get_style_id (), 0); + ASSERT_EQ (s.size (), 5); + ASSERT_TRUE (sm.get_style (1).m_bold); +} + +static void +test_from_str_with_underscore () +{ + style_manager sm; + styled_string s (sm, "\33[04m\33[KA"); + ASSERT_EQ (s[0].get_code (), 'A'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_TRUE (sm.get_style (1).m_underscore); +} + +static void +test_from_str_with_blink () +{ + style_manager sm; + styled_string s (sm, "\33[05m\33[KA"); + ASSERT_EQ (s[0].get_code (), 'A'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_TRUE (sm.get_style (1).m_blink); +} + +// Test of parsing SGR codes. + +static void +test_from_str_with_color () +{ + style_manager sm; + + styled_string s (sm, + ("0" + SGR_SEQ (COLOR_FG_RED) + "R" + SGR_RESET + "2" + SGR_SEQ (COLOR_FG_GREEN) + "G" + SGR_RESET + "4")); + ASSERT_EQ (s.size (), 5); + ASSERT_EQ (sm.get_num_styles (), 3); + ASSERT_EQ (s[0].get_code (), '0'); + ASSERT_EQ (s[0].get_style_id (), 0); + ASSERT_EQ (s[1].get_code (), 'R'); + ASSERT_EQ (s[1].get_style_id (), 1); + ASSERT_EQ (s[2].get_code (), '2'); + ASSERT_EQ (s[2].get_style_id (), 0); + ASSERT_EQ (s[3].get_code (), 'G'); + ASSERT_EQ (s[3].get_style_id (), 2); + ASSERT_EQ (s[4].get_code (), '4'); + ASSERT_EQ (s[4].get_style_id (), 0); + ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED); + ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN); +} + +static void +test_from_str_with_named_color () +{ + style_manager sm; + styled_string s (sm, + ("F" + SGR_SEQ (COLOR_FG_BLACK) "F" + SGR_SEQ (COLOR_FG_RED) "F" + SGR_SEQ (COLOR_FG_GREEN) "F" + SGR_SEQ (COLOR_FG_YELLOW) "F" + SGR_SEQ (COLOR_FG_BLUE) "F" + SGR_SEQ (COLOR_FG_MAGENTA) "F" + SGR_SEQ (COLOR_FG_CYAN) "F" + SGR_SEQ (COLOR_FG_WHITE) "F" + SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F" + SGR_SEQ (COLOR_FG_BRIGHT_RED) "F" + SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F" + SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F" + SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F" + SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F" + SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F" + SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F" + SGR_SEQ (COLOR_BG_BLACK) "B" + SGR_SEQ (COLOR_BG_RED) "B" + SGR_SEQ (COLOR_BG_GREEN) "B" + SGR_SEQ (COLOR_BG_YELLOW) "B" + SGR_SEQ (COLOR_BG_BLUE) "B" + SGR_SEQ (COLOR_BG_MAGENTA) "B" + SGR_SEQ (COLOR_BG_CYAN) "B" + SGR_SEQ (COLOR_BG_WHITE) "B" + SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B" + SGR_SEQ (COLOR_BG_BRIGHT_RED) "B" + SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B" + SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B" + SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B" + SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B" + SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B" + SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B")); + ASSERT_EQ (s.size (), 33); + for (size_t i = 0; i < s.size (); i++) + ASSERT_EQ (s[i].get_style_id (), i); + for (size_t i = 0; i < 17; i++) + ASSERT_EQ (s[i].get_code (), 'F'); + for (size_t i = 17; i < 33; i++) + ASSERT_EQ (s[i].get_code (), 'B'); +} + +static void +test_from_str_with_8_bit_color () +{ + { + style_manager sm; + styled_string s (sm, + ("[38;5;232m[KF")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'F'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232)); + } + { + style_manager sm; + styled_string s (sm, + ("[48;5;231m[KB")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'B'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231)); + } +} + +static void +test_from_str_with_24_bit_color () +{ + { + style_manager sm; + styled_string s (sm, + ("[38;2;243;250;242m[KF")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'F'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242)); + } + { + style_manager sm; + styled_string s (sm, + ("[48;2;253;247;231m[KB")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'B'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231)); + } +} + +static void +test_from_str_combining_characters () +{ + style_manager sm; + styled_string s (sm, + /* CYRILLIC CAPITAL LETTER U (U+0423). */ + "\xD0\xA3" + /* COMBINING BREVE (U+0306). */ + "\xCC\x86"); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 0x423); + ASSERT_EQ (s[0].get_combining_chars ().size (), 1); + ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306); +} + +/* Run all selftests in this file. */ + +void +text_art_styled_string_cc_tests () +{ + test_combining_chars (); + test_empty (); + test_simple (); + test_pi_from_utf8 (); + test_emoji_from_utf8 (); + test_emoji_variant_from_utf8 (); + test_emoji_from_codepoint (); + test_from_mixed_width_utf8 (); + test_url (); + test_from_fmt (); + test_from_fmt_qs (); + test_from_str_with_bold (); + test_from_str_with_underscore (); + test_from_str_with_blink (); + test_from_str_with_color (); + test_from_str_with_named_color (); + test_from_str_with_8_bit_color (); + test_from_str_with_24_bit_color (); + test_from_str_combining_characters (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ |