1 1.1 christos /* Styling for ui_file 2 1.1.1.4 christos Copyright (C) 2018-2024 Free Software Foundation, Inc. 3 1.1 christos 4 1.1 christos This file is part of GDB. 5 1.1 christos 6 1.1 christos This program is free software; you can redistribute it and/or modify 7 1.1 christos it under the terms of the GNU General Public License as published by 8 1.1 christos the Free Software Foundation; either version 3 of the License, or 9 1.1 christos (at your option) any later version. 10 1.1 christos 11 1.1 christos This program is distributed in the hope that it will be useful, 12 1.1 christos but WITHOUT ANY WARRANTY; without even the implied warranty of 13 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 1.1 christos GNU General Public License for more details. 15 1.1 christos 16 1.1 christos You should have received a copy of the GNU General Public License 17 1.1 christos along with this program. If not, see <http://www.gnu.org/licenses/>. */ 18 1.1 christos 19 1.1 christos #include "ui-style.h" 20 1.1.1.3 christos #include "gdbsupport/gdb_regex.h" 21 1.1 christos 22 1.1 christos /* A regular expression that is used for matching ANSI terminal escape 23 1.1 christos sequences. */ 24 1.1 christos 25 1.1.1.3 christos static const char ansi_regex_text[] = 26 1.1 christos /* Introduction. */ 27 1.1 christos "^\033\\[" 28 1.1 christos #define DATA_SUBEXP 1 29 1.1 christos /* Capture parameter and intermediate bytes. */ 30 1.1 christos "(" 31 1.1 christos /* Parameter bytes. */ 32 1.1 christos "[\x30-\x3f]*" 33 1.1 christos /* Intermediate bytes. */ 34 1.1 christos "[\x20-\x2f]*" 35 1.1 christos /* End the first capture. */ 36 1.1 christos ")" 37 1.1 christos /* The final byte. */ 38 1.1 christos #define FINAL_SUBEXP 2 39 1.1 christos "([\x40-\x7e])"; 40 1.1 christos 41 1.1 christos /* The number of subexpressions to allocate space for, including the 42 1.1 christos "0th" whole match subexpression. */ 43 1.1 christos #define NUM_SUBEXPRESSIONS 3 44 1.1 christos 45 1.1 christos /* The compiled form of ansi_regex_text. */ 46 1.1 christos 47 1.1 christos static regex_t ansi_regex; 48 1.1 christos 49 1.1 christos /* This maps bright colors to RGB triples. The index is the bright 50 1.1 christos color index, starting with bright black. The values come from 51 1.1 christos xterm. */ 52 1.1 christos 53 1.1 christos static const uint8_t bright_colors[][3] = { 54 1.1 christos { 127, 127, 127 }, /* Black. */ 55 1.1 christos { 255, 0, 0 }, /* Red. */ 56 1.1 christos { 0, 255, 0 }, /* Green. */ 57 1.1 christos { 255, 255, 0 }, /* Yellow. */ 58 1.1 christos { 92, 92, 255 }, /* Blue. */ 59 1.1 christos { 255, 0, 255 }, /* Magenta. */ 60 1.1 christos { 0, 255, 255 }, /* Cyan. */ 61 1.1 christos { 255, 255, 255 } /* White. */ 62 1.1 christos }; 63 1.1 christos 64 1.1 christos /* See ui-style.h. */ 65 1.1 christos 66 1.1 christos bool 67 1.1 christos ui_file_style::color::append_ansi (bool is_fg, std::string *str) const 68 1.1 christos { 69 1.1 christos if (m_simple) 70 1.1 christos { 71 1.1 christos if (m_value >= BLACK && m_value <= WHITE) 72 1.1 christos str->append (std::to_string (m_value + (is_fg ? 30 : 40))); 73 1.1 christos else if (m_value > WHITE && m_value <= WHITE + 8) 74 1.1 christos str->append (std::to_string (m_value - WHITE + (is_fg ? 90 : 100))); 75 1.1 christos else if (m_value != -1) 76 1.1 christos { 77 1.1 christos str->append (is_fg ? "38;5;" : "48;5;"); 78 1.1 christos str->append (std::to_string (m_value)); 79 1.1 christos } 80 1.1 christos else 81 1.1 christos return false; 82 1.1 christos } 83 1.1 christos else 84 1.1 christos { 85 1.1 christos str->append (is_fg ? "38;2;" : "48;2;"); 86 1.1 christos str->append (std::to_string (m_red) 87 1.1 christos + ";" + std::to_string (m_green) 88 1.1 christos + ";" + std::to_string (m_blue)); 89 1.1 christos } 90 1.1 christos return true; 91 1.1 christos } 92 1.1 christos 93 1.1 christos /* See ui-style.h. */ 94 1.1 christos 95 1.1 christos void 96 1.1 christos ui_file_style::color::get_rgb (uint8_t *rgb) const 97 1.1 christos { 98 1.1 christos if (m_simple) 99 1.1 christos { 100 1.1 christos /* Can't call this for a basic color or NONE -- those will end 101 1.1 christos up in the assert below. */ 102 1.1 christos if (m_value >= 8 && m_value <= 15) 103 1.1 christos memcpy (rgb, bright_colors[m_value - 8], 3 * sizeof (uint8_t)); 104 1.1 christos else if (m_value >= 16 && m_value <= 231) 105 1.1 christos { 106 1.1 christos int value = m_value; 107 1.1 christos value -= 16; 108 1.1 christos /* This obscure formula seems to be what terminals actually 109 1.1 christos do. */ 110 1.1 christos int component = value / 36; 111 1.1 christos rgb[0] = component == 0 ? 0 : (55 + component * 40); 112 1.1 christos value %= 36; 113 1.1 christos component = value / 6; 114 1.1 christos rgb[1] = component == 0 ? 0 : (55 + component * 40); 115 1.1 christos value %= 6; 116 1.1 christos rgb[2] = value == 0 ? 0 : (55 + value * 40); 117 1.1 christos } 118 1.1 christos else if (m_value >= 232) 119 1.1 christos { 120 1.1 christos uint8_t v = (m_value - 232) * 10 + 8; 121 1.1 christos rgb[0] = v; 122 1.1 christos rgb[1] = v; 123 1.1 christos rgb[2] = v; 124 1.1 christos } 125 1.1 christos else 126 1.1 christos gdb_assert_not_reached ("get_rgb called on invalid color"); 127 1.1 christos } 128 1.1 christos else 129 1.1 christos { 130 1.1 christos rgb[0] = m_red; 131 1.1 christos rgb[1] = m_green; 132 1.1 christos rgb[2] = m_blue; 133 1.1 christos } 134 1.1 christos } 135 1.1 christos 136 1.1 christos /* See ui-style.h. */ 137 1.1 christos 138 1.1 christos std::string 139 1.1 christos ui_file_style::to_ansi () const 140 1.1 christos { 141 1.1 christos std::string result ("\033["); 142 1.1 christos bool need_semi = m_foreground.append_ansi (true, &result); 143 1.1 christos if (!m_background.is_none ()) 144 1.1 christos { 145 1.1 christos if (need_semi) 146 1.1 christos result.push_back (';'); 147 1.1 christos m_background.append_ansi (false, &result); 148 1.1 christos need_semi = true; 149 1.1 christos } 150 1.1 christos if (m_intensity != NORMAL) 151 1.1 christos { 152 1.1 christos if (need_semi) 153 1.1 christos result.push_back (';'); 154 1.1 christos result.append (std::to_string (m_intensity)); 155 1.1 christos need_semi = true; 156 1.1 christos } 157 1.1 christos if (m_reverse) 158 1.1 christos { 159 1.1 christos if (need_semi) 160 1.1 christos result.push_back (';'); 161 1.1 christos result.push_back ('7'); 162 1.1 christos } 163 1.1 christos result.push_back ('m'); 164 1.1 christos return result; 165 1.1 christos } 166 1.1 christos 167 1.1 christos /* Read a ";" and a number from STRING. Return the number of 168 1.1 christos characters read and put the number into *NUM. */ 169 1.1 christos 170 1.1 christos static bool 171 1.1.1.3 christos read_semi_number (const char *string, regoff_t *idx, long *num) 172 1.1 christos { 173 1.1 christos if (string[*idx] != ';') 174 1.1 christos return false; 175 1.1 christos ++*idx; 176 1.1 christos if (string[*idx] < '0' || string[*idx] > '9') 177 1.1 christos return false; 178 1.1 christos char *tail; 179 1.1 christos *num = strtol (string + *idx, &tail, 10); 180 1.1 christos *idx = tail - string; 181 1.1 christos return true; 182 1.1 christos } 183 1.1 christos 184 1.1 christos /* A helper for ui_file_style::parse that reads an extended color 185 1.1 christos sequence; that is, and 8- or 24- bit color. */ 186 1.1 christos 187 1.1 christos static bool 188 1.1.1.3 christos extended_color (const char *str, regoff_t *idx, ui_file_style::color *color) 189 1.1 christos { 190 1.1 christos long value; 191 1.1 christos 192 1.1 christos if (!read_semi_number (str, idx, &value)) 193 1.1 christos return false; 194 1.1 christos 195 1.1 christos if (value == 5) 196 1.1 christos { 197 1.1 christos /* 8-bit color. */ 198 1.1 christos if (!read_semi_number (str, idx, &value)) 199 1.1 christos return false; 200 1.1 christos 201 1.1 christos if (value >= 0 && value <= 255) 202 1.1 christos *color = ui_file_style::color (value); 203 1.1 christos else 204 1.1 christos return false; 205 1.1 christos } 206 1.1 christos else if (value == 2) 207 1.1 christos { 208 1.1 christos /* 24-bit color. */ 209 1.1 christos long r, g, b; 210 1.1 christos if (!read_semi_number (str, idx, &r) 211 1.1 christos || r > 255 212 1.1 christos || !read_semi_number (str, idx, &g) 213 1.1 christos || g > 255 214 1.1 christos || !read_semi_number (str, idx, &b) 215 1.1 christos || b > 255) 216 1.1 christos return false; 217 1.1 christos *color = ui_file_style::color (r, g, b); 218 1.1 christos } 219 1.1 christos else 220 1.1 christos { 221 1.1 christos /* Unrecognized sequence. */ 222 1.1 christos return false; 223 1.1 christos } 224 1.1 christos 225 1.1 christos return true; 226 1.1 christos } 227 1.1 christos 228 1.1 christos /* See ui-style.h. */ 229 1.1 christos 230 1.1 christos bool 231 1.1 christos ui_file_style::parse (const char *buf, size_t *n_read) 232 1.1 christos { 233 1.1 christos regmatch_t subexps[NUM_SUBEXPRESSIONS]; 234 1.1 christos 235 1.1 christos int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0); 236 1.1 christos if (match == REG_NOMATCH) 237 1.1 christos { 238 1.1 christos *n_read = 0; 239 1.1 christos return false; 240 1.1 christos } 241 1.1 christos /* Other failures mean the regexp is broken. */ 242 1.1 christos gdb_assert (match == 0); 243 1.1 christos /* The regexp is anchored. */ 244 1.1 christos gdb_assert (subexps[0].rm_so == 0); 245 1.1 christos /* The final character exists. */ 246 1.1 christos gdb_assert (subexps[FINAL_SUBEXP].rm_eo - subexps[FINAL_SUBEXP].rm_so == 1); 247 1.1 christos 248 1.1 christos if (buf[subexps[FINAL_SUBEXP].rm_so] != 'm') 249 1.1 christos { 250 1.1 christos /* We don't handle this sequence, so just drop it. */ 251 1.1 christos *n_read = subexps[0].rm_eo; 252 1.1 christos return false; 253 1.1 christos } 254 1.1 christos 255 1.1 christos /* Examine each setting in the match and apply it to the result. 256 1.1 christos See the Select Graphic Rendition section of 257 1.1 christos https://en.wikipedia.org/wiki/ANSI_escape_code. In essence each 258 1.1 christos code is just a number, separated by ";"; there are some more 259 1.1 christos wrinkles but we don't support them all.. */ 260 1.1 christos 261 1.1 christos /* "\033[m" means the same thing as "\033[0m", so handle that 262 1.1 christos specially here. */ 263 1.1 christos if (subexps[DATA_SUBEXP].rm_so == subexps[DATA_SUBEXP].rm_eo) 264 1.1 christos *this = ui_file_style (); 265 1.1 christos 266 1.1 christos for (regoff_t i = subexps[DATA_SUBEXP].rm_so; 267 1.1 christos i < subexps[DATA_SUBEXP].rm_eo; 268 1.1 christos ++i) 269 1.1 christos { 270 1.1 christos if (buf[i] == ';') 271 1.1 christos { 272 1.1 christos /* Skip. */ 273 1.1 christos } 274 1.1 christos else if (buf[i] >= '0' && buf[i] <= '9') 275 1.1 christos { 276 1.1 christos char *tail; 277 1.1 christos long value = strtol (buf + i, &tail, 10); 278 1.1 christos i = tail - buf; 279 1.1 christos 280 1.1 christos switch (value) 281 1.1 christos { 282 1.1 christos case 0: 283 1.1 christos /* Reset. */ 284 1.1 christos *this = ui_file_style (); 285 1.1 christos break; 286 1.1 christos case 1: 287 1.1 christos /* Bold. */ 288 1.1 christos m_intensity = BOLD; 289 1.1 christos break; 290 1.1 christos case 2: 291 1.1 christos /* Dim. */ 292 1.1 christos m_intensity = DIM; 293 1.1 christos break; 294 1.1 christos case 7: 295 1.1 christos /* Reverse. */ 296 1.1 christos m_reverse = true; 297 1.1 christos break; 298 1.1 christos case 21: 299 1.1 christos m_intensity = NORMAL; 300 1.1 christos break; 301 1.1 christos case 22: 302 1.1 christos /* Normal. */ 303 1.1 christos m_intensity = NORMAL; 304 1.1 christos break; 305 1.1 christos case 27: 306 1.1 christos /* Inverse off. */ 307 1.1 christos m_reverse = false; 308 1.1 christos break; 309 1.1 christos 310 1.1 christos case 30: 311 1.1 christos case 31: 312 1.1 christos case 32: 313 1.1 christos case 33: 314 1.1 christos case 34: 315 1.1 christos case 35: 316 1.1 christos case 36: 317 1.1 christos case 37: 318 1.1.1.5 christos m_foreground = color (value - 30); 319 1.1.1.5 christos break; 320 1.1 christos /* Note: not 38. */ 321 1.1 christos case 39: 322 1.1.1.5 christos m_foreground = NONE; 323 1.1 christos break; 324 1.1 christos 325 1.1 christos case 40: 326 1.1 christos case 41: 327 1.1 christos case 42: 328 1.1 christos case 43: 329 1.1 christos case 44: 330 1.1 christos case 45: 331 1.1 christos case 46: 332 1.1 christos case 47: 333 1.1.1.5 christos m_background = color (value - 40); 334 1.1.1.5 christos break; 335 1.1 christos /* Note: not 48. */ 336 1.1 christos case 49: 337 1.1.1.5 christos m_background = NONE; 338 1.1 christos break; 339 1.1 christos 340 1.1 christos case 90: 341 1.1 christos case 91: 342 1.1 christos case 92: 343 1.1 christos case 93: 344 1.1 christos case 94: 345 1.1 christos case 95: 346 1.1 christos case 96: 347 1.1 christos case 97: 348 1.1 christos m_foreground = color (value - 90 + 8); 349 1.1 christos break; 350 1.1 christos 351 1.1 christos case 100: 352 1.1 christos case 101: 353 1.1 christos case 102: 354 1.1 christos case 103: 355 1.1 christos case 104: 356 1.1 christos case 105: 357 1.1 christos case 106: 358 1.1 christos case 107: 359 1.1 christos m_background = color (value - 100 + 8); 360 1.1 christos break; 361 1.1 christos 362 1.1 christos case 38: 363 1.1 christos /* If we can't parse the extended color, fail. */ 364 1.1 christos if (!extended_color (buf, &i, &m_foreground)) 365 1.1 christos { 366 1.1 christos *n_read = subexps[0].rm_eo; 367 1.1 christos return false; 368 1.1 christos } 369 1.1 christos break; 370 1.1 christos 371 1.1 christos case 48: 372 1.1 christos /* If we can't parse the extended color, fail. */ 373 1.1 christos if (!extended_color (buf, &i, &m_background)) 374 1.1 christos { 375 1.1 christos *n_read = subexps[0].rm_eo; 376 1.1 christos return false; 377 1.1 christos } 378 1.1 christos break; 379 1.1 christos 380 1.1 christos default: 381 1.1 christos /* Ignore everything else. */ 382 1.1 christos break; 383 1.1 christos } 384 1.1 christos } 385 1.1 christos else 386 1.1 christos { 387 1.1 christos /* Unknown, let's just ignore. */ 388 1.1 christos } 389 1.1 christos } 390 1.1 christos 391 1.1 christos *n_read = subexps[0].rm_eo; 392 1.1 christos return true; 393 1.1 christos } 394 1.1 christos 395 1.1 christos /* See ui-style.h. */ 396 1.1 christos 397 1.1 christos bool 398 1.1 christos skip_ansi_escape (const char *buf, int *n_read) 399 1.1 christos { 400 1.1 christos regmatch_t subexps[NUM_SUBEXPRESSIONS]; 401 1.1 christos 402 1.1 christos int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0); 403 1.1 christos if (match == REG_NOMATCH || buf[subexps[FINAL_SUBEXP].rm_so] != 'm') 404 1.1 christos return false; 405 1.1 christos 406 1.1 christos *n_read = subexps[FINAL_SUBEXP].rm_eo; 407 1.1 christos return true; 408 1.1 christos } 409 1.1 christos 410 1.1.1.2 christos void _initialize_ui_style (); 411 1.1 christos void 412 1.1 christos _initialize_ui_style () 413 1.1 christos { 414 1.1 christos int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED); 415 1.1 christos /* If the regular expression was incorrect, it was a programming 416 1.1 christos error. */ 417 1.1 christos gdb_assert (code == 0); 418 1.1 christos } 419