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@xxxxxxxxx> Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> --- 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 827006b..459121d 100644 --- a/Makefile +++ b/Makefile @@ -703,6 +703,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 f5beab1..3eaa5bd 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..e743315 --- /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; + +/* + * 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; +} -- 2.3.0.rc1.137.g477eb31 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html