Branch data Line data Source code
1 : : /* Output colorization.
2 : : Copyright (C) 2011-2025 Free Software Foundation, Inc.
3 : :
4 : : This program is free software; you can redistribute it and/or modify
5 : : it under the terms of the GNU General Public License as published by
6 : : the Free Software Foundation; either version 3, or (at your option)
7 : : any later version.
8 : :
9 : : This program is distributed in the hope that it will be useful,
10 : : but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : GNU General Public License for more details.
13 : :
14 : : You should have received a copy of the GNU General Public License
15 : : along with this program; if not, write to the Free Software
16 : : Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
17 : : 02110-1301, USA. */
18 : :
19 : : #include "config.h"
20 : : #define INCLUDE_VECTOR
21 : : #include "system.h"
22 : : #include "diagnostic-color.h"
23 : : #include "diagnostic-url.h"
24 : : #include "label-text.h"
25 : :
26 : : #ifdef __MINGW32__
27 : : # define WIN32_LEAN_AND_MEAN
28 : : # include <windows.h>
29 : : #endif
30 : :
31 : : #include "color-macros.h"
32 : : #include "selftest.h"
33 : :
34 : : /* The context and logic for choosing default --color screen attributes
35 : : (foreground and background colors, etc.) are the following.
36 : : -- There are eight basic colors available, each with its own
37 : : nominal luminosity to the human eye and foreground/background
38 : : codes (black [0 %, 30/40], blue [11 %, 34/44], red [30 %, 31/41],
39 : : magenta [41 %, 35/45], green [59 %, 32/42], cyan [70 %, 36/46],
40 : : yellow [89 %, 33/43], and white [100 %, 37/47]).
41 : : -- Sometimes, white as a background is actually implemented using
42 : : a shade of light gray, so that a foreground white can be visible
43 : : on top of it (but most often not).
44 : : -- Sometimes, black as a foreground is actually implemented using
45 : : a shade of dark gray, so that it can be visible on top of a
46 : : background black (but most often not).
47 : : -- Sometimes, more colors are available, as extensions.
48 : : -- Other attributes can be selected/deselected (bold [1/22],
49 : : underline [4/24], standout/inverse [7/27], blink [5/25], and
50 : : invisible/hidden [8/28]). They are sometimes implemented by
51 : : using colors instead of what their names imply; e.g., bold is
52 : : often achieved by using brighter colors. In practice, only bold
53 : : is really available to us, underline sometimes being mapped by
54 : : the terminal to some strange color choice, and standout best
55 : : being left for use by downstream programs such as less(1).
56 : : -- We cannot assume that any of the extensions or special features
57 : : are available for the purpose of choosing defaults for everyone.
58 : : -- The most prevalent default terminal backgrounds are pure black
59 : : and pure white, and are not necessarily the same shades of
60 : : those as if they were selected explicitly with SGR sequences.
61 : : Some terminals use dark or light pictures as default background,
62 : : but those are covered over by an explicit selection of background
63 : : color with an SGR sequence; their users will appreciate their
64 : : background pictures not be covered like this, if possible.
65 : : -- Some uses of colors attributes is to make some output items
66 : : more understated (e.g., context lines); this cannot be achieved
67 : : by changing the background color.
68 : : -- For these reasons, the GCC color defaults should strive not
69 : : to change the background color from its default, unless it's
70 : : for a short item that should be highlighted, not understated.
71 : : -- The GCC foreground color defaults (without an explicitly set
72 : : background) should provide enough contrast to be readable on any
73 : : terminal with either a black (dark) or white (light) background.
74 : : This only leaves red, magenta, green, and cyan (and their bold
75 : : counterparts) and possibly bold blue. */
76 : : /* Default colors. The user can overwrite them using environment
77 : : variable GCC_COLORS. */
78 : : struct color_default
79 : : {
80 : : const char *m_name;
81 : : const char *m_val;
82 : : };
83 : :
84 : : /* For GCC_COLORS. */
85 : : static const color_default gcc_color_defaults[] =
86 : : {
87 : : { "error", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED) },
88 : : { "warning", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA) },
89 : : { "note", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN) },
90 : : { "range1", SGR_SEQ (COLOR_FG_GREEN) },
91 : : { "range2", SGR_SEQ (COLOR_FG_BLUE) },
92 : : { "locus", SGR_SEQ (COLOR_BOLD) },
93 : : { "quote", SGR_SEQ (COLOR_BOLD) },
94 : : { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN) },
95 : : { "fnname", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
96 : : { "targs", SGR_SEQ (COLOR_FG_MAGENTA) },
97 : : { "fixit-insert", SGR_SEQ (COLOR_FG_GREEN) },
98 : : { "fixit-delete", SGR_SEQ (COLOR_FG_RED) },
99 : : { "diff-filename", SGR_SEQ (COLOR_BOLD) },
100 : : { "diff-hunk", SGR_SEQ (COLOR_FG_CYAN) },
101 : : { "diff-delete", SGR_SEQ (COLOR_FG_RED) },
102 : : { "diff-insert", SGR_SEQ (COLOR_FG_GREEN) },
103 : : { "type-diff", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
104 : : { "valid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
105 : : { "invalid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED) },
106 : : { "highlight-a", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
107 : : { "highlight-b", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_BLUE) }
108 : : };
109 : :
110 : 24 : class diagnostic_color_dict
111 : : {
112 : : public:
113 : : diagnostic_color_dict (const color_default *default_values,
114 : : size_t num_default_values);
115 : :
116 : : bool parse_envvar_value (const char *const envvar_value);
117 : :
118 : : const char *get_start_by_name (const char *name, size_t name_len) const;
119 : 32 : const char *get_start_by_name (const char *name) const
120 : : {
121 : 32 : return get_start_by_name (name, strlen (name));
122 : : }
123 : :
124 : : private:
125 : 12394481 : struct entry
126 : : {
127 : 12394305 : entry (const color_default &d)
128 : 12394305 : : m_name (d.m_name),
129 : 12394305 : m_name_len (strlen (d.m_name)),
130 : 12394305 : m_val (label_text::borrow (d.m_val))
131 : : {
132 : : }
133 : :
134 : : const char *m_name;
135 : : size_t m_name_len;
136 : : label_text m_val;
137 : : };
138 : :
139 : : const entry *get_entry_by_name (const char *name, size_t name_len) const;
140 : : entry *get_entry_by_name (const char *name, size_t name_len);
141 : :
142 : : std::vector<entry> m_entries;
143 : : };
144 : :
145 : : static diagnostic_color_dict *g_color_dict;
146 : :
147 : : const char *
148 : 81304843 : colorize_start (bool show_color, const char *name, size_t name_len)
149 : : {
150 : 81304843 : if (!show_color)
151 : : return "";
152 : :
153 : 1535 : if (!g_color_dict)
154 : : return "";
155 : :
156 : 1535 : return g_color_dict->get_start_by_name (name, name_len);
157 : : }
158 : :
159 : : /* Look for an entry named NAME of length NAME_LEN within this
160 : : diagnostic_color_dict, or nullptr if there isn't one. */
161 : :
162 : : const diagnostic_color_dict::entry *
163 : 1567 : diagnostic_color_dict::get_entry_by_name (const char *name,
164 : : size_t name_len) const
165 : : {
166 : 17912 : for (auto &iter : m_entries)
167 : 17892 : if (iter.m_name_len == name_len
168 : 4223 : && memcmp (iter.m_name, name, name_len) == 0)
169 : 1567 : return &iter;
170 : : return nullptr;
171 : : }
172 : :
173 : : /* Non-const version of the above. */
174 : :
175 : : diagnostic_color_dict::entry *
176 : 12 : diagnostic_color_dict::get_entry_by_name (const char *name,
177 : : size_t name_len)
178 : : {
179 : 100 : for (auto &iter : m_entries)
180 : 96 : if (iter.m_name_len == name_len
181 : 12 : && memcmp (iter.m_name, name, name_len) == 0)
182 : 12 : return &iter;
183 : : return nullptr;
184 : : }
185 : :
186 : : /* Return the SGR codes to start a color entry named NAME of length
187 : : NAME_LEN within this diagnostic_color_dict, or the empty string if
188 : : there isn't one. */
189 : :
190 : : const char *
191 : 1567 : diagnostic_color_dict::get_start_by_name (const char *name,
192 : : size_t name_len) const
193 : : {
194 : 1567 : if (const entry *e = get_entry_by_name (name, name_len))
195 : 1547 : return e->m_val.get ();
196 : :
197 : : return "";
198 : : }
199 : :
200 : : const char *
201 : 81148171 : colorize_stop (bool show_color)
202 : : {
203 : 81148171 : return show_color ? SGR_RESET : "";
204 : : }
205 : :
206 : : /* diagnostic_color_dict's ctor. Initialize it from the given array
207 : : of color_default values. */
208 : :
209 : 590209 : diagnostic_color_dict::
210 : : diagnostic_color_dict (const color_default *default_values,
211 : 590209 : size_t num_default_values)
212 : : {
213 : 590209 : m_entries.reserve (num_default_values);
214 : 12984514 : for (size_t idx = 0; idx < num_default_values; idx++)
215 : 12394305 : m_entries.push_back (entry (default_values[idx]));
216 : 590209 : }
217 : :
218 : : /* Parse a list of color definitions from an environment variable
219 : : value (such as that of GCC_COLORS).
220 : : No character escaping is needed or supported. */
221 : :
222 : : bool
223 : 485545 : diagnostic_color_dict::parse_envvar_value (const char *const envvar_value)
224 : : {
225 : : /* envvar not set: use the default colors. */
226 : 485545 : if (envvar_value == nullptr)
227 : : return true;
228 : :
229 : : /* envvar set to empty string: disable colorization. */
230 : 52 : if (*envvar_value == '\0')
231 : : return false;
232 : :
233 : : const char *q, *name, *val;
234 : : size_t name_len = 0, val_len = 0;
235 : :
236 : : name = q = envvar_value;
237 : : val = NULL;
238 : : /* From now on, be well-formed or you're gone. */
239 : 184 : for (;;)
240 : 184 : if (*q == ':' || *q == '\0')
241 : : {
242 : 12 : if (val)
243 : 12 : val_len = q - val;
244 : : else
245 : 0 : name_len = q - name;
246 : : /* Empty name without val (empty cap)
247 : : won't match and will be ignored. */
248 : 12 : entry *e = get_entry_by_name (name, name_len);
249 : : /* If name unknown, go on for forward compatibility. */
250 : 12 : if (e && val)
251 : : {
252 : 8 : char *b = XNEWVEC (char, val_len + sizeof (SGR_SEQ ("")));
253 : 8 : memcpy (b, SGR_START, strlen (SGR_START));
254 : 8 : memcpy (b + strlen (SGR_START), val, val_len);
255 : 8 : memcpy (b + strlen (SGR_START) + val_len, SGR_END,
256 : : sizeof (SGR_END));
257 : 8 : e->m_val = label_text::take (b);
258 : : }
259 : 12 : if (*q == '\0')
260 : : return true;
261 : 8 : name = ++q;
262 : 8 : val = NULL;
263 : : }
264 : 172 : else if (*q == '=')
265 : : {
266 : 12 : if (q == name || val)
267 : : return true;
268 : :
269 : 12 : name_len = q - name;
270 : 12 : val = ++q; /* Can be the empty string. */
271 : : }
272 : 160 : else if (val == NULL)
273 : 100 : q++; /* Accumulate name. */
274 : 60 : else if (*q == ';' || (*q >= '0' && *q <= '9'))
275 : 60 : q++; /* Accumulate val. Protect the terminal from being sent
276 : : garbage. */
277 : : else
278 : : return true;
279 : : }
280 : :
281 : : /* Parse GCC_COLORS. The default would look like:
282 : : GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
283 : : range1=32:range2=34:locus=01:quote=01:path=01;36:\
284 : : fixit-insert=32:fixit-delete=31:'\
285 : : diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
286 : : type-diff=01;32'. */
287 : : static bool
288 : 485541 : parse_gcc_colors ()
289 : : {
290 : 485541 : if (!g_color_dict)
291 : : return false;
292 : 485541 : return g_color_dict->parse_envvar_value (getenv ("GCC_COLORS")); /* Plural! */
293 : : }
294 : :
295 : : /* Return true if we should use color when in auto mode, false otherwise. */
296 : : static bool
297 : 1184842 : should_colorize (void)
298 : : {
299 : : #ifdef __MINGW32__
300 : : /* For consistency reasons, one should check the handle returned by
301 : : _get_osfhandle(_fileno(stderr)) because the function
302 : : pp_write_text_to_stream() in pretty-print.cc calls fputs() on
303 : : that stream. However, the code below for non-Windows doesn't seem
304 : : to care about it either... */
305 : : HANDLE handle;
306 : : DWORD mode;
307 : : BOOL isconsole = false;
308 : :
309 : : handle = GetStdHandle (STD_ERROR_HANDLE);
310 : :
311 : : if ((handle != INVALID_HANDLE_VALUE) && (handle != NULL))
312 : : isconsole = GetConsoleMode (handle, &mode);
313 : :
314 : : #ifdef ENABLE_VIRTUAL_TERMINAL_PROCESSING
315 : : if (isconsole)
316 : : {
317 : : /* Try to enable processing of VT100 escape sequences */
318 : : mode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
319 : : SetConsoleMode (handle, mode);
320 : : }
321 : : #endif
322 : :
323 : : return isconsole;
324 : : #else
325 : 1184842 : char const *t = getenv ("TERM");
326 : : /* emacs M-x shell sets TERM="dumb". */
327 : 1184842 : return t && strcmp (t, "dumb") != 0 && isatty (STDERR_FILENO);
328 : : #endif
329 : : }
330 : :
331 : : bool
332 : 1401682 : colorize_init (diagnostic_color_rule_t rule)
333 : : {
334 : 1401682 : if (!g_color_dict)
335 : 590197 : g_color_dict = new diagnostic_color_dict (gcc_color_defaults,
336 : 590197 : ARRAY_SIZE (gcc_color_defaults));
337 : :
338 : 1401682 : switch (rule)
339 : : {
340 : : case DIAGNOSTICS_COLOR_NO:
341 : : return false;
342 : 133 : case DIAGNOSTICS_COLOR_YES:
343 : 133 : return parse_gcc_colors ();
344 : 592421 : case DIAGNOSTICS_COLOR_AUTO:
345 : 592421 : if (should_colorize ())
346 : 485408 : return parse_gcc_colors ();
347 : : else
348 : : return false;
349 : 0 : default:
350 : 0 : gcc_unreachable ();
351 : : }
352 : : }
353 : :
354 : : /* Return URL_FORMAT_XXX which tells how we should emit urls
355 : : when in always mode.
356 : : We use GCC_URLS and if that is not defined TERM_URLS.
357 : : If neither is defined the feature is enabled by default. */
358 : :
359 : : static diagnostic_url_format
360 : 0 : parse_env_vars_for_urls ()
361 : : {
362 : 0 : const char *p;
363 : :
364 : 0 : p = getenv ("GCC_URLS"); /* Plural! */
365 : 0 : if (p == NULL)
366 : 0 : p = getenv ("TERM_URLS");
367 : :
368 : 0 : if (p == NULL)
369 : : return URL_FORMAT_DEFAULT;
370 : :
371 : 0 : if (*p == '\0')
372 : : return URL_FORMAT_NONE;
373 : :
374 : 0 : if (!strcmp (p, "no"))
375 : : return URL_FORMAT_NONE;
376 : :
377 : 0 : if (!strcmp (p, "st"))
378 : : return URL_FORMAT_ST;
379 : :
380 : : if (!strcmp (p, "bel"))
381 : : return URL_FORMAT_BEL;
382 : :
383 : : return URL_FORMAT_DEFAULT;
384 : : }
385 : :
386 : : /* Return true if we should use urls when in auto mode, false otherwise. */
387 : :
388 : : static bool
389 : 592421 : auto_enable_urls ()
390 : : {
391 : 592421 : const char *term, *colorterm;
392 : :
393 : : /* First check the terminal is capable of printing color escapes,
394 : : if not URLs won't work either. */
395 : 592421 : if (!should_colorize ())
396 : : return false;
397 : :
398 : : #ifdef __MINGW32__
399 : : HANDLE handle;
400 : : DWORD mode;
401 : :
402 : : handle = GetStdHandle (STD_ERROR_HANDLE);
403 : : if ((handle == INVALID_HANDLE_VALUE) || (handle == NULL))
404 : : return false;
405 : :
406 : : /* If ansi escape sequences aren't supported by the console, then URLs will
407 : : print mangled from mingw_ansi_fputs's console API translation. It wouldn't
408 : : be useful even if this weren't the case. */
409 : : if (GetConsoleMode (handle, &mode)
410 : : #ifdef ENABLE_VIRTUAL_TERMINAL_PROCESSING
411 : : && !(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
412 : : #endif
413 : : )
414 : : return false;
415 : : #endif
416 : :
417 : : /* xfce4-terminal is known to not implement URLs at this time.
418 : : Recently new installations (0.8) will safely ignore the URL escape
419 : : sequences, but a large number of legacy installations (0.6.3) print
420 : : garbage when URLs are printed. Therefore we lose nothing by
421 : : disabling this feature for that specific terminal type. */
422 : 485408 : colorterm = getenv ("COLORTERM");
423 : 485408 : if (colorterm && !strcmp (colorterm, "xfce4-terminal"))
424 : : return false;
425 : :
426 : : /* Old versions of gnome-terminal where URL escapes cause screen
427 : : corruptions set COLORTERM="gnome-terminal", recent versions
428 : : with working URL support set this to "truecolor". */
429 : 0 : if (colorterm && !strcmp (colorterm, "gnome-terminal"))
430 : : return false;
431 : :
432 : : /* Since the following checks are less specific than the ones
433 : : above, let GCC_URLS and TERM_URLS override the decision. */
434 : 485408 : if (getenv ("GCC_URLS") || getenv ("TERM_URLS"))
435 : 0 : return true;
436 : :
437 : : /* In an ssh session the COLORTERM is not there, but TERM=xterm
438 : : can be used as an indication of a incompatible terminal while
439 : : TERM=xterm-256color appears to be a working terminal. */
440 : 485408 : term = getenv ("TERM");
441 : 485408 : if (!colorterm && term && !strcmp (term, "xterm"))
442 : : return false;
443 : :
444 : : /* When logging in a linux over serial line, we see TERM=linux
445 : : and no COLORTERM, it is unlikely that the URL escapes will
446 : : work in that environmen either. */
447 : 0 : if (!colorterm && term && !strcmp (term, "linux"))
448 : : return false;
449 : :
450 : : return true;
451 : : }
452 : :
453 : : /* Determine if URLs should be enabled, based on RULE,
454 : : and, if so, which format to use.
455 : : This reuses the logic for colorization. */
456 : :
457 : : diagnostic_url_format
458 : 1366505 : determine_url_format (diagnostic_url_rule_t rule)
459 : : {
460 : 1366505 : switch (rule)
461 : : {
462 : : case DIAGNOSTICS_URL_NO:
463 : : return URL_FORMAT_NONE;
464 : 0 : case DIAGNOSTICS_URL_YES:
465 : 0 : return parse_env_vars_for_urls ();
466 : 592421 : case DIAGNOSTICS_URL_AUTO:
467 : 592421 : if (auto_enable_urls ())
468 : 0 : return parse_env_vars_for_urls ();
469 : : else
470 : : return URL_FORMAT_NONE;
471 : 0 : default:
472 : 0 : gcc_unreachable ();
473 : : }
474 : : }
475 : :
476 : : #if CHECKING_P
477 : :
478 : : namespace selftest {
479 : :
480 : : /* Test of an empty diagnostic_color_dict. */
481 : :
482 : : static void
483 : 4 : test_empty_color_dict ()
484 : : {
485 : 4 : diagnostic_color_dict d (nullptr, 0);
486 : 4 : ASSERT_STREQ (d.get_start_by_name ("warning"), "");
487 : 4 : ASSERT_STREQ (d.get_start_by_name ("should-not-be-found"), "");
488 : 4 : }
489 : :
490 : : /* Test of a diagnostic_color_dict with GCC's defaults. */
491 : :
492 : : static void
493 : 4 : test_default_color_dict ()
494 : : {
495 : 4 : diagnostic_color_dict d (gcc_color_defaults,
496 : 4 : ARRAY_SIZE (gcc_color_defaults));
497 : 4 : ASSERT_STREQ (d.get_start_by_name ("warning"),
498 : : SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA));
499 : 4 : ASSERT_STREQ (d.get_start_by_name ("should-not-be-found"), "");
500 : 4 : }
501 : :
502 : : /* Test of a diagnostic_color_dict with GCC's defaults plus overrides from
503 : : an environment variable. */
504 : :
505 : : static void
506 : 4 : test_color_dict_envvar_parsing ()
507 : : {
508 : 4 : diagnostic_color_dict d (gcc_color_defaults,
509 : 4 : ARRAY_SIZE (gcc_color_defaults));
510 : :
511 : 4 : d.parse_envvar_value ("error=01;37:warning=01;42:unknown-value=01;36");
512 : :
513 : 4 : ASSERT_STREQ (d.get_start_by_name ("error"),
514 : : SGR_SEQ ("01;37"));
515 : 4 : ASSERT_STREQ (d.get_start_by_name ("warning"),
516 : : SGR_SEQ ("01;42"));
517 : 4 : ASSERT_STREQ (d.get_start_by_name ("unknown-value"), "");
518 : 4 : ASSERT_STREQ (d.get_start_by_name ("should-not-be-found"), "");
519 : 4 : }
520 : :
521 : :
522 : : /* Run all of the selftests within this file. */
523 : :
524 : : void
525 : 4 : diagnostic_color_cc_tests ()
526 : : {
527 : 4 : test_empty_color_dict ();
528 : 4 : test_default_color_dict ();
529 : 4 : test_color_dict_envvar_parsing ();
530 : 4 : }
531 : :
532 : : } // namespace selftest
533 : :
534 : : #endif /* #if CHECKING_P */
|