17f2ac410Schristos /* Styling for ui_file 2*6881a400Schristos Copyright (C) 2018-2023 Free Software Foundation, Inc. 37f2ac410Schristos 47f2ac410Schristos This file is part of GDB. 57f2ac410Schristos 67f2ac410Schristos This program is free software; you can redistribute it and/or modify 77f2ac410Schristos it under the terms of the GNU General Public License as published by 87f2ac410Schristos the Free Software Foundation; either version 3 of the License, or 97f2ac410Schristos (at your option) any later version. 107f2ac410Schristos 117f2ac410Schristos This program is distributed in the hope that it will be useful, 127f2ac410Schristos but WITHOUT ANY WARRANTY; without even the implied warranty of 137f2ac410Schristos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 147f2ac410Schristos GNU General Public License for more details. 157f2ac410Schristos 167f2ac410Schristos You should have received a copy of the GNU General Public License 177f2ac410Schristos along with this program. If not, see <http://www.gnu.org/licenses/>. */ 187f2ac410Schristos 197f2ac410Schristos #include "defs.h" 207f2ac410Schristos #include "ui-style.h" 21*6881a400Schristos #include "gdbsupport/gdb_regex.h" 227f2ac410Schristos 237f2ac410Schristos /* A regular expression that is used for matching ANSI terminal escape 247f2ac410Schristos sequences. */ 257f2ac410Schristos 26*6881a400Schristos static const char ansi_regex_text[] = 277f2ac410Schristos /* Introduction. */ 287f2ac410Schristos "^\033\\[" 297f2ac410Schristos #define DATA_SUBEXP 1 307f2ac410Schristos /* Capture parameter and intermediate bytes. */ 317f2ac410Schristos "(" 327f2ac410Schristos /* Parameter bytes. */ 337f2ac410Schristos "[\x30-\x3f]*" 347f2ac410Schristos /* Intermediate bytes. */ 357f2ac410Schristos "[\x20-\x2f]*" 367f2ac410Schristos /* End the first capture. */ 377f2ac410Schristos ")" 387f2ac410Schristos /* The final byte. */ 397f2ac410Schristos #define FINAL_SUBEXP 2 407f2ac410Schristos "([\x40-\x7e])"; 417f2ac410Schristos 427f2ac410Schristos /* The number of subexpressions to allocate space for, including the 437f2ac410Schristos "0th" whole match subexpression. */ 447f2ac410Schristos #define NUM_SUBEXPRESSIONS 3 457f2ac410Schristos 467f2ac410Schristos /* The compiled form of ansi_regex_text. */ 477f2ac410Schristos 487f2ac410Schristos static regex_t ansi_regex; 497f2ac410Schristos 507f2ac410Schristos /* This maps bright colors to RGB triples. The index is the bright 517f2ac410Schristos color index, starting with bright black. The values come from 527f2ac410Schristos xterm. */ 537f2ac410Schristos 547f2ac410Schristos static const uint8_t bright_colors[][3] = { 557f2ac410Schristos { 127, 127, 127 }, /* Black. */ 567f2ac410Schristos { 255, 0, 0 }, /* Red. */ 577f2ac410Schristos { 0, 255, 0 }, /* Green. */ 587f2ac410Schristos { 255, 255, 0 }, /* Yellow. */ 597f2ac410Schristos { 92, 92, 255 }, /* Blue. */ 607f2ac410Schristos { 255, 0, 255 }, /* Magenta. */ 617f2ac410Schristos { 0, 255, 255 }, /* Cyan. */ 627f2ac410Schristos { 255, 255, 255 } /* White. */ 637f2ac410Schristos }; 647f2ac410Schristos 657f2ac410Schristos /* See ui-style.h. */ 667f2ac410Schristos 677f2ac410Schristos bool 687f2ac410Schristos ui_file_style::color::append_ansi (bool is_fg, std::string *str) const 697f2ac410Schristos { 707f2ac410Schristos if (m_simple) 717f2ac410Schristos { 727f2ac410Schristos if (m_value >= BLACK && m_value <= WHITE) 737f2ac410Schristos str->append (std::to_string (m_value + (is_fg ? 30 : 40))); 747f2ac410Schristos else if (m_value > WHITE && m_value <= WHITE + 8) 757f2ac410Schristos str->append (std::to_string (m_value - WHITE + (is_fg ? 90 : 100))); 767f2ac410Schristos else if (m_value != -1) 777f2ac410Schristos { 787f2ac410Schristos str->append (is_fg ? "38;5;" : "48;5;"); 797f2ac410Schristos str->append (std::to_string (m_value)); 807f2ac410Schristos } 817f2ac410Schristos else 827f2ac410Schristos return false; 837f2ac410Schristos } 847f2ac410Schristos else 857f2ac410Schristos { 867f2ac410Schristos str->append (is_fg ? "38;2;" : "48;2;"); 877f2ac410Schristos str->append (std::to_string (m_red) 887f2ac410Schristos + ";" + std::to_string (m_green) 897f2ac410Schristos + ";" + std::to_string (m_blue)); 907f2ac410Schristos } 917f2ac410Schristos return true; 927f2ac410Schristos } 937f2ac410Schristos 947f2ac410Schristos /* See ui-style.h. */ 957f2ac410Schristos 967f2ac410Schristos void 977f2ac410Schristos ui_file_style::color::get_rgb (uint8_t *rgb) const 987f2ac410Schristos { 997f2ac410Schristos if (m_simple) 1007f2ac410Schristos { 1017f2ac410Schristos /* Can't call this for a basic color or NONE -- those will end 1027f2ac410Schristos up in the assert below. */ 1037f2ac410Schristos if (m_value >= 8 && m_value <= 15) 1047f2ac410Schristos memcpy (rgb, bright_colors[m_value - 8], 3 * sizeof (uint8_t)); 1057f2ac410Schristos else if (m_value >= 16 && m_value <= 231) 1067f2ac410Schristos { 1077f2ac410Schristos int value = m_value; 1087f2ac410Schristos value -= 16; 1097f2ac410Schristos /* This obscure formula seems to be what terminals actually 1107f2ac410Schristos do. */ 1117f2ac410Schristos int component = value / 36; 1127f2ac410Schristos rgb[0] = component == 0 ? 0 : (55 + component * 40); 1137f2ac410Schristos value %= 36; 1147f2ac410Schristos component = value / 6; 1157f2ac410Schristos rgb[1] = component == 0 ? 0 : (55 + component * 40); 1167f2ac410Schristos value %= 6; 1177f2ac410Schristos rgb[2] = value == 0 ? 0 : (55 + value * 40); 1187f2ac410Schristos } 1197f2ac410Schristos else if (m_value >= 232) 1207f2ac410Schristos { 1217f2ac410Schristos uint8_t v = (m_value - 232) * 10 + 8; 1227f2ac410Schristos rgb[0] = v; 1237f2ac410Schristos rgb[1] = v; 1247f2ac410Schristos rgb[2] = v; 1257f2ac410Schristos } 1267f2ac410Schristos else 1277f2ac410Schristos gdb_assert_not_reached ("get_rgb called on invalid color"); 1287f2ac410Schristos } 1297f2ac410Schristos else 1307f2ac410Schristos { 1317f2ac410Schristos rgb[0] = m_red; 1327f2ac410Schristos rgb[1] = m_green; 1337f2ac410Schristos rgb[2] = m_blue; 1347f2ac410Schristos } 1357f2ac410Schristos } 1367f2ac410Schristos 1377f2ac410Schristos /* See ui-style.h. */ 1387f2ac410Schristos 1397f2ac410Schristos std::string 1407f2ac410Schristos ui_file_style::to_ansi () const 1417f2ac410Schristos { 1427f2ac410Schristos std::string result ("\033["); 1437f2ac410Schristos bool need_semi = m_foreground.append_ansi (true, &result); 1447f2ac410Schristos if (!m_background.is_none ()) 1457f2ac410Schristos { 1467f2ac410Schristos if (need_semi) 1477f2ac410Schristos result.push_back (';'); 1487f2ac410Schristos m_background.append_ansi (false, &result); 1497f2ac410Schristos need_semi = true; 1507f2ac410Schristos } 1517f2ac410Schristos if (m_intensity != NORMAL) 1527f2ac410Schristos { 1537f2ac410Schristos if (need_semi) 1547f2ac410Schristos result.push_back (';'); 1557f2ac410Schristos result.append (std::to_string (m_intensity)); 1567f2ac410Schristos need_semi = true; 1577f2ac410Schristos } 1587f2ac410Schristos if (m_reverse) 1597f2ac410Schristos { 1607f2ac410Schristos if (need_semi) 1617f2ac410Schristos result.push_back (';'); 1627f2ac410Schristos result.push_back ('7'); 1637f2ac410Schristos } 1647f2ac410Schristos result.push_back ('m'); 1657f2ac410Schristos return result; 1667f2ac410Schristos } 1677f2ac410Schristos 1687f2ac410Schristos /* Read a ";" and a number from STRING. Return the number of 1697f2ac410Schristos characters read and put the number into *NUM. */ 1707f2ac410Schristos 1717f2ac410Schristos static bool 172*6881a400Schristos read_semi_number (const char *string, regoff_t *idx, long *num) 1737f2ac410Schristos { 1747f2ac410Schristos if (string[*idx] != ';') 1757f2ac410Schristos return false; 1767f2ac410Schristos ++*idx; 1777f2ac410Schristos if (string[*idx] < '0' || string[*idx] > '9') 1787f2ac410Schristos return false; 1797f2ac410Schristos char *tail; 1807f2ac410Schristos *num = strtol (string + *idx, &tail, 10); 1817f2ac410Schristos *idx = tail - string; 1827f2ac410Schristos return true; 1837f2ac410Schristos } 1847f2ac410Schristos 1857f2ac410Schristos /* A helper for ui_file_style::parse that reads an extended color 1867f2ac410Schristos sequence; that is, and 8- or 24- bit color. */ 1877f2ac410Schristos 1887f2ac410Schristos static bool 189*6881a400Schristos extended_color (const char *str, regoff_t *idx, ui_file_style::color *color) 1907f2ac410Schristos { 1917f2ac410Schristos long value; 1927f2ac410Schristos 1937f2ac410Schristos if (!read_semi_number (str, idx, &value)) 1947f2ac410Schristos return false; 1957f2ac410Schristos 1967f2ac410Schristos if (value == 5) 1977f2ac410Schristos { 1987f2ac410Schristos /* 8-bit color. */ 1997f2ac410Schristos if (!read_semi_number (str, idx, &value)) 2007f2ac410Schristos return false; 2017f2ac410Schristos 2027f2ac410Schristos if (value >= 0 && value <= 255) 2037f2ac410Schristos *color = ui_file_style::color (value); 2047f2ac410Schristos else 2057f2ac410Schristos return false; 2067f2ac410Schristos } 2077f2ac410Schristos else if (value == 2) 2087f2ac410Schristos { 2097f2ac410Schristos /* 24-bit color. */ 2107f2ac410Schristos long r, g, b; 2117f2ac410Schristos if (!read_semi_number (str, idx, &r) 2127f2ac410Schristos || r > 255 2137f2ac410Schristos || !read_semi_number (str, idx, &g) 2147f2ac410Schristos || g > 255 2157f2ac410Schristos || !read_semi_number (str, idx, &b) 2167f2ac410Schristos || b > 255) 2177f2ac410Schristos return false; 2187f2ac410Schristos *color = ui_file_style::color (r, g, b); 2197f2ac410Schristos } 2207f2ac410Schristos else 2217f2ac410Schristos { 2227f2ac410Schristos /* Unrecognized sequence. */ 2237f2ac410Schristos return false; 2247f2ac410Schristos } 2257f2ac410Schristos 2267f2ac410Schristos return true; 2277f2ac410Schristos } 2287f2ac410Schristos 2297f2ac410Schristos /* See ui-style.h. */ 2307f2ac410Schristos 2317f2ac410Schristos bool 2327f2ac410Schristos ui_file_style::parse (const char *buf, size_t *n_read) 2337f2ac410Schristos { 2347f2ac410Schristos regmatch_t subexps[NUM_SUBEXPRESSIONS]; 2357f2ac410Schristos 2367f2ac410Schristos int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0); 2377f2ac410Schristos if (match == REG_NOMATCH) 2387f2ac410Schristos { 2397f2ac410Schristos *n_read = 0; 2407f2ac410Schristos return false; 2417f2ac410Schristos } 2427f2ac410Schristos /* Other failures mean the regexp is broken. */ 2437f2ac410Schristos gdb_assert (match == 0); 2447f2ac410Schristos /* The regexp is anchored. */ 2457f2ac410Schristos gdb_assert (subexps[0].rm_so == 0); 2467f2ac410Schristos /* The final character exists. */ 2477f2ac410Schristos gdb_assert (subexps[FINAL_SUBEXP].rm_eo - subexps[FINAL_SUBEXP].rm_so == 1); 2487f2ac410Schristos 2497f2ac410Schristos if (buf[subexps[FINAL_SUBEXP].rm_so] != 'm') 2507f2ac410Schristos { 2517f2ac410Schristos /* We don't handle this sequence, so just drop it. */ 2527f2ac410Schristos *n_read = subexps[0].rm_eo; 2537f2ac410Schristos return false; 2547f2ac410Schristos } 2557f2ac410Schristos 2567f2ac410Schristos /* Examine each setting in the match and apply it to the result. 2577f2ac410Schristos See the Select Graphic Rendition section of 2587f2ac410Schristos https://en.wikipedia.org/wiki/ANSI_escape_code. In essence each 2597f2ac410Schristos code is just a number, separated by ";"; there are some more 2607f2ac410Schristos wrinkles but we don't support them all.. */ 2617f2ac410Schristos 2627f2ac410Schristos /* "\033[m" means the same thing as "\033[0m", so handle that 2637f2ac410Schristos specially here. */ 2647f2ac410Schristos if (subexps[DATA_SUBEXP].rm_so == subexps[DATA_SUBEXP].rm_eo) 2657f2ac410Schristos *this = ui_file_style (); 2667f2ac410Schristos 2677f2ac410Schristos for (regoff_t i = subexps[DATA_SUBEXP].rm_so; 2687f2ac410Schristos i < subexps[DATA_SUBEXP].rm_eo; 2697f2ac410Schristos ++i) 2707f2ac410Schristos { 2717f2ac410Schristos if (buf[i] == ';') 2727f2ac410Schristos { 2737f2ac410Schristos /* Skip. */ 2747f2ac410Schristos } 2757f2ac410Schristos else if (buf[i] >= '0' && buf[i] <= '9') 2767f2ac410Schristos { 2777f2ac410Schristos char *tail; 2787f2ac410Schristos long value = strtol (buf + i, &tail, 10); 2797f2ac410Schristos i = tail - buf; 2807f2ac410Schristos 2817f2ac410Schristos switch (value) 2827f2ac410Schristos { 2837f2ac410Schristos case 0: 2847f2ac410Schristos /* Reset. */ 2857f2ac410Schristos *this = ui_file_style (); 2867f2ac410Schristos break; 2877f2ac410Schristos case 1: 2887f2ac410Schristos /* Bold. */ 2897f2ac410Schristos m_intensity = BOLD; 2907f2ac410Schristos break; 2917f2ac410Schristos case 2: 2927f2ac410Schristos /* Dim. */ 2937f2ac410Schristos m_intensity = DIM; 2947f2ac410Schristos break; 2957f2ac410Schristos case 7: 2967f2ac410Schristos /* Reverse. */ 2977f2ac410Schristos m_reverse = true; 2987f2ac410Schristos break; 2997f2ac410Schristos case 21: 3007f2ac410Schristos m_intensity = NORMAL; 3017f2ac410Schristos break; 3027f2ac410Schristos case 22: 3037f2ac410Schristos /* Normal. */ 3047f2ac410Schristos m_intensity = NORMAL; 3057f2ac410Schristos break; 3067f2ac410Schristos case 27: 3077f2ac410Schristos /* Inverse off. */ 3087f2ac410Schristos m_reverse = false; 3097f2ac410Schristos break; 3107f2ac410Schristos 3117f2ac410Schristos case 30: 3127f2ac410Schristos case 31: 3137f2ac410Schristos case 32: 3147f2ac410Schristos case 33: 3157f2ac410Schristos case 34: 3167f2ac410Schristos case 35: 3177f2ac410Schristos case 36: 3187f2ac410Schristos case 37: 3197f2ac410Schristos /* Note: not 38. */ 3207f2ac410Schristos case 39: 3217f2ac410Schristos m_foreground = color (value - 30); 3227f2ac410Schristos break; 3237f2ac410Schristos 3247f2ac410Schristos case 40: 3257f2ac410Schristos case 41: 3267f2ac410Schristos case 42: 3277f2ac410Schristos case 43: 3287f2ac410Schristos case 44: 3297f2ac410Schristos case 45: 3307f2ac410Schristos case 46: 3317f2ac410Schristos case 47: 3327f2ac410Schristos /* Note: not 48. */ 3337f2ac410Schristos case 49: 3347f2ac410Schristos m_background = color (value - 40); 3357f2ac410Schristos break; 3367f2ac410Schristos 3377f2ac410Schristos case 90: 3387f2ac410Schristos case 91: 3397f2ac410Schristos case 92: 3407f2ac410Schristos case 93: 3417f2ac410Schristos case 94: 3427f2ac410Schristos case 95: 3437f2ac410Schristos case 96: 3447f2ac410Schristos case 97: 3457f2ac410Schristos m_foreground = color (value - 90 + 8); 3467f2ac410Schristos break; 3477f2ac410Schristos 3487f2ac410Schristos case 100: 3497f2ac410Schristos case 101: 3507f2ac410Schristos case 102: 3517f2ac410Schristos case 103: 3527f2ac410Schristos case 104: 3537f2ac410Schristos case 105: 3547f2ac410Schristos case 106: 3557f2ac410Schristos case 107: 3567f2ac410Schristos m_background = color (value - 100 + 8); 3577f2ac410Schristos break; 3587f2ac410Schristos 3597f2ac410Schristos case 38: 3607f2ac410Schristos /* If we can't parse the extended color, fail. */ 3617f2ac410Schristos if (!extended_color (buf, &i, &m_foreground)) 3627f2ac410Schristos { 3637f2ac410Schristos *n_read = subexps[0].rm_eo; 3647f2ac410Schristos return false; 3657f2ac410Schristos } 3667f2ac410Schristos break; 3677f2ac410Schristos 3687f2ac410Schristos case 48: 3697f2ac410Schristos /* If we can't parse the extended color, fail. */ 3707f2ac410Schristos if (!extended_color (buf, &i, &m_background)) 3717f2ac410Schristos { 3727f2ac410Schristos *n_read = subexps[0].rm_eo; 3737f2ac410Schristos return false; 3747f2ac410Schristos } 3757f2ac410Schristos break; 3767f2ac410Schristos 3777f2ac410Schristos default: 3787f2ac410Schristos /* Ignore everything else. */ 3797f2ac410Schristos break; 3807f2ac410Schristos } 3817f2ac410Schristos } 3827f2ac410Schristos else 3837f2ac410Schristos { 3847f2ac410Schristos /* Unknown, let's just ignore. */ 3857f2ac410Schristos } 3867f2ac410Schristos } 3877f2ac410Schristos 3887f2ac410Schristos *n_read = subexps[0].rm_eo; 3897f2ac410Schristos return true; 3907f2ac410Schristos } 3917f2ac410Schristos 3927f2ac410Schristos /* See ui-style.h. */ 3937f2ac410Schristos 3947f2ac410Schristos bool 3957f2ac410Schristos skip_ansi_escape (const char *buf, int *n_read) 3967f2ac410Schristos { 3977f2ac410Schristos regmatch_t subexps[NUM_SUBEXPRESSIONS]; 3987f2ac410Schristos 3997f2ac410Schristos int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0); 4007f2ac410Schristos if (match == REG_NOMATCH || buf[subexps[FINAL_SUBEXP].rm_so] != 'm') 4017f2ac410Schristos return false; 4027f2ac410Schristos 4037f2ac410Schristos *n_read = subexps[FINAL_SUBEXP].rm_eo; 4047f2ac410Schristos return true; 4057f2ac410Schristos } 4067f2ac410Schristos 4077d62b00eSchristos void _initialize_ui_style (); 4087f2ac410Schristos void 4097f2ac410Schristos _initialize_ui_style () 4107f2ac410Schristos { 4117f2ac410Schristos int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED); 4127f2ac410Schristos /* If the regular expression was incorrect, it was a programming 4137f2ac410Schristos error. */ 4147f2ac410Schristos gdb_assert (code == 0); 4157f2ac410Schristos } 416