Subject: [PATCH] color: allow colors to be prefixed with "reset"

From: "Robert Estelle via GitGitGadget" <[email-masked]>
Date: Tue, 26 Oct 2021 01:03:47 +0000

From: Robert Estelle <[email-masked]>

"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 <[email-masked]>
---
    color: allow color specs to be prefixed by "reset"
    
    The pseudo-attribute "reset", which was previously treated as a
    standalone special color name, can now be combined with other color
    properties.
    
    This allows specification of an "entire" text style without implicitly
    inheriting existing properties (colors, bold/italic/etc) nor requiring a
    comprehensive clearing of each of them.
    
    For example, "reset green" now renders \e[;32m, which is interpreted as
    "default properties; then set foreground to green".
    
    Previously, it was not possible to represent this in a single color due
    to inability to clear the foreground or background (at best, they could
    be overridden to a some explicit value). There is a complementary
    proposed change (color: support "default" to restore fg/bg color,
    https://lore.kernel.org/git/[email-masked]/,
    https://github.com/git/git/pull/1116) which introduces the "default"
    color name to facilitate that, but even then, the above would still
    require disabling each of the attributes explicitly: green default
    no-bold no-dim no-italic no-ul no-blink no-reverse no-strike
    
    This is useful in scenarios that the "reset" word or any of the "no-"
    attribute modifiers might have been used before, e.g.
    colors.diff-highlight.newReset.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1117%2Frwe%2Freset-color-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1117/rwe/reset-color-v1
Pull-Request: https://github.com/git/git/pull/1117

 Documentation/config.txt |  5 +++++
 color.c                  | 18 +++++++++++-------
 color.h                  |  1 +
 t/t4026-color.sh         |  4 ++++
 4 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index bf82766a6a2..b1423b6ce8b 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -280,6 +280,11 @@ The position of any attributes with respect to the colors
 be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`,
 `no-ul`, etc).
 +
+The pseudo-attribute `reset` resets all colors and attributes before
+applying the specified coloring. For example, `reset green` will result
+in a green foreground and default background without any active
+attributes.
++
 An empty color string produces no color effect at all. This can be used
 to avoid coloring specific elements without disabling color entirely.
 +
diff --git a/color.c b/color.c
index 64f52a4f93a..29ac83b2d6e 100644
--- a/color.c
+++ b/color.c
@@ -234,6 +234,7 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 	const char *ptr = value;
 	int len = value_len;
 	char *end = dst + COLOR_MAXLEN;
+	unsigned int has_reset = 0;
 	unsigned int attr = 0;
 	struct color fg = { COLOR_UNSPECIFIED };
 	struct color bg = { COLOR_UNSPECIFIED };
@@ -248,12 +249,7 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 		return 0;
 	}
 
-	if (!strncasecmp(ptr, "reset", len)) {
-		xsnprintf(dst, end - dst, GIT_COLOR_RESET);
-		return 0;
-	}
-
-	/* [fg [bg]] [attr]... */
+	/* [reset] [fg [bg]] [attr]... */
 	while (len > 0) {
 		const char *word = ptr;
 		struct color c = { COLOR_UNSPECIFIED };
@@ -270,6 +266,11 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 			len--;
 		}
 
+		if (match_word(word, wordlen, "reset")) {
+			has_reset = 1;
+			continue;
+		}
+
 		if (!parse_color(&c, word, wordlen)) {
 			if (fg.type == COLOR_UNSPECIFIED) {
 				fg = c;
@@ -295,13 +296,16 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 	*dst++ = (x); \
 } while(0)
 
-	if (attr || !color_empty(&fg) || !color_empty(&bg)) {
+	if (has_reset || attr || !color_empty(&fg) || !color_empty(&bg)) {
 		int sep = 0;
 		int i;
 
 		OUT('\033');
 		OUT('[');
 
+		if (has_reset)
+			sep++;
+
 		for (i = 0; attr; i++) {
 			unsigned bit = (1 << i);
 			if (!(attr & bit))
diff --git a/color.h b/color.h
index 98894d6a175..684dbd3bceb 100644
--- a/color.h
+++ b/color.h
@@ -6,6 +6,7 @@ struct strbuf;
 /*
  * The maximum length of ANSI color sequence we would generate:
  * - leading ESC '['            2
+ * - reset ';' .................1
  * - attr + ';'                 2 * num_attr (e.g. "1;")
  * - no-attr + ';'              3 * num_attr (e.g. "22;")
  * - fg color + ';'             17 (e.g. "38;2;255;255;255;")
diff --git a/t/t4026-color.sh b/t/t4026-color.sh
index c0b642c1ab0..5ef2ff28c48 100755
--- a/t/t4026-color.sh
+++ b/t/t4026-color.sh
@@ -58,6 +58,10 @@ test_expect_success 'fg bg attr...' '
 	color "blue bold dim ul blink reverse" "[1;2;4;5;7;34m"
 '
 
+test_expect_success 'reset fg bg attr...' '
+	color "reset blue bold dim ul blink reverse" "[;1;2;4;5;7;34m"
+'
+
 # note that nobold and nodim are the same code (22)
 test_expect_success 'attr negation' '
 	color "nobold nodim noul noblink noreverse" "[22;24;25;27m"

base-commit: 225bc32a989d7a22fa6addafd4ce7dcd04675dbf
-- 
gitgitgadget