ui-style.c revision 1.1 1 1.1 christos /* Styling for ui_file
2 1.1 christos Copyright (C) 2018-2019 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 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 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 christos read_semi_number (const char *string, int *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 christos extended_color (const char *str, int *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 christos /* Note: not 38. */
319 1.1 christos case 39:
320 1.1 christos m_foreground = color (value - 30);
321 1.1 christos break;
322 1.1 christos
323 1.1 christos case 40:
324 1.1 christos case 41:
325 1.1 christos case 42:
326 1.1 christos case 43:
327 1.1 christos case 44:
328 1.1 christos case 45:
329 1.1 christos case 46:
330 1.1 christos case 47:
331 1.1 christos /* Note: not 48. */
332 1.1 christos case 49:
333 1.1 christos m_background = color (value - 40);
334 1.1 christos break;
335 1.1 christos
336 1.1 christos case 90:
337 1.1 christos case 91:
338 1.1 christos case 92:
339 1.1 christos case 93:
340 1.1 christos case 94:
341 1.1 christos case 95:
342 1.1 christos case 96:
343 1.1 christos case 97:
344 1.1 christos m_foreground = color (value - 90 + 8);
345 1.1 christos break;
346 1.1 christos
347 1.1 christos case 100:
348 1.1 christos case 101:
349 1.1 christos case 102:
350 1.1 christos case 103:
351 1.1 christos case 104:
352 1.1 christos case 105:
353 1.1 christos case 106:
354 1.1 christos case 107:
355 1.1 christos m_background = color (value - 100 + 8);
356 1.1 christos break;
357 1.1 christos
358 1.1 christos case 38:
359 1.1 christos /* If we can't parse the extended color, fail. */
360 1.1 christos if (!extended_color (buf, &i, &m_foreground))
361 1.1 christos {
362 1.1 christos *n_read = subexps[0].rm_eo;
363 1.1 christos return false;
364 1.1 christos }
365 1.1 christos break;
366 1.1 christos
367 1.1 christos case 48:
368 1.1 christos /* If we can't parse the extended color, fail. */
369 1.1 christos if (!extended_color (buf, &i, &m_background))
370 1.1 christos {
371 1.1 christos *n_read = subexps[0].rm_eo;
372 1.1 christos return false;
373 1.1 christos }
374 1.1 christos break;
375 1.1 christos
376 1.1 christos default:
377 1.1 christos /* Ignore everything else. */
378 1.1 christos break;
379 1.1 christos }
380 1.1 christos }
381 1.1 christos else
382 1.1 christos {
383 1.1 christos /* Unknown, let's just ignore. */
384 1.1 christos }
385 1.1 christos }
386 1.1 christos
387 1.1 christos *n_read = subexps[0].rm_eo;
388 1.1 christos return true;
389 1.1 christos }
390 1.1 christos
391 1.1 christos /* See ui-style.h. */
392 1.1 christos
393 1.1 christos bool
394 1.1 christos skip_ansi_escape (const char *buf, int *n_read)
395 1.1 christos {
396 1.1 christos regmatch_t subexps[NUM_SUBEXPRESSIONS];
397 1.1 christos
398 1.1 christos int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
399 1.1 christos if (match == REG_NOMATCH || buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
400 1.1 christos return false;
401 1.1 christos
402 1.1 christos *n_read = subexps[FINAL_SUBEXP].rm_eo;
403 1.1 christos return true;
404 1.1 christos }
405 1.1 christos
406 1.1 christos void
407 1.1 christos _initialize_ui_style ()
408 1.1 christos {
409 1.1 christos int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED);
410 1.1 christos /* If the regular expression was incorrect, it was a programming
411 1.1 christos error. */
412 1.1 christos gdb_assert (code == 0);
413 1.1 christos }
414