--- grep.c.orig Tue Mar 20 14:27:06 2001 +++ grep.c Tue Mar 20 16:35:26 2001 @@ -70,8 +70,4 @@ static int color_option; -/* The color string used. The user can overwrite it using the environment - variable GREP_COLOR. The default is to print red. */ -static const char *grep_color = "31;5"; - static struct exclude *excluded_patterns; static struct exclude *included_patterns; @@ -91,4 +87,63 @@ }; +/* Null is a valid character in a color indicator (think about Epson + printers, for example) so we have to use a length/buffer string + type. */ +struct bin_str + { + int len; /* Number of bytes */ + char *string; /* Pointer to the same */ + }; + +enum color_type + { + color_never, /* 0: default or --color=never */ + color_always, /* 1: --color=always */ + color_if_tty /* 2: --color=tty */ + }; + +enum indicator_no + { C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_LINE, C_BYTE, C_COUNT, C_COUNT_0, C_CONTEXT, C_PAT }; + +static const char *const indicator_name[]= + { "lc", "rc", "ec", "no", "fi", "ln", "by", "cn", "c0", "cx", "pt", NULL }; + +struct col_ext_type + { + struct bin_str ext; /* The extension we're looking for */ + struct bin_str seq; /* The sequence to output when we do */ + struct col_ext_type *next; /* Next in list */ + }; + +#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 ("1;36") }, /* fi: Filename: bright cyan */ + { LEN_STR_PAIR ("31") }, /* ln: Line number: red */ + { LEN_STR_PAIR ("35") }, /* by: Byte number: magenta */ + { LEN_STR_PAIR ("32") }, /* cn: Count: green */ + { LEN_STR_PAIR ("34") }, /* c0: Count when 0: blue */ + { LEN_STR_PAIR ("33") }, /* cx: Context: yellow */ + { LEN_STR_PAIR ("31;5") } /* pt: Pattern: red on gray */ + }; + +/* FIXME: comment ( taken from GNU ls) */ +struct col_ext_type *col_ext_list = NULL; + +/* Buffer for color sequences */ +static char *color_buf; + +/* color functions */ +static void parse_grep_color PARAMS ((void)); +static void prep_non_filename_text PARAMS ((void)); +static void print_color_indicator PARAMS ((unsigned int type)); +static void put_indicator PARAMS ((const struct bin_str *ind)); +static int get_funky_string PARAMS ((char **dest, const char **src, int equals_end)); + + /* Long options equivalences. */ static struct option const long_options[] = @@ -100,6 +155,6 @@ {"byte-offset", no_argument, NULL, 'b'}, {"context", required_argument, NULL, 'C'}, - {"color", no_argument, NULL, COLOR_OPTION}, - {"colour", no_argument, NULL, COLOR_OPTION}, + {"color", optional_argument, NULL, COLOR_OPTION}, + {"colour", optional_argument, NULL, COLOR_OPTION}, {"count", no_argument, NULL, 'c'}, {"directories", required_argument, NULL, 'd'}, @@ -481,5 +536,5 @@ /* Print a byte offset, followed by a character separator. */ static void -print_offset_sep (uintmax_t pos, char sep) +print_offset_sep (uintmax_t pos, char sep, int type) { /* Do not rely on printf to print pos, since uintmax_t may be longer @@ -494,17 +549,27 @@ while ((pos /= 10) != 0); + if (color_option) + print_color_indicator (type); fwrite (p, 1, buf + sizeof buf - p, stdout); + if (color_option) + print_color_indicator (C_NORM); } static void -prline (char const *beg, char const *lim, int sep) +prline (char const *beg, char const *lim, int sep, int type) { if (out_file) - printf ("%s%c", filename, sep & filename_mask); + { + if (color_option) + print_color_indicator (C_FILE); + printf ("%s%c", filename, sep & filename_mask); + if (color_option) + print_color_indicator (C_NORM); + } if (out_line) { nlscan (beg); totalnl = add_count (totalnl, 1); - print_offset_sep (totalnl, sep); + print_offset_sep (totalnl, sep, C_LINE); lastnl = lim; } @@ -515,23 +580,35 @@ pos = dossified_pos (pos); #endif - print_offset_sep (pos, sep); + print_offset_sep (pos, sep, C_BYTE); } if (color_option) { - size_t match_size; - size_t match_offset; - while ((match_offset = (*execute) (beg, lim - beg, &match_size, 1)) - != (size_t) -1) - { - char const *b = beg + match_offset; - /* Avoid matching the empty line at the end of the buffer. */ - if (b == lim) - break; - fwrite (beg, sizeof (char), match_offset, stdout); - printf ("\33[%sm", grep_color); - fwrite (b, sizeof (char), match_size, stdout); - fputs ("\33[00m", stdout); - beg = b + match_size; - } + /* for context lines, whole line is a given color */ + if (type == C_CONTEXT) + { + print_color_indicator(type); + fwrite (beg, 1, lim - beg, stdout); + print_color_indicator(C_NORM); + beg = lim; /* so we don't print it out twice */ + } + else + /* for matches, the pattern is highlighted */ + { + size_t match_size; + size_t match_offset; + while ((match_offset = (*execute) (beg, lim - beg, &match_size, 1)) + != (size_t) -1) + { + char const *b = beg + match_offset; + /* Avoid matching the empty line at the end of the buffer. */ + if (b == lim) + break; + fwrite (beg, sizeof (char), match_offset, stdout); + print_color_indicator (C_PAT); + fwrite (b, sizeof (char), match_size, stdout); + print_color_indicator (C_NORM); + beg = b + match_size; + } + } } fwrite (beg, 1, lim - beg, stdout); @@ -546,5 +623,5 @@ at the next matching line when OUTLEFT is 0. */ static void -prpending (char const *lim) +prpending (char const *lim, int type) { if (!lastout) @@ -558,5 +635,5 @@ || (((*execute) (lastout, nl - lastout, &match_size, 0) == (size_t) -1) == !out_invert)) - prline (lastout, nl + 1, '-'); + prline (lastout, nl + 1, '-', type); else pending = 0; @@ -575,5 +652,5 @@ if (!out_quiet && pending > 0) - prpending (beg); + prpending (beg, C_CONTEXT); p = beg; @@ -599,5 +676,5 @@ char const *nl = memchr (p, eol, beg - p); nl++; - prline (p, nl, '-'); + prline (p, nl, '-', C_CONTEXT); p = nl; } @@ -612,5 +689,5 @@ nl++; if (!out_quiet) - prline (p, nl, ':'); + prline (p, nl, ':', C_NORM); p = nl; } @@ -622,5 +699,5 @@ else if (!out_quiet) - prline (beg, lim, ':'); + prline (beg, lim, ':', C_NORM); pending = out_quiet ? 0 : out_after; @@ -762,5 +839,5 @@ nlines += grepbuf (beg, lim); if (pending) - prpending (lim); + prpending (lim, C_CONTEXT); if((!outleft && !pending) || (nlines && done_on_match && !out_invert)) goto finish_grep; @@ -803,5 +880,5 @@ nlines += grepbuf (bufbeg + save - residue, buflim); if (pending) - prpending (buflim); + prpending (buflim, C_CONTEXT); } @@ -810,5 +887,17 @@ out_quiet -= not_text; if ((not_text & ~out_quiet) && nlines != 0) - printf (_("Binary file %s matches\n"), filename); + { + if (color_option) + { + printf (_("Binary file ")); + print_color_indicator (C_FILE); + printf ("%s", filename); + print_color_indicator (C_NORM); + printf (_(" matches\n")); + + } + else + printf (_("Binary file %s matches\n"), filename); + } return nlines; } @@ -886,11 +975,27 @@ { if (out_file) - printf ("%s%c", filename, ':' & filename_mask); + { + if (color_option) + print_color_indicator (C_FILE); + printf ("%s%c", filename, ':' & filename_mask); + if (color_option) + print_color_indicator (C_NORM); + } + if (color_option) + print_color_indicator (count == 0 ? C_COUNT_0 : C_COUNT); printf ("%d\n", count); + if (color_option) + print_color_indicator (C_NORM); } status = !count; if (list_files == 1 - 2 * status) - printf ("%s%c", filename, '\n' & filename_mask); + { + if (color_option) + print_color_indicator (C_FILE); + printf ("%s%c", filename, '\n' & filename_mask); + if (color_option) + print_color_indicator (C_NORM); + } if (! file) @@ -1042,5 +1147,6 @@ -C, --context=NUM print NUM lines of output context\n\ -NUM same as --context=NUM\n\ - --color, --colour use markers to distinguish the matching string\n\ + --color[=WHEN], use markers to distinguish the matching string\n\ + --colour[=WHEN] WHEN may be `never', `always', or `auto'\n\ -U, --binary do not strip CR characters at EOL (MSDOS)\n\ -u, --unix-byte-offsets report offsets as if CRs were not there (MSDOS)\n\ @@ -1212,4 +1318,5 @@ int opt, cc, status; int default_context; + int i; FILE *fp; extern char *optarg; @@ -1474,5 +1581,32 @@ case COLOR_OPTION: - color_option = 1; + if (optarg) + { + if ( (strcmp (optarg, "always") == 0) + || (strcmp (optarg, "yes") == 0) + || (strcmp (optarg, "force") == 0) ) + i = color_always; + else if ( (strcmp (optarg, "auto") == 0) + || (strcmp (optarg, "tty") == 0) + || (strcmp (optarg, "if-tty") == 0) ) + i = color_if_tty; + else if ( (strcmp (optarg, "never") == 0) + || (strcmp (optarg, "none") == 0) + || (strcmp (optarg, "no") == 0) ) + i = color_never; + else { + error (2, 0, _("invalid colorization criterion")); + } + } + else + { + /* Using --color with no argument is equivalent to using + --color=always. */ + i = color_always; + } + + color_option = (i == color_always + || (i == color_if_tty + && isatty (1))); break; @@ -1513,4 +1647,10 @@ } + if (color_option) + { + parse_grep_color (); + prep_non_filename_text (); + } + /* POSIX.2 says that -q overrides -l, which in turn overrides the other output options. */ @@ -1529,11 +1669,4 @@ out_before = default_context; - if (color_option) - { - char *userval = getenv ("GREP_COLOR"); - if (userval != NULL && *userval != '\0') - grep_color = userval; - } - if (! matcher) matcher = "grep"; @@ -1620,3 +1753,372 @@ /* We register via atexit() to test stdout. */ exit (errseen ? 2 : status); +} + +static void +prep_non_filename_text (void) +{ + if (color_indicator[C_END].string != NULL) + put_indicator (&color_indicator[C_END]); + else + { + put_indicator (&color_indicator[C_LEFT]); + put_indicator (&color_indicator[C_NORM]); + put_indicator (&color_indicator[C_RIGHT]); + } +} + +static void +print_color_indicator (unsigned int type) +{ + struct col_ext_type *ext; /* Color extension */ + + put_indicator (&color_indicator[C_LEFT]); + put_indicator (&color_indicator[type]); + put_indicator (&color_indicator[C_RIGHT]); +} + +/* Output a color indicator (which may contain nulls). */ +static void +put_indicator (const struct bin_str *ind) +{ + register int i; + register char *p; + + p = ind->string; + + for (i = ind->len; i > 0; --i) + { + putchar (*p); + p++; + } +} + +/* Parse a string as part of the GREP_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. Returns the number of characters output, or -1 on failure. + + 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. + + The default GREP_COLORS value, as specified by color_indicator[]: + + no=0:fi=1;36:ln=31:by=35:cn=32:c0=34:cx=33:pt=31;5 + +*/ + +static int +get_funky_string (char **dest, const char **src, int equals_end) +{ + int num; /* For numerical codes */ + int 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 = 7; /* Not all C compilers know what \a means */ + 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; + + return state == ST_ERROR ? -1 : count; +} + +static void +parse_grep_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 col_ext_type *ext; /* Extension we are working on */ + struct col_ext_type *ext2; /* Extra pointer */ + + if ((p = getenv ("GREP_COLORS")) == NULL || *p == '\0') + return; + + ext = NULL; + strcpy (label, "??"); + + /* NOTE: the 'ls' code used xstrdup here, but I didn't want to mess with it (EA) */ + buf = color_buf = strdup (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 = (struct col_ext_type *) + xmalloc (sizeof (struct col_ext_type)); + ext->next = col_ext_list; + col_ext_list = ext; + + ++p; + ext->ext.string = buf; + + state = (ext->ext.len = + get_funky_string (&buf, &p, 1)) < 0 ? -1 : 4; + 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 (strcmp (label, indicator_name[ind_no]) == 0) + { + color_indicator[ind_no].string = buf; + state = ((color_indicator[ind_no].len = + get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1); + break; + } + } + if (state == -1) + error (2, 0, _("unrecognized prefix")); + } + break; + + case 4: /* Equal sign after *.ext */ + if (*(p++) == '=') + { + ext->seq.string = buf; + state = (ext->seq.len = + get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1; + } + else + state = -1; + break; + } + } + + if (state < 0) + { + struct col_ext_type *e; + + error (2, 0, _("unparsable value for GREP_COLORS environment variable")); + free (color_buf); + for (e = col_ext_list; e != NULL ; /* empty */) + { + ext2 = e; + e = e->next; + free (ext2); + } + color_option = 0; + } }