Line | Count | Source |
1 | | #define DISABLE_SIGN_COMPARE_WARNINGS |
2 | | |
3 | | #include "git-compat-util.h" |
4 | | #include "config.h" |
5 | | #include "color.h" |
6 | | #include "editor.h" |
7 | | #include "gettext.h" |
8 | | #include "hex-ll.h" |
9 | | #include "pager.h" |
10 | | #include "strbuf.h" |
11 | | |
12 | | static enum git_colorbool git_use_color_default = GIT_COLOR_AUTO; |
13 | | int color_stdout_is_tty = -1; |
14 | | |
15 | | /* |
16 | | * The list of available column colors. |
17 | | */ |
18 | | const char *column_colors_ansi[] = { |
19 | | GIT_COLOR_RED, |
20 | | GIT_COLOR_GREEN, |
21 | | GIT_COLOR_YELLOW, |
22 | | GIT_COLOR_BLUE, |
23 | | GIT_COLOR_MAGENTA, |
24 | | GIT_COLOR_CYAN, |
25 | | GIT_COLOR_BOLD_RED, |
26 | | GIT_COLOR_BOLD_GREEN, |
27 | | GIT_COLOR_BOLD_YELLOW, |
28 | | GIT_COLOR_BOLD_BLUE, |
29 | | GIT_COLOR_BOLD_MAGENTA, |
30 | | GIT_COLOR_BOLD_CYAN, |
31 | | GIT_COLOR_RESET, |
32 | | }; |
33 | | |
34 | | enum { |
35 | | COLOR_BACKGROUND_OFFSET = 10, |
36 | | COLOR_FOREGROUND_ANSI = 30, |
37 | | COLOR_FOREGROUND_RGB = 38, |
38 | | COLOR_FOREGROUND_256 = 38, |
39 | | COLOR_FOREGROUND_BRIGHT_ANSI = 90, |
40 | | }; |
41 | | |
42 | | /* Ignore the RESET at the end when giving the size */ |
43 | | const int column_colors_ansi_max = ARRAY_SIZE(column_colors_ansi) - 1; |
44 | | |
45 | | /* An individual foreground or background color. */ |
46 | | struct color { |
47 | | enum { |
48 | | COLOR_UNSPECIFIED = 0, |
49 | | COLOR_NORMAL, |
50 | | COLOR_ANSI, /* basic 0-7 ANSI colors + "default" (value = 9) */ |
51 | | COLOR_256, |
52 | | COLOR_RGB |
53 | | } type; |
54 | | /* The numeric value for ANSI and 256-color modes */ |
55 | | unsigned char value; |
56 | | /* 24-bit RGB color values */ |
57 | | unsigned char red, green, blue; |
58 | | }; |
59 | | |
60 | | /* |
61 | | * "word" is a buffer of length "len"; does it match the NUL-terminated |
62 | | * "match" exactly? |
63 | | */ |
64 | | static int match_word(const char *word, int len, const char *match) |
65 | 0 | { |
66 | 0 | return !strncasecmp(word, match, len) && !match[len]; |
67 | 0 | } |
68 | | |
69 | | static int get_hex_color(const char **inp, int width, unsigned char *out) |
70 | 0 | { |
71 | 0 | const char *in = *inp; |
72 | 0 | unsigned int val; |
73 | |
|
74 | 0 | assert(width == 1 || width == 2); |
75 | 0 | val = (hexval(in[0]) << 4) | hexval(in[width - 1]); |
76 | 0 | if (val & ~0xff) |
77 | 0 | return -1; |
78 | 0 | *inp += width; |
79 | 0 | *out = val; |
80 | 0 | return 0; |
81 | 0 | } |
82 | | |
83 | | /* |
84 | | * If an ANSI color is recognized in "name", fill "out" and return 0. |
85 | | * Otherwise, leave out unchanged and return -1. |
86 | | */ |
87 | | static int parse_ansi_color(struct color *out, const char *name, int len) |
88 | 0 | { |
89 | | /* Positions in array must match ANSI color codes */ |
90 | 0 | static const char * const color_names[] = { |
91 | 0 | "black", "red", "green", "yellow", |
92 | 0 | "blue", "magenta", "cyan", "white" |
93 | 0 | }; |
94 | 0 | int i; |
95 | 0 | int color_offset = COLOR_FOREGROUND_ANSI; |
96 | |
|
97 | 0 | if (match_word(name, len, "default")) { |
98 | | /* |
99 | | * Restores to the terminal's default color, which may not be |
100 | | * the same as explicitly setting "white" or "black". |
101 | | * |
102 | | * ECMA-48 - Control Functions \ |
103 | | * for Coded Character Sets, 5th edition (June 1991): |
104 | | * > 39 default display colour (implementation-defined) |
105 | | * > 49 default background colour (implementation-defined) |
106 | | * |
107 | | * Although not supported /everywhere/--according to terminfo, |
108 | | * some terminals define "op" (original pair) as a blunt |
109 | | * "set to white on black", or even "send full SGR reset"-- |
110 | | * it's standard and well-supported enough that if a user |
111 | | * asks for it in their config this will do the right thing. |
112 | | */ |
113 | 0 | out->type = COLOR_ANSI; |
114 | 0 | out->value = 9 + color_offset; |
115 | 0 | return 0; |
116 | 0 | } |
117 | | |
118 | 0 | if (strncasecmp(name, "bright", 6) == 0) { |
119 | 0 | color_offset = COLOR_FOREGROUND_BRIGHT_ANSI; |
120 | 0 | name += 6; |
121 | 0 | len -= 6; |
122 | 0 | } |
123 | 0 | for (i = 0; i < ARRAY_SIZE(color_names); i++) { |
124 | 0 | if (match_word(name, len, color_names[i])) { |
125 | 0 | out->type = COLOR_ANSI; |
126 | 0 | out->value = i + color_offset; |
127 | 0 | return 0; |
128 | 0 | } |
129 | 0 | } |
130 | 0 | return -1; |
131 | 0 | } |
132 | | |
133 | | static int parse_color(struct color *out, const char *name, int len) |
134 | 0 | { |
135 | 0 | char *end; |
136 | 0 | long val; |
137 | | |
138 | | /* First try the special word "normal"... */ |
139 | 0 | if (match_word(name, len, "normal")) { |
140 | 0 | out->type = COLOR_NORMAL; |
141 | 0 | return 0; |
142 | 0 | } |
143 | | |
144 | | /* Try a 24- or 12-bit RGB value prefixed with '#' */ |
145 | 0 | if ((len == 7 || len == 4) && name[0] == '#') { |
146 | 0 | int width_per_color = (len == 7) ? 2 : 1; |
147 | 0 | const char *color = name + 1; |
148 | |
|
149 | 0 | if (!get_hex_color(&color, width_per_color, &out->red) && |
150 | 0 | !get_hex_color(&color, width_per_color, &out->green) && |
151 | 0 | !get_hex_color(&color, width_per_color, &out->blue)) { |
152 | 0 | out->type = COLOR_RGB; |
153 | 0 | return 0; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | /* Then pick from our human-readable color names... */ |
158 | 0 | if (parse_ansi_color(out, name, len) == 0) { |
159 | 0 | return 0; |
160 | 0 | } |
161 | | |
162 | | /* And finally try a literal 256-color-mode number */ |
163 | 0 | val = strtol(name, &end, 10); |
164 | 0 | if (end - name == len) { |
165 | | /* |
166 | | * Allow "-1" as an alias for "normal", but other negative |
167 | | * numbers are bogus. |
168 | | */ |
169 | 0 | if (val < -1) |
170 | 0 | ; /* fall through to error */ |
171 | 0 | else if (val < 0) { |
172 | 0 | out->type = COLOR_NORMAL; |
173 | 0 | return 0; |
174 | | /* Rewrite 0-7 as more-portable standard colors. */ |
175 | 0 | } else if (val < 8) { |
176 | 0 | out->type = COLOR_ANSI; |
177 | 0 | out->value = val + COLOR_FOREGROUND_ANSI; |
178 | 0 | return 0; |
179 | | /* Rewrite 8-15 as more-portable aixterm colors. */ |
180 | 0 | } else if (val < 16) { |
181 | 0 | out->type = COLOR_ANSI; |
182 | 0 | out->value = val - 8 + COLOR_FOREGROUND_BRIGHT_ANSI; |
183 | 0 | return 0; |
184 | 0 | } else if (val < 256) { |
185 | 0 | out->type = COLOR_256; |
186 | 0 | out->value = val; |
187 | 0 | return 0; |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | 0 | return -1; |
192 | 0 | } |
193 | | |
194 | | static int parse_attr(const char *name, size_t len) |
195 | 0 | { |
196 | 0 | static const struct { |
197 | 0 | const char *name; |
198 | 0 | size_t len; |
199 | 0 | int val, neg; |
200 | 0 | } attrs[] = { |
201 | 0 | #define ATTR(x, val, neg) { (x), sizeof(x)-1, (val), (neg) } |
202 | 0 | ATTR("bold", 1, 22), |
203 | 0 | ATTR("dim", 2, 22), |
204 | 0 | ATTR("italic", 3, 23), |
205 | 0 | ATTR("ul", 4, 24), |
206 | 0 | ATTR("blink", 5, 25), |
207 | 0 | ATTR("reverse", 7, 27), |
208 | 0 | ATTR("strike", 9, 29) |
209 | 0 | #undef ATTR |
210 | 0 | }; |
211 | 0 | int negate = 0; |
212 | 0 | int i; |
213 | |
|
214 | 0 | if (skip_prefix_mem(name, len, "no", &name, &len)) { |
215 | 0 | skip_prefix_mem(name, len, "-", &name, &len); |
216 | 0 | negate = 1; |
217 | 0 | } |
218 | |
|
219 | 0 | for (i = 0; i < ARRAY_SIZE(attrs); i++) { |
220 | 0 | if (attrs[i].len == len && !memcmp(attrs[i].name, name, len)) |
221 | 0 | return negate ? attrs[i].neg : attrs[i].val; |
222 | 0 | } |
223 | 0 | return -1; |
224 | 0 | } |
225 | | |
226 | | /* |
227 | | * Write the ANSI color codes for "c" to "out"; the string should |
228 | | * already have the ANSI escape code in it. "out" should have enough |
229 | | * space in it to fit any color. |
230 | | */ |
231 | | static char *color_output(char *out, int len, const struct color *c, int background) |
232 | 0 | { |
233 | 0 | int offset = 0; |
234 | |
|
235 | 0 | if (background) |
236 | 0 | offset = COLOR_BACKGROUND_OFFSET; |
237 | 0 | switch (c->type) { |
238 | 0 | case COLOR_UNSPECIFIED: |
239 | 0 | case COLOR_NORMAL: |
240 | 0 | break; |
241 | 0 | case COLOR_ANSI: |
242 | 0 | out += xsnprintf(out, len, "%d", c->value + offset); |
243 | 0 | break; |
244 | 0 | case COLOR_256: |
245 | 0 | out += xsnprintf(out, len, "%d;5;%d", COLOR_FOREGROUND_256 + offset, |
246 | 0 | c->value); |
247 | 0 | break; |
248 | 0 | case COLOR_RGB: |
249 | 0 | out += xsnprintf(out, len, "%d;2;%d;%d;%d", |
250 | 0 | COLOR_FOREGROUND_RGB + offset, |
251 | 0 | c->red, c->green, c->blue); |
252 | 0 | break; |
253 | 0 | } |
254 | 0 | return out; |
255 | 0 | } |
256 | | |
257 | | static int color_empty(const struct color *c) |
258 | 0 | { |
259 | 0 | return c->type <= COLOR_NORMAL; |
260 | 0 | } |
261 | | |
262 | | static int color_parse_mem_1(const char *value, int value_len, |
263 | | char *dst, int quiet) |
264 | 0 | { |
265 | 0 | const char *ptr = value; |
266 | 0 | int len = value_len; |
267 | 0 | char *end = dst + COLOR_MAXLEN; |
268 | 0 | unsigned int has_reset = 0; |
269 | 0 | unsigned int attr = 0; |
270 | 0 | struct color fg = { COLOR_UNSPECIFIED }; |
271 | 0 | struct color bg = { COLOR_UNSPECIFIED }; |
272 | |
|
273 | 0 | while (len > 0 && isspace(*ptr)) { |
274 | 0 | ptr++; |
275 | 0 | len--; |
276 | 0 | } |
277 | |
|
278 | 0 | if (!len) { |
279 | 0 | dst[0] = '\0'; |
280 | 0 | return 0; |
281 | 0 | } |
282 | | |
283 | | /* [reset] [fg [bg]] [attr]... */ |
284 | 0 | while (len > 0) { |
285 | 0 | const char *word = ptr; |
286 | 0 | struct color c = { COLOR_UNSPECIFIED }; |
287 | 0 | int val, wordlen = 0; |
288 | |
|
289 | 0 | while (len > 0 && !isspace(word[wordlen])) { |
290 | 0 | wordlen++; |
291 | 0 | len--; |
292 | 0 | } |
293 | |
|
294 | 0 | ptr = word + wordlen; |
295 | 0 | while (len > 0 && isspace(*ptr)) { |
296 | 0 | ptr++; |
297 | 0 | len--; |
298 | 0 | } |
299 | |
|
300 | 0 | if (match_word(word, wordlen, "reset")) { |
301 | 0 | has_reset = 1; |
302 | 0 | continue; |
303 | 0 | } |
304 | | |
305 | 0 | if (!parse_color(&c, word, wordlen)) { |
306 | 0 | if (fg.type == COLOR_UNSPECIFIED) { |
307 | 0 | fg = c; |
308 | 0 | continue; |
309 | 0 | } |
310 | 0 | if (bg.type == COLOR_UNSPECIFIED) { |
311 | 0 | bg = c; |
312 | 0 | continue; |
313 | 0 | } |
314 | 0 | goto bad; |
315 | 0 | } |
316 | 0 | val = parse_attr(word, wordlen); |
317 | 0 | if (0 <= val) |
318 | 0 | attr |= (1 << val); |
319 | 0 | else |
320 | 0 | goto bad; |
321 | 0 | } |
322 | | |
323 | 0 | #undef OUT |
324 | 0 | #define OUT(x) do { \ |
325 | 0 | if (dst == end) \ |
326 | 0 | BUG("color parsing ran out of space"); \ |
327 | 0 | *dst++ = (x); \ |
328 | 0 | } while(0) |
329 | | |
330 | 0 | if (has_reset || attr || !color_empty(&fg) || !color_empty(&bg)) { |
331 | 0 | int sep = 0; |
332 | 0 | int i; |
333 | |
|
334 | 0 | OUT('\033'); |
335 | 0 | OUT('['); |
336 | | |
337 | 0 | if (has_reset) |
338 | 0 | sep++; |
339 | |
|
340 | 0 | for (i = 0; attr; i++) { |
341 | 0 | unsigned bit = (1 << i); |
342 | 0 | if (!(attr & bit)) |
343 | 0 | continue; |
344 | 0 | attr &= ~bit; |
345 | 0 | if (sep++) |
346 | 0 | OUT(';'); |
347 | 0 | dst += xsnprintf(dst, end - dst, "%d", i); |
348 | 0 | } |
349 | 0 | if (!color_empty(&fg)) { |
350 | 0 | if (sep++) |
351 | 0 | OUT(';'); |
352 | 0 | dst = color_output(dst, end - dst, &fg, 0); |
353 | 0 | } |
354 | 0 | if (!color_empty(&bg)) { |
355 | 0 | if (sep++) |
356 | 0 | OUT(';'); |
357 | 0 | dst = color_output(dst, end - dst, &bg, 1); |
358 | 0 | } |
359 | 0 | OUT('m'); |
360 | 0 | } |
361 | 0 | OUT(0); |
362 | 0 | return 0; |
363 | 0 | bad: |
364 | 0 | return quiet ? -1 : error(_("invalid color value: %.*s"), value_len, value); |
365 | 0 | #undef OUT |
366 | 0 | } |
367 | | |
368 | | int color_parse_mem(const char *value, int value_len, char *dst) |
369 | 0 | { |
370 | 0 | return color_parse_mem_1(value, value_len, dst, 0); |
371 | 0 | } |
372 | | |
373 | | int color_parse(const char *value, char *dst) |
374 | 0 | { |
375 | 0 | return color_parse_mem(value, strlen(value), dst); |
376 | 0 | } |
377 | | |
378 | | int color_parse_quietly(const char *value, char *dst) |
379 | 0 | { |
380 | 0 | return color_parse_mem_1(value, strlen(value), dst, 1); |
381 | 0 | } |
382 | | |
383 | | enum git_colorbool git_config_colorbool(const char *var, const char *value) |
384 | 0 | { |
385 | 0 | if (value) { |
386 | 0 | if (!strcasecmp(value, "never")) |
387 | 0 | return GIT_COLOR_NEVER; |
388 | 0 | if (!strcasecmp(value, "always")) |
389 | 0 | return GIT_COLOR_ALWAYS; |
390 | 0 | if (!strcasecmp(value, "auto")) |
391 | 0 | return GIT_COLOR_AUTO; |
392 | 0 | } |
393 | | |
394 | 0 | if (!var) |
395 | 0 | return GIT_COLOR_UNKNOWN; |
396 | | |
397 | | /* Missing or explicit false to turn off colorization */ |
398 | 0 | if (!git_config_bool(var, value)) |
399 | 0 | return GIT_COLOR_NEVER; |
400 | | |
401 | | /* any normal truth value defaults to 'auto' */ |
402 | 0 | return GIT_COLOR_AUTO; |
403 | 0 | } |
404 | | |
405 | | static bool check_auto_color(int fd) |
406 | 0 | { |
407 | 0 | static int color_stderr_is_tty = -1; |
408 | 0 | int *is_tty_p = fd == 1 ? &color_stdout_is_tty : &color_stderr_is_tty; |
409 | 0 | if (*is_tty_p < 0) |
410 | 0 | *is_tty_p = isatty(fd); |
411 | 0 | if (*is_tty_p || (fd == 1 && pager_in_use() && pager_use_color)) { |
412 | 0 | if (!is_terminal_dumb()) |
413 | 0 | return true; |
414 | 0 | } |
415 | 0 | return false; |
416 | 0 | } |
417 | | |
418 | | bool want_color_fd(int fd, enum git_colorbool var) |
419 | 0 | { |
420 | | /* |
421 | | * NEEDSWORK: This function is sometimes used from multiple threads, and |
422 | | * we end up using want_auto racily. That "should not matter" since |
423 | | * we always write the same value, but it's still wrong. This function |
424 | | * is listed in .tsan-suppressions for the time being. |
425 | | */ |
426 | |
|
427 | 0 | static int want_auto[3] = { -1, -1, -1 }; |
428 | |
|
429 | 0 | if (fd < 1 || fd >= ARRAY_SIZE(want_auto)) |
430 | 0 | BUG("file descriptor out of range: %d", fd); |
431 | | |
432 | 0 | if (var == GIT_COLOR_UNKNOWN) |
433 | 0 | var = git_use_color_default; |
434 | |
|
435 | 0 | if (var == GIT_COLOR_AUTO) { |
436 | 0 | if (want_auto[fd] < 0) |
437 | 0 | want_auto[fd] = check_auto_color(fd); |
438 | 0 | return want_auto[fd]; |
439 | 0 | } |
440 | 0 | return var == GIT_COLOR_ALWAYS; |
441 | 0 | } |
442 | | |
443 | | int git_color_config(const char *var, const char *value, void *cb UNUSED) |
444 | 0 | { |
445 | 0 | if (!strcmp(var, "color.ui")) { |
446 | 0 | git_use_color_default = git_config_colorbool(var, value); |
447 | 0 | return 0; |
448 | 0 | } |
449 | | |
450 | 0 | return 0; |
451 | 0 | } |
452 | | |
453 | | void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb) |
454 | 0 | { |
455 | 0 | if (*color) |
456 | 0 | fprintf(fp, "%s", color); |
457 | 0 | fprintf(fp, "%s", sb->buf); |
458 | 0 | if (*color) |
459 | 0 | fprintf(fp, "%s", GIT_COLOR_RESET); |
460 | 0 | } |
461 | | |
462 | | static int color_vfprintf(FILE *fp, const char *color, const char *fmt, |
463 | | va_list args, const char *trail) |
464 | 0 | { |
465 | 0 | int r = 0; |
466 | |
|
467 | 0 | if (*color) |
468 | 0 | r += fprintf(fp, "%s", color); |
469 | 0 | r += vfprintf(fp, fmt, args); |
470 | 0 | if (*color) |
471 | 0 | r += fprintf(fp, "%s", GIT_COLOR_RESET); |
472 | 0 | if (trail) |
473 | 0 | r += fprintf(fp, "%s", trail); |
474 | 0 | return r; |
475 | 0 | } |
476 | | |
477 | | int color_fprintf(FILE *fp, const char *color, const char *fmt, ...) |
478 | 0 | { |
479 | 0 | va_list args; |
480 | 0 | int r; |
481 | 0 | va_start(args, fmt); |
482 | 0 | r = color_vfprintf(fp, color, fmt, args, NULL); |
483 | 0 | va_end(args); |
484 | 0 | return r; |
485 | 0 | } |
486 | | |
487 | | int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) |
488 | 0 | { |
489 | 0 | va_list args; |
490 | 0 | int r; |
491 | 0 | va_start(args, fmt); |
492 | 0 | r = color_vfprintf(fp, color, fmt, args, "\n"); |
493 | 0 | va_end(args); |
494 | 0 | return r; |
495 | 0 | } |
496 | | |
497 | | int color_is_nil(const char *c) |
498 | 0 | { |
499 | 0 | return !strcmp(c, "NIL"); |
500 | 0 | } |