[ Generated with ude,
    the unified-format diff-output ("unidiff") editor,
    version 0.2-pre1 of 2005-01-16.  ]
--- src/grep.c.cvs-1.91	2004-12-16 08:19:29 -0500
+++ src/grep.c	2005-01-23 23:11:15 -0500
@@ -77,16 +77,54 @@
 
 /* If nonzero, show only the part of a line matching the expression. */
 static int only_matching;
+
+/* If nonzero, make sure first content char in a line is on a tab stop. */
+static int align_tabs;
 
-/* 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 = "01;31";
+/* The color string used for matched text.
+   The user can overwrite it using the deprecated
+   environment variable GREP_COLOR or the new GREP_COLORS.  */
+static const char *grep_color = "01;31";	/* bold red */
 
+/* Other colors.  Defaults look damn good.
+   Only use the four colors in the middle of the luminosity range as
+   foregrounds so that they are visible on dark or light backgrounds.  */
+static const char *filename_color = "35";	/* magenta */
+static const char *num_color = "32";		/* green */
+static const char *sep_color = "36";		/* cyan */
+
+/* Select Graphic Rendition (SGR) strings.  */
+#define SGR_ARG "\33[%sm"
+static const char *sgr_end = "\33[m";
+#define SGR_END sgr_end
+
+/* Allow for val_if_true to be NULL for booleans.  */
+#define CAP_WITH_VAL	((char *) 64U)
+#define CAP_BOOL_TRUE	((char *) 128U)
+
+struct color_cap
+  {
+    const char *name;
+    const char **var;
+    const char *val_if_true;
+  };
+
+/* For GREP_COLORS.  */
+static struct color_cap color_dict[] =
+  {
+    { "mt", &grep_color, CAP_WITH_VAL },	/* matched text */
+    { "fn", &filename_color, CAP_WITH_VAL },
+    { "nu", &num_color, CAP_WITH_VAL },
+    { "se", &sep_color, CAP_WITH_VAL },
+    { "cl", &sgr_end, "\33[m\33[K" },		/* clear to eol on sgr_end */
+    { NULL, NULL, NULL }
+  };
+
 static struct exclude *excluded_patterns;
 static struct exclude *included_patterns;
 /* Short options.  */
 static char const short_options[] =
-"0123456789A:B:C:D:EFGHIPUVX:abcd:e:f:hiKLlm:noqRrsuvwxyZz";
+"0123456789A:B:C:D:EFGHIPTUVX:abcd:e:f:hiKLlm:noqRrsuvwxyZz";
 
 /* Non-boolean long options that have no corresponding short equivalents.  */
 enum
@@ -125,6 +163,7 @@ static struct option const long_options[
   {"help", no_argument, &show_help, 1},
   {"include", required_argument, NULL, INCLUDE_OPTION},
   {"ignore-case", no_argument, NULL, 'i'},
+  {"initial-tab", no_argument, NULL, 'T'},
   {"label", required_argument, NULL, LABEL_OPTION},
   {"line-buffered", no_argument, NULL, LINE_BUFFERED_OPTION},
   {"line-number", no_argument, NULL, 'n'},
@@ -483,4 +522,26 @@ nlscan (char const *lim)
   totalnl = add_count (totalnl, newlines);
   lastnl = lim;
 }
+
+/* Print the current filename.  */
+static void
+print_filename (void)
+{
+  if (color_option)
+    printf(SGR_ARG, filename_color);
+  fputs(filename, stdout);
+  if (color_option)
+    fputs(SGR_END, stdout);
+}
+
+/* Print a character separator.  */
+static void
+print_sep (char sep)
+{
+  if (color_option)
+    printf(SGR_ARG, sep_color);
+  fputc(sep, stdout);
+  if (color_option)
+    fputs(SGR_END, stdout);
+}
 
@@ -487,41 +548,78 @@ nlscan (char const *lim)
-/* Print a byte offset, followed by a character separator.  */
+/* Print a byte offset.  */
 static void
-print_offset_sep (uintmax_t pos, char sep)
+print_offset (uintmax_t pos, int min_width)
 {
   /* Do not rely on printf to print pos, since uintmax_t may be longer
      than long, and long long is not portable.  */
 
   char buf[sizeof pos * CHAR_BIT];
-  char *p = buf + sizeof buf - 1;
+  char *p = buf + sizeof buf;
-  *p = sep;
 
   do
-    *--p = '0' + pos % 10;
+    {
+      *--p = '0' + pos % 10;
+      --min_width;
+    }
   while ((pos /= 10) != 0);
 
+  /* Do this to maximize the probability of alignment across lines.  */
+  if (align_tabs)
+    while (--min_width >= 0)
+      *--p = ' ';
+
+  if (color_option)
+    printf(SGR_ARG, num_color);
   fwrite (p, 1, buf + sizeof buf - p, stdout);
+  if (color_option)
+    fputs(SGR_END, stdout);
 }
 
 static void
 prline (char const *beg, char const *lim, int sep)
 {
+  int pending_sep = 0;
+
   if (out_file)
-    printf ("%s%c", filename, sep & filename_mask);
+    {
+      print_filename();
+      if (filename_mask)
+	pending_sep = 1;
+      else
+	fputc(0, stdout);
+    }
   if (out_line)
     {
       nlscan (beg);
       totalnl = add_count (totalnl, 1);
+      if (pending_sep)
+	print_sep(sep);
-      print_offset_sep (totalnl, sep);
+      print_offset (totalnl, 4);
+      pending_sep = 1;
       lastnl = lim;
     }
   if (out_byte)
     {
       uintmax_t pos = add_count (totalcc, beg - bufbeg);
 #if defined(HAVE_DOS_FILE_CONTENTS)
       pos = dossified_pos (pos);
 #endif
+      if (pending_sep)
+	print_sep(sep);
-      print_offset_sep (pos, sep);
+      print_offset (pos, 6);
+      pending_sep = 1;
     }
+  if (pending_sep)
+    {
+      /* This assumes sep is one column wide.
+       * Try doing this any other way with Unicode
+       * (and its combining and wide characters)
+       * filenames and you're wasting your efforts.  */
+      if (align_tabs)
+	fputs("\t\b", stdout);
+
+      print_sep(sep);
+      pending_sep = 0;
+    }
   if (only_matching)
     {
       size_t match_size;
@@ -544,10 +642,10 @@ prline (char const *beg, char const *lim
 	      if (match_size == 0)
 		break;
 	      if(color_option)
-		printf("\33[%sm", grep_color);
+		printf(SGR_ARG, grep_color);
 	      fwrite(b, sizeof (char), match_size, stdout);
 	      if(color_option)
-		fputs("\33[00m", stdout);
+		fputs(SGR_END, stdout);
 	      fputs("\n", stdout);
 	      ibeg = b + match_size;
 	    }
@@ -566,10 +664,10 @@ prline (char const *beg, char const *lim
 	  if (match_size == 0)
 	    break;
 	  if(color_option)
-	    printf("\33[%sm", grep_color);
+	    printf(SGR_ARG, grep_color);
 	  fwrite(b, sizeof (char), match_size, stdout);
 	  if(color_option)
-	    fputs("\33[00m", stdout);
+	    fputs(SGR_END, stdout);
 	  fputs("\n", stdout);
 	  beg = b + match_size;
         }
@@ -600,9 +698,9 @@ prline (char const *beg, char const *lim
 	      if (match_size == 0)
 	        break;
 	      fwrite (beg, sizeof (char), match_offset, stdout);
-	      printf ("\33[%sm", grep_color);
+	      printf (SGR_ARG, grep_color);
 	      fwrite (b, sizeof (char), match_size, stdout);
-	      fputs ("\33[00m", stdout);
+	      fputs (SGR_END, stdout);
 	      beg = b + match_size;
 	      ibeg = ibeg + match_offset + match_size;
 	    }
@@ -622,9 +720,9 @@ prline (char const *beg, char const *lim
 	  if (match_size == 0)
 	    break;
 	  fwrite (beg, sizeof (char), match_offset, stdout);
-	  printf ("\33[%sm", grep_color);
+	  printf (SGR_ARG, grep_color);
 	  fwrite (b, sizeof (char), match_size, stdout);
-	  fputs ("\33[00m", stdout);
+	  fputs (SGR_END, stdout);
 	  beg = b + match_size;
 	}
     }
@@ -686,7 +784,14 @@ prtext (char const *beg, char const *lim
       /* We print the "--" separator only if our output is
 	 discontiguous from the last output in the file. */
       if ((out_before || out_after) && used && p != lastout)
-	puts ("--");
+	{
+	  if (color_option)
+	    printf(SGR_ARG, sep_color);
+	  fputs ("--", stdout);
+	  if (color_option)
+	    fputs(SGR_END, stdout);
+	  fputc('\n', stdout);
+	}
 
       while (p < beg)
 	{
@@ -992,13 +1097,22 @@ grepfile (char const *file, struct stats
       if (count_matches)
 	{
 	  if (out_file)
-	    printf ("%s%c", filename, ':' & filename_mask);
+	    {
+	      print_filename();
+	      if (filename_mask)
+		print_sep(':');
+	      else
+		fputc(0, stdout);
+	    }
 	  printf ("%d\n", count);
 	}
 
       status = !count;
       if (list_files == 1 - 2 * status)
-	printf ("%s%c", filename, '\n' & filename_mask);
+	{
+	  print_filename();
+	  fputc('\n' & filename_mask, stdout);
+	}
 
       if (! file)
 	{
@@ -1144,6 +1258,7 @@ usage (int status)
   -L, --files-without-match print only names of FILEs containing no match\n\
   -l, --files-with-matches  print only names of FILEs containing matches\n\
   -c, --count               print only a count of matching lines per FILE\n\
+  -T, --initial-tab         make tabs line up (if needed)\n\
   -Z, --null                print 0 byte after FILE name\n"));
       printf (_("\
 \n\
@@ -1316,6 +1431,85 @@ get_nondigit_option (int argc, char *con
   return opt;
 }
 
+/* Parse GREP_COLORS.  The  default would look like:
+     GREP_COLORS='mt=01;31:fn=35:nu=32:se=36'
+   No character escaping is needed or supported.  */
+static void
+parse_grep_colors (void)
+{
+  const char *p;
+  char *q;
+  char *name;
+  char *val;
+
+  p = getenv("GREP_COLORS"); /* Plural! */
+  if (p == NULL || *p == '\0')
+    return;
+
+  /* Work off a writable copy.  */
+  q = xmalloc(strlen(p) + 1);
+  if (q == NULL)
+    return;
+  strcpy(q, p);
+
+  name = q;
+  cap = NULL;
+  val = NULL;
+  /* From now on, be well-formed or you're gone.  */
+  for (;;)
+    if (*q == ':' || *q == '\0')
+      {
+	char c = *q;
+	struct color_cap *cap;
+
+	*q++ = '\0'; /* Terminate name or val.  */
+	/* Empty name without val (empty cap)
+	 * won't match and will be ignored.  */
+	for (cap = color_dict; cap->name; cap++)
+	  if (strcmp(cap->name, name) == 0)
+	    break;
+	/* If name unknown, go on for forward compatibility.  */
+	if (cap->name)
+	  if (cap->val_if_true == CAP_WITH_VAL)
+	    {
+	      if (val)
+		*(cap->var) = val;
+	      else
+		fprintf(stderr,
+			_("%s: In GREP_COLORS=\"%s\", the \"%s\" capacity needs a value (\"=...\"); skipped.\n"),
+			program_name, p, name);
+	    }
+	  else if (val)
+	    fprintf(stderr,
+		    _("%s: In GREP_COLORS=\"%s\", the \"%s\" capacity is boolean and cannot take a value (\"=%s\"); skipped.\n"),
+		    program_name, p, name, val);
+	  else
+	    *(cap->var) = cap->val_if_true;
+	if (c == '\0')
+	  return;
+	name = q;
+	val = NULL;
+      }
+    else if (*q == '=')
+      {
+	if (q == name || val)
+	  goto ill_formed;
+	*q++ = '\0'; /* Terminate name.  */
+	val = q; /* Can be the empty string.  */
+      }
+    else if (val == NULL)
+      q++; /* Accumulate name.  */
+    else if (*q == ';' || (*q >= '0' && *q <= '9'))
+      q++; /* Accumulate val.  Protect the terminal from being sent crap.  */
+    else
+      goto ill_formed;
+
+ ill_formed:
+  fprintf(stderr,
+	  _("%s: Stopped processing of ill-formed GREP_COLORS=\"%s\" at remaining substring \"%s\".\n"),
+	  program_name, p, q);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -1441,6 +1635,10 @@ main (int argc, char **argv)
 	binary_files = WITHOUT_MATCH_BINARY_FILES;
 	break;
 
+      case 'T':
+	align_tabs = 1;
+	break;
+
       case 'U':
 #if defined(HAVE_DOS_FILE_CONTENTS)
 	dos_use_file_type = DOS_BINARY;
@@ -1688,9 +1886,13 @@ main (int argc, char **argv)
 
   if (color_option)
     {
+      /* Legacy.  */
       char *userval = getenv ("GREP_COLOR");
       if (userval != NULL && *userval != '\0')
 	grep_color = userval;
+
+      /* New GREP_COLORS has priority.  */
+      parse_grep_colors();
     }
 
   if (! matcher)
--- doc/grep.texi.cvs-1.50	2004-12-15 14:33:38 -0500
+++ doc/grep.texi	2005-01-17 02:07:15 -0500
@@ -261,13 +261,20 @@
 @cindex context
 Print @var{num} lines of output context.
 
-@item --colour[=@var{WHEN}]
-@itemx --color[=@var{WHEN}]
+@item --color[=@var{WHEN}]
+@itemx --colour[=@var{WHEN}]
+@opindex --color
 @opindex --colour
 @cindex highlight, color, colour
-Surround the matching strings with escape sequences to display them in color
-on the terminal.  The color is defined by the environment variable
-@var{GREP_COLOR} and defaults to `01;31' which means the text is red.
+Surround the matching strings, file names, numbers (line numbers and
+octet offsets), and separators (for fields and groups of context lines)
+with escape sequences to display them in color on the terminal.
+The colors are defined by the environment variable @var{GREP_COLORS}
+and default to `mt=01;31:fn=35:nu=32:se=36' for bold red matched text,
+magenta file names, green numbers, and cyan separators.
+The deprecated environment variable @var{GREP_COLOR} is still supported,
+but its setting does not have priority; it defaults to `01;31' (bold red)
+which only covers the color for matched text.
 @var{WHEN} is `never', `always', or `auto'.
 
 @item -@var{num}
@@ -513,6 +520,22 @@
 behavior (including core dumps) if an input file shrinks while
 @command{grep} is operating, or if an I/O error occurs.
 
+@item -T
+@itemx --initial-tab
+@opindex -T
+@opindex --initial-tab
+@cindex tab-aligned content lines
+Makes sure that the first character of actual line content lies on a
+tab stop, so that the alignment of tabs looks normal.
+This is useful when combined with @samp{-H} (which is implicit when
+there is more than one file to search), @samp{-n}, and @samp{-b};
+these options prepend their output at the beginning of the displayed
+line, before the actual content.
+In order to improve the probability that all matched or context lines
+from a single file will all start at the same column, this also causes
+the line number and octet offset (if present) to be printed in a minimum
+size field width.
+
 @item -Z
 @itemx --null
 @opindex -Z
@@ -575,8 +598,30 @@
 @item GREP_COLOR
 @vindex GREP_COLOR
 @cindex highlight markers
-This variable specifies the color used to highlight the matching text.
-The default is `01;31' which means red text on the default background.
+This variable is deprecated but still supported; its setting does not
+have priority over that of @code{GREP_COLORS}.
+It specifies the color used to highlight the matching text.
+The default is `01;31' which means bold red text on the default
+background.
+
+@item GREP_COLORS
+@vindex GREP_COLORS
+@cindex highlight markers
+This variable specifies the colors used to highlight the matching text
+(mt), file names (fn), numbers (nu, for line numbers and octet offsets),
+and separators (se, for fields and groups of context lines).
+It is a colon-separated list of color specification assignments.
+The default is `mt=01;31:fn=35:nu=32:se=36' which means bold red, magenta,
+green, and cyan, all text foregrounds on the default background.
+See the Select Graphic Rendition (SGR, for character attributes)
+section in the documentation of the text terminal that is used
+for permissible values (semicolon-separated lists of integers)
+and their meaning.
+@code{GREP_COLORS} also supports a boolean `cl' capability (with no
+`=...' part) to clear to the end of line each time a colorized item ends
+(needed on terminals with the @code{back_color_erase} (@code{bce})
+terminfo capability when the chosen highlight colors affect the
+background).
 
 @item LC_ALL
 @itemx LC_COLLATE
--- doc/grep.1.cvs-1.28	2004-12-15 14:33:38 -0500
+++ doc/grep.1	2005-01-17 02:09:33 -0500
@@ -126,11 +126,27 @@
 which can have nasty side effects if the output is a terminal and if the
 terminal driver interprets some of it as commands.
 .TP
-.BI \-\^\-colour[=\fIWHEN\fR] ", " \-\^\-color[=\fIWHEN\fR]
-Surround the matching strings with escape sequences to display them in color
-on the terminal.  The color is defined by the environment variable
-.B GREP_COLOR
-and defaults to `01;31' which means the text is red.
+.BI \-\^\-color[=\fIWHEN\fR] ", " \-\^\-colour[=\fIWHEN\fR]
+Surround the matching strings, file names, numbers (line numbers and
+octet offsets), and separators (for fields and groups of context lines)
+with escape sequences to display them in color on the terminal.
+The colors are defined by the environment variable
+.B GREP_COLORS
+and default to `mt=01;31:fn=35:nu=32:se=36' for bold red matched text,
+magenta file names, green numbers, and cyan separators.
+See the Select Graphic Rendition (SGR, for character attributes)
+section in the documentation of the text terminal that is used
+for permissible values (semicolon-separated lists of integers)
+and their meaning.
+.B GREP_COLORS
+also supports a boolean `cl' capability (with no `=...' part) to clear
+to the end of line each time a colorized item ends (needed on terminals
+with the `back_color_erase' (`bce') terminfo capability when the chosen
+highlight colors affect the background).
+The deprecated environment variable
+.B GREP_COLOR
+is still supported, but its setting does not have priority; it defaults to
+`01;31' (bold red) which only covers the color for matched text.
 .I WHEN
 is `never', `always', or `auto'.
 .TP
@@ -354,6 +370,22 @@
 and
 .B \-s
 and should redirect output to /dev/null instead.
+.TP
+.BR \-T ", " \-\^\-initial\-tab
+Makes sure that the first character of actual line content lies on a
+tab stop, so that the alignment of tabs looks normal.
+This is useful when combined with
+.B \-H
+(which is implicit when there is more than one file to search),
+.BR \-n ,
+and
+.BR \-b ;
+these options prepend their output at the beginning of the displayed
+line, before the actual content.
+In order to improve the probability that all matched or context lines
+from a single file will all start at the same column, this also causes
+the line number and octet offset (if present) to be printed in a minimum
+size field width.
 .TP
 .BR \-U ", " \-\^\-binary
 Treat the file(s) as binary.  By default, under MS-DOS and MS-Windows,
@@ -693,7 +725,14 @@
 so it can be used to specify an option containing whitespace or a backslash.
 .TP
 .B GREP_COLOR
+Deprecated in favor of
+.BR GREP_COLORS ,
+which has priority.
 Specifies the marker for highlighting.
+.TP
+.B GREP_COLORS
+Specifies the markers for highlighting matched text, file names, numbers,
+and separators.
 .TP
 \fBLC_ALL\fP, \fBLC_COLLATE\fP, \fBLANG\fP
 These variables specify the
