git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 01/17] ls_colors.c: add $LS_COLORS parsing code

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Mar 26, 2014, 13:48 UTC
Message-ID
<1395841697-11742-2-git-send-email-pclouds@gmail.com>
In-Reply-To
<1395841697-11742-1-git-send-email-pclouds@gmail.com>

Reusing color settings from $LS_COLORS could give a native look and feel on file coloring.

This code is basically from coreutils.git [1], rewritten to fit Git.

As this is from GNU ls, the environment variable CLICOLOR is not tested. It is to be decided later whether we should ignore $LS_COLORS if $CLICOLOR is not set on Mac or FreeBSD.

[1] commit 7326d1f1a67edf21947ae98194f98c38b6e9e527 file
    src/ls.c. This is the last GPL-2 commit before coreutils turns to
    GPL-3.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Makefile          |   1 +
 color.h           |   8 ++
 ls_colors.c (new) | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 407 insertions(+)
 create mode 100644 ls_colors.c
diff --git a/Makefile b/Makefile
index f818eec..f6a6e14 100644
--- a/Makefile
+++ b/Makefile
@@ -819,6 +819,7 @@ LIB_OBJS += list-objects.o
 LIB_OBJS += ll-merge.o
 LIB_OBJS += lockfile.o
 LIB_OBJS += log-tree.o
+LIB_OBJS += ls_colors.o
 LIB_OBJS += mailmap.o
 LIB_OBJS += match-trees.o
 LIB_OBJS += merge.o
diff --git a/color.h b/color.h
index 9a8495b..640fc48 100644
--- a/color.h
+++ b/color.h
@@ -45,6 +45,12 @@ struct strbuf;
 #define GIT_COLOR_BG_MAGENTA	"\033[45m"
 #define GIT_COLOR_BG_CYAN	"\033[46m"
 
+#define GIT_COLOR_WHITE_ON_RED    "\033[37;41m"
+#define GIT_COLOR_WHITE_ON_BLUE   "\033[37;44m"
+#define GIT_COLOR_BLACK_ON_YELLOW "\033[30;43m"
+#define GIT_COLOR_BLUE_ON_GREEN   "\033[34;42m"
+#define GIT_COLOR_BLACK_ON_GREEN  "\033[30;42m"
+
 /* A special value meaning "no color selected" */
 #define GIT_COLOR_NIL "NIL"
 
@@ -87,4 +93,6 @@ void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb);
 
 int color_is_nil(const char *color);
 
+void parse_ls_color(void);
+
 #endif /* COLOR_H */
diff --git a/ls_colors.c b/ls_colors.c
new file mode 100644
index 0000000..eb944f4
--- /dev/null
+++ b/ls_colors.c
@@ -0,0 +1,398 @@
+#include "cache.h"
+#include "color.h"
+
+enum color_ls {
+	LS_LC,			/* left, unused */
+	LS_RC,			/* right, unused */
+	LS_EC,			/* end color, unused */
+	LS_RS,			/* reset */
+	LS_NO,			/* normal */
+	LS_FL,			/* file, default */
+	LS_DI,			/* directory */
+	LS_LN,			/* symlink */
+
+	LS_PI,			/* pipe */
+	LS_SO,			/* socket */
+	LS_BD,			/* block device */
+	LS_CD,			/* char device */
+	LS_MI,			/* missing file */
+	LS_OR,			/* orphaned symlink */
+	LS_EX,			/* executable */
+	LS_DO,			/* Solaris door */
+
+	LS_SU,			/* setuid */
+	LS_SG,			/* setgid */
+	LS_ST,			/* sticky */
+	LS_OW,			/* other-writable */
+	LS_TW,			/* ow with sticky */
+	LS_CA,			/* cap */
+	LS_MH,			/* multi hardlink */
+	LS_CL,			/* clear end of line */
+
+	MAX_LS
+};
+
+static char ls_colors[MAX_LS][COLOR_MAXLEN] = {
+	"",
+	"",
+	"",
+	GIT_COLOR_RESET,
+	GIT_COLOR_NORMAL,
+	GIT_COLOR_NORMAL,
+	GIT_COLOR_BOLD_BLUE,
+	GIT_COLOR_BOLD_CYAN,
+
+	GIT_COLOR_YELLOW,
+	GIT_COLOR_BOLD_MAGENTA,
+	GIT_COLOR_BOLD_YELLOW,
+	GIT_COLOR_BOLD_YELLOW,
+	GIT_COLOR_NORMAL,
+	GIT_COLOR_NORMAL,
+	GIT_COLOR_BOLD_GREEN,
+	GIT_COLOR_BOLD_MAGENTA,
+
+	GIT_COLOR_WHITE_ON_RED,
+	GIT_COLOR_BLACK_ON_YELLOW,
+	GIT_COLOR_WHITE_ON_BLUE,
+	GIT_COLOR_BLUE_ON_GREEN,
+	GIT_COLOR_BLACK_ON_GREEN,
+	"",
+	"",
+	""
+};
+
+static const char *const indicator_name[] = {
+	"lc", "rc", "ec", "rs", "no", "fi", "di", "ln",
+	"pi", "so", "bd", "cd", "mi", "or", "ex", "do",
+	"su", "sg", "st", "ow", "tw", "ca", "mh", "cl",
+	NULL
+};
+
+struct bin_str {
+	size_t len;			/* Number of bytes */
+	const char *string;		/* Pointer to the same */
+};
+
+struct color_ext_type {
+	struct bin_str ext;		/* The extension we're looking for */
+	struct bin_str seq;		/* The sequence to output when we do */
+	struct color_ext_type *next;	/* Next in list */
+};
+
+static struct color_ext_type *color_ext_list = NULL;
+
+/*
+ * When true, in a color listing, color each symlink name according to the
+ * type of file it points to.  Otherwise, color them according to the `ln'
+ * directive in LS_COLORS.  Dangling (orphan) symlinks are treated specially,
+ * regardless.  This is set when `ln=target' appears in LS_COLORS.
+ */
+static int color_symlink_as_referent;
+
+/*
+ * Parse a string as part of the LS_COLORS variable; this may involve
+ * decoding all kinds of escape characters.  If equals_end is set an
+ * unescaped equal sign ends the string, otherwise only a : or \0
+ * does.  Set *OUTPUT_COUNT to the number of bytes output.  Return
+ * true if successful.
+ *
+ * The resulting string is *not* null-terminated, but may contain
+ * embedded nulls.
+ *
+ * Note that both dest and src are char **; on return they point to
+ * the first free byte after the array and the character that ended
+ * the input string, respectively.
+ */
+static int get_funky_string(char **dest, const char **src, int equals_end,
+			    size_t *output_count)
+{
+	char num;			/* For numerical codes */
+	size_t count;			/* Something to count with */
+	enum {
+		ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX,
+		ST_CARET, ST_END, ST_ERROR
+	} state;
+	const char *p;
+	char *q;
+
+	p = *src;			/* We don't want to double-indirect */
+	q = *dest;			/* the whole darn time.  */
+
+	count = 0;			/* No characters counted in yet.  */
+	num = 0;
+
+	state = ST_GND;		/* Start in ground state.  */
+	while (state < ST_END) {
+		switch (state) {
+		case ST_GND:		/* Ground state (no escapes) */
+			switch (*p) {
+			case ':':
+			case '\0':
+				state = ST_END;	/* End of string */
+				break;
+			case '\\':
+				state = ST_BACKSLASH; /* Backslash scape sequence */
+				++p;
+				break;
+			case '^':
+				state = ST_CARET; /* Caret escape */
+				++p;
+				break;
+			case '=':
+				if (equals_end) {
+					state = ST_END; /* End */
+					break;
+				}
+				/* else fall through */
+			default:
+				*(q++) = *(p++);
+				++count;
+				break;
+			}
+			break;
+
+		case ST_BACKSLASH:	/* Backslash escaped character */
+			switch (*p) {
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+				state = ST_OCTAL;	/* Octal sequence */
+				num = *p - '0';
+				break;
+			case 'x':
+			case 'X':
+				state = ST_HEX;	/* Hex sequence */
+				num = 0;
+				break;
+			case 'a':		/* Bell */
+				num = '\a';
+				break;
+			case 'b':		/* Backspace */
+				num = '\b';
+				break;
+			case 'e':		/* Escape */
+				num = 27;
+				break;
+			case 'f':		/* Form feed */
+				num = '\f';
+				break;
+			case 'n':		/* Newline */
+				num = '\n';
+				break;
+			case 'r':		/* Carriage return */
+				num = '\r';
+				break;
+			case 't':		/* Tab */
+				num = '\t';
+				break;
+			case 'v':		/* Vtab */
+				num = '\v';
+				break;
+			case '?':		/* Delete */
+				num = 127;
+				break;
+			case '_':		/* Space */
+				num = ' ';
+				break;
+			case '\0':		/* End of string */
+				state = ST_ERROR;	/* Error! */
+				break;
+			default:		/* Escaped character like \ ^ : = */
+				num = *p;
+				break;
+			}
+			if (state == ST_BACKSLASH) {
+				*(q++) = num;
+				++count;
+				state = ST_GND;
+			}
+			++p;
+			break;
+
+		case ST_OCTAL:		/* Octal sequence */
+			if (*p < '0' || *p > '7') {
+				*(q++) = num;
+				++count;
+				state = ST_GND;
+			} else
+				num = (num << 3) + (*(p++) - '0');
+			break;
+
+		case ST_HEX:		/* Hex sequence */
+			switch (*p) {
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				num = (num << 4) + (*(p++) - '0');
+				break;
+			case 'a':
+			case 'b':
+			case 'c':
+			case 'd':
+			case 'e':
+			case 'f':
+				num = (num << 4) + (*(p++) - 'a') + 10;
+				break;
+			case 'A':
+			case 'B':
+			case 'C':
+			case 'D':
+			case 'E':
+			case 'F':
+				num = (num << 4) + (*(p++) - 'A') + 10;
+				break;
+			default:
+				*(q++) = num;
+				++count;
+				state = ST_GND;
+				break;
+			}
+			break;
+
+		case ST_CARET:		/* Caret escape */
+			state = ST_GND;	/* Should be the next state... */
+			if (*p >= '@' && *p <= '~') {
+				*(q++) = *(p++) & 037;
+				++count;
+			} else if (*p == '?') {
+				*(q++) = 127;
+				++count;
+			} else
+				state = ST_ERROR;
+			break;
+
+		default:
+			abort();
+		}
+	}
+
+	*dest = q;
+	*src = p;
+	*output_count = count;
+
+	return state != ST_ERROR;
+}
+
+void parse_ls_color(void)
+{
+	const char *p;			/* Pointer to character being parsed */
+	char *buf;			/* color_buf buffer pointer */
+	int state;			/* State of parser */
+	int ind_no;			/* Indicator number */
+	char label[3];			/* Indicator label */
+	struct color_ext_type *ext;	/* Extension we are working on */
+	static char *color_buf;
+	char *start;
+	size_t len;
+
+	if ((p = getenv("LS_COLORS")) == NULL || *p == '\0')
+		return;
+
+	ext = NULL;
+	strcpy (label, "??");
+
+	/*
+	 * This is an overly conservative estimate, but any possible
+	 * LS_COLORS string will *not* generate a color_buf longer
+	 * than itself, so it is a safe way of allocating a buffer in
+	 * advance.
+	 */
+	buf = color_buf = xstrdup(p);
+
+	state = 1;
+	while (state > 0) {
+		switch (state) {
+		case 1:		/* First label character */
+			switch (*p) {
+			case ':':
+				++p;
+				break;
+
+			case '*':
+				/*
+				 * Allocate new extension block and add to head of
+				 * linked list (this way a later definition will
+				 * override an earlier one, which can be useful for
+				 * having terminal-specific defs override global).
+				 */
+
+				ext = xmalloc(sizeof *ext);
+				ext->next = color_ext_list;
+				color_ext_list = ext;
+
+				++p;
+				ext->ext.string = buf;
+
+				state = (get_funky_string(&buf, &p, 1, &ext->ext.len)
+					 ? 4 : -1);
+				break;
+
+			case '\0':
+				state = 0;	/* Done! */
+				break;
+
+			default:	/* Assume it is file type label */
+				label[0] = *(p++);
+				state = 2;
+				break;
+			}
+			break;
+
+		case 2:		/* Second label character */
+			if (*p) {
+				label[1] = *(p++);
+				state = 3;
+			} else
+				state = -1;	/* Error */
+			break;
+
+		case 3:		/* Equal sign after indicator label */
+			state = -1;	/* Assume failure...  */
+			if (*(p++) != '=')
+				break;
+			for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) {
+				if (!strcmp(label, indicator_name[ind_no])) {
+					start = buf;
+					if (get_funky_string(&buf, &p, 0, &len))
+						state = 1;
+					else
+						state = -1;
+					break;
+				}
+			}
+			if (state == -1)
+				error(_("unrecognized prefix: %s"), label);
+			else if (ind_no == LS_LN && len == 6 &&
+				 starts_with(start, "target"))
+				color_symlink_as_referent = 1;
+			else
+				sprintf(ls_colors[ind_no], "\033[%.*sm",
+				       (int)len, start);
+			break;
+
+		case 4:		/* Equal sign after *.ext */
+			if (*(p++) == '=') {
+				ext->seq.string = buf;
+				state = (get_funky_string(&buf, &p, 0, &ext->seq.len)
+					 ? 1 : -1);
+			} else
+				state = -1;
+			break;
+		}
+	}
+
+	if (!strcmp(ls_colors[LS_LN], "target"))
+		color_symlink_as_referent = 1;
+}
-- 
1.9.1.345.ga1a145c
Previous: Nguyễn Thái Ngọc DuyNext: Nguyễn Thái Ngọc Duy
Message 26 of 79 in “git-ls”
  1. 0/8 git-lsNguyễn Thái Ngọc Duy, Mar 20, 2014
  2. 1/8 Import $LS_COLORS parsing code from coreutilsNguyễn Thái Ngọc Duy, Mar 20, 2014
  3. David TranMar 20, 2014
  4. Duy NguyenMar 21, 2014
  5. David TranMar 21, 2014
  6. 2/8 ls_colors.c: a bit of document on print_color_indicator inputNguyễn Thái Ngọc Duy, Mar 20, 2014
  7. 3/8 ls_colors.c: enable coloring on u+x filesNguyễn Thái Ngọc Duy, Mar 20, 2014
  8. Matthieu MoyMar 20, 2014
  9. Duy NguyenMar 20, 2014
  10. Junio C HamanoMar 20, 2014
  11. Duy NguyenMar 21, 2014
  12. 4/8 ls_colors.c: new color descriptorsNguyễn Thái Ngọc Duy, Mar 20, 2014
  13. 5/8 ls-files: add --color to highlight based on $LS_COLORSNguyễn Thái Ngọc Duy, Mar 20, 2014
  14. 6/8 ls-files: add --columnNguyễn Thái Ngọc Duy, Mar 20, 2014
  15. Matthieu MoyMar 25, 2014
  16. 7/8 ls-files: support --max-depthNguyễn Thái Ngọc Duy, Mar 20, 2014
  17. Matthieu MoyMar 25, 2014
  18. Duy NguyenMar 25, 2014
  19. Duy NguyenMar 27, 2014
  20. Matthieu MoyMar 28, 2014
  21. Duy NguyenMar 28, 2014
  22. Duy NguyenMar 28, 2014
  23. 8/8 Add git-ls, a user friendly version of ls-files and moreNguyễn Thái Ngọc Duy, Mar 20, 2014
  24. Matthieu MoyMar 20, 2014
  25. 00/17 git-lsNguyễn Thái Ngọc Duy, Mar 26, 2014
  26. 01/17 ls_colors.c: add $LS_COLORS parsing codeNguyễn Thái Ngọc Duy, Mar 26, 2014
  27. 02/17 ls_colors.c: parse color.ls.* from config fileNguyễn Thái Ngọc Duy, Mar 26, 2014
  28. 03/17 ls_colors.c: add function to color a file nameNguyễn Thái Ngọc Duy, Mar 26, 2014
  29. Eric SunshineMar 26, 2014
  30. 04/17 ls_colors.c: highlight submodules like directoriesNguyễn Thái Ngọc Duy, Mar 26, 2014
  31. 05/17 ls-files: buffer full item in strbuf before printingNguyễn Thái Ngọc Duy, Mar 26, 2014
  32. Eric SunshineMar 26, 2014
  33. Duy NguyenMar 26, 2014
  34. Eric SunshineMar 27, 2014
  35. 06/17 ls-files: add --color to highlight file namesNguyễn Thái Ngọc Duy, Mar 26, 2014
  36. Eric SunshineMar 26, 2014
  37. Duy NguyenMar 26, 2014
  38. Eric SunshineMar 28, 2014
  39. 07/17 ls-files: add --columnNguyễn Thái Ngọc Duy, Mar 26, 2014
  40. Eric SunshineMar 26, 2014
  41. 08/17 ls-files: support --max-depthNguyễn Thái Ngọc Duy, Mar 26, 2014
  42. Eric SunshineMar 26, 2014
  43. 09/17 ls-files: split main ls-files logic into ls_files() functionNguyễn Thái Ngọc Duy, Mar 26, 2014
  44. 10/17 Add git-ls, a user friendly version of ls-files and moreNguyễn Thái Ngọc Duy, Mar 26, 2014
  45. Eric SunshineMar 26, 2014
  46. 11/17 ls: -u does not imply showing stagesNguyễn Thái Ngọc Duy, Mar 26, 2014
  47. 12/17 ls: add -R/--recursive short for --max-depth=-1Nguyễn Thái Ngọc Duy, Mar 26, 2014
  48. 13/17 ls: add -1 short for --no-column in the spirit of GNU lsNguyễn Thái Ngọc Duy, Mar 26, 2014
  49. Eric SunshineMar 28, 2014
  50. 14/17 ls: add -t backNguyễn Thái Ngọc Duy, Mar 26, 2014
  51. 15/17 ls: sort output and remove duplicatesNguyễn Thái Ngọc Duy, Mar 26, 2014
  52. 16/17 ls: do not show duplicate cached entriesNguyễn Thái Ngọc Duy, Mar 26, 2014
  53. Eric SunshineMar 28, 2014
  54. ls-files: do not trust stat info if lstat() failsNguyễn Thái Ngọc Duy, Mar 28, 2014
  55. Junio C HamanoApr 2, 2014
  56. Duy NguyenApr 3, 2014
  57. Junio C HamanoApr 3, 2014
  58. Duy NguyenApr 5, 2014
  59. Junio C HamanoApr 7, 2014
  60. 17/17 ls: show directories as well as filesNguyễn Thái Ngọc Duy, Mar 26, 2014
  61. 00/18 git-lsNguyễn Thái Ngọc Duy, Mar 30, 2014
  62. 01/18 ls_colors.c: add $LS_COLORS parsing codeNguyễn Thái Ngọc Duy, Mar 30, 2014
  63. 02/18 ls_colors.c: parse color.ls.* from config fileNguyễn Thái Ngọc Duy, Mar 30, 2014
  64. 03/18 ls_colors.c: add a function to color a file nameNguyễn Thái Ngọc Duy, Mar 30, 2014
  65. 04/18 ls_colors.c: highlight submodules like directoriesNguyễn Thái Ngọc Duy, Mar 30, 2014
  66. 05/18 ls-files: buffer full item in strbuf before printingNguyễn Thái Ngọc Duy, Mar 30, 2014
  67. 06/18 ls-files: add --color to highlight file namesNguyễn Thái Ngọc Duy, Mar 30, 2014
  68. 07/18 ls-files: add --columnNguyễn Thái Ngọc Duy, Mar 30, 2014
  69. 08/18 ls-files: support --max-depthNguyễn Thái Ngọc Duy, Mar 30, 2014
  70. 09/18 Add git-list-files, a user friendly version of ls-files and moreNguyễn Thái Ngọc Duy, Mar 30, 2014
  71. 10/18 list-files: -u does not imply showing stagesNguyễn Thái Ngọc Duy, Mar 30, 2014
  72. 11/18 list-files: add -R/--recursive short for --max-depth=-1Nguyễn Thái Ngọc Duy, Mar 30, 2014
  73. 12/18 list-files: add -1 short for --no-columnNguyễn Thái Ngọc Duy, Mar 30, 2014
  74. 13/18 list-files: add -t backNguyễn Thái Ngọc Duy, Mar 30, 2014
  75. 14/18 list-files: sort output and remove duplicatesNguyễn Thái Ngọc Duy, Mar 30, 2014
  76. 15/18 list-files: do not show duplicate cached entriesNguyễn Thái Ngọc Duy, Mar 30, 2014
  77. 16/18 list-files: show directories as well as filesNguyễn Thái Ngọc Duy, Mar 30, 2014
  78. 17/18 list-files: add -F/--classifyNguyễn Thái Ngọc Duy, Mar 30, 2014
  79. 18/18 list-files -F: show submodules with the new indicator '&'Nguyễn Thái Ngọc Duy, Mar 30, 2014

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.