1/* Output colorization.
2   Copyright (C) 2011-2020 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.  */
74struct 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.  */
83static 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
104const char *
105colorize_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
122const char *
123colorize_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.  */
135static bool
136parse_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. */
202static bool
203should_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.c 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
224bool
225colorize_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
248static diagnostic_url_format
249parse_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
277static bool
278auto_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
331diagnostic_url_format
332determine_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