This could could help highlight files in ls-files or status output, or even diff --name-only (but that's questionable). This code is from coreutils.git commit 7326d1f1a67edf21947ae98194f98c38b6e9e527 file src/ls.c. This is the last GPL-2 commit before coreutils turns to GPL-3. The code is reformatted to fit Git coding style, which is more than just adding and removing spaces. For example, "bool" is replaced with "int", or true/false replaced with 1/0, or the use of git's error() instead of error(3). There are also two "#if 0" to make it build with git-compat-util.h. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx> --- Makefile | 1 + ls_colors.c (new) | 477 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ls_colors.h (new) | 20 +++ 3 files changed, 498 insertions(+) create mode 100644 ls_colors.c create mode 100644 ls_colors.h 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/ls_colors.c b/ls_colors.c new file mode 100644 index 0000000..6385446 --- /dev/null +++ b/ls_colors.c @@ -0,0 +1,477 @@ +#include "git-compat-util.h" +#include "gettext.h" +#include "ls_colors.h" + +#define STREQ(a, b) (strcmp(a, b) == 0) + +enum indicator_no { + C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK, + C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR, C_SETUID, C_SETGID, + C_STICKY, C_OTHER_WRITABLE, C_STICKY_OTHER_WRITABLE +}; + +#define FILETYPE_INDICATORS \ + { \ + C_ORPHAN, C_FIFO, C_CHR, C_DIR, C_BLK, C_FILE, \ + C_LINK, C_SOCK, C_FILE, C_DIR \ + } + +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 const char *const indicator_name[]= { + "lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so", + "bd", "cd", "mi", "or", "ex", "do", "su", "sg", "st", + "ow", "tw", NULL +}; + +#define LEN_STR_PAIR(s) sizeof(s) - 1, s +static struct bin_str color_indicator[] = { + { LEN_STR_PAIR("\033[") }, /* lc: Left of color sequence */ + { LEN_STR_PAIR("m") }, /* rc: Right of color sequence */ + { 0, NULL }, /* ec: End color (replaces lc+no+rc) */ + { LEN_STR_PAIR("0") }, /* no: Normal */ + { LEN_STR_PAIR("0") }, /* fi: File: default */ + { LEN_STR_PAIR("01;34") }, /* di: Directory: bright blue */ + { LEN_STR_PAIR("01;36") }, /* ln: Symlink: bright cyan */ + { LEN_STR_PAIR("33") }, /* pi: Pipe: yellow/brown */ + { LEN_STR_PAIR("01;35") }, /* so: Socket: bright magenta */ + { LEN_STR_PAIR("01;33") }, /* bd: Block device: bright yellow */ + { LEN_STR_PAIR("01;33") }, /* cd: Char device: bright yellow */ + { 0, NULL }, /* mi: Missing file: undefined */ + { 0, NULL }, /* or: Orphaned symlink: undefined */ + { LEN_STR_PAIR("01;32") }, /* ex: Executable: bright green */ + { LEN_STR_PAIR("01;35") }, /* do: Door: bright magenta */ + { LEN_STR_PAIR("37;41") }, /* su: setuid: white on red */ + { LEN_STR_PAIR("30;43") }, /* sg: setgid: black on yellow */ + { LEN_STR_PAIR("37;44") }, /* st: sticky: black on blue */ + { LEN_STR_PAIR("34;42") }, /* ow: other-writable: blue on green */ + { LEN_STR_PAIR("30;42") }, /* tw: ow w/ sticky: black on green */ +}; + +static struct color_ext_type *color_ext_list = NULL; +/* Buffer for color sequences */ +static char *color_buf; + +/* + * True means use colors to mark types. Also define the different + * colors as well as the stuff for the LS_COLORS environment variable. + * The LS_COLORS variable is now in a termcap-like format. + */ +static int print_with_color; + +/* + * 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 */ + + 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++) == '=') { /* It *should* be... */ + for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) { + if (STREQ (label, indicator_name[ind_no])) { + color_indicator[ind_no].string = buf; + state = (get_funky_string(&buf, &p, 0, + &color_indicator[ind_no].len) + ? 1 : -1); + break; + } + } + if (state == -1) + error(_("unrecognized prefix: %s"), label); + } + 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 (state < 0) { + struct color_ext_type *e; + struct color_ext_type *e2; + + error(_("unparsable value for LS_COLORS environment variable")); + free(color_buf); + for (e = color_ext_list; e != NULL; /* empty */) { + e2 = e; + e = e->next; + free(e2); + } + print_with_color = 0; + } + + if (color_indicator[C_LINK].len == 6 && + !strncmp(color_indicator[C_LINK].string, "target", 6)) + color_symlink_as_referent = 1; +} + +/* Output a color indicator (which may contain nulls). */ +static void put_indicator(const struct bin_str *ind) +{ + size_t i; + const char *p; + + p = ind->string; + + for (i = ind->len; i != 0; --i) + putchar(*(p++)); +} + +void print_color_indicator(const char *name, mode_t mode, int linkok, + int stat_ok, enum filetype filetype) +{ + int type; + struct color_ext_type *ext; /* Color extension */ + size_t len; /* Length of name */ + + /* Is this a nonexistent file? If so, linkok == -1. */ + + if (linkok == -1 && color_indicator[C_MISSING].string != NULL) + type = C_MISSING; + else if (!stat_ok) { + static enum indicator_no filetype_indicator[] = FILETYPE_INDICATORS; + type = filetype_indicator[filetype]; + } else { + if (S_ISREG(mode)) { + type = C_FILE; + if ((mode & S_ISUID) != 0) + type = C_SETUID; + else if ((mode & S_ISGID) != 0) + type = C_SETGID; +#if 0 + else if ((mode & S_IXUGO) != 0) + type = C_EXEC; +#endif + } else if (S_ISDIR(mode)) { + if ((mode & S_ISVTX) && (mode & S_IWOTH)) + type = C_STICKY_OTHER_WRITABLE; + else if ((mode & S_IWOTH) != 0) + type = C_OTHER_WRITABLE; + else if ((mode & S_ISVTX) != 0) + type = C_STICKY; + else + type = C_DIR; + } else if (S_ISLNK(mode)) + type = ((!linkok && color_indicator[C_ORPHAN].string) + ? C_ORPHAN : C_LINK); + else if (S_ISFIFO(mode)) + type = C_FIFO; + else if (S_ISSOCK(mode)) + type = C_SOCK; + else if (S_ISBLK(mode)) + type = C_BLK; + else if (S_ISCHR(mode)) + type = C_CHR; +#if 0 + else if (S_ISDOOR(mode)) + type = C_DOOR; +#endif + else { + /* Classify a file of some other type as C_ORPHAN. */ + type = C_ORPHAN; + } + } + + /* Check the file's suffix only if still classified as C_FILE. */ + ext = NULL; + if (type == C_FILE) { + /* Test if NAME has a recognized suffix. */ + + len = strlen(name); + name += len; /* Pointer to final \0. */ + for (ext = color_ext_list; ext != NULL; ext = ext->next) { + if (ext->ext.len <= len + && strncmp(name - ext->ext.len, ext->ext.string, + ext->ext.len) == 0) + break; + } + } + + put_indicator(&color_indicator[C_LEFT]); + put_indicator(ext ? &(ext->seq) : &color_indicator[type]); + put_indicator(&color_indicator[C_RIGHT]); +} diff --git a/ls_colors.h b/ls_colors.h new file mode 100644 index 0000000..3201be6 --- /dev/null +++ b/ls_colors.h @@ -0,0 +1,20 @@ +#ifndef LS_COLORS_H +#define LS_COLORS_H + +enum filetype { + unknown, + fifo, + chardev, + directory, + blockdev, + normal, + symbolic_link, + sock, + whiteout, + arg_directory +}; + +void parse_ls_color(void); +void print_color_indicator(const char *name, mode_t mode, int linkok, + int stat_ok, enum filetype filetype); +#endif -- 1.9.0.40.gaa8c3ea -- 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