1 /* Output colorization.
2 Copyright (C) 2011-2024 Free Software Foundation, Inc.
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)
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.
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
20 #define INCLUDE_VECTOR
22 #include "diagnostic-color.h"
23 #include "diagnostic-url.h"
24 #include "label-text.h"
27 # define WIN32_LEAN_AND_MEAN
31 #include "color-macros.h"
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. */
85 static const color_default gcc_color_defaults
[] =
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
) }
110 class diagnostic_color_dict
113 diagnostic_color_dict (const color_default
*default_values
,
114 size_t num_default_values
);
116 bool parse_envvar_value (const char *const envvar_value
);
118 const char *get_start_by_name (const char *name
, size_t name_len
) const;
119 const char *get_start_by_name (const char *name
) const
121 return get_start_by_name (name
, strlen (name
));
127 entry (const color_default
&d
)
129 m_name_len (strlen (d
.m_name
)),
130 m_val (label_text::borrow (d
.m_val
))
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
);
142 std::vector
<entry
> m_entries
;
145 static diagnostic_color_dict
*g_color_dict
;
148 colorize_start (bool show_color
, const char *name
, size_t name_len
)
156 return g_color_dict
->get_start_by_name (name
, name_len
);
159 /* Look for an entry named NAME of length NAME_LEN within this
160 diagnostic_color_dict, or nullptr if there isn't one. */
162 const diagnostic_color_dict::entry
*
163 diagnostic_color_dict::get_entry_by_name (const char *name
,
164 size_t name_len
) const
166 for (auto &iter
: m_entries
)
167 if (iter
.m_name_len
== name_len
168 && memcmp (iter
.m_name
, name
, name_len
) == 0)
173 /* Non-const version of the above. */
175 diagnostic_color_dict::entry
*
176 diagnostic_color_dict::get_entry_by_name (const char *name
,
179 for (auto &iter
: m_entries
)
180 if (iter
.m_name_len
== name_len
181 && memcmp (iter
.m_name
, name
, name_len
) == 0)
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
191 diagnostic_color_dict::get_start_by_name (const char *name
,
192 size_t name_len
) const
194 if (const entry
*e
= get_entry_by_name (name
, name_len
))
195 return e
->m_val
.get ();
201 colorize_stop (bool show_color
)
203 return show_color
? SGR_RESET
: "";
206 /* diagnostic_color_dict's ctor. Initialize it from the given array
207 of color_default values. */
209 diagnostic_color_dict::
210 diagnostic_color_dict (const color_default
*default_values
,
211 size_t num_default_values
)
213 m_entries
.reserve (num_default_values
);
214 for (size_t idx
= 0; idx
< num_default_values
; idx
++)
215 m_entries
.push_back (entry (default_values
[idx
]));
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. */
223 diagnostic_color_dict::parse_envvar_value (const char *const envvar_value
)
225 /* envvar not set: use the default colors. */
226 if (envvar_value
== nullptr)
229 /* envvar set to empty string: disable colorization. */
230 if (*envvar_value
== '\0')
233 const char *q
, *name
, *val
;
234 size_t name_len
= 0, val_len
= 0;
236 name
= q
= envvar_value
;
238 /* From now on, be well-formed or you're gone. */
240 if (*q
== ':' || *q
== '\0')
246 /* Empty name without val (empty cap)
247 won't match and will be ignored. */
248 entry
*e
= get_entry_by_name (name
, name_len
);
249 /* If name unknown, go on for forward compatibility. */
252 char *b
= XNEWVEC (char, val_len
+ sizeof (SGR_SEQ ("")));
253 memcpy (b
, SGR_START
, strlen (SGR_START
));
254 memcpy (b
+ strlen (SGR_START
), val
, val_len
);
255 memcpy (b
+ strlen (SGR_START
) + val_len
, SGR_END
,
257 e
->m_val
= label_text::take (b
);
266 if (q
== name
|| val
)
270 val
= ++q
; /* Can be the empty string. */
272 else if (val
== NULL
)
273 q
++; /* Accumulate name. */
274 else if (*q
== ';' || (*q
>= '0' && *q
<= '9'))
275 q
++; /* Accumulate val. Protect the terminal from being sent
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:\
292 return g_color_dict
->parse_envvar_value (getenv ("GCC_COLORS")); /* Plural! */
295 /* Return true if we should use color when in auto mode, false otherwise. */
297 should_colorize (void)
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... */
307 BOOL isconsole
= false;
309 handle
= GetStdHandle (STD_ERROR_HANDLE
);
311 if ((handle
!= INVALID_HANDLE_VALUE
) && (handle
!= NULL
))
312 isconsole
= GetConsoleMode (handle
, &mode
);
314 #ifdef ENABLE_VIRTUAL_TERMINAL_PROCESSING
317 /* Try to enable processing of VT100 escape sequences */
318 mode
|= ENABLE_PROCESSED_OUTPUT
| ENABLE_VIRTUAL_TERMINAL_PROCESSING
;
319 SetConsoleMode (handle
, mode
);
325 char const *t
= getenv ("TERM");
326 /* emacs M-x shell sets TERM="dumb". */
327 return t
&& strcmp (t
, "dumb") != 0 && isatty (STDERR_FILENO
);
332 colorize_init (diagnostic_color_rule_t rule
)
335 g_color_dict
= new diagnostic_color_dict (gcc_color_defaults
,
336 ARRAY_SIZE (gcc_color_defaults
));
340 case DIAGNOSTICS_COLOR_NO
:
342 case DIAGNOSTICS_COLOR_YES
:
343 return parse_gcc_colors ();
344 case DIAGNOSTICS_COLOR_AUTO
:
345 if (should_colorize ())
346 return parse_gcc_colors ();
354 /* Return URL_FORMAT_XXX which tells how we should emit urls
356 We use GCC_URLS and if that is not defined TERM_URLS.
357 If neither is defined the feature is enabled by default. */
359 static diagnostic_url_format
360 parse_env_vars_for_urls ()
364 p
= getenv ("GCC_URLS"); /* Plural! */
366 p
= getenv ("TERM_URLS");
369 return URL_FORMAT_DEFAULT
;
372 return URL_FORMAT_NONE
;
374 if (!strcmp (p
, "no"))
375 return URL_FORMAT_NONE
;
377 if (!strcmp (p
, "st"))
378 return URL_FORMAT_ST
;
380 if (!strcmp (p
, "bel"))
381 return URL_FORMAT_BEL
;
383 return URL_FORMAT_DEFAULT
;
386 /* Return true if we should use urls when in auto mode, false otherwise. */
391 const char *term
, *colorterm
;
393 /* First check the terminal is capable of printing color escapes,
394 if not URLs won't work either. */
395 if (!should_colorize ())
402 handle
= GetStdHandle (STD_ERROR_HANDLE
);
403 if ((handle
== INVALID_HANDLE_VALUE
) || (handle
== NULL
))
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
)
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 colorterm
= getenv ("COLORTERM");
423 if (colorterm
&& !strcmp (colorterm
, "xfce4-terminal"))
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 if (colorterm
&& !strcmp (colorterm
, "gnome-terminal"))
432 /* Since the following checks are less specific than the ones
433 above, let GCC_URLS and TERM_URLS override the decision. */
434 if (getenv ("GCC_URLS") || getenv ("TERM_URLS"))
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 term
= getenv ("TERM");
441 if (!colorterm
&& term
&& !strcmp (term
, "xterm"))
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 if (!colorterm
&& term
&& !strcmp (term
, "linux"))
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. */
457 diagnostic_url_format
458 determine_url_format (diagnostic_url_rule_t rule
)
462 case DIAGNOSTICS_URL_NO
:
463 return URL_FORMAT_NONE
;
464 case DIAGNOSTICS_URL_YES
:
465 return parse_env_vars_for_urls ();
466 case DIAGNOSTICS_URL_AUTO
:
467 if (auto_enable_urls ())
468 return parse_env_vars_for_urls ();
470 return URL_FORMAT_NONE
;
480 /* Test of an empty diagnostic_color_dict. */
483 test_empty_color_dict ()
485 diagnostic_color_dict
d (nullptr, 0);
486 ASSERT_STREQ (d
.get_start_by_name ("warning"), "");
487 ASSERT_STREQ (d
.get_start_by_name ("should-not-be-found"), "");
490 /* Test of a diagnostic_color_dict with GCC's defaults. */
493 test_default_color_dict ()
495 diagnostic_color_dict
d (gcc_color_defaults
,
496 ARRAY_SIZE (gcc_color_defaults
));
497 ASSERT_STREQ (d
.get_start_by_name ("warning"),
498 SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA
));
499 ASSERT_STREQ (d
.get_start_by_name ("should-not-be-found"), "");
502 /* Test of a diagnostic_color_dict with GCC's defaults plus overrides from
503 an environment variable. */
506 test_color_dict_envvar_parsing ()
508 diagnostic_color_dict
d (gcc_color_defaults
,
509 ARRAY_SIZE (gcc_color_defaults
));
511 d
.parse_envvar_value ("error=01;37:warning=01;42:unknown-value=01;36");
513 ASSERT_STREQ (d
.get_start_by_name ("error"),
515 ASSERT_STREQ (d
.get_start_by_name ("warning"),
517 ASSERT_STREQ (d
.get_start_by_name ("unknown-value"), "");
518 ASSERT_STREQ (d
.get_start_by_name ("should-not-be-found"), "");
522 /* Run all of the selftests within this file. */
525 diagnostic_color_cc_tests ()
527 test_empty_color_dict ();
528 test_default_color_dict ();
529 test_color_dict_envvar_parsing ();
532 } // namespace selftest
534 #endif /* #if CHECKING_P */