From ccd027ce5e325bc5f8e04089432ffe3c6c7f63f4 Mon Sep 17 00:00:00 2001
From: Brad Town <brad@bradtown.com>
Date: Mon, 1 Nov 2021 20:42:08 -0700
Subject: [PATCH] Improve color support.

---
 doc/nano.texi        |  25 +++++-
 doc/nanorc.5         |  26 +++++-
 src/color.c          |  15 +++-
 src/definitions.h    |   7 +-
 src/nano.c           |  10 ++-
 src/rcfile.c         | 205 +++++++++++++++++++++++++++++++++++++------
 syntax/nanorc.nanorc |   4 +-
 7 files changed, 251 insertions(+), 41 deletions(-)

diff --git a/doc/nano.texi b/doc/nano.texi
index f75b1ef6..2d701a0d 100644
--- a/doc/nano.texi
+++ b/doc/nano.texi
@@ -705,6 +705,11 @@ The state flags are displayed only when @code{--stateflags} is used.
 When neither the file's name nor its first line give a clue,
 try using libmagic to determine the applicable syntax.
 
+@item -8
+@itemx --nocolormatchansi
+Disable matching RGB colors to ANSI colors for terminals supporting fewer than
+256 colors.
+
 @end table
 
 
@@ -907,6 +912,12 @@ Each of these eight names may be prefixed with the word
 The word @code{grey} or @code{gray} may be used as
 a synonym for @code{lightblack}.
 
+Foreground and background colors may be given as RGB hexadecimal values
+such as @code{rgb:FF/00/00} (maximum red) or @code{rgb:66/33/99}
+(rebeccapurple).  The color closest to the requested color and supported by the
+terminal will be used.  This behavior can be disabled for terminals supporting
+fewer than 256 colors using @code{set nocolormatchansi}.
+
 On terminal emulators that can do at least 256 colors,
 other valid (but unprefixable) color names are:
 @code{pink}, @code{purple}, @code{mauve},
@@ -967,6 +978,10 @@ cursor, set the mark (with a double click), or execute shortcuts.
 @item set multibuffer
 When reading in a file with @kbd{^R}, insert it into a new buffer by default.
 
+@item set nocolormatchansi
+Disable matching RGB colors to ANSI colors for terminals supporting fewer than
+256 colors.
+
 @item set noconvert
 Don't convert files from DOS/Mac format.
 
@@ -1227,12 +1242,20 @@ This overrides the setting of the @code{tabstospaces} option.
 @item color [bold,][italic,]@var{fgcolor},@var{bgcolor} "@var{regex}" @dots{}
 Paint all pieces of text that match the extended regular expression "regex"
 with the given foreground and background colors, at least one of which must
-be specified.  Valid color names are:
+be specified.  Valid names for the foreground and background colors are:
 @code{red}, @code{green}, @code{blue},
 @code{magenta}, @code{yellow}, @code{cyan},
 @code{white}, and @code{black}.
 Each of these eight names may be prefixed with the word
 @code{light} to get a brighter version of that color.
+The word @code{grey} or @code{gray} may be used as
+a synonym for @code{lightblack}.
+
+Foreground and background colors may be given as RGB hexadecimal values
+such as @code{rgb:FF/00/00} (maximum red) or @code{rgb:66/33/99}
+(rebeccapurple).  The color closest to the requested color and supported by the
+terminal will be used.  This behavior can be disabled for terminals supporting
+fewer than 256 colors using @code{set nocolormatchansi}.
 
 On terminal emulators that can do at least 256 colors,
 other valid (but unprefixable) color names are:
diff --git a/doc/nanorc.5 b/doc/nanorc.5
index 5982f54e..7b6dd6c7 100644
--- a/doc/nanorc.5
+++ b/doc/nanorc.5
@@ -214,6 +214,10 @@ selected through dragging by holding down the Shift key.
 .B set multibuffer
 When reading in a file with \fB^R\fR, insert it into a new buffer by default.
 .TP
+.B set nocolormatchansi
+Disable matching RGB colors to ANSI colors for terminals supporting fewer than
+256 colors.
+.TP
 .B set noconvert
 Don't convert files from DOS/Mac format.
 .TP
@@ -358,6 +362,13 @@ Each of these eight names may be prefixed with the word \fBlight\fR
 to get a brighter version of that color.
 The word \fBgrey\fR or \fBgray\fR may be used
 as a synonym for \fBlightblack\fR.
+.sp
+Foreground and background colors may be given as RGB hexadecimal values
+such as \fBrgb:FF/00/00\fR (maximum red) or \fBrgb:66/33/99\fR (rebeccapurple).
+The color closest to the requested color and supported by the terminal will be
+used.  This behavior can be disabled for terminals supporting fewer than 256
+colors using \fBset nocolormatchansi\fR.
+.sp
 On terminal emulators that can do at least 256 colors,
 other valid (but unprefixable) color names are:
 .BR pink ", " purple ", " mauve ", " lagoon ", " mint ", "
@@ -478,17 +489,28 @@ This overrides the setting of the \fBtabstospaces\fR option.
 .BI "color \fR[\fBbold,\fR][\fBitalic,\fR]" fgcolor , bgcolor " """ regex """ " \fR...
 Paint all pieces of text that match the extended regular expression
 \fIregex\fP with the given foreground and background colors, at least
-one of which must be specified.  Valid color names are:
+one of which must be specified.
+Valid names for the foreground and background colors are:
 .BR red ", " green ", " blue ", " magenta ", " yellow ", " cyan ", "
 .BR white ", and " black .
 Each of these eight names may be prefixed with the word \fBlight\fR
 to get a brighter version of that color.
+The word \fBgrey\fR or \fBgray\fR may be used
+as a synonym for \fBlightblack\fR.
+.sp
+Foreground and background colors may be given as RGB hexadecimal values
+such as \fBrgb:FF/00/00\fR (maximum red) or \fBrgb:66/33/99\fR (rebeccapurple).
+The color closest to the requested color and supported by the terminal will be
+used.  This behavior can be disabled for terminals supporting fewer than 256
+colors using \fBset nocolormatchansi\fR.
+.sp
 On terminal emulators that can do at least 256 colors,
 other valid (but unprefixable) color names are:
 .BR pink ", " purple ", " mauve ", " lagoon ", " mint ", "
 .BR lime ", " peach ", " orange ", " latte ", and " normal
 -- where \fBnormal\fR means the default foreground or background color.
-The color pair may be preceded by \fBbold\fR and/or \fBitalic\fR
+Either "\fIfgcolor\fR" or "\fB,\fIbgcolor\fR" may be left out,
+and the pair may be preceded by \fBbold\fR and/or \fBitalic\fR
 (separated by commas) to get a bold and/or slanting typeface,
 if your terminal can do those.
 .sp
diff --git a/src/color.c b/src/color.c
index ad46d67f..10af806f 100644
--- a/src/color.c
+++ b/src/color.c
@@ -29,6 +29,12 @@
 #endif
 #include <string.h>
 
+#if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20170401)
+#define INIT_PAIR(pair, f, b) init_extended_pair((pair), (f), (b))
+#else
+#define INIT_PAIR(pair, f, b) init_pair((pair), (f), (b))
+#endif /* defined(NCURSES_VERSION) && (NCURSES_VERSION_PATCH >= 20170401) */
+
 static bool defaults_allowed = FALSE;
 		/* Whether ncurses accepts -1 to mean "default color". */
 
@@ -51,7 +57,7 @@ void set_interface_colorpairs(void)
 				if (combo->bg == THE_DEFAULT)
 					combo->bg = COLOR_BLACK;
 			}
-			init_pair(index + 1, combo->fg, combo->bg);
+			INIT_PAIR(index + 1, combo->fg, combo->bg);
 			interface_color_pair[index] = COLOR_PAIR(index + 1) | combo->attributes;
 		} else {
 			if (index == FUNCTION_TAG || index == SCROLL_BAR)
@@ -59,12 +65,13 @@ void set_interface_colorpairs(void)
 			else if (index == GUIDE_STRIPE)
 				interface_color_pair[index] = A_REVERSE;
 			else if (index == SPOTLIGHTED) {
-				init_pair(index + 1, COLOR_BLACK, COLOR_YELLOW + (COLORS > 15 ? 8 : 0));
+				INIT_PAIR(index + 1, COLOR_BLACK,
+					COLOR_YELLOW + ((COLORS >= 16 && COLORS <= 256) ? 8 : 0));
 				interface_color_pair[index] = COLOR_PAIR(index + 1);
 			} else if (index == MINI_INFOBAR || index == PROMPT_BAR)
 				interface_color_pair[index] = interface_color_pair[TITLE_BAR];
 			else if (index == ERROR_MESSAGE) {
-				init_pair(index + 1, COLOR_WHITE, COLOR_RED);
+				INIT_PAIR(index + 1, COLOR_WHITE, COLOR_RED);
 				interface_color_pair[index] = COLOR_PAIR(index + 1) | A_BOLD;
 			} else
 				interface_color_pair[index] = hilite_attribute;
@@ -108,7 +115,7 @@ void prepare_palette(void)
 	/* For each unique pair number, tell ncurses the combination of colors. */
 	for (colortype *ink = openfile->syntax->color; ink != NULL; ink = ink->next)
 		if (ink->pairnum > number) {
-			init_pair(ink->pairnum, ink->fg, ink->bg);
+			INIT_PAIR(ink->pairnum, ink->fg, ink->bg);
 			number = ink->pairnum;
 		}
 
diff --git a/src/definitions.h b/src/definitions.h
index 916585e4..be844d91 100644
--- a/src/definitions.h
+++ b/src/definitions.h
@@ -357,7 +357,8 @@ enum {
 	BOOKSTYLE,
 	STATEFLAGS,
 	USE_MAGIC,
-	MINIBAR
+	MINIBAR,
+	NO_COLOR_MATCH_ANSI
 };
 
 /* Structure types. */
@@ -365,9 +366,9 @@ enum {
 typedef struct colortype {
 	short id;
 		/* An ordinal number (if this color combo is for a multiline regex). */
-	short fg;
+	int fg;
 		/* This combo's foreground color. */
-	short bg;
+	int bg;
 		/* This combo's background color. */
 	short pairnum;
 		/* The pair number for this foreground/background color combination. */
diff --git a/src/nano.c b/src/nano.c
index caa48e8f..8537ee32 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -1750,6 +1750,9 @@ int main(int argc, char **argv)
 #endif
 #ifdef HAVE_LIBMAGIC
 		{"magic", 0, NULL, '!'},
+#endif
+#ifdef ENABLE_COLOR
+		{"nocolormatchansi", 0, NULL, '8'},
 #endif
 		{NULL, 0, NULL, 0}
 	};
@@ -1791,7 +1794,7 @@ int main(int argc, char **argv)
 		SET(RESTRICTED);
 
 	while ((optchr = getopt_long(argc, argv, "ABC:DEFGHIJ:KLMNOPQ:RST:UVWX:Y:Z"
-				"abcdef:ghijklmno:pqr:s:tuvwxyz$%_!", long_options, NULL)) != -1) {
+				"abcdef:ghijklmno:pqr:s:tuvwxyz$%_!8", long_options, NULL)) != -1) {
 		switch (optchr) {
 #ifndef NANO_TINY
 			case 'A':
@@ -2038,6 +2041,11 @@ int main(int argc, char **argv)
 			case '!':
 				SET(USE_MAGIC);
 				break;
+#endif
+#ifdef ENABLE_COLOR
+			case '8':
+				SET(NO_COLOR_MATCH_ANSI);
+				break;
 #endif
 			default:
 				printf(_("Type '%s -h' for a list of available options.\n"), argv[0]);
diff --git a/src/rcfile.c b/src/rcfile.c
index f7485999..2b8d523c 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -28,6 +28,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <glob.h>
+#include <stdint.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -109,6 +110,7 @@ static const rcoption rcopts[] = {
 	{"locking", LOCKING},
 	{"matchbrackets", 0},
 	{"minibar", MINIBAR},
+	{"nocolormatchansi", NO_COLOR_MATCH_ANSI},
 	{"noconvert", NO_CONVERT},
 	{"showcursor", SHOW_CURSOR},
 	{"smarthome", SMART_HOME},
@@ -462,6 +464,8 @@ keystruct *strtosc(const char *input)
 		else if (!strcmp(input, "whitespacedisplay"))
 			s->toggle = WHITESPACE_DISPLAY;
 #ifdef ENABLE_COLOR
+		else if (!strcmp(input, "nocolormatchansi"))
+			s->toggle = NO_COLOR_MATCH_ANSI;
 		else if (!strcmp(input, "nosyntax"))
 			s->toggle = NO_SYNTAX;
 #endif
@@ -987,27 +991,164 @@ void parse_includes(char *ptr)
 	free(expanded);
 }
 
-#define COLORCOUNT  20
+static const struct {
+	const char *name;
+	bool is_rgb;
+	unsigned int color;
+} colornames[] = {
+	{ "red", FALSE, COLOR_RED }, { "green", FALSE, COLOR_GREEN },
+	{ "blue", FALSE, COLOR_BLUE }, { "yellow", FALSE, COLOR_YELLOW },
+	{ "cyan", FALSE, COLOR_CYAN }, { "magenta", FALSE, COLOR_MAGENTA },
+	{ "white", FALSE, COLOR_WHITE }, { "black", FALSE, COLOR_BLACK },
+	{ "normal", FALSE, THE_DEFAULT },
+	{ "pink", TRUE, 0xFF5F87 }, { "purple", TRUE, 0xD700AF },
+	{ "mauve", TRUE, 0xAF5FD7 }, { "lagoon", TRUE, 0x00AFD7 },
+	{ "mint", TRUE, 0x00FF87 }, { "lime", TRUE, 0xAFD700 },
+	{ "peach", TRUE, 0xFFAF5F }, { "orange", TRUE, 0xFF8700 },
+	{ "latte", TRUE, 0xAF875F },
+	{ "grey", FALSE, COLOR_BLACK + 8 }, { "gray", FALSE, COLOR_BLACK + 8 }
+};
+
+static int rgblevel_to_6x6x6_side_offset(uint8_t level, uint8_t *out)
+{
+	/* Calculated using midpoints between the color cube RGB steps in linear
+	 * color space. */
+	if (level > 236) {
+		*out = 0xFF;
+		return 5;
+	} else if (level > 196) {
+		*out = 0xD7;
+		return 4;
+	} else if (level > 156) {
+		*out = 0xAF;
+		return 3;
+	} else if (level > 117) {
+		*out = 0x87;
+		return 2;
+	} else if (level > 71) {
+		*out = 0x5F;
+		return 1;
+	} else {
+		*out = 0;
+		return 0;
+	}
+}
+
+static int rgblevel_to_grayramp_index(uint8_t level, uint8_t *out)
+{
+	/* Calculated using midpoints between the grayscale ramp steps in linear
+	 * color space. */
+	int index = ((int)level + 2316) / 10;
+	if (level > 233)
+		index = 255;
+	else if (level < 15)
+		index = 232;
+	*out = 10 * index - 2312;
+	return index;
+}
 
-const char hues[COLORCOUNT][8] = { "red", "green", "blue",
-								   "yellow", "cyan", "magenta",
-								   "white", "black", "normal",
-								   "pink", "purple", "mauve",
-								   "lagoon", "mint", "lime",
-								   "peach", "orange", "latte",
-								   "grey", "gray" };
+static int color_distance_squared(uint8_t r1, uint8_t g1, uint8_t b1,
+								  uint8_t r2, uint8_t g2, uint8_t b2)
+{
+	int rsum = (int)r1 + (int)r2;
+	int rr = (int)r1 - (int)r2; rr *= rr;
+	int gg = (int)g1 - (int)g2; gg *= gg;
+	int bb = (int)b1 - (int)b2; bb *= bb;
+	return 2*rr + 4*gg + 3*bb + (rr - bb) * rsum / 512;
+}
 
-short indices[COLORCOUNT] = { COLOR_RED, COLOR_GREEN, COLOR_BLUE,
-							  COLOR_YELLOW, COLOR_CYAN, COLOR_MAGENTA,
-							  COLOR_WHITE, COLOR_BLACK, THE_DEFAULT,
-							  204, 163, 134, 38, 48, 148, 215, 208, 137,
-							  COLOR_BLACK + 8, COLOR_BLACK + 8 };
+static int get_closest_curses_color(uint8_t r, uint8_t g, uint8_t b)
+{
+	/* Remember the most recent color to avoid recalculation. */
+	static struct {
+		int curses_color;
+		uint8_t r;
+		uint8_t g;
+		uint8_t b;
+	} previous_color = { 0, 0, 0, BAD_COLOR };
+	if (previous_color.curses_color != BAD_COLOR && previous_color.r == r &&
+		previous_color.g == g && previous_color.b == b) {
+		return previous_color.curses_color;
+	}
+
+	int curses_color = THE_DEFAULT;
+	if (COLORS >= 0x1000000) {
+		curses_color = ((unsigned int)r << 16 | (unsigned int)g << 8 |
+						(unsigned int)b);
+		/* For terminfo entries like `xterm-direct`, 0 through 7 are the usual
+		 * ANSI colors. */
+		if (curses_color < 4) {
+			curses_color = 0;
+		} else if (curses_color < 8) {
+			curses_color = 8;
+		}
+	} else if (COLORS == 256) {
+		/* Assume it uses the `xterm-256color` 6x6x6 color cube and grayscale
+		 * ramp. */
+		uint8_t ro, go, bo;
+		int ri = rgblevel_to_6x6x6_side_offset(r, &ro);
+		int gi = rgblevel_to_6x6x6_side_offset(g, &go);
+		int bi = rgblevel_to_6x6x6_side_offset(b, &bo);
+		curses_color = (36 * ri) + (6 * gi) + bi + 16;
+		if (ri == gi && ri == bi) {
+			/* It's a shade of gray, so look for the best one. */
+			int cube_dist2 = color_distance_squared(r, g, b, ro, go, bo);
+			if (cube_dist2 != 0) {
+				uint8_t gray;
+				int grayi = rgblevel_to_grayramp_index(r, &gray);
+				int dist2 = color_distance_squared(r, g, b, gray, gray, gray);
+				if (dist2 < cube_dist2) {
+					curses_color = grayi;
+				}
+			}
+		}
+	} else if (!ISSET(NO_COLOR_MATCH_ANSI) && COLORS < 256 && COLORS >= 8) {
+		/* We can't know the actual colors used by an emulator. These are the
+		 * defaults for XTerm. Note that blue, white, and bright blue do not fit
+		 * the pattern. */
+		static const unsigned int xterm_16colors[] = {
+			0x000000, 0xCD0000, 0x00CD00, 0xCDCD00, 0x0000EE, 0xCD00CD,
+			0x00CDCD, 0xE5E5E5, 0x7F7F7F, 0xFF0000, 0x00FF00, 0xFFFF00,
+			0x5C5CFF, 0xFF00FF, 0x00FFFF, 0xFFFFFF
+		};
+
+		int i, dist2, best_dist2 = -1;
+		for (i = 0; i < ((COLORS >= 16) ? 16 : 8); ++i) {
+			dist2 = color_distance_squared(
+				r, g, b, xterm_16colors[i] >> 16 & 0xFF,
+				xterm_16colors[i] >> 8 & 0xFF, xterm_16colors[i] & 0xFF);
+			if (best_dist2 < 0 || dist2 < best_dist2) {
+				curses_color = i;
+				best_dist2 = dist2;
+			}
+			if (dist2 == 0) {
+				break;
+			}
+		}
+	}
+
+	previous_color.curses_color = curses_color;
+	previous_color.r = r;
+	previous_color.g = g;
+	previous_color.b = b;
+	return curses_color;
+}
 
 /* Return the short value corresponding to the given color name, and set
  * vivid to TRUE for a lighter color, and thick for a heavier typeface. */
-short color_to_short(const char *colorname, bool *vivid, bool *thick)
+int color_to_curses_color(const char *colorname, bool *vivid, bool *thick)
 {
-	if (strncmp(colorname, "bright", 6) == 0 && colorname[6] != '\0') {
+	if (strncmp(colorname, "rgb:", 4) == 0 && colorname[4] != '\0') {
+		unsigned int r, g, b;
+		*vivid = FALSE;
+		*thick = FALSE;
+		if (sscanf(colorname, "rgb:%2X/%2X/%2X", &r, &g, &b) == 3) {
+			return get_closest_curses_color(r, g, b);
+		}
+
+		jot_error(N_("Color \"%s\" not understood"), colorname);
+		return BAD_COLOR;
+	} else if (strncmp(colorname, "bright", 6) == 0 && colorname[6] != '\0') {
 		/* Prefix "bright" is deprecated; remove in 2024. */
 		*vivid = TRUE;
 		*thick = TRUE;
@@ -1021,15 +1162,23 @@ short color_to_short(const char *colorname, bool *vivid, bool *thick)
 		*thick = FALSE;
 	}
 
-	for (int index = 0; index < COLORCOUNT; index++)
-		if (strcmp(colorname, hues[index]) == 0) {
-			if (index > 7 && *vivid) {
+	for (int index = 0; index < sizeof(colornames) / sizeof(colornames[0]);
+		 index++)
+		if (strcmp(colorname, colornames[index].name) == 0) {
+			if ((colornames[index].is_rgb || colornames[index].color >= 8) &&
+				*vivid) {
 				jot_error(N_("Color '%s' takes no prefix"), colorname);
 				return BAD_COLOR;
-			} else if (index > 8 && COLORS < 255)
+			} else if (colornames[index].is_rgb) {
+				unsigned int rgbcolor = colornames[index].color;
+				return get_closest_curses_color(rgbcolor >> 16 & 0xFF,
+												rgbcolor >> 8 & 0xFF,
+												rgbcolor & 0xFF);
+			} else if (colornames[index].color < COLORS) {
+				return colornames[index].color;
+			} else {
 				return THE_DEFAULT;
-			else
-				return indices[index];
+			}
 		}
 
 	jot_error(N_("Color \"%s\" not understood"), colorname);
@@ -1038,7 +1187,7 @@ short color_to_short(const char *colorname, bool *vivid, bool *thick)
 
 /* Parse the color name (or pair of color names) in the given string.
  * Return FALSE when any color name is invalid; otherwise return TRUE. */
-bool parse_combination(char *combostr, short *fg, short *bg, int *attributes)
+bool parse_combination(char *combostr, int *fg, int *bg, int *attributes)
 {
 	bool vivid, thick;
 	char *comma;
@@ -1071,21 +1220,21 @@ bool parse_combination(char *combostr, short *fg, short *bg, int *attributes)
 		*comma = '\0';
 
 	if (!comma || comma > combostr) {
-		*fg = color_to_short(combostr, &vivid, &thick);
+		*fg = color_to_curses_color(combostr, &vivid, &thick);
 		if (*fg == BAD_COLOR)
 			return FALSE;
-		if (vivid && !thick && COLORS > 8)
+		if (vivid && !thick && (COLORS >= 16 && COLORS <= 256))
 			*fg += 8;
-		else if (vivid)
+		else if (vivid && COLORS < 16)
 			*attributes |= A_BOLD;
 	} else
 		*fg = THE_DEFAULT;
 
 	if (comma) {
-		*bg = color_to_short(comma + 1, &vivid, &thick);
+		*bg = color_to_curses_color(comma + 1, &vivid, &thick);
 		if (*bg == BAD_COLOR)
 			return FALSE;
-		if (vivid && COLORS > 8)
+		if (vivid && (COLORS >= 16 && COLORS <= 256))
 			*bg += 8;
 	} else
 		*bg = THE_DEFAULT;
@@ -1099,7 +1248,7 @@ bool parse_combination(char *combostr, short *fg, short *bg, int *attributes)
 void parse_rule(char *ptr, int rex_flags)
 {
 	char *names, *regexstring;
-	short fg, bg;
+	int fg, bg;
 	int attributes;
 
 	if (*ptr == '\0') {
diff --git a/syntax/nanorc.nanorc b/syntax/nanorc.nanorc
index 308bd7c4..37a46d86 100644
--- a/syntax/nanorc.nanorc
+++ b/syntax/nanorc.nanorc
@@ -7,10 +7,10 @@ comment "#"
 color brightred ".*"
 
 # Color names
-color yellow "^[[:blank:]]*(i?color|set[[:blank:]]+((error|function|key|mini|number|prompt|scroller|selected|spotlight|status|stripe|title)color))[[:blank:]]+(bold,)?(italic,)?(((bright|light)?(white|black|red|blue|green|yellow|magenta|cyan))|normal|pink|purple|mauve|lagoon|mint|lime|peach|orange|latte|grey|gray)?(,(((light)?(white|black|red|blue|green|yellow|magenta|cyan))|normal|pink|purple|mauve|lagoon|mint|lime|peach|orange|latte|grey|gray))?\>"
+color yellow "^[[:blank:]]*(i?color|set[[:blank:]]+((error|function|key|mini|number|prompt|scroller|selected|spotlight|status|stripe|title)color))[[:blank:]]+(bold,)?(italic,)?(((bright|light)?(white|black|red|blue|green|yellow|magenta|cyan))|normal|pink|purple|mauve|lagoon|mint|lime|peach|orange|latte|grey|gray|rgb:[0-9A-Fa-f]{2}/[0-9A-Fa-f]{2}/[0-9A-Fa-f]{2})?(,(((light)?(white|black|red|blue|green|yellow|magenta|cyan))|normal|pink|purple|mauve|lagoon|mint|lime|peach|orange|latte|grey|gray|rgb:[0-9A-Fa-f]{2}/[0-9A-Fa-f]{2}/[0-9A-Fa-f]{2}))?\>"
 
 # Keywords
-color brightgreen "^[[:blank:]]*(set|unset)[[:blank:]]+(afterends|allow_insecure_backup|atblanks|autoindent|backup|boldtext|bookstyle|breaklonglines|casesensitive|constantshow|cutfromcursor|emptyline|historylog|indicator|jumpyscrolling|linenumbers|locking|magic|minibar|mouse|multibuffer|noconvert|nohelp|nonewlines|positionlog|preserve|quickblank|rawsequences|rebinddelete|regexp|saveonexit|showcursor|smarthome|softwrap|stateflags|tabstospaces|trimblanks|unix|wordbounds|zap)\>"
+color brightgreen "^[[:blank:]]*(set|unset)[[:blank:]]+(afterends|allow_insecure_backup|atblanks|autoindent|backup|boldtext|bookstyle|breaklonglines|casesensitive|constantshow|cutfromcursor|emptyline|historylog|indicator|jumpyscrolling|linenumbers|locking|magic|minibar|mouse|multibuffer|nocolormatchansi|noconvert|nohelp|nonewlines|positionlog|preserve|quickblank|rawsequences|rebinddelete|regexp|saveonexit|showcursor|smarthome|softwrap|stateflags|tabstospaces|trimblanks|unix|wordbounds|zap)\>"
 color brightgreen "^[[:blank:]]*set[[:blank:]]+(backupdir|brackets|errorcolor|functioncolor|keycolor|matchbrackets|minicolor|numbercolor|operatingdir|promptcolor|punct|quotestr|scrollercolor|selectedcolor|speller|spotlightcolor|statuscolor|stripecolor|titlecolor|whitespace|wordchars)[[:blank:]]+"
 color brightgreen "^[[:blank:]]*set[[:blank:]]+(fill[[:blank:]]+-?[[:digit:]]+|(guidestripe|tabsize)[[:blank:]]+[1-9][0-9]*)\>"
 color brightgreen "^[[:blank:]]*bind[[:blank:]]+((\^([A-Za-z]|[]/@\^_`-]|Space)|([Ss][Hh]-)?[Mm]-[A-Za-z]|[Mm]-([][!"#$%&'()*+,./0-9:;<=>?@\^_`{|}~-]|Space))|F([1-9]|1[0-9]|2[0-4])|Ins|Del)[[:blank:]]+([a-z]+|".*")[[:blank:]]+(main|help|search|replace(with)?|yesno|gotoline|writeout|insert|browser|whereisfile|gotodir|execute|spell|linter|all)([[:blank:]]+#|[[:blank:]]*$)"
-- 
2.33.1.1089.g2158813163f-goog

