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