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