color: allow colors to be prefixed with "reset"

"reset" was previously treated as a standalone special color name representing `\e[m`. Now, it can apply to other color properties, allowing exact specifications without implicit attribute inheritance. For example, "reset green" now renders `\e[;32m`, which is interpreted as "reset everything; then set foreground to green". This means the background and other attributes are also reset to their defaults. Previously, this was impossible to represent in a single color: "reset" could be specified alone, or a color with attributes, but some thing like clearing a background color were impossible. There is a separate change that introduces the "default" color name to assist with that, but even then, the above could only to be represented by explicitly disabling each of the attributes: green default no-bold no-dim no-italic no-ul no-blink no-reverse no-strike Signed-off-by: Robert Estelle <robertestelle@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Robert Estelle committed Oct 26, 2021 at 01:03 UTC de658515ae1166577441da09fe7624769e263a3e
4 files changed +21 -7
Documentation/config.txt
+5
@@ -288,6 +288,11 @@ The position of any attributes with respect to the colors
288 be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`,
289 `no-ul`, etc).
290 +
291 +The pseudo-attribute `reset` resets all colors and attributes before
292 +applying the specified coloring. For example, `reset green` will result
293 +in a green foreground and default background without any active
294 +attributes.
295 ++
296 An empty color string produces no color effect at all. This can be used
297 to avoid coloring specific elements without disabling color entirely.
298 +
color.c
+11 -7
@@ -255,6 +255,7 @@ int color_parse_mem(const char *value, int value_len, char *dst)
255 const char *ptr = value;
256 int len = value_len;
257 char *end = dst + COLOR_MAXLEN;
258 + unsigned int has_reset = 0;
259 unsigned int attr = 0;
260 struct color fg = { COLOR_UNSPECIFIED };
261 struct color bg = { COLOR_UNSPECIFIED };
@@ -269,12 +270,7 @@ int color_parse_mem(const char *value, int value_len, char *dst)
270 return 0;
271 }
272
272 - if (!strncasecmp(ptr, "reset", len)) {
273 - xsnprintf(dst, end - dst, GIT_COLOR_RESET);
274 - return 0;
275 - }
276 -
277 - /* [fg [bg]] [attr]... */
273 + /* [reset] [fg [bg]] [attr]... */
274 while (len > 0) {
275 const char *word = ptr;
276 struct color c = { COLOR_UNSPECIFIED };
@@ -291,6 +287,11 @@ int color_parse_mem(const char *value, int value_len, char *dst)
287 len--;
288 }
289
290 + if (match_word(word, wordlen, "reset")) {
291 + has_reset = 1;
292 + continue;
293 + }
294 +
295 if (!parse_color(&c, word, wordlen)) {
296 if (fg.type == COLOR_UNSPECIFIED) {
297 fg = c;
@@ -316,13 +317,16 @@ int color_parse_mem(const char *value, int value_len, char *dst)
317 *dst++ = (x); \
318 } while(0)
319
319 - if (attr || !color_empty(&fg) || !color_empty(&bg)) {
320 + if (has_reset || attr || !color_empty(&fg) || !color_empty(&bg)) {
321 int sep = 0;
322 int i;
323
324 OUT('\033');
325 OUT('[');
326
327 + if (has_reset)
328 + sep++;
329 +
330 for (i = 0; attr; i++) {
331 unsigned bit = (1 << i);
332 if (!(attr & bit))
color.h
+1
@@ -6,6 +6,7 @@ struct strbuf;
6 /*
7 * The maximum length of ANSI color sequence we would generate:
8 * - leading ESC '[' 2
9 + * - reset ';' .................1
10 * - attr + ';' 2 * num_attr (e.g. "1;")
11 * - no-attr + ';' 3 * num_attr (e.g. "22;")
12 * - fg color + ';' 17 (e.g. "38;2;255;255;255;")
t/t4026-color.sh
+4
@@ -60,6 +60,10 @@ test_expect_success 'fg bg attr...' '
60 color "blue bold dim ul blink reverse" "[1;2;4;5;7;34m"
61 '
62
63 +test_expect_success 'reset fg bg attr...' '
64 + color "reset blue bold dim ul blink reverse" "[;1;2;4;5;7;34m"
65 +'
66 +
67 # note that nobold and nodim are the same code (22)
68 test_expect_success 'attr negation' '
69 color "nobold nodim noul noblink noreverse" "[22;24;25;27m"