1 /* Output colorization.
2    Copyright (C) 2011-2022 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 #include "system.h"
21 #include "diagnostic-color.h"
22 #include "diagnostic-url.h"
23 
24 #ifdef __MINGW32__
25 #  include <windows.h>
26 #endif
27 
28 #include "color-macros.h"
29 
30 /* The context and logic for choosing default --color screen attributes
31    (foreground and background colors, etc.) are the following.
32       -- There are eight basic colors available, each with its own
33            nominal luminosity to the human eye and foreground/background
34            codes (black [0 %, 30/40], blue [11 %, 34/44], red [30 %, 31/41],
35            magenta [41 %, 35/45], green [59 %, 32/42], cyan [70 %, 36/46],
36            yellow [89 %, 33/43], and white [100 %, 37/47]).
37       -- Sometimes, white as a background is actually implemented using
38            a shade of light gray, so that a foreground white can be visible
39            on top of it (but most often not).
40       -- Sometimes, black as a foreground is actually implemented using
41            a shade of dark gray, so that it can be visible on top of a
42            background black (but most often not).
43       -- Sometimes, more colors are available, as extensions.
44       -- Other attributes can be selected/deselected (bold [1/22],
45            underline [4/24], standout/inverse [7/27], blink [5/25], and
46            invisible/hidden [8/28]).  They are sometimes implemented by
47            using colors instead of what their names imply; e.g., bold is
48            often achieved by using brighter colors.  In practice, only bold
49            is really available to us, underline sometimes being mapped by
50            the terminal to some strange color choice, and standout best
51            being left for use by downstream programs such as less(1).
52       -- We cannot assume that any of the extensions or special features
53            are available for the purpose of choosing defaults for everyone.
54       -- The most prevalent default terminal backgrounds are pure black
55            and pure white, and are not necessarily the same shades of
56            those as if they were selected explicitly with SGR sequences.
57            Some terminals use dark or light pictures as default background,
58            but those are covered over by an explicit selection of background
59            color with an SGR sequence; their users will appreciate their
60            background pictures not be covered like this, if possible.
61       -- Some uses of colors attributes is to make some output items
62            more understated (e.g., context lines); this cannot be achieved
63            by changing the background color.
64       -- For these reasons, the GCC color defaults should strive not
65            to change the background color from its default, unless it's
66            for a short item that should be highlighted, not understated.
67       -- The GCC foreground color defaults (without an explicitly set
68            background) should provide enough contrast to be readable on any
69            terminal with either a black (dark) or white (light) background.
70            This only leaves red, magenta, green, and cyan (and their bold
71            counterparts) and possibly bold blue.  */
72 /* Default colors. The user can overwrite them using environment
73    variable GCC_COLORS.  */
74 struct color_cap
75 {
76   const char *name;
77   const char *val;
78   unsigned char name_len;
79   bool free_val;
80 };
81 
82 /* For GCC_COLORS.  */
83 static struct color_cap color_dict[] =
84 {
85   { "error", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED), 5, false },
86   { "warning", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA),
87                  7, false },
88   { "note", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
89   { "range1", SGR_SEQ (COLOR_FG_GREEN), 6, false },
90   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
91   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
92   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
93   { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
94   { "fixit-insert", SGR_SEQ (COLOR_FG_GREEN), 12, false },
95   { "fixit-delete", SGR_SEQ (COLOR_FG_RED), 12, false },
96   { "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
97   { "diff-hunk", SGR_SEQ (COLOR_FG_CYAN), 9, false },
98   { "diff-delete", SGR_SEQ (COLOR_FG_RED), 11, false },
99   { "diff-insert", SGR_SEQ (COLOR_FG_GREEN), 11, false },
100   { "type-diff", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN), 9, false },
101   { NULL, NULL, 0, false }
102 };
103 
104 const char *
colorize_start(bool show_color,const char * name,size_t name_len)105 colorize_start (bool show_color, const char *name, size_t name_len)
106 {
107   struct color_cap const *cap;
108 
109   if (!show_color)
110     return "";
111 
112   for (cap = color_dict; cap->name; cap++)
113     if (cap->name_len == name_len
114           && memcmp (cap->name, name, name_len) == 0)
115       break;
116   if (cap->name == NULL)
117     return "";
118 
119   return cap->val;
120 }
121 
122 const char *
colorize_stop(bool show_color)123 colorize_stop (bool show_color)
124 {
125   return show_color ? SGR_RESET : "";
126 }
127 
128 /* Parse GCC_COLORS.  The default would look like:
129    GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
130    range1=32:range2=34:locus=01:quote=01:path=01;36:\
131    fixit-insert=32:fixit-delete=31:'\
132    diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
133    type-diff=01;32'
134    No character escaping is needed or supported.  */
135 static bool
parse_gcc_colors(void)136 parse_gcc_colors (void)
137 {
138   const char *p, *q, *name, *val;
139   char *b;
140   size_t name_len = 0, val_len = 0;
141 
142   p = getenv ("GCC_COLORS"); /* Plural! */
143   if (p == NULL)
144     return true;
145   if (*p == '\0')
146     return false;
147 
148   name = q = p;
149   val = NULL;
150   /* From now on, be well-formed or you're gone.  */
151   for (;;)
152     if (*q == ':' || *q == '\0')
153       {
154           struct color_cap *cap;
155 
156           if (val)
157             val_len = q - val;
158           else
159             name_len = q - name;
160           /* Empty name without val (empty cap)
161              won't match and will be ignored.  */
162           for (cap = color_dict; cap->name; cap++)
163             if (cap->name_len == name_len
164                 && memcmp (cap->name, name, name_len) == 0)
165               break;
166           /* If name unknown, go on for forward compatibility.  */
167           if (cap->val && val)
168             {
169               if (cap->free_val)
170                 free (CONST_CAST (char *, cap->val));
171               b = XNEWVEC (char, val_len + sizeof (SGR_SEQ ("")));
172               memcpy (b, SGR_START, strlen (SGR_START));
173               memcpy (b + strlen (SGR_START), val, val_len);
174               memcpy (b + strlen (SGR_START) + val_len, SGR_END,
175                         sizeof (SGR_END));
176               cap->val = (const char *) b;
177               cap->free_val = true;
178             }
179           if (*q == '\0')
180             return true;
181           name = ++q;
182           val = NULL;
183       }
184     else if (*q == '=')
185       {
186           if (q == name || val)
187             return true;
188 
189           name_len = q - name;
190           val = ++q; /* Can be the empty string.  */
191       }
192     else if (val == NULL)
193       q++; /* Accumulate name.  */
194     else if (*q == ';' || (*q >= '0' && *q <= '9'))
195       q++; /* Accumulate val.  Protect the terminal from being sent
196                 garbage.  */
197     else
198       return true;
199 }
200 
201 /* Return true if we should use color when in auto mode, false otherwise. */
202 static bool
should_colorize(void)203 should_colorize (void)
204 {
205 #ifdef __MINGW32__
206   /* For consistency reasons, one should check the handle returned by
207      _get_osfhandle(_fileno(stderr)) because the function
208      pp_write_text_to_stream() in pretty-print.cc calls fputs() on
209      that stream.  However, the code below for non-Windows doesn't seem
210      to care about it either...  */
211   HANDLE h;
212   DWORD m;
213 
214   h = GetStdHandle (STD_ERROR_HANDLE);
215   return (h != INVALID_HANDLE_VALUE) && (h != NULL)
216             && GetConsoleMode (h, &m);
217 #else
218   char const *t = getenv ("TERM");
219   /* emacs M-x shell sets TERM="dumb".  */
220   return t && strcmp (t, "dumb") != 0 && isatty (STDERR_FILENO);
221 #endif
222 }
223 
224 bool
colorize_init(diagnostic_color_rule_t rule)225 colorize_init (diagnostic_color_rule_t rule)
226 {
227   switch (rule)
228     {
229     case DIAGNOSTICS_COLOR_NO:
230       return false;
231     case DIAGNOSTICS_COLOR_YES:
232       return parse_gcc_colors ();
233     case DIAGNOSTICS_COLOR_AUTO:
234       if (should_colorize ())
235           return parse_gcc_colors ();
236       else
237           return false;
238     default:
239       gcc_unreachable ();
240     }
241 }
242 
243 /* Return URL_FORMAT_XXX which tells how we should emit urls
244    when in always mode.
245    We use GCC_URLS and if that is not defined TERM_URLS.
246    If neither is defined the feature is enabled by default.  */
247 
248 static diagnostic_url_format
parse_env_vars_for_urls()249 parse_env_vars_for_urls ()
250 {
251   const char *p;
252 
253   p = getenv ("GCC_URLS"); /* Plural! */
254   if (p == NULL)
255     p = getenv ("TERM_URLS");
256 
257   if (p == NULL)
258     return URL_FORMAT_DEFAULT;
259 
260   if (*p == '\0')
261     return URL_FORMAT_NONE;
262 
263   if (!strcmp (p, "no"))
264     return URL_FORMAT_NONE;
265 
266   if (!strcmp (p, "st"))
267     return URL_FORMAT_ST;
268 
269   if (!strcmp (p, "bel"))
270     return URL_FORMAT_BEL;
271 
272   return URL_FORMAT_DEFAULT;
273 }
274 
275 /* Return true if we should use urls when in auto mode, false otherwise.  */
276 
277 static bool
auto_enable_urls()278 auto_enable_urls ()
279 {
280 #ifdef __MINGW32__
281   return false;
282 #else
283   const char *term, *colorterm;
284 
285   /* First check the terminal is capable of printing color escapes,
286      if not URLs won't work either.  */
287   if (!should_colorize ())
288     return false;
289 
290   /* xfce4-terminal is known to not implement URLs at this time.
291      Recently new installations (0.8) will safely ignore the URL escape
292      sequences, but a large number of legacy installations (0.6.3) print
293      garbage when URLs are printed.  Therefore we lose nothing by
294      disabling this feature for that specific terminal type.  */
295   colorterm = getenv ("COLORTERM");
296   if (colorterm && !strcmp (colorterm, "xfce4-terminal"))
297     return false;
298 
299   /* Old versions of gnome-terminal where URL escapes cause screen
300      corruptions set COLORTERM="gnome-terminal", recent versions
301      with working URL support set this to "truecolor".  */
302   if (colorterm && !strcmp (colorterm, "gnome-terminal"))
303     return false;
304 
305   /* Since the following checks are less specific than the ones
306      above, let GCC_URLS and TERM_URLS override the decision.  */
307   if (getenv ("GCC_URLS") || getenv ("TERM_URLS"))
308     return true;
309 
310   /* In an ssh session the COLORTERM is not there, but TERM=xterm
311      can be used as an indication of a incompatible terminal while
312      TERM=xterm-256color appears to be a working terminal.  */
313   term = getenv ("TERM");
314   if (!colorterm && term && !strcmp (term, "xterm"))
315     return false;
316 
317   /* When logging in a linux over serial line, we see TERM=linux
318      and no COLORTERM, it is unlikely that the URL escapes will
319      work in that environmen either.  */
320   if (!colorterm && term && !strcmp (term, "linux"))
321     return false;
322 
323   return true;
324 #endif
325 }
326 
327 /* Determine if URLs should be enabled, based on RULE,
328    and, if so, which format to use.
329    This reuses the logic for colorization.  */
330 
331 diagnostic_url_format
determine_url_format(diagnostic_url_rule_t rule)332 determine_url_format (diagnostic_url_rule_t rule)
333 {
334   switch (rule)
335     {
336     case DIAGNOSTICS_URL_NO:
337       return URL_FORMAT_NONE;
338     case DIAGNOSTICS_URL_YES:
339       return parse_env_vars_for_urls ();
340     case DIAGNOSTICS_URL_AUTO:
341       if (auto_enable_urls ())
342           return parse_env_vars_for_urls ();
343       else
344           return URL_FORMAT_NONE;
345     default:
346       gcc_unreachable ();
347     }
348 }
349