GNU bug report logs - #20062
[PATCH] diff: add support for --color

Previous Next

Package: diffutils;

Reported by: Giuseppe Scrivano <gscrivan <at> redhat.com>

Date: Sun, 8 Mar 2015 21:57:02 UTC

Severity: normal

Tags: patch

Done: Jim Meyering <jim <at> meyering.net>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 20062 in the body.
You can then email your comments to 20062 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 08 Mar 2015 21:57:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Giuseppe Scrivano <gscrivan <at> redhat.com>:
New bug report received and forwarded. Copy sent to bug-diffutils <at> gnu.org. (Sun, 08 Mar 2015 21:57:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: bug-diffutils <at> gnu.org
Subject: [PATCH] diff: add support for --color
Date: Sun,  8 Mar 2015 22:56:31 +0100
* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Add calls to set_color_context.
* src/diff.h (enum colors): New enum to register the current color to use.
(enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c (specify_colors_style): New function.
(BINARY_OPTION): Add COLOR_OPTION.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Add calls to set_color_context
* src/side.c (print_1sdiff_line): Add calls to set_color_context.
* src/util.c (colors_enabled): New boolean variable.
(check_color_output): New function.
(begin_output): Call check_color_output every time the output file is changed.
(set_color_context): New function.  If colors are enabled, print the right
command for the terminal to change the color.
---

I got so used to having colors in git diff that I become unable to
quickly spot differences in the plain diff output.  This patch fills
the gap between the two versions.

doc/diffutils.texi | 22 ++++++++++++++++++++++
 src/context.c      |  4 ++++
 src/diff.c         | 27 ++++++++++++++++++++++++++-
 src/diff.h         | 30 ++++++++++++++++++++++++++++++
 src/normal.c       | 16 ++++++++++++----
 src/side.c         | 15 +++++++++++++++
 src/util.c         | 36 ++++++++++++++++++++++++++++++++++++
 7 files changed, 145 insertions(+), 5 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..bc7169a 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,28 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+- Do not use color at all.  This is the default.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+- Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+- Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=always}.
+Piping a colorized listing through a pager like @command{more} or
+@command{less} usually produces unreadable results.  However, using
+@code{more -f} does seem to work.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..88ddfde 100644
--- a/src/context.c
+++ b/src/context.c
@@ -366,10 +366,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
+              set_color_context (DELETE);
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* Then output the inserted part. */
@@ -378,10 +380,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+              set_color_context (ADD);
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
diff --git a/src/diff.c b/src/diff.c
index ff28377..7d14877 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+        case COLOR_OPTION:
+          specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'auto',"),
+  N_("                             or 'always' (the default); more info below"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else if (STREQ (value, "auto"))
+    colors_style = AUTO;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..cad961b 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,32 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors
+{
+  /* Reset to the default color.  */
+  RESET,
+
+  /* Delete lines.  Show output in red.  */
+  DELETE,
+
+  /* Added lines.  Show them in green.  */
+  ADD,
+};
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +109,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* True if colors are printed.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +419,4 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_color_context (enum colors);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..c5211cb 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -56,14 +56,22 @@ print_normal_hunk (struct change *hunk)
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_color_context (DELETE);
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      set_color_context (RESET);
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_color_context (ADD);
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      set_color_context (RESET);
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..e52454e 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..3687b5e 100644
--- a/src/util.c
+++ b/src/util.c
@@ -153,6 +153,17 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (void)
+{
+  if (! outfile)
+    return;
+
+  colors_enabled = (colors_style == ALWAYS)
+    || (colors_style == AUTO && isatty (fileno (outfile)));
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +324,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+            check_color_output ();
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +332,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+        check_color_output ();
 	free (command);
 #endif
       }
@@ -330,6 +343,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output ();
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -717,6 +731,28 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+void
+set_color_context (enum colors con)
+{
+  if (! colors_enabled)
+    return;
+
+  switch (con)
+    {
+    case DELETE:
+      fprintf (outfile, "\x1B[31m");
+      break;
+
+    case ADD:
+      fprintf (outfile, "\x1B[32m");
+      break;
+
+    case RESET:
+      fprintf(outfile, "\x1b[0m");
+      break;
+    }
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 09 Mar 2015 14:52:02 GMT) Full text and rfc822 format available.

Message #8 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 09 Mar 2015 08:50:59 -0600
[Message part 1 (text/plain, inline)]
On 03/08/2015 03:56 PM, Giuseppe Scrivano wrote:
> * doc/diffutils.texi (diff Options): Add documentation for --color.
> Copied from coreutils ls --color.

Cool!

> +@item --color [=@var{when}]
> +@cindex color, distinguishing file types with
> +Specify whether to use color for distinguishing file types.  @var{when}
> +may be omitted, or one of:
> +@itemize @bullet
> +@item none
> +@vindex none @r{color option}
> +- Do not use color at all.  This is the default.

At first, I was worried that this meant that '--color' ==
'--color=none'; so maybe you want this to read "This is the default when
no --color option is present"

> +@item auto
> +@vindex auto @r{color option}
> +@cindex terminal, using color iff
> +- Only use color if standard output is a terminal.
> +@item always
> +@vindex always @r{color option}
> +- Always use color.
> +@end itemize
> +Specifying @option{--color} and no @var{when} is equivalent to
> +@option{--color=always}.

Is this the right default?  Some GNU programs default --color to
--color=auto; others to --color=always.  I'm 50:50 on which default is
better, but it's worth thinking about rather than just blindly picking
one, as we have both GNU styles as precedent.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 09 Mar 2015 21:23:02 GMT) Full text and rfc822 format available.

Message #11 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Eric Blake <eblake <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 09 Mar 2015 22:21:55 +0100
Eric Blake <eblake <at> redhat.com> writes:

>> +@item auto
>> +@vindex auto @r{color option}
>> +@cindex terminal, using color iff
>> +- Only use color if standard output is a terminal.
>> +@item always
>> +@vindex always @r{color option}
>> +- Always use color.
>> +@end itemize
>> +Specifying @option{--color} and no @var{when} is equivalent to
>> +@option{--color=always}.
>
> Is this the right default?  Some GNU programs default --color to
> --color=auto; others to --color=always.  I'm 50:50 on which default is
> better, but it's worth thinking about rather than just blindly picking
> one, as we have both GNU styles as precedent.

I thought about it as well, and then I decided to go for the same
default as ls but I still believe that auto would be a better one.

If it is fine for everyone, I can rework the patch and change the
default to auto.

Regards,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 10 Mar 2015 10:12:01 GMT) Full text and rfc822 format available.

Message #14 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Eric Blake <eblake <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Tue, 10 Mar 2015 11:10:51 +0100
Giuseppe Scrivano <gscrivan <at> redhat.com> writes:

> Eric Blake <eblake <at> redhat.com> writes:
>
>>> +@item auto
>>> +@vindex auto @r{color option}
>>> +@cindex terminal, using color iff
>>> +- Only use color if standard output is a terminal.
>>> +@item always
>>> +@vindex always @r{color option}
>>> +- Always use color.
>>> +@end itemize
>>> +Specifying @option{--color} and no @var{when} is equivalent to
>>> +@option{--color=always}.
>>
>> Is this the right default?  Some GNU programs default --color to
>> --color=auto; others to --color=always.  I'm 50:50 on which default is
>> better, but it's worth thinking about rather than just blindly picking
>> one, as we have both GNU styles as precedent.
>
> I thought about it as well, and then I decided to go for the same
> default as ls but I still believe that auto would be a better one.
>
> If it is fine for everyone, I can rework the patch and change the
> default to auto.

This new version changes the default for --color to auto:

From 7c8c9a6258eb37e6d5832362309fef1906f9a939 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Add calls to set_color_context.
* src/diff.h (enum colors): New enum to register the current color to use.
(enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c (specify_colors_style): New function.
(BINARY_OPTION): Add COLOR_OPTION.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Add calls to set_color_context
* src/side.c (print_1sdiff_line): Add calls to set_color_context.
* src/util.c (colors_enabled): New boolean variable.
(check_color_output): New function.
(begin_output): Call check_color_output every time the output file is changed.
(set_color_context): New function.  If colors are enabled, print the right
command for the terminal to change the color.
---
 doc/diffutils.texi | 23 +++++++++++++++++++++++
 src/context.c      |  4 ++++
 src/diff.c         | 27 ++++++++++++++++++++++++++-
 src/diff.h         | 30 ++++++++++++++++++++++++++++++
 src/normal.c       | 16 ++++++++++++----
 src/side.c         | 15 +++++++++++++++
 src/util.c         | 36 ++++++++++++++++++++++++++++++++++++
 7 files changed, 146 insertions(+), 5 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..f5e1804 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,29 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+- Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+- Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+- Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+Piping a colorized listing through a pager like @command{more} or
+@command{less} usually produces unreadable results.  However, using
+@code{more -f} does seem to work.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..88ddfde 100644
--- a/src/context.c
+++ b/src/context.c
@@ -366,10 +366,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
+              set_color_context (DELETE);
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* Then output the inserted part. */
@@ -378,10 +380,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+              set_color_context (ADD);
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
diff --git a/src/diff.c b/src/diff.c
index ff28377..5874646 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+        case COLOR_OPTION:
+          specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..cad961b 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,32 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors
+{
+  /* Reset to the default color.  */
+  RESET,
+
+  /* Delete lines.  Show output in red.  */
+  DELETE,
+
+  /* Added lines.  Show them in green.  */
+  ADD,
+};
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +109,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* True if colors are printed.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +419,4 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_color_context (enum colors);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..c5211cb 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -56,14 +56,22 @@ print_normal_hunk (struct change *hunk)
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_color_context (DELETE);
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      set_color_context (RESET);
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_color_context (ADD);
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      set_color_context (RESET);
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..e52454e 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..3687b5e 100644
--- a/src/util.c
+++ b/src/util.c
@@ -153,6 +153,17 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (void)
+{
+  if (! outfile)
+    return;
+
+  colors_enabled = (colors_style == ALWAYS)
+    || (colors_style == AUTO && isatty (fileno (outfile)));
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +324,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+            check_color_output ();
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +332,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+        check_color_output ();
 	free (command);
 #endif
       }
@@ -330,6 +343,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output ();
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -717,6 +731,28 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+void
+set_color_context (enum colors con)
+{
+  if (! colors_enabled)
+    return;
+
+  switch (con)
+    {
+    case DELETE:
+      fprintf (outfile, "\x1B[31m");
+      break;
+
+    case ADD:
+      fprintf (outfile, "\x1B[32m");
+      break;
+
+    case RESET:
+      fprintf(outfile, "\x1b[0m");
+      break;
+    }
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 10 Mar 2015 16:23:02 GMT) Full text and rfc822 format available.

Message #17 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>, 
 Eric Blake <eblake <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Tue, 10 Mar 2015 09:22:36 -0700
What happens if I type Control-C while colored output is streaming by my 
terminal?  Can it leave the terminal in a funny colored state?




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 10 Mar 2015 22:20:02 GMT) Full text and rfc822 format available.

Message #20 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Tue, 10 Mar 2015 23:19:26 +0100
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> What happens if I type Control-C while colored output is streaming by
> my terminal?  Can it leave the terminal in a funny colored state?

No, the Control-C would leave the terminal in a funny state.  Thanks to
have pointed it out, I completely missed the ls.c code that handles
that.

What about amending the previous version with the patch below?

Regards,
Giuseppe


diff --git a/src/util.c b/src/util.c
index 3687b5e..5b28a07 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,7 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -731,24 +732,56 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+static sigset_t old_sigproc_set;
 void
 set_color_context (enum colors con)
 {
+  int j;
+  sigset_t set;
+  static int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
   if (! colors_enabled)
     return;
 
+  sigemptyset (&set);
+  for (j = 0; j < (sizeof (sig) / sizeof (*sig)); j++)
+    sigaddset (&set, sig[j]);
+
   switch (con)
     {
     case DELETE:
+      sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
       fprintf (outfile, "\x1B[31m");
       break;
 
     case ADD:
+      sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
       fprintf (outfile, "\x1B[32m");
       break;
 
     case RESET:
-      fprintf(outfile, "\x1b[0m");
+      fprintf (outfile, "\x1b[0m");
+      fflush (outfile);
+      sigprocmask (SIG_SETMASK, &old_sigproc_set, NULL);
       break;
     }
 }




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 10 Mar 2015 23:00:03 GMT) Full text and rfc822 format available.

Message #23 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Tue, 10 Mar 2015 15:59:14 -0700
Giuseppe Scrivano wrote:

> +  sigemptyset (&set);
> +  for (j = 0; j < (sizeof (sig) / sizeof (*sig)); j++)
> +    sigaddset (&set, sig[j]);

Shouldn't the above part be done just once?  Also, it has redundant parentheses.

> +      sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);

The sigprocmask module needs to be added to bootstrap.conf.

Can the proposed implementation block signals for an unbounded amount of time? 
That would be bad.  ls.c attempts to avoid this problem, and 'diff' should too.

Also, 'diff --color' needn't mess with signal handling unless isatty (fileno 
(outfile)).

Also, surely --color is incompatible with --paginate, in the sense that signals 
arriving while paginating will put outfile into a weird state if outfile is a 
tty, so I expect that combination of options should be rejected.

> What about amending the previous version with the patch below?

Yes, we do need the patch to be amended something along these lines.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 10 Mar 2015 23:45:04 GMT) Full text and rfc822 format available.

Message #26 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Wed, 11 Mar 2015 00:43:57 +0100
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Giuseppe Scrivano wrote:
>
>> +  sigemptyset (&set);
>> +  for (j = 0; j < (sizeof (sig) / sizeof (*sig)); j++)
>> +    sigaddset (&set, sig[j]);
>
> Shouldn't the above part be done just once?  Also, it has redundant parentheses.
>
>> +      sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
>
> The sigprocmask module needs to be added to bootstrap.conf.
>
> Can the proposed implementation block signals for an unbounded amount
> of time? That would be bad.  ls.c attempts to avoid this problem, and
> 'diff' should too.

The change of color context is done per line (except in normal.c where
at the moment it is per hunk but I will change it to be per line) so the
most it can hang is the time needed to print a single line.


> Also, 'diff --color' needn't mess with signal handling unless isatty
> (fileno (outfile)).

Should this also be done if --color=always is used?

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 11 Mar 2015 00:07:02 GMT) Full text and rfc822 format available.

Message #29 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Tue, 10 Mar 2015 17:06:36 -0700
Giuseppe Scrivano wrote:
> the
> most it can hang is the time needed to print a single line.

There's no limit on line length though, right?  Other than available memory.  So 
this could be a problem.  ('ls' doesn't have a similar problem, if I understand 
it correctly, as OSes typically have reasonably short limits on file name length.)

>> >Also, 'diff --color' needn't mess with signal handling unless isatty
>> >(fileno (outfile)).
> Should this also be done if --color=always is used?

Yes.  diff needs to mess with signal handling only if both (1) diff is 
outputting colors and (2) the output is a terminal.  This is independent of why 
diff is doing (1).




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 11 Mar 2015 20:29:02 GMT) Full text and rfc822 format available.

Message #32 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Wed, 11 Mar 2015 20:58:15 +0100
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> There's no limit on line length though, right?  Other than available
> memory.  So this could be a problem.  ('ls' doesn't have a similar
> problem, if I understand it correctly, as OSes typically have
> reasonably short limits on file name length.)
>
>>> >Also, 'diff --color' needn't mess with signal handling unless isatty
>>> >(fileno (outfile)).
>> Should this also be done if --color=always is used?
>
> Yes.  diff needs to mess with signal handling only if both (1) diff is
> outputting colors and (2) the output is a terminal.  This is
> independent of why diff is doing (1).

ok, thanks for the explanation.  I've addressed your comments in the
version below.

Now I make sure that we don't write more than "max_chunk" bytes in
output_1_line and unblock/block the signals.

Regards,
Giuseppe


From 41b868be5adb8c3de2c57c235693d1f2ec426b12 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* bootstrap.conf (gnulib_modules): Add "sigprocmask".
* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Add calls to set_color_context.
* src/diff.h (enum colors): New enum to register the current color to use.
(enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Add calls to set_color_context
* src/side.c (print_1sdiff_line): Add calls to set_color_context.
* src/util.c (colors_enabled): New boolean variable.
(colors_enabled): New boolean variable.
(check_color_output): New function.
(begin_output): Call check_color_output every time the output file is changed.
(set_color_context): New function.  If colors are enabled, print the right
command for the terminal to change the color.
---
 bootstrap.conf     |   1 +
 doc/diffutils.texi |  20 +++++++++
 src/context.c      |   4 ++
 src/diff.c         |  30 +++++++++++++-
 src/diff.h         |  34 +++++++++++++++
 src/normal.c       |  20 +++++++--
 src/side.c         |  15 +++++++
 src/util.c         | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 8 files changed, 234 insertions(+), 10 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 9b2de22..63be732 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -61,6 +61,7 @@ readme-release
 regex
 sh-quote
 signal
+sigprocmask
 stat
 stat-macros
 stat-time
diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..92a7243 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,26 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+- Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+- Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+- Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..88ddfde 100644
--- a/src/context.c
+++ b/src/context.c
@@ -366,10 +366,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
+              set_color_context (DELETE);
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* Then output the inserted part. */
@@ -378,10 +380,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+              set_color_context (ADD);
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
diff --git a/src/diff.c b/src/diff.c
index ff28377..07fcb57 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+        case COLOR_OPTION:
+          specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -645,6 +653,9 @@ main (int argc, char **argv)
 	specify_style (OUTPUT_NORMAL);
     }
 
+  if (colors_style != NEVER && paginate)
+    error (EXIT_TROUBLE, 0, _("Cannot specify both --color and --paginate."));
+
   if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
     {
 #if (defined STAT_TIMESPEC || defined STAT_TIMESPEC_NS \
@@ -940,6 +951,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1021,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..0291fdd 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,36 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors
+{
+  /* Reset to the default color.  */
+  RESET,
+
+  /* Delete lines.  Show output in red.  */
+  DELETE,
+
+  /* Added lines.  Show them in green.  */
+  ADD,
+
+  /* Does not modify the context.  Use it to periodically process pending
+     signals.  */
+  SAME,
+};
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +113,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* True if colors are printed.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +423,4 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_color_context (enum colors);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..0d53033 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -56,14 +56,26 @@ print_normal_hunk (struct change *hunk)
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      for (i = first0; i <= last0; i++)
+        {
+          set_color_context (DELETE);
+          print_1_line ("<", &files[0].linbuf[i]);
+          set_color_context (RESET);
+        }
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      for (i = first1; i <= last1; i++)
+        {
+          set_color_context (ADD);
+          print_1_line (">", &files[1].linbuf[i]);
+          set_color_context (RESET);
+        }
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..e52454e 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..dcb3a59 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,7 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -153,6 +154,20 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+static bool output_is_tty;
+
+static void
+check_color_output (void)
+{
+  if (! outfile)
+    return;
+
+  output_is_tty = isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS)
+    || (colors_style == AUTO && output_is_tty);
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +328,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+            check_color_output ();
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +336,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+        check_color_output ();
 	free (command);
 #endif
       }
@@ -330,6 +347,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output ();
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +690,20 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  /* Try to avoid to block for too long and write more than MAX_CHUNK bytes before
+     checking for pending signals.  */
+  const size_t max_chunk = 4096;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t len = MIN (left, max_chunk);
+          fwrite (base, sizeof (char), len, outfile);
+          set_color_context (SAME);
+          left -= len;
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,6 +711,7 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t written = 0;
 
       while (t < limit)
 	switch ((c = *t++))
@@ -690,7 +721,9 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
 	      size_t spaces = tab_size - column % tab_size;
 	      column += spaces;
 	      do
-		putc (' ', out);
+                {
+                  written += putc (' ', out);
+                }
 	      while (--spaces);
 	    }
 	    break;
@@ -698,7 +731,7 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
 	  case '\r':
 	    putc (c, out);
 	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
+	      written += fprintf (out, flag_format, line_flag);
 	    column = 0;
 	    break;
 
@@ -706,17 +739,94 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
 	    if (column == 0)
 	      continue;
 	    column--;
-	    putc (c, out);
+	    written += putc (c, out);
 	    break;
 
 	  default:
 	    column += isprint (c) != 0;
-	    putc (c, out);
+	    written += putc (c, out);
+            if (written >= max_chunk)
+              {
+                set_color_context (SAME);
+                written = 0;
+              }
 	    break;
 	  }
     }
 }
 
+static sigset_t old_sigproc_set;
+void
+set_color_context (enum colors con)
+{
+  int j;
+  static sigset_t set;
+  static bool set_initialized;
+  static enum colors last_color_context = RESET;
+  static int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
+  if (!colors_enabled || (con == SAME && !output_is_tty))
+    return;
+
+  if (output_is_tty && !set_initialized)
+    {
+      sigemptyset (&set);
+      for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
+        sigaddset (&set, sig[j]);
+      set_initialized = true;
+    }
+
+repeat:
+  switch (con)
+    {
+    case DELETE:
+      if (output_is_tty)
+        sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
+      fprintf (outfile, "\x1B[31m");
+      break;
+
+    case ADD:
+      if (output_is_tty)
+        sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
+      fprintf (outfile, "\x1B[32m");
+      break;
+
+    case SAME:
+    case RESET:
+      fprintf (outfile, "\x1b[0m");
+      fflush (outfile);
+      if (output_is_tty)
+        sigprocmask (SIG_SETMASK, &old_sigproc_set, NULL);
+      if (con == SAME)
+        {
+          con = last_color_context;
+          goto repeat;
+        }
+      break;
+    }
+
+  last_color_context = con;
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 11 Mar 2015 23:19:01 GMT) Full text and rfc822 format available.

Message #35 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Wed, 11 Mar 2015 16:18:33 -0700
Giuseppe Scrivano wrote:

>   signal
> +sigprocmask
>   stat

Your mailer is inserting spaces at the start of lines, making the patch hard to 
read.  Perhaps attach the patch instead next time?

> +- Do not use color at all.  This is the default when no --color option
> +is present.

That leading "-" doesn't look right.  I'd remove it.  (Similarly elsewhere.)

+  if (! outfile)
+    return;
+
+  output_is_tty = isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS)
+    || (colors_style == AUTO && output_is_tty);

The indenting and parentheses should be something like this:

   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));

More important, don't call isatty unless it's needed, as isatty can be somewhat 
expensive on some hosts.  It's not needed if COLORS_STYLE == NEVER.

+            check_color_output ();
...
+        check_color_output ();
...
       outfile = stdout;
+      check_color_output ();

The first two calls to check_color_output do unnecessary work, since 'outfile' 
must be a pipe in that case, so there's no need to call isatty.  Only in the 
last case might isatty be needed.

+      size_t left = limit - base;
+      while (left)
+        {
+          size_t len = MIN (left, max_chunk);
+          fwrite (base, sizeof (char), len, outfile);
+          set_color_context (SAME);
+          left -= len;
+        }
+    }

I'm afraid this won't work in general, as set_color_context (SAME) sends bytes 
to stdout if stdout is a tty, whereas it shouldn't output anything in the normal 
case.  For example, it might try to change color in the middle of a multibyte 
character, and that's a no-no.

Also, I'm a bit dubious about all those calls to sigprocmask.  Can't we solve 
this without having to execute a sigmask-related system call for each buffer? 
How about using the method that 'ls' uses instead?  Install a signal handler 
that merely sets a static variable.  Perhaps the relevant 'ls' code should be 
Gnulib-ized, so that it can be shared between 'ls' and 'diff'.

+      fprintf (outfile, "\x1b[0m");
+      fflush (outfile);
+      if (output_is_tty)
+        sigprocmask (SIG_SETMASK, &old_sigproc_set, NULL);

No need to call fflush if output is not a tty.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 12 Mar 2015 09:15:01 GMT) Full text and rfc822 format available.

Message #38 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Thu, 12 Mar 2015 10:14:03 +0100
Hi Paul,

Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Also, I'm a bit dubious about all those calls to sigprocmask.  Can't
> we solve this without having to execute a sigmask-related system call
> for each buffer? How about using the method that 'ls' uses instead?
> Install a signal handler that merely sets a static variable.  Perhaps
> the relevant 'ls' code should be Gnulib-ized, so that it can be shared
> between 'ls' and 'diff'.

is it ok for now to keep these calls to sigprocmask and fix all the
other issues you reported (until the ls.c code is Gnulib-ized)?
They won't affect any existing use-case anyway as they will be used only
when --color is used on a tty.

Thanks for the detailed review.

Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 12 Mar 2015 14:46:01 GMT) Full text and rfc822 format available.

Message #41 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Thu, 12 Mar 2015 07:44:56 -0700
Giuseppe Scrivano wrote:
> is it ok for now to keep these calls to sigprocmask and fix all the
> other issues you reported (until the ls.c code is Gnulib-ized)?

Sure, if removing sigprocmask calls is merely an efficiency thing.  (It may be 
that the cost of the sigprocmask calls is not significant, so we can leave them 
in.)  Correctness is the bigger problem here.

Another possibility is to refuse to output colors to a terminal.  That would 
simplify a lot of things; we wouldn't need to worry about signals at all.

A third possibility is to have a signal handler that resets the terminal (using 
'write' not 'fwrite', to make it async-signal-safe).  That should remove the 
need for sigprocmask.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 12 Mar 2015 15:45:02 GMT) Full text and rfc822 format available.

Message #44 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Gisle Vanem <gvanem <at> yahoo.no>
To: bug-diffutils <at> gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH]
 diff: add support for --color
Date: Thu, 12 Mar 2015 09:26:47 +0100
Paul Eggert wrote:

> +      fprintf (outfile, "\x1b[0m");
> +      fflush (outfile);
> +      if (output_is_tty)
> +        sigprocmask (SIG_SETMASK, &old_sigproc_set, NULL);
>
> No need to call fflush if output is not a tty.

And what support do GnuLib have for parsing ANSI-codes
and showing colours on Windows? None I guess.

But I've written a simple ANSI-color decoder elsewhere I could
contribute. Or we could replace those "\x1b[0m" with calls to
SetConsoleTextAttribute() instead.

-- 
--gv




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 12 Mar 2015 21:19:01 GMT) Full text and rfc822 format available.

Message #47 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Thu, 12 Mar 2015 22:18:18 +0100
[Message part 1 (text/plain, inline)]
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Giuseppe Scrivano wrote:
>> is it ok for now to keep these calls to sigprocmask and fix all the
>> other issues you reported (until the ls.c code is Gnulib-ized)?
>
> Sure, if removing sigprocmask calls is merely an efficiency thing.
> (It may be that the cost of the sigprocmask calls is not significant,
> so we can leave them in.)  Correctness is the bigger problem here.

I've attached a new version where I leave sigprocmask when colors are
used on a tty.

Regards,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 68c9ac2ba4c3baf8ce3202c59fa931367a0ec215 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* bootstrap.conf (gnulib_modules): Add "sigprocmask" and "mbiter".
* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Add calls to set_color_context.
* src/diff.h (enum colors): New enum to register the current color to use.
(enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Add calls to set_color_context
* src/side.c (print_1sdiff_line): Add calls to set_color_context.
* src/util.c (colors_enabled): New boolean variable.
(colors_enabled): New boolean variable.
(check_color_output): New function.
(begin_output): Call check_color_output every time the output file is changed.
(set_color_context): New function.  If colors are enabled, print the right
command for the terminal to change the color.
---
 bootstrap.conf     |   2 +
 doc/diffutils.texi |  20 ++++++++
 src/context.c      |   4 ++
 src/diff.c         |  30 +++++++++++-
 src/diff.h         |  34 ++++++++++++++
 src/normal.c       |  20 ++++++--
 src/side.c         |  15 ++++++
 src/util.c         | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 8 files changed, 251 insertions(+), 10 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 9b2de22..4825401 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -51,6 +51,7 @@ largefile
 lstat
 maintainer-makefile
 manywarnings
+mbiter
 mbrtowc
 mkstemp
 mktime
@@ -61,6 +62,7 @@ readme-release
 regex
 sh-quote
 signal
+sigprocmask
 stat
 stat-macros
 stat-time
diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..0a2f1fc 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,26 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..88ddfde 100644
--- a/src/context.c
+++ b/src/context.c
@@ -366,10 +366,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
+              set_color_context (DELETE);
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* Then output the inserted part. */
@@ -378,10 +380,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+              set_color_context (ADD);
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+              set_color_context (RESET);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
diff --git a/src/diff.c b/src/diff.c
index ff28377..07fcb57 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+        case COLOR_OPTION:
+          specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -645,6 +653,9 @@ main (int argc, char **argv)
 	specify_style (OUTPUT_NORMAL);
     }
 
+  if (colors_style != NEVER && paginate)
+    error (EXIT_TROUBLE, 0, _("Cannot specify both --color and --paginate."));
+
   if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
     {
 #if (defined STAT_TIMESPEC || defined STAT_TIMESPEC_NS \
@@ -940,6 +951,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1021,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..0291fdd 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,36 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors
+{
+  /* Reset to the default color.  */
+  RESET,
+
+  /* Delete lines.  Show output in red.  */
+  DELETE,
+
+  /* Added lines.  Show them in green.  */
+  ADD,
+
+  /* Does not modify the context.  Use it to periodically process pending
+     signals.  */
+  SAME,
+};
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +113,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* True if colors are printed.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +423,4 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_color_context (enum colors);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..0d53033 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -56,14 +56,26 @@ print_normal_hunk (struct change *hunk)
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      for (i = first0; i <= last0; i++)
+        {
+          set_color_context (DELETE);
+          print_1_line ("<", &files[0].linbuf[i]);
+          set_color_context (RESET);
+        }
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      for (i = first1; i <= last1; i++)
+        {
+          set_color_context (ADD);
+          print_1_line (">", &files[1].linbuf[i]);
+          set_color_context (RESET);
+        }
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..e52454e 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..e7161d2 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,8 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+#include <mbiter.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -153,6 +155,21 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+static bool output_is_tty;
+
+static void
+check_color_output (bool is_pipe)
+{
+  if (! outfile)
+    return;
+
+  output_is_tty = (colors_style != NEVER && !is_pipe
+                   && isatty (fileno (outfile)));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +330,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+            check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +338,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+        check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +349,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +692,32 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  /* Try to avoid to block for too long and write more than MAX_CHUNK bytes before
+     checking for pending signals.  */
+  const size_t max_chunk = 4096;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      if (!colors_enabled || !output_is_tty)
+        {
+          fwrite (base, sizeof (char), limit - base, outfile);
+        }
+      else
+        {
+          size_t written = 0;
+          mbi_iterator_t iter;
+          for (mbi_init (iter, base, limit - base); mbi_avail (iter); mbi_advance (iter))
+            {
+              size_t mbi_len = mb_len (mbi_cur (iter));
+              fwrite (mbi_cur_ptr (iter), mbi_len, 1, outfile);
+              written += mbi_len;
+              if (written >= max_chunk)
+                {
+                  set_color_context (SAME);
+                  written = 0;
+                }
+            }
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,6 +725,7 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t written = 0;
 
       while (t < limit)
 	switch ((c = *t++))
@@ -690,7 +735,9 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
 	      size_t spaces = tab_size - column % tab_size;
 	      column += spaces;
 	      do
-		putc (' ', out);
+                {
+                  written += putc (' ', out);
+                }
 	      while (--spaces);
 	    }
 	    break;
@@ -698,7 +745,7 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
 	  case '\r':
 	    putc (c, out);
 	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
+	      written += fprintf (out, flag_format, line_flag);
 	    column = 0;
 	    break;
 
@@ -706,17 +753,96 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
 	    if (column == 0)
 	      continue;
 	    column--;
-	    putc (c, out);
+	    written += putc (c, out);
 	    break;
 
 	  default:
 	    column += isprint (c) != 0;
-	    putc (c, out);
+	    written += putc (c, out);
+            if (written >= max_chunk)
+              {
+                set_color_context (SAME);
+                written = 0;
+              }
 	    break;
 	  }
     }
 }
 
+static sigset_t old_sigproc_set;
+void
+set_color_context (enum colors con)
+{
+  int j;
+  static sigset_t set;
+  static bool set_initialized;
+  static enum colors last_color_context = RESET;
+  static int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
+  if (!colors_enabled || (con == SAME && !output_is_tty))
+    return;
+
+  if (output_is_tty && !set_initialized)
+    {
+      sigemptyset (&set);
+      for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
+        sigaddset (&set, sig[j]);
+      set_initialized = true;
+    }
+
+repeat:
+  switch (con)
+    {
+    case DELETE:
+      if (output_is_tty)
+        sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
+      fprintf (outfile, "\x1B[31m");
+      break;
+
+    case ADD:
+      if (output_is_tty)
+        sigprocmask (SIG_BLOCK, &set, &old_sigproc_set);
+      fprintf (outfile, "\x1B[32m");
+      break;
+
+    case SAME:
+    case RESET:
+      fprintf (outfile, "\x1b[0m");
+      if (output_is_tty)
+        {
+          fflush (outfile);
+          sigprocmask (SIG_SETMASK, &old_sigproc_set, NULL);
+        }
+      if (con == SAME)
+        {
+          con = last_color_context;
+          goto repeat;
+        }
+      break;
+    }
+
+  last_color_context = con;
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 12 Mar 2015 21:47:02 GMT) Full text and rfc822 format available.

Message #50 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Thu, 12 Mar 2015 14:46:28 -0700
On 03/12/2015 02:18 PM, Giuseppe Scrivano wrote:

> +mbiter

Ouch.  I was hoping we didn't need to do that.  I expect the use of 
mbiter to hurt performance significantly.  I hope there's a better way, 
one that doesn't require counting individual multibyte characters.  It 
may be time to think about having a signal handler that resets the 
output tty.

>            char const * const *line = &files[0].linbuf[i++];
> +              set_color_context (DELETE);

Please use the same indenting (if the original uses tabs, do that), so 
that it's easier to read the diffs.

> +    error (EXIT_TROUBLE, 0, _("Cannot specify both --color and 
--paginate."));

"cannot", not "Cannot", and no period at the end (it's not a sentence).

> +              fwrite (mbi_cur_ptr (iter), mbi_len, 1, outfile);
> +              written += mbi_len;

fwrite may write fewer than mbi_len characters.

> -        putc (' ', out);
> +                {
> +                  written += putc (' ', out);
> +                }

No need for {} here.  On the other hand, putc does not return 1-or-0 so 
this usage is problematic.

> +          written += fprintf (out, flag_format, line_flag);

fprintf might return -1.

> +        written += putc (c, out);
> ...
> +        written += putc (c, out);

Again, putc might return values other than 0 and 1.

> +  if (output_is_tty && !set_initialized)
> +    {
> +      sigemptyset (&set);
> +      for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
> +        sigaddset (&set, sig[j]);
> +      set_initialized = true;
> +    }

This stuff can be done once, by 'main', with no need for a
set_initialized var.

> +repeat:

Let's rephrase it without the goto; it'll be easier to understand that 
way, and just as efficient I expect.





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 13 Mar 2015 00:30:03 GMT) Full text and rfc822 format available.

Message #53 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Fri, 13 Mar 2015 01:29:09 +0100
[Message part 1 (text/plain, inline)]
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> On 03/12/2015 02:18 PM, Giuseppe Scrivano wrote:
>
>> +mbiter
>
> Ouch.  I was hoping we didn't need to do that.  I expect the use of
> mbiter to hurt performance significantly.  I hope there's a better
> way, one that doesn't require counting individual multibyte
> characters.  It may be time to think about having a signal handler
> that resets the output tty.

Thanks once again for your prompt and detailed review.

I have dropped that and implemented the solution you suggested: now the
signal handler directly write(2) to reset the tty if needed and then
restore the default handler.  It contributes to simplify the code in
util.c as the output throttling is not required anymore.

Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 45372bec9789e0ec3879c8710dc0e4c799dd0950 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* bootstrap.conf (gnulib_modules): Add "sigprocmask".
* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Add calls to set_color_context.
* src/diff.h (enum colors): New enum to register the current color to use.
(enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Call install_signal_handlers.  Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Add calls to set_color_context
* src/side.c (print_1sdiff_line): Add calls to set_color_context.
* src/util.c (colors_enabled): New boolean variable.
(colors_enabled): New boolean variable.
(check_color_output): New function.
(install_signal_handlers): New function.
(signal_handler): New function.
(begin_output): Call check_color_output every time the output file is changed.
(set_color_context): New function.  If colors are enabled, print the right
command for the terminal to change the color.
---
 bootstrap.conf     |   1 +
 doc/diffutils.texi |  20 +++++++++++
 src/context.c      |   4 +++
 src/diff.c         |  32 ++++++++++++++++-
 src/diff.h         |  31 +++++++++++++++++
 src/normal.c       |  20 ++++++++---
 src/side.c         |  15 ++++++++
 src/util.c         | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 218 insertions(+), 5 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 9b2de22..63be732 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -61,6 +61,7 @@ readme-release
 regex
 sh-quote
 signal
+sigprocmask
 stat
 stat-macros
 stat-time
diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..0a2f1fc 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,26 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..f07a581 100644
--- a/src/context.c
+++ b/src/context.c
@@ -366,10 +366,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
+	      set_color_context (DELETE, false);
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+	      set_color_context (RESET, false);
 	    }
 
 	  /* Then output the inserted part. */
@@ -378,10 +380,12 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_color_context (ADD, false);
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line (NULL, line);
+	      set_color_context (RESET, false);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
diff --git a/src/diff.c b/src/diff.c
index ff28377..637f716 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -285,6 +289,8 @@ main (int argc, char **argv)
   re_set_syntax (RE_SYNTAX_GREP | RE_NO_POSIX_BACKTRACKING);
   excluded = new_exclude ();
 
+  install_signal_handlers ();
+
   /* Decode the options.  */
 
   while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
@@ -627,6 +633,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+        case COLOR_OPTION:
+          specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -645,6 +655,9 @@ main (int argc, char **argv)
 	specify_style (OUTPUT_NORMAL);
     }
 
+  if (colors_style != NEVER && paginate)
+    error (EXIT_TROUBLE, 0, _("cannot specify both --color and --paginate"));
+
   if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
     {
 #if (defined STAT_TIMESPEC || defined STAT_TIMESPEC_NS \
@@ -940,6 +953,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1023,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..c18786e 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,32 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors
+{
+  /* Reset to the default color.  */
+  RESET,
+
+  /* Delete lines.  Show output in red.  */
+  DELETE,
+
+  /* Added lines.  Show them in green.  */
+  ADD,
+};
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +109,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* True if colors are printed.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +419,5 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_color_context (enum colors, bool force);
+extern void install_signal_handlers (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..41a97b9 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -56,14 +56,26 @@ print_normal_hunk (struct change *hunk)
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      for (i = first0; i <= last0; i++)
+        {
+          set_color_context (DELETE, false);
+          print_1_line ("<", &files[0].linbuf[i]);
+          set_color_context (RESET, false);
+        }
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      for (i = first1; i <= last1; i++)
+        {
+          set_color_context (ADD, false);
+          print_1_line (">", &files[1].linbuf[i]);
+          set_color_context (RESET, false);
+        }
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..4d04942 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE, false);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD, false);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET, false);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..4cbd402 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,7 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -153,6 +154,21 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+static bool output_is_tty;
+
+static void
+check_color_output (bool is_pipe)
+{
+  if (! outfile)
+    return;
+
+  output_is_tty = (colors_style != NEVER && !is_pipe
+                   && isatty (fileno (outfile)));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +329,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +337,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +348,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -717,6 +736,87 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+static void
+signal_handler (int signal)
+{
+  struct sigaction act;
+
+  if (output_is_tty)
+    set_color_context (RESET, true);
+
+  /* Restore the default handler, and report the signal again.  */
+  sigaction (signal, NULL, &act);
+  act.sa_handler = SIG_DFL;
+  sigaction (signal, &act, NULL);
+  raise (signal);
+}
+
+void
+install_signal_handlers (void)
+{
+  int j;
+  struct sigaction act;
+  int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
+  for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
+    {
+      sigaction (sig[j], NULL, &act);
+      if (act.sa_handler != SIG_IGN)
+        {
+          act.sa_handler = signal_handler;
+          sigaction (sig[j], &act, NULL);
+        }
+    }
+}
+
+void
+set_color_context (enum colors con, bool force)
+{
+  const char *const reset_sequence = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+  switch (con)
+    {
+    case DELETE:
+      fprintf (outfile, "\x1B[31m");
+      break;
+
+    case ADD:
+      fprintf (outfile, "\x1B[32m");
+      break;
+
+    case RESET:
+      if (! force)
+        fprintf (outfile, "%s", reset_sequence);
+      else
+        {
+          if (write (fileno (outfile), reset_sequence,
+                     strlen (reset_sequence)) < 0)
+            error (EXIT_TROUBLE, 0, "%s", _("write failed"));
+        }
+      break;
+    }
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 13 Mar 2015 01:09:01 GMT) Full text and rfc822 format available.

Message #56 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Thu, 12 Mar 2015 18:07:47 -0700
Giuseppe Scrivano wrote:

> +sigprocmask

This part of the change isn't needed any more.

> +        case COLOR_OPTION:
> +          specify_colors_style (optarg);
> +	  break;

Please be consistent about tabs vs spaces.

> +  if (colors_style != NEVER && paginate)
> +    error (EXIT_TROUBLE, 0, _("cannot specify both --color and --paginate"));

This part isn't needed any more, if I understand aright.

> +/* What kind of changes a hunk contains.  */
> +enum colors_style

The comment doesn't match the code.

> +/* True if colors are printed.  */
> +XTERN enum colors_style colors_style;

The comment doesn't match the code (it's not a boolean).

> -    for (i = first0; i <= last0; i++)
> -      print_1_line ("<", &files[0].linbuf[i]);
> +    {
> +      for (i = first0; i <= last0; i++)
> +        {
> +          set_color_context (DELETE, false);
> +          print_1_line ("<", &files[0].linbuf[i]);
> +          set_color_context (RESET, false);
> +        }
> +    }
> ...
>    if (changes & NEW)
> -    for (i = first1; i <= last1; i++)
> -      print_1_line (">", &files[1].linbuf[i]);
> +    {
> +      for (i = first1; i <= last1; i++)
> +        {
> +          set_color_context (ADD, false);
> +          print_1_line (">", &files[1].linbuf[i]);
> +          set_color_context (RESET, false);
> +        }
> +    }

No need for the outer braces (twice).

> +  /* Restore the default handler, and report the signal again.  */
> +  sigaction (signal, NULL, &act);
> +  act.sa_handler = SIG_DFL;
> +  sigaction (signal, &act, NULL);
> +  raise (signal);

This assumes sigaction, but GNU diff is portable to hosts that lack sigaction. 
Please see the code in sdiff.c, which uses sigaction only if available.  You may 
want to steal and/or librarize it, instead of using the ls.c-inspired code.

> +  const char *const reset_sequence = "\x1b[0m";

This should be:

   static char const reset_sequence[] = "\x1b[0m";

(I prefer putting the 'const' after the type, for consistency with "char *const 
*".)  That is, there's no need for the pointer variable here, and you can use 
'sizeof reset_sequence - 1' instead of 'strlen (reset_sequence)'.

> +void
> +set_color_context (enum colors con, bool force)

This would be a bit clearer as separate functions for each combination of 
arguments that are used.  In particular, the separate function that can be 
called from a signal handler should be commented as such, as it has more 
constraints on its actions.

> +      fprintf (outfile, "\x1B[31m");
> ...
> +      fprintf (outfile, "\x1B[32m");
> ...
> +        fprintf (outfile, "%s", reset_sequence);

Use fputs instead of fprintf.

> +          if (write (fileno (outfile), reset_sequence,
> +                     strlen (reset_sequence)) < 0)
> +            error (EXIT_TROUBLE, 0, "%s", _("write failed"));

We can't use 'fileno' or 'error' inside a signal handler; they're not 
async-signal-safe.  And we can't use 'outfile', as it's not volatile.

Instead, I suggest redoing begin_output so that 'outfile' is always stdout and 
its file descriptor is 1; that way, 'write' can use STDOUT_FILENO instead of 
fileno (stdout).  (You can use dup2 to arrange for this after the pipe calls.) 
Do not call 'error'; just return and let the signal handler re-raise the signal 
and exit.

Also, don't assume that 'write' will write out the whole string; it might be a 
partial write.

Also, what happens if a signal occurs when 'diff' is outputting an escape 
sequence?  E.g., suppose 'diff' is in the middle of outputting "\x1B[31m" and 
gets interrupted after the '[', so that it outputs "\x1B[\x1b[0m".  Is this 
guaranteed to reset the terminal?  If not, what sequence of bytes is guaranteed 
to reset the terminal color even if the sequence is output immediately after a 
truncated escape sequence?

> +static void
> +signal_handler (int signal)
> +{
> +  struct sigaction act;
> +
> +  if (output_is_tty)
> +    set_color_context (RESET, true);

Don't have the signal handler inspect output_is_tty, as that would mean 
output_is_tty would have to be volatile.  Instead, install the signal handler 
only if output_is_tty; that way, the handler itself can assume output_is_tty 
without having to check it.

Isn't signal-handling fun?

By the way, are you running "./configure --enable-gcc-warnings"?  If not, please 
do that.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 13 Mar 2015 01:25:02 GMT) Full text and rfc822 format available.

Message #59 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Thu, 12 Mar 2015 18:24:46 -0700
One other thing.  This:

+  sigaction (signal, NULL, &act);
+  act.sa_handler = SIG_DFL;
+  sigaction (signal, &act, NULL);

can be done more simply as 'signal (sig, SIG_DFL)' (assuming you change the 
local variable's name from 'signal' to 'sig').




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 13 Mar 2015 10:58:01 GMT) Full text and rfc822 format available.

Message #62 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Fri, 13 Mar 2015 11:57:11 +0100
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> We can't use 'fileno' or 'error' inside a signal handler; they're not
> async-signal-safe.  And we can't use 'outfile', as it's not volatile.
>
> Instead, I suggest redoing begin_output so that 'outfile' is always
> stdout and its file descriptor is 1; that way, 'write' can use
> STDOUT_FILENO instead of fileno (stdout).  (You can use dup2 to
> arrange for this after the pipe calls.) Do not call 'error'; just
> return and let the signal handler re-raise the signal and exit.

we can do it right now I think, as anyway we use signals only when we
are outputting to a terminal so it doesn't cover the pipe case.

> Also, what happens if a signal occurs when 'diff' is outputting an
> escape sequence?  E.g., suppose 'diff' is in the middle of outputting
> "\x1B[31m" and gets interrupted after the '[', so that it outputs
> "\x1B[\x1b[0m".  Is this guaranteed to reset the terminal?  If not,
> what sequence of bytes is guaranteed to reset the terminal color even
> if the sequence is output immediately after a truncated escape
> sequence?

I could not find any immediate answer, neither in the ncurses, but on
the terminals I have tried it is enough to reset even in the middle of
another sequence.  We could play safe and output it twice, but IMHO it
doesn't seem to be needed.

I'll send an updated version later today.

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 13 Mar 2015 18:09:01 GMT) Full text and rfc822 format available.

Message #65 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Fri, 13 Mar 2015 19:08:24 +0100
[Message part 1 (text/plain, inline)]
Giuseppe Scrivano <gscrivan <at> redhat.com> writes:

> I'll send an updated version later today.

and here it is, I've attached the patch.

Regards,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 749a7466adc192c230b1dde963be18a2c9f408e2 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Add calls to set_color_context.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Add calls to set_color_context
* src/side.c (print_1sdiff_line): Add calls to set_color_context.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(colors_enabled): New boolean variable.
(check_color_output): New function.
(install_signal_handlers): Likewise.
(signal_handler): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
---
 doc/diffutils.texi |  20 ++++++++++
 src/context.c      |  17 +++++++-
 src/diff.c         |  27 ++++++++++++-
 src/diff.h         |  19 +++++++++
 src/normal.c       |  12 +++++-
 src/side.c         |  15 +++++++
 src/util.c         | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 218 insertions(+), 4 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..0a2f1fc 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,26 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..65b7485 100644
--- a/src/context.c
+++ b/src/context.c
@@ -360,9 +360,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +383,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -385,7 +399,8 @@ pr_unidiff_hunk (struct change *hunk)
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
-
+          if (reset_context)
+            reset_color_context (false);
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index ff28377..be6e6e3 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..abecf2b 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,6 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (bool from_signal);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..6fa48be 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -57,7 +57,11 @@ print_normal_hunk (struct change *hunk)
   /* Print the lines that the first file has.  */
   if (changes & OLD)
     for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+      {
+        set_delete_color_context ();
+        print_1_line ("<", &files[0].linbuf[i]);
+        reset_color_context (false);
+      }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
@@ -65,5 +69,9 @@ print_normal_hunk (struct change *hunk)
   /* Print the lines that the second file has.  */
   if (changes & NEW)
     for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+      {
+        set_add_color_context ();
+        print_1_line (">", &files[1].linbuf[i]);
+        reset_color_context (false);
+      }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..8327d11 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context (false);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..133e42b 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,7 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +144,59 @@ print_message_queue (void)
     }
 }
 
+static void
+signal_handler (int sig)
+{
+  reset_color_context (true);
+
+  /* Restore the default handler, and report the signal again.  */
+  signal (sig, SIG_DFL);
+  raise (sig);
+}
+
+static void
+install_signal_handlers (void)
+{
+  int j;
+#if HAVE_SIGACTION
+  struct sigaction act;
+#endif
+  int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
+  for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
+    {
+#if HAVE_SIGACTION
+      sigaction (sig[j], NULL, &act);
+      if (act.sa_handler != SIG_IGN)
+        {
+          act.sa_handler = signal_handler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+      signal (sig[j], signal_handler);
+#endif
+    }
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -153,6 +207,24 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+static bool output_is_tty;
+
+static void
+check_color_output (bool is_pipe)
+{
+  if (! outfile)
+    return;
+
+  output_is_tty = (colors_style != NEVER && !is_pipe
+                   && isatty (fileno (outfile)));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +385,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +393,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +404,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -717,6 +792,43 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+void
+set_add_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[32m");
+}
+
+void
+set_delete_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[31m");
+}
+
+void
+reset_color_context (bool from_signal)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  if (! from_signal)
+    fputs (reset_sequence, outfile);
+  else
+    {
+      size_t written = 0;
+      while (written < sizeof reset_sequence - 1)
+        {
+          int ret = write (STDOUT_FILENO, reset_sequence + written,
+                           sizeof reset_sequence - 1 - written);
+          if (ret < 0)
+            return;
+          written += ret;
+        }
+    }
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 14 Mar 2015 19:00:04 GMT) Full text and rfc822 format available.

Message #68 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Andreas Grünbacher <agruen <at> gnu.org>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Sat, 14 Mar 2015 19:59:52 +0100
Giuseppe,

2015-03-13 19:08 GMT+01:00 Giuseppe Scrivano <gscrivan <at> redhat.com>:
> Giuseppe Scrivano <gscrivan <at> redhat.com> writes:
>
>> I'll send an updated version later today.

here are some more comments:

 * Please have a look at your changes to doc/diffutils.texi, they make no sense.

 * colors_style is never initialized and defaults to NEVER; it should
default to AUTO.

 * Please colorize the output of non-unified diffs as well.

 * Please use one of the common existing color schemes, not a new one -- for
   example, the one used by vim or "git diff". Those colorize patch
header lines as well.

Thanks,
Andreas




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 14 Mar 2015 20:30:04 GMT) Full text and rfc822 format available.

Message #71 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Andreas Grünbacher <agruen <at> gnu.org>, 
 Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Sat, 14 Mar 2015 13:29:42 -0700
Andreas Grünbacher wrote:
>   * colors_style is never initialized and defaults to NEVER; it should
> default to AUTO.

'grep' and 'ls' default to the equivalent of NEVER; shouldn't 'diff' do the same?




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 14 Mar 2015 20:56:02 GMT) Full text and rfc822 format available.

Message #74 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Andreas Grünbacher <agruen <at> gnu.org>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Giuseppe Scrivano <gscrivan <at> redhat.com>, 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Sat, 14 Mar 2015 21:55:03 +0100
2015-03-14 21:29 GMT+01:00 Paul Eggert <eggert <at> cs.ucla.edu>:
> 'grep' and 'ls' default to the equivalent of NEVER; shouldn't 'diff' do the
> same?

I think color terminals are the standard nowadays, but if you think
that the default should be 'never', I could live with that. In any
case, the program help text is wrong, too:

>      --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',
>                               or 'auto' (the default)

The default for WHEN is still 'always', but when the option is not
used at all, the default is either 'auto' (my preference) or 'never'.
Confusing ...

Thanks,
Andreas




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 15 Mar 2015 20:20:03 GMT) Full text and rfc822 format available.

Message #77 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Andreas Grünbacher <agruen <at> gnu.org>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Sun, 15 Mar 2015 21:19:22 +0100
Andreas Grünbacher <agruen <at> gnu.org> writes:

> Giuseppe,
>
> 2015-03-13 19:08 GMT+01:00 Giuseppe Scrivano <gscrivan <at> redhat.com>:
>> Giuseppe Scrivano <gscrivan <at> redhat.com> writes:
>>
>>> I'll send an updated version later today.
>
> here are some more comments:
>
>  * Please have a look at your changes to doc/diffutils.texi, they make no sense.

I've basically copied this section from coreutils.  What doesn't make
sense to you?

Thanks,
Giuseppe






Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 15 Mar 2015 21:07:02 GMT) Full text and rfc822 format available.

Message #80 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Andreas Grünbacher <agruen <at> gnu.org>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Sun, 15 Mar 2015 22:06:16 +0100
Andreas Grünbacher <agruen <at> gnu.org> writes:

> 2015-03-14 21:29 GMT+01:00 Paul Eggert <eggert <at> cs.ucla.edu>:
>> 'grep' and 'ls' default to the equivalent of NEVER; shouldn't 'diff' do the
>> same?
>
> I think color terminals are the standard nowadays, but if you think
> that the default should be 'never', I could live with that. In any
> case, the program help text is wrong, too:
>
>>      --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',
>>                               or 'auto' (the default)
>
> The default for WHEN is still 'always', but when the option is not
> used at all, the default is either 'auto' (my preference) or 'never'.
> Confusing ...

I think the default for --color should be auto.  Outputting stdout to a
file happens more often with diff than with ls and having the colors
commands in a diff file is to be avoided as much as possible (if people
really want it, then they can force it with always).

grep seems to default to "auto" as well when --color is specified.

Regards,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 15 Mar 2015 23:50:02 GMT) Full text and rfc822 format available.

Message #83 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Norihiro Tanaka <noritnk <at> kcn.ne.jp>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Andreas Grunbacher <agruen <at> gnu.org>, 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Mon, 16 Mar 2015 08:49:29 +0900
Giuseppe Scrivano <gscrivan <at> redhat.com> wrote:

> I think the default for --color should be auto.  Outputting stdout to a
> file happens more often with diff than with ls and having the colors
> commands in a diff file is to be avoided as much as possible (if people
> really want it, then they can force it with always).

I disagree.  I think we should not make difference for default among
diff, ls and grep, and they should not also be neither AUTO nor ALWAYS.

AUTO and ALWAYS are POSIX incompatible, as they change output in byte
level.  So I think that if only users and/or distributors expect it,
they should manually change color option into AUTO or ALWAYS.

> grep seems to default to "auto" as well when --color is specified.

However, grep will suffer from the disadvantage of performance down with
--color option.





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 15 Mar 2015 23:55:01 GMT) Full text and rfc822 format available.

Message #86 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Norihiro Tanaka <noritnk <at> kcn.ne.jp>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Andreas Grunbacher <agruen <at> gnu.org>, 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Mon, 16 Mar 2015 08:54:09 +0900
On Mon, 16 Mar 2015 08:49:29 +0900
Norihiro Tanaka <noritnk <at> kcn.ne.jp> wrote:

> AUTO and ALWAYS are POSIX incompatible,

In addition, if the default is AUTO, and they are also imcompatible with
previous versions of DIFF by default.





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 16 Mar 2015 00:57:02 GMT) Full text and rfc822 format available.

Message #89 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Norihiro Tanaka <noritnk <at> kcn.ne.jp>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, Andreas Grunbacher <agruen <at> gnu.org>,
 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Mon, 16 Mar 2015 01:56:36 +0100
[Message part 1 (text/plain, inline)]
Norihiro Tanaka <noritnk <at> kcn.ne.jp> writes:

> On Mon, 16 Mar 2015 08:49:29 +0900
> Norihiro Tanaka <noritnk <at> kcn.ne.jp> wrote:
>
>> AUTO and ALWAYS are POSIX incompatible,
>
> In addition, if the default is AUTO, and they are also imcompatible with
> previous versions of DIFF by default.

I meant the default if --color is specified.  That is not incompatible
with any previous version since --color was not present.

Apparently, ls and grep already behaves differently for --color so diff
is not going to make any other difference here.  "ls --color" means "ls
--color=always" while "grep --color" behaves like "grep --color=auto".
Having to choose between these two, --color=auto makes more sense for
diff where redirection to files is a common operation.

I am attaching the latest version of the patch, taking into account
comments from Andreas.  I have added colors for header and line numbers,
following the "git diff" colors scheme.

Thanks,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 2d7ae3ebba0eaeda9d8f1e2fca6e8646ec187334 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(colors_enabled): New boolean variable.
(check_color_output): New function.
(install_signal_handlers): Likewise.
(signal_handler): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
---
 doc/diffutils.texi |  20 +++++++++
 src/context.c      |  51 ++++++++++++++++++----
 src/diff.c         |  27 +++++++++++-
 src/diff.h         |  21 +++++++++
 src/normal.c       |  18 ++++++--
 src/side.c         |  15 +++++++
 src/util.c         | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 264 insertions(+), 14 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..0a2f1fc 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,26 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
+may be omitted, or one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..9b23d36 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_header_color_context ();
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  reset_color_context (false);
 }
 
 /* Print an edit script in context format.  */
@@ -215,6 +217,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,12 +228,18 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_delete_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            reset_color_context (false);
 	}
     }
 
@@ -244,6 +253,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +264,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_add_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            reset_color_context (false);
 	}
     }
 }
@@ -330,11 +345,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_line_numbers_color_context ();
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  reset_color_context (false);
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +377,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +400,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -386,6 +417,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            reset_color_context (false);
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index ff28377..be6e6e3 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..553bad1 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,8 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_header_color_context (void);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (bool from_signal);
+extern void set_line_numbers_color_context (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..0ce01ee 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_line_numbers_color_context ();
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  reset_color_context (false);
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_delete_color_context ();
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      reset_color_context (false);
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_add_color_context ();
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      reset_color_context (false);
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..8327d11 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context (false);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..0f8f7a8 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,7 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +144,59 @@ print_message_queue (void)
     }
 }
 
+static void
+signal_handler (int sig)
+{
+  reset_color_context (true);
+
+  /* Restore the default handler, and report the signal again.  */
+  signal (sig, SIG_DFL);
+  raise (sig);
+}
+
+static void
+install_signal_handlers (void)
+{
+  int j;
+#if HAVE_SIGACTION
+  struct sigaction act;
+#endif
+  int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
+  for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
+    {
+#if HAVE_SIGACTION
+      sigaction (sig[j], NULL, &act);
+      if (act.sa_handler != SIG_IGN)
+        {
+          act.sa_handler = signal_handler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+      signal (sig[j], signal_handler);
+#endif
+    }
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -153,6 +207,24 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+static bool output_is_tty;
+
+static void
+check_color_output (bool is_pipe)
+{
+  if (! outfile)
+    return;
+
+  output_is_tty = (colors_style != NEVER && !is_pipe
+                   && isatty (fileno (outfile)));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +385,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +393,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +404,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -717,6 +792,57 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+void
+set_header_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[1;39m");
+}
+
+void
+set_line_numbers_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[36m");
+}
+
+void
+set_add_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[32m");
+}
+
+void
+set_delete_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[31m");
+}
+
+void
+reset_color_context (bool from_signal)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  if (! from_signal)
+    fputs (reset_sequence, outfile);
+  else
+    {
+      size_t written = 0;
+      while (written < sizeof reset_sequence - 1)
+        {
+          int ret = write (STDOUT_FILENO, reset_sequence + written,
+                           sizeof reset_sequence - 1 - written);
+          if (ret < 0)
+            return;
+          written += ret;
+        }
+    }
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 16 Mar 2015 13:57:02 GMT) Full text and rfc822 format available.

Message #92 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Norihiro Tanaka <noritnk <at> kcn.ne.jp>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, Andreas Grunbacher <agruen <at> gnu.org>,
 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Mon, 16 Mar 2015 22:56:49 +0900
On Mon, 16 Mar 2015 01:56:36 +0100
Giuseppe Scrivano <gscrivan <at> redhat.com> wrote:

> Norihiro Tanaka <noritnk <at> kcn.ne.jp> writes:
> 
> > On Mon, 16 Mar 2015 08:49:29 +0900
> > Norihiro Tanaka <noritnk <at> kcn.ne.jp> wrote:
> >
> >> AUTO and ALWAYS are POSIX incompatible,
> >
> > In addition, if the default is AUTO, and they are also imcompatible with
> > previous versions of DIFF by default.
> 
> I meant the default if --color is specified.  That is not incompatible
> with any previous version since --color was not present.
> 
> Apparently, ls and grep already behaves differently for --color so diff
> is not going to make any other difference here.  "ls --color" means "ls
> --color=always" while "grep --color" behaves like "grep --color=auto".
> Having to choose between these two, --color=auto makes more sense for
> diff where redirection to files is a common operation.
> 
> I am attaching the latest version of the patch, taking into account
> comments from Andreas.  I have added colors for header and line numbers,
> following the "git diff" colors scheme.
> 
> Thanks,
> Giuseppe

I understood that you said, agree.

Thanks,
Norihiro





Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 16 Mar 2015 16:22:03 GMT) Full text and rfc822 format available.

Message #95 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Andreas Grünbacher <agruen <at> gnu.org>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Mon, 16 Mar 2015 17:21:40 +0100
2015-03-15 21:19 GMT+01:00 Giuseppe Scrivano <gscrivan <at> redhat.com>:
> I've basically copied this section from coreutils.  What doesn't make
> sense to you?

+@item --color [=@var{when}]
+@cindex color, distinguishing file types with
+Specify whether to use color for distinguishing file types.  @var{when}
[...]

Color is used to distinguish between file types in ls, but not in
diff. Simply reading what you've copied from coreutils would have been
enough to figure that out.

Andreas




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 16 Mar 2015 18:31:03 GMT) Full text and rfc822 format available.

Message #98 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Andreas Grünbacher <agruen <at> gnu.org>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Mon, 16 Mar 2015 19:30:27 +0100
Andreas Grünbacher <agruen <at> gnu.org> writes:

> 2015-03-15 21:19 GMT+01:00 Giuseppe Scrivano <gscrivan <at> redhat.com>:
>> I've basically copied this section from coreutils.  What doesn't make
>> sense to you?
>
> +@item --color [=@var{when}]
> +@cindex color, distinguishing file types with
> +Specify whether to use color for distinguishing file types.  @var{when}
> [...]
>
> Color is used to distinguish between file types in ls, but not in
> diff. Simply reading what you've copied from coreutils would have been
> enough to figure that out.

I see, sorry for that.  I copied it straight without paying much
attention, I had to be more careful.

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 18 Mar 2015 23:39:02 GMT) Full text and rfc822 format available.

Message #101 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Thu, 19 Mar 2015 00:38:15 +0100
[Message part 1 (text/plain, inline)]
Hi Paul,

I've attached the last version of the patch.  Is there anything left
that should be done?

Thanks,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 8b39f7297c0137af62336ad79044271803b66299 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(colors_enabled): New boolean variable.
(check_color_output): New function.
(install_signal_handlers): Likewise.
(signal_handler): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
---
 doc/diffutils.texi |  21 +++++++++
 src/context.c      |  51 ++++++++++++++++++----
 src/diff.c         |  27 +++++++++++-
 src/diff.h         |  21 +++++++++
 src/normal.c       |  18 ++++++--
 src/side.c         |  15 +++++++
 src/util.c         | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 265 insertions(+), 14 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 3e25807..dd5a1c6 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3745,6 +3745,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..9b23d36 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_header_color_context ();
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  reset_color_context (false);
 }
 
 /* Print an edit script in context format.  */
@@ -215,6 +217,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,12 +228,18 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_delete_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            reset_color_context (false);
 	}
     }
 
@@ -244,6 +253,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +264,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_add_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            reset_color_context (false);
 	}
     }
 }
@@ -330,11 +345,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_line_numbers_color_context ();
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  reset_color_context (false);
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +377,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +400,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -386,6 +417,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            reset_color_context (false);
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index ff28377..be6e6e3 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..553bad1 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,8 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_header_color_context (void);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (bool from_signal);
+extern void set_line_numbers_color_context (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..0ce01ee 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_line_numbers_color_context ();
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  reset_color_context (false);
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_delete_color_context ();
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      reset_color_context (false);
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_add_color_context ();
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      reset_color_context (false);
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..8327d11 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context (false);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..0f8f7a8 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,7 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +144,59 @@ print_message_queue (void)
     }
 }
 
+static void
+signal_handler (int sig)
+{
+  reset_color_context (true);
+
+  /* Restore the default handler, and report the signal again.  */
+  signal (sig, SIG_DFL);
+  raise (sig);
+}
+
+static void
+install_signal_handlers (void)
+{
+  int j;
+#if HAVE_SIGACTION
+  struct sigaction act;
+#endif
+  int const sig[] =
+    {
+      SIGTSTP,
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+
+  for (j = 0; j < sizeof (sig) / sizeof (*sig); j++)
+    {
+#if HAVE_SIGACTION
+      sigaction (sig[j], NULL, &act);
+      if (act.sa_handler != SIG_IGN)
+        {
+          act.sa_handler = signal_handler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+      signal (sig[j], signal_handler);
+#endif
+    }
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -153,6 +207,24 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+static bool output_is_tty;
+
+static void
+check_color_output (bool is_pipe)
+{
+  if (! outfile)
+    return;
+
+  output_is_tty = (colors_style != NEVER && !is_pipe
+                   && isatty (fileno (outfile)));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +385,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +393,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +404,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -717,6 +792,57 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+void
+set_header_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[1;39m");
+}
+
+void
+set_line_numbers_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[36m");
+}
+
+void
+set_add_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[32m");
+}
+
+void
+set_delete_color_context (void)
+{
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[31m");
+}
+
+void
+reset_color_context (bool from_signal)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  if (! from_signal)
+    fputs (reset_sequence, outfile);
+  else
+    {
+      size_t written = 0;
+      while (written < sizeof reset_sequence - 1)
+        {
+          int ret = write (STDOUT_FILENO, reset_sequence + written,
+                           sizeof reset_sequence - 1 - written);
+          if (ret < 0)
+            return;
+          written += ret;
+        }
+    }
+}
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.1.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 28 Mar 2015 10:14:01 GMT) Full text and rfc822 format available.

Message #104 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: 20062 <at> debbugs.gnu.org
Cc: Paul Eggert <eggert <at> cs.ucla.edu>
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Sat, 28 Mar 2015 11:13:35 +0100
Giuseppe Scrivano <gscrivan <at> redhat.com> writes:

> I've attached the last version of the patch.  Is there anything left
> that should be done?

Is there anything holding this version of the patch?

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 29 Mar 2015 01:38:02 GMT) Full text and rfc822 format available.

Message #107 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Sat, 28 Mar 2015 18:37:14 -0700
Giuseppe Scrivano wrote:
> Is there anything holding this version of the patch?

Sorry, I haven't had time to review it carefully yet.  It's definitely on my 
list of things to do.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 20 Apr 2015 20:32:02 GMT) Full text and rfc822 format available.

Message #110 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 20 Apr 2015 22:30:54 +0200
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Sorry, I haven't had time to review it carefully yet.  It's definitely
> on my list of things to do.

Nobody else is entitled/interested in the review for this patch?

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 20 Apr 2015 22:55:01 GMT) Full text and rfc822 format available.

Message #113 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 20 Apr 2015 15:54:12 -0700
Giuseppe Scrivano wrote:
> Nobody else is entitled/interested in the review for this patch?

Apparently nobody else is interested right now, yes.  (I don't know what 
"entitled" means; it's public info that anybody can review.)




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 22 Apr 2015 21:00:03 GMT) Full text and rfc822 format available.

Message #116 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Wed, 22 Apr 2015 22:59:39 +0200
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Giuseppe Scrivano wrote:
>> Nobody else is entitled/interested in the review for this patch?
>
> Apparently nobody else is interested right now, yes.  (I don't know
> what "entitled" means; it's public info that anybody can review.)

someone who can decide if the patch is fine or not.  Everyone can review
it and express an opinion, but not decide about its acceptance.

Regards,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 22 Apr 2015 23:01:01 GMT) Full text and rfc822 format available.

Message #119 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Wed, 22 Apr 2015 16:00:23 -0700
Giuseppe Scrivano wrote:
> Everyone can review
> it and express an opinion, but not decide about its acceptance.

Ah, that would be one of the maintainers, either Jim Meyering or me.  We're both 
busy guys, I'm afraid, and this is not a high-priority item.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 23 Apr 2015 08:01:02 GMT) Full text and rfc822 format available.

Message #122 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Gisle Vanem <gvanem <at> yahoo.no>
To: bug-diffutils <at> gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add
 support for --color
Date: Thu, 23 Apr 2015 10:00:12 +0200
Giuseppe Scrivano wrote:

>> Giuseppe Scrivano wrote:
>>> Nobody else is entitled/interested in the review for this patch?

I'm very interested, but I'm on Windows where your use of ANSI-codes
are no good. I've fixed that in my private version.

> someone who can decide if the patch is fine or not.  Everyone can review
> it and express an opinion, but not decide about its acceptance.

Could fix this?
  util.c(166) : error C2065: 'SIGTSTP' : undeclared identifier
  util.c(167) : error C2065: 'SIGALRM' : undeclared identifier
  util.c(167) : error C2065: 'SIGQUIT' : undeclared identifier

MSVC/MingW (with Gnulib) doesn't have these signals.


-- 
--gv




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 19 Jun 2015 13:41:03 GMT) Full text and rfc822 format available.

Message #125 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 19 Jun 2015 15:40:07 +0200
Hi Paul,

Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Giuseppe Scrivano wrote:
>> Everyone can review
>> it and express an opinion, but not decide about its acceptance.
>
> Ah, that would be one of the maintainers, either Jim Meyering or me.
> We're both busy guys, I'm afraid, and this is not a high-priority
> item.

I agree it is not a high-priority item but it was delayed for almost 3
months now.

Is there any hope that this patch is going to be reviewed?

Regards,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 20 Jun 2015 00:25:03 GMT) Full text and rfc822 format available.

Message #128 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 19 Jun 2015 17:24:43 -0700
Giuseppe Scrivano wrote:
> Is there any hope that this patch is going to be reviewed?

Yes, though not right now.  My schedule doesn't free up for at least a week.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 07:03:01 GMT) Full text and rfc822 format available.

Message #131 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: 20062 <at> debbugs.gnu.org
Cc: Paul Eggert <eggert <at> cs.ucla.edu>
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 11 Sep 2015 09:02:34 +0200
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Giuseppe Scrivano wrote:
>> Is there any hope that this patch is going to be reviewed?
>
> Yes, though not right now.  My schedule doesn't free up for at least a week.

Can someone please review this patch?  It has been waiting for 6 months now...

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 07:34:01 GMT) Full text and rfc822 format available.

Message #134 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 11 Sep 2015 00:33:00 -0700
I finally got around to looking at it.  Doesn't it have the problem that if you 
type Control-C while it is in the middle of outputting a change-color sequence, 
your terminal may end up in a strange state?  I thought there was a standard way 
in other GNU programs to deal with this, but am not familiar with the topic.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 09:27:01 GMT) Full text and rfc822 format available.

Message #137 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Gisle Vanem <gvanem <at> yahoo.no>
To: bug-diffutils <at> gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Fri, 11 Sep 2015 11:26:13 +0200
Paul Eggert wrote:

> I finally got around to looking at it.  Doesn't it have the problem that if you type Control-C while it is in the middle
> of outputting a change-color sequence, your terminal may end up in a strange state?

No. He added a 'reset_color_context (true);' inside
'signal_handler()'. I've applied the colour-patches + my own WinCon
patches to get colours on Windows. It has worked fine for 5 mounths.

Reading diff-output with colours if so much more plesant than
moin-chrome. Please add the patches ASAP.

-- 
--gv




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 15:29:01 GMT) Full text and rfc822 format available.

Message #140 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Gisle Vanem <gvanem <at> yahoo.no>, 20062 <at> debbugs.gnu.org
Cc: Giuseppe Scrivano <gscrivan <at> redhat.com>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Fri, 11 Sep 2015 08:28:04 -0700
Gisle Vanem wrote:
> No. He added a 'reset_color_context (true);' inside
> 'signal_handler()'.

Ah, sorry, I was looking at the original edition of the patch.

I just now looked at <http://bugs.gnu.org/20062#101> and still see some 
problems.  Mainly, it doesn't handle SIGTSTP properly.  With SIGTSTP an 
application is supposed to reset the terminal and then resignal itself with 
SIGSTOP before really stopping.  When restarting the application then needs to 
restore the terminal to the current state, if the current state is not the 
default.  This is nearly impossible to do correctly with the proposed design.  I 
suggest looking at how GNU ls does it: the signal handler merely sets a flag, 
and the application checks occasionally at safe places whether a signal has arrived.

A nit: when installing signals via 'signal', don't install a handler if 'signal' 
says the signal is being ignored.

Also, copyright papers need to be filed for diffutils before a nontrivial patch 
like this can be installed.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 16:42:02 GMT) Full text and rfc822 format available.

Message #143 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 11 Sep 2015 18:41:01 +0200
Hi Paul,

Paul Eggert <eggert <at> cs.ucla.edu> writes:

> I finally got around to looking at it.  Doesn't it have the problem
> that if you type Control-C while it is in the middle of outputting a
> change-color sequence, your terminal may end up in a strange state?  I
> thought there was a standard way in other GNU programs to deal with
> this, but am not familiar with the topic.

we are dealing with the issue in the last version of the patch, and that
was the main issue reported and that took a while to get addressed :)

No, unfortunately there is not a standard way to deal with it.  Last
time I looked at it, grep was suffering from this issue (Ctrl-C in the
middle of a color sequence) would leave the terminal in a funny state.

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 16:57:02 GMT) Full text and rfc822 format available.

Message #146 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 11 Sep 2015 09:56:25 -0700
Giuseppe Scrivano wrote:
> unfortunately there is not a standard way to deal with it.  Last
> time I looked at it, grep was suffering from this issue (Ctrl-C in the
> middle of a color sequence) would leave the terminal in a funny state.

That's a bug in grep, and I just now filed a bug report for it here:

http://bugs.gnu.org/21461

Admittedly it is a pain to handle signals correctly, but as long as we're adding 
the feature to diff we should do it right.  I suggest looking at how GNU 'ls' 
does it; 'ls' is reasonably bullet-proof against the problem.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 21:48:02 GMT) Full text and rfc822 format available.

Message #149 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Gisle Vanem <gvanem <at> yahoo.no>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: [PATCH] diff:
 add support for --color
Date: Fri, 11 Sep 2015 23:47:46 +0200
Hi Paul,

thanks for the review:

Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Gisle Vanem wrote:
>> No. He added a 'reset_color_context (true);' inside
>> 'signal_handler()'.
>
> Ah, sorry, I was looking at the original edition of the patch.
>
> I just now looked at <http://bugs.gnu.org/20062#101> and still see
> some problems.  Mainly, it doesn't handle SIGTSTP properly.  With
> SIGTSTP an application is supposed to reset the terminal and then
> resignal itself with SIGSTOP before really stopping.  When restarting
> the application then needs to restore the terminal to the current
> state, if the current state is not the default.  This is nearly
> impossible to do correctly with the proposed design.  I suggest
> looking at how GNU ls does it: the signal handler merely sets a flag,
> and the application checks occasionally at safe places whether a
> signal has arrived.

IIRC we agreed on this design so that we react immediately to signals
without blocking (now the most we can block is to write the reset
sequence, but we can't cut this), blocking signals was done in an older
version of the patch.

I've tried to handle SIGTSTP with this addition and everything seems to
work as expected, I'll test it more before propose a new version of the
patch.

Do you see any issue with it?

Regards,
Giuseppe


diff --git a/src/util.c b/src/util.c
index 0f8f7a8..7f11318 100644
--- a/src/util.c
+++ b/src/util.c
@@ -28,6 +28,8 @@
 
 char const pr_program[] = PR_PROGRAM;
 
+static const char *last_context_str;
+
 /* Queue up one-line messages to be printed at the end,
    when -l is specified.  Each message is recorded with a 'struct msg'.  */
 
@@ -145,8 +147,35 @@ print_message_queue (void)
 }
 
 static void
+safe_write_from_signal (const char *str, size_t len)
+{
+  size_t written = 0;
+  while (written < len)
+    {
+      int ret = write (STDOUT_FILENO, str + written,
+                       len - written);
+      if (ret < 0)
+        return;
+      written += ret;
+    }
+}
+
+static void
 signal_handler (int sig)
 {
+  if (sig == SIGCONT)
+    {
+      if (last_context_str)
+        safe_write_from_signal (last_context_str, strlen (last_context_str));
+      return;
+    }
+  if (sig == SIGTSTP)
+    {
+      reset_color_context (true);
+      raise (SIGSTOP);
+      return;
+    }
+
   reset_color_context (true);
 
   /* Restore the default handler, and report the signal again.  */
@@ -163,6 +192,7 @@ install_signal_handlers (void)
 #endif
   int const sig[] =
     {
+      SIGCONT,
       SIGTSTP,
       SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
 #ifdef SIGPOLL
@@ -796,28 +826,40 @@ void
 set_header_color_context (void)
 {
   if (colors_enabled)
-    fprintf (outfile, "\x1B[1;39m");
+    {
+      last_context_str = "\x1B[1;39m";
+      fprintf (outfile, "%s", last_context_str);
+    }
 }
 
 void
 set_line_numbers_color_context (void)
 {
   if (colors_enabled)
-    fprintf (outfile, "\x1B[36m");
+    {
+      last_context_str = "\x1B[36m";
+      fprintf (outfile, "%s", last_context_str);
+    }
 }
 
 void
 set_add_color_context (void)
 {
   if (colors_enabled)
-    fprintf (outfile, "\x1B[32m");
+    {
+      last_context_str = "\x1B[32m";
+      fprintf (outfile, "%s", last_context_str);
+    }
 }
 
 void
 set_delete_color_context (void)
 {
   if (colors_enabled)
-    fprintf (outfile, "\x1B[31m");
+    {
+      last_context_str = "\x1B[31m";
+      fprintf (outfile, "%s", last_context_str);
+    }
 }
 
 void
@@ -827,19 +869,12 @@ reset_color_context (bool from_signal)
   if (! colors_enabled)
     return;
 
-  if (! from_signal)
-    fputs (reset_sequence, outfile);
+  if (from_signal)
+    safe_write_from_signal (reset_sequence, sizeof reset_sequence - 1);
   else
     {
-      size_t written = 0;
-      while (written < sizeof reset_sequence - 1)
-        {
-          int ret = write (STDOUT_FILENO, reset_sequence + written,
-                           sizeof reset_sequence - 1 - written);
-          if (ret < 0)
-            return;
-          written += ret;
-        }
+      fputs (reset_sequence, outfile);
+      last_context_str = NULL;
     }
 }
 




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 22:10:03 GMT) Full text and rfc822 format available.

Message #152 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>, Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Fri, 11 Sep 2015 16:09:27 -0600
[Message part 1 (text/plain, inline)]
On 09/11/2015 03:47 PM, Giuseppe Scrivano wrote:

>  static void
> +safe_write_from_signal (const char *str, size_t len)
> +{
> +  size_t written = 0;
> +  while (written < len)
> +    {
> +      int ret = write (STDOUT_FILENO, str + written,
> +                       len - written);

This writes unbuffered...

> @@ -796,28 +826,40 @@ void
>  set_header_color_context (void)
>  {
>    if (colors_enabled)
> -    fprintf (outfile, "\x1B[1;39m");
> +    {
> +      last_context_str = "\x1B[1;39m";
> +      fprintf (outfile, "%s", last_context_str);

...while this is buffering output and might not be output right away. It
is not safe to mix stdio and low-level write() to the same fd without
flush()ing between transitions.  But at the same time, I don't think
that you want to convert everything to use low-level write().

Is it safe to block SIGTSTP around critical sections of when you must
not be stopped, then pay attention to the volatile flag to know that you
must flush and re-raise() TSTP as soon as possible? I'm a bit fuzzy on
what the proper way is to get the terminal to a proper state,
particularly if the TSTP arrived in the middle of stdio outputting a
terminal escape sequence.  It seems like the only safe way is to block
TSTP except for in places where you know that you do NOT have pending
data that would mess with the terminal, even if the cost of getting the
process to a point where it will actually stop in response to the user
request takes longer.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 11 Sep 2015 22:15:02 GMT) Full text and rfc822 format available.

Message #155 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Eric Blake <eblake <at> redhat.com>, Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Fri, 11 Sep 2015 15:14:53 -0700
Eric Blake wrote:
> It seems like the only safe way is to block
> TSTP except for in places where you know that you do NOT have pending
> data that would mess with the terminal, even if the cost of getting the
> process to a point where it will actually stop in response to the user
> request takes longer.

That's one way to do it, yes.  In practice, though, it's easier (and probably 
faster) to do what GNU ls does, namely to have the the signal handler just set a 
volatile flag, and to periodically inspect the flag during normal computation in 
places where you can easily arrange for the terminal to be in a known state 
before acting on the signal.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 12 Sep 2015 08:55:01 GMT) Full text and rfc822 format available.

Message #158 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Sat, 12 Sep 2015 10:54:24 +0200
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Eric Blake wrote:
>> It seems like the only safe way is to block
>> TSTP except for in places where you know that you do NOT have pending
>> data that would mess with the terminal, even if the cost of getting the
>> process to a point where it will actually stop in response to the user
>> request takes longer.
>
> That's one way to do it, yes.  In practice, though, it's easier (and
> probably faster) to do what GNU ls does, namely to have the the signal
> handler just set a volatile flag, and to periodically inspect the flag
> during normal computation in places where you can easily arrange for
> the terminal to be in a known state before acting on the signal.

in a previous version of the patch, I was blocking signals when diff
starts using colored output and re-enable them again once exiting from a
colored context, but it was rejected because it could block indefinitely
on long lines.  Would block signals between a set_*_color_context and
reset_color_context be enough or do we need more granularity (there are
many places where printf is used in the code)?

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 12 Sep 2015 09:38:01 GMT) Full text and rfc822 format available.

Message #161 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Sat, 12 Sep 2015 02:37:46 -0700
Giuseppe Scrivano wrote:
>   Would block signals between a set_*_color_context and
> reset_color_context be enough or do we need more granularity (there are
> many places where printf is used in the code)?

No, for reasons Eric described: output is buffered.

I suggest looking at the source code of GNU ls and seeing how it does things. 
In normal execution it never blocks signals at all.  (It temporarily blocks 
signals only when processing a signal.)




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 12 Sep 2015 11:41:02 GMT) Full text and rfc822 format available.

Message #164 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivan <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Sat, 12 Sep 2015 13:40:08 +0200
Paul Eggert <eggert <at> cs.ucla.edu> writes:

> Giuseppe Scrivano wrote:
>>   Would block signals between a set_*_color_context and
>> reset_color_context be enough or do we need more granularity (there are
>> many places where printf is used in the code)?
>
> No, for reasons Eric described: output is buffered.

can't we solve this by flushing the output before enabling signals?  In
case of using buffered output we would still need to flush it to be sure
the reset sequence is printed.


> I suggest looking at the source code of GNU ls and seeing how it does
> things. In normal execution it never blocks signals at all.  (It
> temporarily blocks signals only when processing a signal.)

we already had this discussion about an older version of the patch where
signals were processed after every line.  We agreed that one difference
between ls and diff is that ls has a limit on the line length, while
diff hasn't such a limit and as you noted, it is bound only to the
available memory.  This was the reason for reacting to signals as soon
as diff receives one.

Either we block signals or we catch them and process as ls does (calling
'process_signals' periodically) that problem will still be present.

What is your call on this?

Thanks,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 12 Sep 2015 15:53:02 GMT) Full text and rfc822 format available.

Message #167 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivan <at> redhat.com>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Sat, 12 Sep 2015 08:52:14 -0700
Giuseppe Scrivano wrote:
> Paul Eggert <eggert <at> cs.ucla.edu> writes:
>
>> Giuseppe Scrivano wrote:
>>>    Would block signals between a set_*_color_context and
>>> reset_color_context be enough or do we need more granularity (there are
>>> many places where printf is used in the code)?
>>
>> No, for reasons Eric described: output is buffered.
>
> can't we solve this by flushing the output before enabling signals?

"enabling"?  Do you mean, flushing the output when a signal is caught?  No, that 
won't work, as stdio is not safe in the presence of signals.  Or do you mean 
fflush the output before unblocking signals?  Yes, that will work, but it's slow.

> we already had this discussion about an older version of the patch where
> signals were processed after every line.  We agreed that one difference
> between ls and diff is that ls has a limit on the line length, while
> diff hasn't such a limit and as you noted, it is bound only to the
> available memory.  This was the reason for reacting to signals as soon
> as diff receives one.

Sorry, I don't remember that discussion.  Yes, the point is a valid one, and 
needs to be addressed.

> Either we block signals or we catch them and process as ls does (calling
> 'process_signals' periodically) that problem will still be present.

True.  However, blocking signals means we'll need to periodically 
fflush-unblock-block, which will slow us down; the whole point of stdio is 
efficiency via buffering, after all.  Doing it the 'ls' way avoids this 
overhead, as we'll need to fflush only when a signal has actually arrived, plus 
we avoid the syscall overhead of periodic unblock-block.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 23 Sep 2015 10:15:02 GMT) Full text and rfc822 format available.

Message #170 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Wed, 23 Sep 2015 12:14:22 +0200
[Message part 1 (text/plain, inline)]
Hi Paul,

Paul Eggert <eggert <at> cs.ucla.edu> writes:

>> Either we block signals or we catch them and process as ls does (calling
>> 'process_signals' periodically) that problem will still be present.
>
> True.  However, blocking signals means we'll need to periodically
> fflush-unblock-block, which will slow us down; the whole point of
> stdio is efficiency via buffering, after all.  Doing it the 'ls' way
> avoids this overhead, as we'll need to fflush only when a signal has
> actually arrived, plus we avoid the syscall overhead of periodic
> unblock-block.

I have attached a new version of the patch where I've copied the signals
handling code from coreutils and ensure that output_1_line won't block
for too long without checking `process_signals'.

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 2568e589ad38845ab8eaf895a98a84b94a29b72d Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(output_1_line): Periodically call `process_signals'.
(caught_signals): New sigset_t.
(colors_enabled): New boolean variable.
(interrupt_signal): New sig_atomic_t.
(stop_signal_count): New sig_atomic_t.
(check_color_output): New function.
(install_signal_handlers): Likewise. Copied from coreutils ls.
(process_signals): Likewise.  Copied from coreutils ls.
(reset_color_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(sighandler): Likewise.  Copied from coreutils ls.
(stophandler): Likewise.  Copied from coreutils ls.
---
 doc/diffutils.texi |  21 ++++
 src/context.c      |  51 +++++++--
 src/diff.c         |  27 ++++-
 src/diff.h         |  21 ++++
 src/normal.c       |  18 +++-
 src/side.c         |  15 +++
 src/util.c         | 309 +++++++++++++++++++++++++++++++++++++++++++++++------
 7 files changed, 418 insertions(+), 44 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 091257f..0944b44 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3742,6 +3742,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..8e9a74f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_header_color_context ();
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  reset_color_context ();
 }
 
 /* Print an edit script in context format.  */
@@ -215,6 +217,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,12 +228,18 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_delete_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 
@@ -244,6 +253,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +264,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_add_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 }
@@ -330,11 +345,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_line_numbers_color_context ();
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  reset_color_context ();
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +377,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +400,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -386,6 +417,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            reset_color_context ();
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index ff28377..be6e6e3 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..472fa93 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,8 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_header_color_context (void);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (void);
+extern void set_line_numbers_color_context (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..227af10 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_line_numbers_color_context ();
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  reset_color_context ();
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_delete_color_context ();
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      reset_color_context ();
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_add_color_context ();
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      reset_color_context ();
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..b762d31 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context ();
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..b5b39a2 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,22 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +159,152 @@ print_message_queue (void)
     }
 }
 
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      reset_color_context ();
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+static void
+install_signal_handlers (void)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = sizeof (sig) / sizeof *(sig) };
+
+#if ! SA_NOCLDSTOP
+  bool caught_sig[nsigs];
+#endif
+  {
+    int j;
+#if SA_NOCLDSTOP
+    struct sigaction act;
+
+    sigemptyset (&caught_signals);
+    for (j = 0; j < nsigs; j++)
+      {
+        sigaction (sig[j], NULL, &act);
+        if (act.sa_handler != SIG_IGN)
+          sigaddset (&caught_signals, sig[j]);
+      }
+
+    act.sa_mask = caught_signals;
+    act.sa_flags = SA_RESTART;
+
+    for (j = 0; j < nsigs; j++)
+      if (sigismember (&caught_signals, sig[j]))
+        {
+          act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+    for (j = 0; j < nsigs; j++)
+      {
+        caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+        if (caught_sig[j])
+          {
+            signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+            siginterrupt (sig[j], 0);
+          }
+      }
+#endif
+    }
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -153,6 +315,24 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (bool is_pipe)
+{
+  bool output_is_tty;
+
+  if (! outfile || colors_style == NEVER)
+    return;
+
+  output_is_tty = !is_pipe && isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +493,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +501,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +512,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +855,21 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  const size_t MAX_CHUNK = 1024;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t to_write = MIN (left, MAX_CHUNK);
+          size_t written = fwrite (base, sizeof (char), to_write, outfile);
+          if (written < to_write)
+            return;
+          base += written;
+          left -= written;
+          process_signals ();
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,40 +877,93 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t counter_proc_signals = 0;
 
       while (t < limit)
-	switch ((c = *t++))
-	  {
-	  case '\t':
-	    {
-	      size_t spaces = tab_size - column % tab_size;
-	      column += spaces;
-	      do
-		putc (' ', out);
-	      while (--spaces);
-	    }
-	    break;
+        {
+          counter_proc_signals++;
+          if (counter_proc_signals == MAX_CHUNK)
+            {
+              process_signals ();
+              counter_proc_signals = 0;
+            }
+
+          switch ((c = *t++))
+            {
+            case '\t':
+              {
+                size_t spaces = tab_size - column % tab_size;
+                column += spaces;
+                do
+                  putc (' ', out);
+                while (--spaces);
+              }
+              break;
+
+            case '\r':
+              putc (c, out);
+              if (flag_format && t < limit && *t != '\n')
+                fprintf (out, flag_format, line_flag);
+              column = 0;
+              break;
+
+            case '\b':
+              if (column == 0)
+                continue;
+              column--;
+              putc (c, out);
+              break;
+
+            default:
+              column += isprint (c) != 0;
+              putc (c, out);
+              break;
+            }
+        }
+    }
+}
 
-	  case '\r':
-	    putc (c, out);
-	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
-	    column = 0;
-	    break;
+void
+set_header_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[1;39m");
+}
 
-	  case '\b':
-	    if (column == 0)
-	      continue;
-	    column--;
-	    putc (c, out);
-	    break;
+void
+set_line_numbers_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[36m");
+}
 
-	  default:
-	    column += isprint (c) != 0;
-	    putc (c, out);
-	    break;
-	  }
-    }
+void
+set_add_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[32m");
+  fflush (outfile);
+}
+
+void
+set_delete_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[31m");
+}
+
+void
+reset_color_context (void)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  fputs (reset_sequence, outfile);
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.4.3

[Message part 3 (text/plain, inline)]
Regards,
Giuseppe

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 14 Oct 2015 09:34:02 GMT) Full text and rfc822 format available.

Message #173 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 [PATCH] diff: add support for --color
Date: Wed, 14 Oct 2015 11:33:42 +0200
[Message part 1 (text/plain, inline)]
Giuseppe Scrivano <gscrivano <at> gnu.org> writes:

> Hi Paul,
>
> Paul Eggert <eggert <at> cs.ucla.edu> writes:
>
>>> Either we block signals or we catch them and process as ls does (calling
>>> 'process_signals' periodically) that problem will still be present.
>>
>> True.  However, blocking signals means we'll need to periodically
>> fflush-unblock-block, which will slow us down; the whole point of
>> stdio is efficiency via buffering, after all.  Doing it the 'ls' way
>> avoids this overhead, as we'll need to fflush only when a signal has
>> actually arrived, plus we avoid the syscall overhead of periodic
>> unblock-block.
>
> I have attached a new version of the patch where I've copied the signals
> handling code from coreutils and ensure that output_1_line won't block
> for too long without checking `process_signals'.

Any comments?

Thanks,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 1c74f569083f2dcc7456f6c6fde93f5436dc0c90 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(output_1_line): Periodically call `process_signals'.
(caught_signals): New sigset_t.
(colors_enabled): New boolean variable.
(interrupt_signal): New sig_atomic_t.
(stop_signal_count): New sig_atomic_t.
(check_color_output): New function.
(install_signal_handlers): Likewise. Copied from coreutils ls.
(process_signals): Likewise.  Copied from coreutils ls.
(reset_color_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(sighandler): Likewise.  Copied from coreutils ls.
(stophandler): Likewise.  Copied from coreutils ls.
---
 doc/diffutils.texi |  21 ++++
 src/context.c      |  51 +++++++--
 src/diff.c         |  27 ++++-
 src/diff.h         |  21 ++++
 src/normal.c       |  18 +++-
 src/side.c         |  15 +++
 src/util.c         | 309 +++++++++++++++++++++++++++++++++++++++++++++++------
 7 files changed, 418 insertions(+), 44 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 091257f..0944b44 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3742,6 +3742,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..8e9a74f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_header_color_context ();
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  reset_color_context ();
 }
 
 /* Print an edit script in context format.  */
@@ -215,6 +217,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,12 +228,18 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_delete_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 
@@ -244,6 +253,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +264,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_add_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 }
@@ -330,11 +345,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_line_numbers_color_context ();
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  reset_color_context ();
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +377,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +400,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -386,6 +417,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            reset_color_context ();
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index efd7e47..4e0f602 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..472fa93 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,8 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_header_color_context (void);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (void);
+extern void set_line_numbers_color_context (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..227af10 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_line_numbers_color_context ();
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  reset_color_context ();
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_delete_color_context ();
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      reset_color_context ();
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_add_color_context ();
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      reset_color_context ();
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..b762d31 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context ();
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..b5b39a2 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,22 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +159,152 @@ print_message_queue (void)
     }
 }
 
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      reset_color_context ();
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+static void
+install_signal_handlers (void)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = sizeof (sig) / sizeof *(sig) };
+
+#if ! SA_NOCLDSTOP
+  bool caught_sig[nsigs];
+#endif
+  {
+    int j;
+#if SA_NOCLDSTOP
+    struct sigaction act;
+
+    sigemptyset (&caught_signals);
+    for (j = 0; j < nsigs; j++)
+      {
+        sigaction (sig[j], NULL, &act);
+        if (act.sa_handler != SIG_IGN)
+          sigaddset (&caught_signals, sig[j]);
+      }
+
+    act.sa_mask = caught_signals;
+    act.sa_flags = SA_RESTART;
+
+    for (j = 0; j < nsigs; j++)
+      if (sigismember (&caught_signals, sig[j]))
+        {
+          act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+    for (j = 0; j < nsigs; j++)
+      {
+        caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+        if (caught_sig[j])
+          {
+            signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+            siginterrupt (sig[j], 0);
+          }
+      }
+#endif
+    }
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -153,6 +315,24 @@ print_message_queue (void)
 static char const *current_name0;
 static char const *current_name1;
 static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (bool is_pipe)
+{
+  bool output_is_tty;
+
+  if (! outfile || colors_style == NEVER)
+    return;
+
+  output_is_tty = !is_pipe && isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
 
 void
 setup_output (char const *name0, char const *name1, bool recursive)
@@ -313,6 +493,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +501,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +512,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +855,21 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  const size_t MAX_CHUNK = 1024;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t to_write = MIN (left, MAX_CHUNK);
+          size_t written = fwrite (base, sizeof (char), to_write, outfile);
+          if (written < to_write)
+            return;
+          base += written;
+          left -= written;
+          process_signals ();
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,40 +877,93 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t counter_proc_signals = 0;
 
       while (t < limit)
-	switch ((c = *t++))
-	  {
-	  case '\t':
-	    {
-	      size_t spaces = tab_size - column % tab_size;
-	      column += spaces;
-	      do
-		putc (' ', out);
-	      while (--spaces);
-	    }
-	    break;
+        {
+          counter_proc_signals++;
+          if (counter_proc_signals == MAX_CHUNK)
+            {
+              process_signals ();
+              counter_proc_signals = 0;
+            }
+
+          switch ((c = *t++))
+            {
+            case '\t':
+              {
+                size_t spaces = tab_size - column % tab_size;
+                column += spaces;
+                do
+                  putc (' ', out);
+                while (--spaces);
+              }
+              break;
+
+            case '\r':
+              putc (c, out);
+              if (flag_format && t < limit && *t != '\n')
+                fprintf (out, flag_format, line_flag);
+              column = 0;
+              break;
+
+            case '\b':
+              if (column == 0)
+                continue;
+              column--;
+              putc (c, out);
+              break;
+
+            default:
+              column += isprint (c) != 0;
+              putc (c, out);
+              break;
+            }
+        }
+    }
+}
 
-	  case '\r':
-	    putc (c, out);
-	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
-	    column = 0;
-	    break;
+void
+set_header_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[1;39m");
+}
 
-	  case '\b':
-	    if (column == 0)
-	      continue;
-	    column--;
-	    putc (c, out);
-	    break;
+void
+set_line_numbers_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[36m");
+}
 
-	  default:
-	    column += isprint (c) != 0;
-	    putc (c, out);
-	    break;
-	  }
-    }
+void
+set_add_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[32m");
+  fflush (outfile);
+}
+
+void
+set_delete_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[31m");
+}
+
+void
+reset_color_context (void)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  fputs (reset_sequence, outfile);
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.4.3


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 18 Oct 2015 17:27:02 GMT) Full text and rfc822 format available.

Message #176 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: [PATCH] diff: add support for --color
Date: Sun, 18 Oct 2015 10:26:00 -0700
Thank you for that patch and for your patience.
I've skimmed through and so far have only a question and a request:

Regarding this section:

+void
+set_header_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[1;39m");
+}
+
+void
+set_line_numbers_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[36m");
+}
+
+void
+set_add_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[32m");
+  fflush (outfile);
+}
+
+void
+set_delete_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fprintf (outfile, "\x1B[31m");
+}
+
+void
+reset_color_context (void)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  fputs (reset_sequence, outfile);
+}

Why does set_add_color_context call fflush, yet the others do not?
Please use fputs rather than fprintf for those literal strings.
The former is often far more efficient.

Finally, should there be some way to specify different colors,
e.g., for those who use different-background-colored terminals,
or for the color blind?




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 20 Oct 2015 16:24:02 GMT) Full text and rfc822 format available.

Message #179 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: [PATCH] diff: add support for --color
Date: Tue, 20 Oct 2015 18:23:06 +0200
[Message part 1 (text/plain, inline)]
Hi Jim,

Jim Meyering <jim <at> meyering.net> writes:

> Thank you for that patch and for your patience.

thank you very much for the review!

> I've skimmed through and so far have only a question and a request:
>
> Regarding this section:
>
> +void
> +set_header_color_context (void)
> +{
> +  process_signals ();
> +  if (colors_enabled)
> +    fprintf (outfile, "\x1B[1;39m");
> +}
> +
> +void
> +set_line_numbers_color_context (void)
> +{
> +  process_signals ();
> +  if (colors_enabled)
> +    fprintf (outfile, "\x1B[36m");
> +}
> +
> +void
> +set_add_color_context (void)
> +{
> +  process_signals ();
> +  if (colors_enabled)
> +    fprintf (outfile, "\x1B[32m");
> +  fflush (outfile);
> +}
> +
> +void
> +set_delete_color_context (void)
> +{
> +  process_signals ();
> +  if (colors_enabled)
> +    fprintf (outfile, "\x1B[31m");
> +}
> +
> +void
> +reset_color_context (void)
> +{
> +  static char const reset_sequence[] = "\x1b[0m";
> +  if (! colors_enabled)
> +    return;
> +
> +  fputs (reset_sequence, outfile);
> +}
>
> Why does set_add_color_context call fflush, yet the others do not?
> Please use fputs rather than fprintf for those literal strings.
> The former is often far more efficient.

Yes, it shouldn't as well.  I have changed it.


> Finally, should there be some way to specify different colors,
> e.g., for those who use different-background-colored terminals,
> or for the color blind?

I have took more code from coreutils ls and diff honors DIFF_COLORS
now.  I added it in a separate patch to facilitate the review.  Probably
all the shared code should end up in a gnulib module, but it probably
needs a better API before it can happen.

Changes in the first patch:

1) dropped fflush from set_add_color_context
2) use fputs instead of fprintf (the second patch replaces it)
3) change the code color of the header to the default color to match
the "git diff" output.

Regards,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 581c602ef651f99418a086025ad6a035959a7ad7 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH 1/2] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(output_1_line): Periodically call `process_signals'.
(caught_signals): New sigset_t.
(colors_enabled): New boolean variable.
(interrupt_signal): New sig_atomic_t.
(stop_signal_count): New sig_atomic_t.
(check_color_output): New function.
(install_signal_handlers): Likewise. Copied from coreutils ls.
(process_signals): Likewise.  Copied from coreutils ls.
(reset_color_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(sighandler): Likewise.  Copied from coreutils ls.
(stophandler): Likewise.  Copied from coreutils ls.
---
 doc/diffutils.texi |  21 ++++
 src/context.c      |  51 +++++++--
 src/diff.c         |  27 ++++-
 src/diff.h         |  21 ++++
 src/normal.c       |  18 ++-
 src/side.c         |  15 +++
 src/util.c         | 316 +++++++++++++++++++++++++++++++++++++++++++++++------
 7 files changed, 421 insertions(+), 48 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 091257f..0944b44 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3742,6 +3742,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..8e9a74f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_header_color_context ();
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  reset_color_context ();
 }
 
 /* Print an edit script in context format.  */
@@ -215,6 +217,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,12 +228,18 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_delete_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 
@@ -244,6 +253,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +264,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_add_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 }
@@ -330,11 +345,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_line_numbers_color_context ();
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  reset_color_context ();
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +377,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +400,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -386,6 +417,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            reset_color_context ();
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index efd7e47..4e0f602 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..472fa93 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,8 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_header_color_context (void);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (void);
+extern void set_line_numbers_color_context (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..227af10 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_line_numbers_color_context ();
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  reset_color_context ();
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_delete_color_context ();
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      reset_color_context ();
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_add_color_context ();
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      reset_color_context ();
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..b762d31 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context ();
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..6cc1411 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,22 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +159,174 @@ print_message_queue (void)
     }
 }
 
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      reset_color_context ();
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+static void
+install_signal_handlers (void)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = sizeof (sig) / sizeof *(sig) };
+
+#if ! SA_NOCLDSTOP
+  bool caught_sig[nsigs];
+#endif
+  {
+    int j;
+#if SA_NOCLDSTOP
+    struct sigaction act;
+
+    sigemptyset (&caught_signals);
+    for (j = 0; j < nsigs; j++)
+      {
+        sigaction (sig[j], NULL, &act);
+        if (act.sa_handler != SIG_IGN)
+          sigaddset (&caught_signals, sig[j]);
+      }
+
+    act.sa_mask = caught_signals;
+    act.sa_flags = SA_RESTART;
+
+    for (j = 0; j < nsigs; j++)
+      if (sigismember (&caught_signals, sig[j]))
+        {
+          act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+    for (j = 0; j < nsigs; j++)
+      {
+        caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+        if (caught_sig[j])
+          {
+            signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+            siginterrupt (sig[j], 0);
+          }
+      }
+#endif
+    }
+}
+
+static char const *current_name0;
+static char const *current_name1;
+static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (bool is_pipe)
+{
+  bool output_is_tty;
+
+  if (! outfile || colors_style == NEVER)
+    return;
+
+  output_is_tty = !is_pipe && isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -150,10 +334,6 @@ print_message_queue (void)
    we fork off a 'pr' and make OUTFILE a pipe to it.
    'pr' then outputs to our stdout.  */
 
-static char const *current_name0;
-static char const *current_name1;
-static bool currently_recursive;
-
 void
 setup_output (char const *name0, char const *name1, bool recursive)
 {
@@ -313,6 +493,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +501,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +512,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +855,21 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  const size_t MAX_CHUNK = 1024;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t to_write = MIN (left, MAX_CHUNK);
+          size_t written = fwrite (base, sizeof (char), to_write, outfile);
+          if (written < to_write)
+            return;
+          base += written;
+          left -= written;
+          process_signals ();
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,40 +877,92 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t counter_proc_signals = 0;
 
       while (t < limit)
-	switch ((c = *t++))
-	  {
-	  case '\t':
-	    {
-	      size_t spaces = tab_size - column % tab_size;
-	      column += spaces;
-	      do
-		putc (' ', out);
-	      while (--spaces);
-	    }
-	    break;
+        {
+          counter_proc_signals++;
+          if (counter_proc_signals == MAX_CHUNK)
+            {
+              process_signals ();
+              counter_proc_signals = 0;
+            }
+
+          switch ((c = *t++))
+            {
+            case '\t':
+              {
+                size_t spaces = tab_size - column % tab_size;
+                column += spaces;
+                do
+                  putc (' ', out);
+                while (--spaces);
+              }
+              break;
+
+            case '\r':
+              putc (c, out);
+              if (flag_format && t < limit && *t != '\n')
+                fprintf (out, flag_format, line_flag);
+              column = 0;
+              break;
+
+            case '\b':
+              if (column == 0)
+                continue;
+              column--;
+              putc (c, out);
+              break;
+
+            default:
+              column += isprint (c) != 0;
+              putc (c, out);
+              break;
+            }
+        }
+    }
+}
 
-	  case '\r':
-	    putc (c, out);
-	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
-	    column = 0;
-	    break;
+void
+set_header_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[1m", outfile);
+}
 
-	  case '\b':
-	    if (column == 0)
-	      continue;
-	    column--;
-	    putc (c, out);
-	    break;
+void
+set_line_numbers_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[36m", outfile);
+}
 
-	  default:
-	    column += isprint (c) != 0;
-	    putc (c, out);
-	    break;
-	  }
-    }
+void
+set_add_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[32m", outfile);
+}
+
+void
+set_delete_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[31m", outfile);
+}
+
+void
+reset_color_context (void)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  fputs (reset_sequence, outfile);
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.4.3

[0002-diff-honor-env-variable-DIFF_COLORS.patch (text/x-patch, inline)]
From 2e2e078878d04311791c2facd9cac79ec969f24d Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 19 Oct 2015 10:29:41 +0200
Subject: [PATCH 2/2] diff: honor env variable DIFF_COLORS

doc/diffutils.texi: Add documentation for DIFF_COLORS
src/utils.c (struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_header_color_context): Use put_indicator instead of directly
outputting the sequence.
(set_line_numbers_colors_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
---
 doc/diffutils.texi |   5 +
 src/util.c         | 431 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 427 insertions(+), 9 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 0944b44..75ad0d7 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3763,6 +3763,11 @@ Always use color.
 Specifying @option{--color} and no @var{when} is equivalent to
 @option{--color=auto}.
 
+The colors are defined by the environment variable @env{DIFF_COLORS}
+and default to @samp{rs=0:hd=1:ad=32:de=31:ln=36}
+for red added lines, green deleted lines, cyan line numbers, bold header.
+
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/util.c b/src/util.c
index 6cc1411..8d27bc7 100644
--- a/src/util.c
+++ b/src/util.c
@@ -310,6 +310,388 @@ static char const *current_name1;
 static bool currently_recursive;
 static bool colors_enabled;
 
+static struct color_ext_type *color_ext_list = 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 */
+  };
+
+/* Parse a string as part of the DIFF_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 bool
+get_funky_string (char **dest, const char **src, bool 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;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+#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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { LEN_STR_PAIR ("1") },		/* hd: Header */
+    { LEN_STR_PAIR ("32") },		/* ad: Add line */
+    { LEN_STR_PAIR ("31") },		/* de: Delete line */
+    { LEN_STR_PAIR ("36") },		/* ln: Line number */
+  };
+
+static const char *const indicator_name[]=
+  {
+    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
+  };
+
+static void
+parse_diff_color (void)
+{
+  char *color_buf;
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = getenv ("DIFF_COLORS")) == NULL || *p == '\0')
+    return;
+
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     DIFF_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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), label);
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for DIFF_COLORS environment variable"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      colors_enabled = false;
+    }
+}
+
 static void
 check_color_output (bool is_pipe)
 {
@@ -323,6 +705,9 @@ check_color_output (bool is_pipe)
   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));
 
+  if (colors_enabled)
+    parse_diff_color ();
+
   if (output_is_tty)
     install_signal_handlers ();
 }
@@ -923,12 +1308,27 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE
+  };
+
+static void
+put_indicator (const struct bin_str *ind)
+{
+  fwrite (ind->string, ind->len, 1, outfile);
+}
+
 void
 set_header_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[1m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_HEADER]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -936,7 +1336,11 @@ set_line_numbers_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[36m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_LINE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -944,7 +1348,11 @@ set_add_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[32m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_ADD]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -952,17 +1360,22 @@ set_delete_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[31m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_DELETE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
 reset_color_context (void)
 {
-  static char const reset_sequence[] = "\x1b[0m";
-  if (! colors_enabled)
-    return;
-
-  fputs (reset_sequence, outfile);
+  if (colors_enabled)
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_RESET]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.4.3


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 20 Oct 2015 16:27:01 GMT) Full text and rfc822 format available.

Message #182 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>, Jim Meyering <jim <at> meyering.net>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: [PATCH] diff: add support for --color
Date: Tue, 20 Oct 2015 09:25:55 -0700
Giuseppe Scrivano wrote:
> diff honors DIFF_COLORS
> now.

Aren't we trying to move away from environment variables' affecting program 
behavior, given all the maintenance and security hassles that entails?




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 20 Oct 2015 16:43:01 GMT) Full text and rfc822 format available.

Message #185 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Paul Eggert <eggert <at> cs.ucla.edu>, Giuseppe Scrivano <gscrivano <at> gnu.org>,
 Jim Meyering <jim <at> meyering.net>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: bug#20062: [PATCH] diff: add support for --color
Date: Tue, 20 Oct 2015 10:42:32 -0600
[Message part 1 (text/plain, inline)]
On 10/20/2015 10:25 AM, Paul Eggert wrote:
> Giuseppe Scrivano wrote:
>> diff honors DIFF_COLORS
>> now.
> 
> Aren't we trying to move away from environment variables' affecting
> program behavior, given all the maintenance and security hassles that
> entails?

I think there's a difference between an environment variable which only
affects optional behavior (LS_COLORS, DIFF_COLORS - only affect things
for 'ls --color' or 'diff --color') vs. those that affect ALL behavior
(GREP_OPTIONS - cruelly affects all executions of grep, no matter what
options were in use).

You do have a point that allowing the choice to come through a command
line option, and wrapping diff behind an alias or shell function, will
let the interactive user set up their terminal to get the same effect
(typing 'diff' gets the colored version, whether the details were hidden
behind an env-var or a shell alias).

Other arguments:
In favor of an env-var:
LS_COLORS is now understood by more than just 'ls' - at least tcsh
honors the contents of LS_COLORS (sometimes good, but also annoying when
we extend 'ls' to understand new options and then tcsh spews out error
messages about unknown directives in LS_COLORS).
Likewise, other programs could be taught to honor DIFF_COLORS (git,
perhaps?) - but do we then need a common location to specify how it
works so that all future programs buy in to the same interpretation?

Against env-var:
LS_COLORS is already quite large, and we may be adding yet another large
env-var in DIFF_COLORS. On some platforms, the environment space is
already limited; on mingw, every byte added in the environment is one
less byte towards the command line maximum limit (argv and env share the
same 64k block of memory - not quite POSIX-y, but not all the world is
POSIX).
In an interactive environment, the user is already likely to be creating
a shell alias/function to turn 'diff' into 'diff --color' - it is not
that much harder to have the alias be 'diff --color
--option-to-set-colors' compared to 'DIFF_COLORS=... diff --color', but
conceptually having it all through the command line rather than split
among two sources makes it easier to trace where the change is made.

I don't have a strong opinion on whether an env-var is right or wrong,
so long as we have at least SOME way to override default colors.  But I
_do_ think that this is at least a safe env-var if we want to go that
route (since it only affects the behavior of opt-in --color usage,
rather than all situations).

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 20 Oct 2015 17:33:02 GMT) Full text and rfc822 format available.

Message #188 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Aaron Davies <aaron.davies <at> gmail.com>
To: Eric Blake <eblake <at> redhat.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, Giuseppe Scrivano <gscrivano <at> gnu.org>,
 "20062 <at> debbugs.gnu.org" <20062 <at> debbugs.gnu.org>,
 Jim Meyering <jim <at> meyering.net>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: bug#20062: bug#20062: [PATCH] diff: add support for --color
Date: Tue, 20 Oct 2015 13:32:29 -0400
On Oct 20, 2015, at 12:42 PM, Eric Blake <eblake <at> redhat.com> wrote:

>> On 10/20/2015 10:25 AM, Paul Eggert wrote:
>> Giuseppe Scrivano wrote:
>>> diff honors DIFF_COLORS
>>> now.
>> 
>> Aren't we trying to move away from environment variables' affecting
>> program behavior, given all the maintenance and security hassles that
>> entails?
> 
> I think there's a difference between an environment variable which only
> affects optional behavior (LS_COLORS, DIFF_COLORS - only affect things
> for 'ls --color' or 'diff --color') vs. those that affect ALL behavior
> (GREP_OPTIONS - cruelly affects all executions of grep, no matter what
> options were in use).
> 
> You do have a point that allowing the choice to come through a command
> line option, and wrapping diff behind an alias or shell function, will
> let the interactive user set up their terminal to get the same effect
> (typing 'diff' gets the colored version, whether the details were hidden
> behind an env-var or a shell alias).
> 
> Other arguments:
> In favor of an env-var:
> LS_COLORS is now understood by more than just 'ls' - at least tcsh
> honors the contents of LS_COLORS (sometimes good, but also annoying when
> we extend 'ls' to understand new options and then tcsh spews out error
> messages about unknown directives in LS_COLORS).
> Likewise, other programs could be taught to honor DIFF_COLORS (git,
> perhaps?) - but do we then need a common location to specify how it
> works so that all future programs buy in to the same interpretation?
> 
> Against env-var:
> LS_COLORS is already quite large, and we may be adding yet another large
> env-var in DIFF_COLORS. On some platforms, the environment space is
> already limited; on mingw, every byte added in the environment is one
> less byte towards the command line maximum limit (argv and env share the
> same 64k block of memory - not quite POSIX-y, but not all the world is
> POSIX).
> In an interactive environment, the user is already likely to be creating
> a shell alias/function to turn 'diff' into 'diff --color' - it is not
> that much harder to have the alias be 'diff --color
> --option-to-set-colors' compared to 'DIFF_COLORS=... diff --color', but
> conceptually having it all through the command line rather than split
> among two sources makes it easier to trace where the change is made.
> 
> I don't have a strong opinion on whether an env-var is right or wrong,
> so long as we have at least SOME way to override default colors.  But I
> _do_ think that this is at least a safe env-var if we want to go that
> route (since it only affects the behavior of opt-in --color usage,
> rather than all situations).

I'm very new here, so feel free to take my opinions with a grain of salt, but one thing I've been noticing in my scripting work is that environment variables' biggest drawback is often also their big advantage: you don't have to propagate them manually.

Suppose you want to encapsulate some common diff operation in a small shell script. Then anything you want that diff to do that you specified in an alias or function in your interactive shell has to be written out again.

Alternatively, you could have a diff wrapper in your ~/bin setting the option, but it's hard to actually write those correctly to handle things like stdin vs. file args.

None of this comes with env vars.

Still, I understand your other concerns.

Maybe a ~/.diffrc is the solution?



Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Wed, 28 Oct 2015 15:30:03 GMT) Full text and rfc822 format available.

Message #191 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Aaron Davies <aaron.davies <at> gmail.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, Eric Blake <eblake <at> redhat.com>,
 Jim Meyering <jim <at> meyering.net>,
 "20062 <at> debbugs.gnu.org" <20062 <at> debbugs.gnu.org>
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: bug#20062: bug#20062: [PATCH] diff: add support for --color
Date: Wed, 28 Oct 2015 16:29:06 +0100
Aaron Davies <aaron.davies <at> gmail.com> writes:

> I'm very new here, so feel free to take my opinions with a grain of salt, but one thing I've been noticing in my scripting work is that environment variables' biggest drawback is often also their big advantage: you don't have to propagate them manually.
>
> Suppose you want to encapsulate some common diff operation in a small shell script. Then anything you want that diff to do that you specified in an alias or function in your interactive shell has to be written out again.
>
> Alternatively, you could have a diff wrapper in your ~/bin setting the option, but it's hard to actually write those correctly to handle things like stdin vs. file args.
>
> None of this comes with env vars.
>
> Still, I understand your other concerns.
>
> Maybe a ~/.diffrc is the solution?

that could be a good solution, and probably other kind of options can be
moved but IMHO it seems a bit over-engineered to read just the colors
settings.

If the choice is between an env variable or adding a new command line
argument, the former makes more sense as it works similarly to other
tools that people already know.

If there is any strong argument against not using an environment
variable as this patch is doing, I will rework the patch to use a
command line argument instead.

Regards,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 05:08:01 GMT) Full text and rfc822 format available.

Message #194 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: [PATCH] diff: add support for --color
Date: Sun, 1 Nov 2015 21:06:43 -0800
On Tue, Oct 20, 2015 at 9:23 AM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
> Hi Jim,
>
> Jim Meyering <jim <at> meyering.net> writes:
>
>> Thank you for that patch and for your patience.
>
> thank you very much for the review!
>
>> I've skimmed through and so far have only a question and a request:
...
>> Why does set_add_color_context call fflush, yet the others do not?
>> Please use fputs rather than fprintf for those literal strings.
>> The former is often far more efficient.
>
> Yes, it shouldn't as well.  I have changed it.

Thanks for the quick work.
I'll try to be quicker, this time.

>> Finally, should there be some way to specify different colors,
>> e.g., for those who use different-background-colored terminals,
>> or for the color blind?
>
> I have took more code from coreutils ls and diff honors DIFF_COLORS
> now.  I added it in a separate patch to facilitate the review.  Probably
> all the shared code should end up in a gnulib module, but it probably
> needs a better API before it can happen.
>
> Changes in the first patch:
>
> 1) dropped fflush from set_add_color_context
> 2) use fputs instead of fprintf (the second patch replaces it)
> 3) change the code color of the header to the default color to match
> the "git diff" output.

Those revisions to your first patch look fine, and I will look over that
one once more in the morning, then expect to push.

Regarding the support for configurable colors, as already mentioned
here, we really do want to avoid adding support for any more
environment variables.  Would you please adjust the second
patch to take the settings from a command line argument instead?
With that, it will be trivial to add an envvar-honoring wrapper script,
function or alias, for those who desire that convenience.

The most important reason not to provide this functionality via
an envvar is that it would then allow that envvar setting to alter
the behavior of any program that invokes diff, and that sort of
interaction is often surprising and undesirable.

Thanks again for contributing!




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 15:07:02 GMT) Full text and rfc822 format available.

Message #197 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: [PATCH] diff: add support for --color
Date: Mon, 02 Nov 2015 16:06:31 +0100
[Message part 1 (text/plain, inline)]
Hi Jim,

Jim Meyering <jim <at> meyering.net> writes:

> Regarding the support for configurable colors, as already mentioned
> here, we really do want to avoid adding support for any more
> environment variables.  Would you please adjust the second
> patch to take the settings from a command line argument instead?
> With that, it will be trivial to add an envvar-honoring wrapper script,
> function or alias, for those who desire that convenience.
>
> The most important reason not to provide this functionality via
> an envvar is that it would then allow that envvar setting to alter
> the behavior of any program that invokes diff, and that sort of
> interaction is often surprising and undesirable.
>
> Thanks again for contributing!

Thanks for your review.

Would something like this patch work?

I have attached the difference from the previous version to make the
review easier as few lines are changed.

I have included the full ChangeLog entries in the commit message so it
will be easier to squash the patch on top of the previous one.

Regards,
Giuseppe

[0001-squash-diff-honor-env-variable-DIFF_COLORS.patch (text/x-patch, inline)]
From 0ed880f2e6f76809563597cc698da4c15b19ba1d Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Mon, 2 Nov 2015 15:43:23 +0100
Subject: [PATCH] squash! diff: honor env variable DIFF_COLORS

diff: add --color-scheme

doc/diffutils.texi: Add documentation for --color-scheme
src/diff.h (set_color_scheme): New prototype.
src/diff.c (set_color_scheme): New function.
(color_scheme): New variable.
src/utils.c (struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_header_color_context): Use put_indicator instead of directly
outputting the sequence.
(set_line_numbers_colors_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
---
 doc/diffutils.texi |  7 ++++---
 src/diff.c         |  6 ++++++
 src/diff.h         |  1 +
 src/util.c         | 10 +++++++++-
 4 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 75ad0d7..9f2cdbb 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3763,9 +3763,10 @@ Always use color.
 Specifying @option{--color} and no @var{when} is equivalent to
 @option{--color=auto}.
 
-The colors are defined by the environment variable @env{DIFF_COLORS}
-and default to @samp{rs=0:hd=1:ad=32:de=31:ln=36}
-for red added lines, green deleted lines, cyan line numbers, bold header.
+@item --color-scheme=@var{scheme}
+It allows to specify what colors are used to colorize the output.  It
+defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red added lines,
+green deleted lines, cyan line numbers, bold header.
 
 
 @item -C @var{lines}
diff --git a/src/diff.c b/src/diff.c
index 4e0f602..55ed69d 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -140,6 +140,7 @@ enum
   CHANGED_GROUP_FORMAT_OPTION,
 
   COLOR_OPTION,
+  COLOR_SCHEME_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -163,6 +164,7 @@ static struct option const longopts[] =
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
   {"color", 2, 0, COLOR_OPTION},
+  {"color-scheme", 1, 0, COLOR_SCHEME_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -635,6 +637,10 @@ main (int argc, char **argv)
 	  specify_colors_style (optarg);
 	  break;
 
+	case COLOR_SCHEME_OPTION:
+	  set_color_scheme (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
diff --git a/src/diff.h b/src/diff.h
index 472fa93..883119e 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -411,3 +411,4 @@ extern void set_add_color_context (void);
 extern void set_delete_color_context (void);
 extern void reset_color_context (void);
 extern void set_line_numbers_color_context (void);
+extern void set_color_scheme (const char *scheme);
diff --git a/src/util.c b/src/util.c
index 8d27bc7..108b8e0 100644
--- a/src/util.c
+++ b/src/util.c
@@ -562,6 +562,14 @@ static const char *const indicator_name[]=
     "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
   };
 
+static const char *color_scheme;
+
+void
+set_color_scheme (const char *scheme)
+{
+  color_scheme = scheme;
+}
+
 static void
 parse_diff_color (void)
 {
@@ -572,7 +580,7 @@ parse_diff_color (void)
   char label[3];		/* Indicator label */
   struct color_ext_type *ext;	/* Extension we are working on */
 
-  if ((p = getenv ("DIFF_COLORS")) == NULL || *p == '\0')
+  if ((p = color_scheme) == NULL || *p == '\0')
     return;
 
   ext = NULL;
-- 
2.5.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 17:17:01 GMT) Full text and rfc822 format available.

Message #200 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: bug#20062: [PATCH] diff: add support for --color
Date: Mon, 2 Nov 2015 09:15:37 -0800
On Sun, Nov 1, 2015 at 9:06 PM, Jim Meyering <jim <at> meyering.net> wrote:
> On Tue, Oct 20, 2015 at 9:23 AM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
>> Hi Jim,
>>
>> Jim Meyering <jim <at> meyering.net> writes:
>>
>>> Thank you for that patch and for your patience.
>>
>> thank you very much for the review!
>>
>>> I've skimmed through and so far have only a question and a request:
> ...
>>> Why does set_add_color_context call fflush, yet the others do not?
>>> Please use fputs rather than fprintf for those literal strings.
>>> The former is often far more efficient.
>>
>> Yes, it shouldn't as well.  I have changed it.
>
> Thanks for the quick work.
> I'll try to be quicker, this time.
>
>>> Finally, should there be some way to specify different colors,
>>> e.g., for those who use different-background-colored terminals,
>>> or for the color blind?
>>
>> I have took more code from coreutils ls and diff honors DIFF_COLORS
>> now.  I added it in a separate patch to facilitate the review.  Probably
>> all the shared code should end up in a gnulib module, but it probably
>> needs a better API before it can happen.
>>
>> Changes in the first patch:
>>
>> 1) dropped fflush from set_add_color_context
>> 2) use fputs instead of fprintf (the second patch replaces it)
>> 3) change the code color of the header to the default color to match
>> the "git diff" output.
>
> Those revisions to your first patch look fine, and I will look over that
> one once more in the morning, then expect to push.

Your first patch adds a useful new feature, but includes
neither a NEWS addition nor any test. Would you please add those?




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 19:18:01 GMT) Full text and rfc822 format available.

Message #203 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: bug#20062: bug#20062:
 bug#20062: bug#20062: [PATCH] diff: add support for --color
Date: Mon, 02 Nov 2015 20:17:07 +0100
Jim Meyering <jim <at> meyering.net> writes:

> On Sun, Nov 1, 2015 at 9:06 PM, Jim Meyering <jim <at> meyering.net> wrote:
>> On Tue, Oct 20, 2015 at 9:23 AM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
>>> Hi Jim,
>>>
>>> Jim Meyering <jim <at> meyering.net> writes:
>>>
>>>> Thank you for that patch and for your patience.
>>>
>>> thank you very much for the review!
>>>
>>>> I've skimmed through and so far have only a question and a request:
>> ...
>>>> Why does set_add_color_context call fflush, yet the others do not?
>>>> Please use fputs rather than fprintf for those literal strings.
>>>> The former is often far more efficient.
>>>
>>> Yes, it shouldn't as well.  I have changed it.
>>
>> Thanks for the quick work.
>> I'll try to be quicker, this time.
>>
>>>> Finally, should there be some way to specify different colors,
>>>> e.g., for those who use different-background-colored terminals,
>>>> or for the color blind?
>>>
>>> I have took more code from coreutils ls and diff honors DIFF_COLORS
>>> now.  I added it in a separate patch to facilitate the review.  Probably
>>> all the shared code should end up in a gnulib module, but it probably
>>> needs a better API before it can happen.
>>>
>>> Changes in the first patch:
>>>
>>> 1) dropped fflush from set_add_color_context
>>> 2) use fputs instead of fprintf (the second patch replaces it)
>>> 3) change the code color of the header to the default color to match
>>> the "git diff" output.
>>
>> Those revisions to your first patch look fine, and I will look over that
>> one once more in the morning, then expect to push.
>
> Your first patch adds a useful new feature, but includes
> neither a NEWS addition nor any test. Would you please add those?

sure, I will do.

I forgot to update "option_help_msgid" in my previous patch which adds
--color-scheme to define the colors used by diff.

Anyway, I would like an opinion about the new name before I send te full
series again.  I avoided --colors as it is very similar to --color, but
I am not sure how --color-scheme sounds for a native speaker.

Regards,
Giuseppe




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 19:35:02 GMT) Full text and rfc822 format available.

Message #206 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>, Jim Meyering <jim <at> meyering.net>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 2 Nov 2015 12:34:04 -0700
[Message part 1 (text/plain, inline)]
On 11/02/2015 12:17 PM, Giuseppe Scrivano wrote:

> Anyway, I would like an opinion about the new name before I send te full
> series again.  I avoided --colors as it is very similar to --color, but
> I am not sure how --color-scheme sounds for a native speaker.

--color-scheme sounds okay to a native, but has the issue that it cannot
be abbreviated (--col would be short for --color, not --color-scheme).
Can you come up with a name that does not share a common prefix?  Maybe
--palette?

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 21:22:01 GMT) Full text and rfc822 format available.

Message #209 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: Giuseppe Scrivano <gscrivano <at> gnu.org>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 2 Nov 2015 13:21:08 -0800
On Mon, Nov 2, 2015 at 11:34 AM, Eric Blake <eblake <at> redhat.com> wrote:
> On 11/02/2015 12:17 PM, Giuseppe Scrivano wrote:
>
>> Anyway, I would like an opinion about the new name before I send te full
>> series again.  I avoided --colors as it is very similar to --color, but
>> I am not sure how --color-scheme sounds for a native speaker.
>
> --color-scheme sounds okay to a native, but has the issue that it cannot
> be abbreviated (--col would be short for --color, not --color-scheme).
> Can you come up with a name that does not share a common prefix?  Maybe
> --palette?

Good point.
I too prefer --palette='...'
That does have the tiny downside that diff's rarely used
--paginate option will no longer be able to be abbreviated
as "--pa", but that is barely worth mentioning.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Mon, 02 Nov 2015 21:27:02 GMT) Full text and rfc822 format available.

Message #212 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Jim Meyering <jim <at> meyering.net>
Cc: Giuseppe Scrivano <gscrivano <at> gnu.org>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Mon, 2 Nov 2015 14:26:07 -0700
[Message part 1 (text/plain, inline)]
On 11/02/2015 02:21 PM, Jim Meyering wrote:
> I too prefer --palette='...'
> That does have the tiny downside that diff's rarely used
> --paginate option will no longer be able to be abbreviated
> as "--pa", but that is barely worth mentioning.

Why do we even have -l/--paginate instead of telling people to run 'diff
| pr'?  Is it worth deprecating that option?

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 03 Nov 2015 02:22:02 GMT) Full text and rfc822 format available.

Message #215 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Eric Blake <eblake <at> redhat.com>, Jim Meyering <jim <at> meyering.net>
Cc: Giuseppe Scrivano <gscrivano <at> gnu.org>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Mon, 2 Nov 2015 18:21:33 -0800
Eric Blake wrote:
> Why do we even have -l/--paginate

4.3BSD had it.  As you mention, -l was a bad idea, but we wanted to be 
compatible with BSD.  I suppose we could deprecate it at some point....




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 03 Nov 2015 17:06:02 GMT) Full text and rfc822 format available.

Message #218 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Tue, 03 Nov 2015 18:05:29 +0100
[Message part 1 (text/plain, inline)]
Jim Meyering <jim <at> meyering.net> writes:

> On Mon, Nov 2, 2015 at 11:34 AM, Eric Blake <eblake <at> redhat.com> wrote:
>> On 11/02/2015 12:17 PM, Giuseppe Scrivano wrote:
>>
>>> Anyway, I would like an opinion about the new name before I send te full
>>> series again.  I avoided --colors as it is very similar to --color, but
>>> I am not sure how --color-scheme sounds for a native speaker.
>>
>> --color-scheme sounds okay to a native, but has the issue that it cannot
>> be abbreviated (--col would be short for --color, not --color-scheme).
>> Can you come up with a name that does not share a common prefix?  Maybe
>> --palette?
>
> Good point.
> I too prefer --palette='...'
> That does have the tiny downside that diff's rarely used
> --paginate option will no longer be able to be abbreviated
> as "--pa", but that is barely worth mentioning.

I have attached the patches that implement --palette, the missing tests
and update the NEWS file.

Regards,
Giuseppe

[0001-diff-add-palette.patch (text/x-patch, inline)]
From f7bf589e9a0c4b893837cfbbdaae5543d72c2b85 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 19 Oct 2015 10:29:41 +0200
Subject: [PATCH 1/3] diff: add --palette

* doc/diffutils.texi: Add documentation for --palette
* src/diff.h (set_color_palette): New prototype.
* src/diff.c (set_color_palette): New function.
(color_palette): New variable.
* src/utils.c (struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_header_color_context): Use put_indicator instead of directly
outputting the sequence.
(set_line_numbers_colors_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
---
 doc/diffutils.texi |   6 +
 src/diff.c         |   7 +
 src/diff.h         |   1 +
 src/util.c         | 439 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 444 insertions(+), 9 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 0944b44..b689673 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3763,6 +3763,7 @@ Always use color.
 Specifying @option{--color} and no @var{when} is equivalent to
 @option{--color=auto}.
 
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
@@ -3890,6 +3891,11 @@ if-then-else format.  @xref{Line Formats}.
 @itemx --show-c-function
 Show which C function each change is in.  @xref{C Function Headings}.
 
+@item --palette=@var{scheme}
+It allows to specify what colors are used to colorize the output.  It
+defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red added lines,
+green deleted lines, cyan line numbers, bold header.
+
 @item -q
 @itemx --brief
 Report only whether the files differ, not the details of the
diff --git a/src/diff.c b/src/diff.c
index 4e0f602..f5298e1 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -140,6 +140,7 @@ enum
   CHANGED_GROUP_FORMAT_OPTION,
 
   COLOR_OPTION,
+  COLOR_PALETTE_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -196,6 +197,7 @@ static struct option const longopts[] =
   {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
   {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
   {"paginate", 0, 0, 'l'},
+  {"palette", 1, 0, COLOR_PALETTE_OPTION},
   {"rcs", 0, 0, 'n'},
   {"recursive", 0, 0, 'r'},
   {"report-identical-files", 0, 0, 's'},
@@ -635,6 +637,10 @@ main (int argc, char **argv)
 	  specify_colors_style (optarg);
 	  break;
 
+	case COLOR_PALETTE_OPTION:
+	  set_color_palette (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -950,6 +956,7 @@ static char const * const option_help_msgid[] = {
   N_("    --speed-large-files  assume large files and many scattered small changes"),
   N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
   N_("                             or 'auto' (the default)"),
+  N_("    --palette=SCHEME    specify the colors to use when --color is active"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
diff --git a/src/diff.h b/src/diff.h
index 472fa93..5930cd1 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -411,3 +411,4 @@ extern void set_add_color_context (void);
 extern void set_delete_color_context (void);
 extern void reset_color_context (void);
 extern void set_line_numbers_color_context (void);
+extern void set_color_palette (const char *palette);
diff --git a/src/util.c b/src/util.c
index 6cc1411..50dce82 100644
--- a/src/util.c
+++ b/src/util.c
@@ -310,6 +310,396 @@ static char const *current_name1;
 static bool currently_recursive;
 static bool colors_enabled;
 
+static struct color_ext_type *color_ext_list = 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 */
+  };
+
+/* Parse a string as part of the DIFF_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 bool
+get_funky_string (char **dest, const char **src, bool 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;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+#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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { LEN_STR_PAIR ("1") },		/* hd: Header */
+    { LEN_STR_PAIR ("32") },		/* ad: Add line */
+    { LEN_STR_PAIR ("31") },		/* de: Delete line */
+    { LEN_STR_PAIR ("36") },		/* ln: Line number */
+  };
+
+static const char *const indicator_name[]=
+  {
+    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
+  };
+
+static const char *color_palette;
+
+void
+set_color_palette (const char *palette)
+{
+  color_palette = palette;
+}
+
+static void
+parse_diff_color (void)
+{
+  char *color_buf;
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = color_palette) == NULL || *p == '\0')
+    return;
+
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     DIFF_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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), label);
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for DIFF_COLORS environment variable"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      colors_enabled = false;
+    }
+}
+
 static void
 check_color_output (bool is_pipe)
 {
@@ -323,6 +713,9 @@ check_color_output (bool is_pipe)
   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));
 
+  if (colors_enabled)
+    parse_diff_color ();
+
   if (output_is_tty)
     install_signal_handlers ();
 }
@@ -923,12 +1316,27 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE
+  };
+
+static void
+put_indicator (const struct bin_str *ind)
+{
+  fwrite (ind->string, ind->len, 1, outfile);
+}
+
 void
 set_header_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[1m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_HEADER]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -936,7 +1344,11 @@ set_line_numbers_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[36m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_LINE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -944,7 +1356,11 @@ set_add_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[32m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_ADD]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -952,17 +1368,22 @@ set_delete_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[31m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_DELETE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
 reset_color_context (void)
 {
-  static char const reset_sequence[] = "\x1b[0m";
-  if (! colors_enabled)
-    return;
-
-  fputs (reset_sequence, outfile);
+  if (colors_enabled)
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_RESET]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.5.0

[0002-doc-mention-color-and-palette-in-NEWS.patch (text/x-patch, inline)]
From 1e9f443fcd587bd6e137d7c627d3a250d09ca5c2 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Mon, 2 Nov 2015 19:03:32 +0000
Subject: [PATCH 2/3] doc: mention --color and --palette in NEWS

---
 NEWS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/NEWS b/NEWS
index 7cdfedd..685dc9d 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,13 @@ GNU diffutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New features
+
+   diff accepts two new options --color and --palette to set a colored
+   output.  --color takes an optional argument specifying when to
+   colorize a line: --color=always, --color=auto, --color=never.
+   --palette is used to change the used colors.
+
 ** Bug fixes
 
   When binary files differ, diff now exits with status 1 as POSIX requires.
-- 
2.5.0

[0003-tests-Add-tests-for-color-and-palette.patch (text/x-patch, inline)]
From 091ffa10f77e0e006d7173b2e7efb21966075824 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Mon, 2 Nov 2015 19:05:10 +0000
Subject: [PATCH 3/3] tests: Add tests for --color and --palette

* tests/colors: New file.
* tests/Makefile.am (TESTS): Add colors.
---
 tests/Makefile.am |  3 ++-
 tests/colors      | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)
 create mode 100755 tests/colors

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 438fbdf..805ccc2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,8 @@ TESTS = \
   no-newline-at-eof \
   stdin \
   strcoll-0-names \
-  filename-quoting
+  filename-quoting \
+  colors
 
 EXTRA_DIST = \
   $(TESTS) init.sh t-local.sh
diff --git a/tests/colors b/tests/colors
new file mode 100755
index 0000000..9e1ef40
--- /dev/null
+++ b/tests/colors
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+
+TZ=UTC0
+export TZ
+
+fail=0
+
+echo a > a
+echo b > b
+
+touch --date='1970-01-01 00:00:00' a b
+
+# Compare with some known outputs
+
+diff --color=always a b | sha1sum \
+     | grep dbed959c9975cf761ff4950d93d342d7c271c11f || fail=1
+
+diff --color=auto a b | sha1sum \
+     | grep 90742ce0d628cc2f2067b232404578e000b80cce || fail=1
+
+diff --color=never a b | sha1sum \
+     | grep 90742ce0d628cc2f2067b232404578e000b80cce || fail=1
+
+diff --color a b | sha1sum \
+     | grep 90742ce0d628cc2f2067b232404578e000b80cce || fail=1
+
+diff -u --color=always a b | sha1sum \
+     | grep 5712fbffc94c501eaeec5cb02468ce2bbed6d7c9 || fail=1
+
+diff -c --color=always a b | sha1sum \
+     | grep 904a91f82474e3532459b759fdacbdb339070e14 || fail=1
+
+diff -N --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b \
+     | sha1sum | grep 7796a82c2e7bd1f4ee04cb44352d83e1db87c092 || fail=1
+
+Exit $fail
-- 
2.5.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 03 Nov 2015 17:28:01 GMT) Full text and rfc822 format available.

Message #221 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>, Jim Meyering <jim <at> meyering.net>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Tue, 3 Nov 2015 10:27:10 -0700
[Message part 1 (text/plain, inline)]
On 11/03/2015 10:05 AM, Giuseppe Scrivano wrote:

> I have attached the patches that implement --palette, the missing tests
> and update the NEWS file.
> 

> +++ b/doc/diffutils.texi
> @@ -3763,6 +3763,7 @@ Always use color.
>  Specifying @option{--color} and no @var{when} is equivalent to
>  @option{--color=auto}.
>  
> +
>  @item -C @var{lines}

Spurious change?

>  @itemx --context <at> r{[}=@var{lines}@r{]}
>  Use the context output format, showing @var{lines} (an integer) lines of
> @@ -3890,6 +3891,11 @@ if-then-else format.  @xref{Line Formats}.
>  @itemx --show-c-function
>  Show which C function each change is in.  @xref{C Function Headings}.
>  
> +@item --palette=@var{scheme}
> +It allows to specify what colors are used to colorize the output.  It

Passive voice.  Would sound better as:

Specify what color palette to use when colored output to use.

> +defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red added lines,
> +green deleted lines, cyan line numbers, bold header.

That says the default, but doesn't say what conventions to apply to get
something other than the default.  What are 'rs', 'hd', 'ad', 'de', and
'ln'?  Can we link to the fact that the values of these variables are
generally applied inside a terminal '\e[Xm' sequence, for a given X?
Can we link to dircolors output for comparison?  Do we need parameters
for the '\e[' and 'm' prefix/suffix, in case the terminal understands
something different than ANSI escapes for colors?

> @@ -950,6 +956,7 @@ static char const * const option_help_msgid[] = {
>    N_("    --speed-large-files  assume large files and many scattered small changes"),
>    N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
>    N_("                             or 'auto' (the default)"),
> +  N_("    --palette=SCHEME    specify the colors to use when --color is active"),
>    "",
>    N_("    --help               display this help and exit"),
>    N_("-v, --version            output version information and exit"),

Where is SCHEME documented in --help?  Should it be?


> +
> +/* Parse a string as part of the DIFF_COLORS variable; this may involve

Stale reference to envvar instead of --palette.

> +
> +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+rs+rc) */
> +    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
> +    { LEN_STR_PAIR ("1") },		/* hd: Header */
> +    { LEN_STR_PAIR ("32") },		/* ad: Add line */
> +    { LEN_STR_PAIR ("31") },		/* de: Delete line */
> +    { LEN_STR_PAIR ("36") },		/* ln: Line number */
> +  };

This needs to be documented in more than just code comments.


> +  ext = NULL;
> +  strcpy (label, "??");
> +
> +  /* This is an overly conservative estimate, but any possible
> +     DIFF_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);

Another stale ref to DIFF_COLORS.

> 0002-doc-mention-color-and-palette-in-NEWS.patch
> 

Looked okay to me.

> 0003-tests-Add-tests-for-color-and-palette.patch
> 

> +
> +# Compare with some known outputs
> +

> +diff -c --color=always a b | sha1sum \
> +     | grep 904a91f82474e3532459b759fdacbdb339070e14 || fail=1
> +
> +diff -N --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b \
> +     | sha1sum | grep 7796a82c2e7bd1f4ee04cb44352d83e1db87c092 || fail=1

Nice; a test of a non-default palette.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 03 Nov 2015 17:32:02 GMT) Full text and rfc822 format available.

Message #224 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: Giuseppe Scrivano <gscrivano <at> gnu.org>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Tue, 3 Nov 2015 09:30:42 -0800
On Tue, Nov 3, 2015 at 9:27 AM, Eric Blake <eblake <at> redhat.com> wrote:
> On 11/03/2015 10:05 AM, Giuseppe Scrivano wrote:
>
>> I have attached the patches that implement --palette, the missing tests
>> and update the NEWS file.
>>
>
>> +++ b/doc/diffutils.texi
>> @@ -3763,6 +3763,7 @@ Always use color.
>>  Specifying @option{--color} and no @var{when} is equivalent to
>>  @option{--color=auto}.
>>
>> +
>>  @item -C @var{lines}
>
> Spurious change?
>
>>  @itemx --context <at> r{[}=@var{lines}@r{]}
>>  Use the context output format, showing @var{lines} (an integer) lines of
>> @@ -3890,6 +3891,11 @@ if-then-else format.  @xref{Line Formats}.
>>  @itemx --show-c-function
>>  Show which C function each change is in.  @xref{C Function Headings}.
>>
>> +@item --palette=@var{scheme}
>> +It allows to specify what colors are used to colorize the output.  It
>
> Passive voice.  Would sound better as:
>
> Specify what color palette to use when colored output to use.

Thanks for the quick review, Eric.
I'll wait for the next iteration.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 03 Nov 2015 17:39:02 GMT) Full text and rfc822 format available.

Message #227 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>, Jim Meyering <jim <at> meyering.net>
Cc: 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: bug#20062: [PATCH] diff: add support
 for --color
Date: Tue, 3 Nov 2015 10:38:38 -0700
[Message part 1 (text/plain, inline)]
On 11/03/2015 10:27 AM, Eric Blake wrote:

>> +@item --palette=@var{scheme}
>> +It allows to specify what colors are used to colorize the output.  It
> 
> Passive voice.  Would sound better as:
> 
> Specify what color palette to use when colored output to use.

Bad editing on my part.

Specify what color palette to use when colored output is enabled.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Tue, 17 Nov 2015 00:20:02 GMT) Full text and rfc822 format available.

Message #230 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Tue, 17 Nov 2015 01:19:19 +0100
[Message part 1 (text/plain, inline)]
Jim Meyering <jim <at> meyering.net> writes:

> On Tue, Nov 3, 2015 at 9:27 AM, Eric Blake <eblake <at> redhat.com> wrote:
>> On 11/03/2015 10:05 AM, Giuseppe Scrivano wrote:
>>
>>> I have attached the patches that implement --palette, the missing tests
>>> and update the NEWS file.
>>>
>>
>>> +++ b/doc/diffutils.texi
>>> @@ -3763,6 +3763,7 @@ Always use color.
>>>  Specifying @option{--color} and no @var{when} is equivalent to
>>>  @option{--color=auto}.
>>>
>>> +
>>>  @item -C @var{lines}
>>
>> Spurious change?
>>
>>>  @itemx --context <at> r{[}=@var{lines}@r{]}
>>>  Use the context output format, showing @var{lines} (an integer) lines of
>>> @@ -3890,6 +3891,11 @@ if-then-else format.  @xref{Line Formats}.
>>>  @itemx --show-c-function
>>>  Show which C function each change is in.  @xref{C Function Headings}.
>>>
>>> +@item --palette=@var{scheme}
>>> +It allows to specify what colors are used to colorize the output.  It
>>
>> Passive voice.  Would sound better as:
>>
>> Specify what color palette to use when colored output to use.
>
> Thanks for the quick review, Eric.
> I'll wait for the next iteration.

sorry for taking so long, I hope the attached version is fine.

Regards,
Giuseppe


[0001-diff-add-palette.patch (text/x-patch, inline)]
From 0861846757aa711d1ee5a0176205320e807c306b Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 19 Oct 2015 10:29:41 +0200
Subject: [PATCH 1/3] diff: add --palette

* doc/diffutils.texi: Add documentation for --palette
* src/diff.h (set_color_palette): New prototype.
* src/diff.c (set_color_palette): New function.
(color_palette): New variable.
* src/utils.c (struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_header_color_context): Use put_indicator instead of directly
outputting the sequence.
(set_line_numbers_colors_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
---
 doc/diffutils.texi |  34 +++++
 src/diff.c         |   8 +
 src/diff.h         |   1 +
 src/util.c         | 439 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 473 insertions(+), 9 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 0944b44..39ba35d 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3890,6 +3890,40 @@ if-then-else format.  @xref{Line Formats}.
 @itemx --show-c-function
 Show which C function each change is in.  @xref{C Function Headings}.
 
+@item --palette=@var{palette}
+Specify what color palette to use when colored output is enabled.  It
+defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red deleted lines,
+green added lines, cyan line numbers, bold header.
+
+Supported capabilities are as follows.
+
+@table @code
+@item ad=32
+@vindex ad @r{capability}
+
+SGR substring for added lines.
+The default is green foreground.
+
+@item de=31
+@vindex de @r{capability}
+
+SGR substring for deleted lines.
+The default is red foreground.
+
+@item hd=1
+@vindex hd @r{capability}
+
+SGR substring for chunk header.
+The default is bold foreground.
+
+@item ln=36
+@vindex ln @r{capability}
+
+SGR substring for line numbers.
+The default is cyan foreground.
+@end table
+
+
 @item -q
 @itemx --brief
 Report only whether the files differ, not the details of the
diff --git a/src/diff.c b/src/diff.c
index 4e0f602..4c3d29a 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -140,6 +140,7 @@ enum
   CHANGED_GROUP_FORMAT_OPTION,
 
   COLOR_OPTION,
+  COLOR_PALETTE_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -196,6 +197,7 @@ static struct option const longopts[] =
   {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
   {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
   {"paginate", 0, 0, 'l'},
+  {"palette", 1, 0, COLOR_PALETTE_OPTION},
   {"rcs", 0, 0, 'n'},
   {"recursive", 0, 0, 'r'},
   {"report-identical-files", 0, 0, 's'},
@@ -635,6 +637,10 @@ main (int argc, char **argv)
 	  specify_colors_style (optarg);
 	  break;
 
+	case COLOR_PALETTE_OPTION:
+	  set_color_palette (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -950,6 +956,8 @@ static char const * const option_help_msgid[] = {
   N_("    --speed-large-files  assume large files and many scattered small changes"),
   N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
   N_("                             or 'auto' (the default)"),
+  N_("    --palette=PALETTE    specify the colors to use when --color is active"),
+  N_("                         PALETTE is a colon-separated list terminfo capabilities"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
diff --git a/src/diff.h b/src/diff.h
index 472fa93..5930cd1 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -411,3 +411,4 @@ extern void set_add_color_context (void);
 extern void set_delete_color_context (void);
 extern void reset_color_context (void);
 extern void set_line_numbers_color_context (void);
+extern void set_color_palette (const char *palette);
diff --git a/src/util.c b/src/util.c
index 6cc1411..dedf3b3 100644
--- a/src/util.c
+++ b/src/util.c
@@ -310,6 +310,396 @@ static char const *current_name1;
 static bool currently_recursive;
 static bool colors_enabled;
 
+static struct color_ext_type *color_ext_list = 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 */
+  };
+
+/* Parse a string as part of the --palette argument; 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 bool
+get_funky_string (char **dest, const char **src, bool 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;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+#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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { LEN_STR_PAIR ("1") },		/* hd: Header */
+    { LEN_STR_PAIR ("32") },		/* ad: Add line */
+    { LEN_STR_PAIR ("31") },		/* de: Delete line */
+    { LEN_STR_PAIR ("36") },		/* ln: Line number */
+  };
+
+static const char *const indicator_name[]=
+  {
+    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
+  };
+
+static const char *color_palette;
+
+void
+set_color_palette (const char *palette)
+{
+  color_palette = palette;
+}
+
+static void
+parse_diff_color (void)
+{
+  char *color_buf;
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = color_palette) == NULL || *p == '\0')
+    return;
+
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     --palette 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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), label);
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for --palette"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      colors_enabled = false;
+    }
+}
+
 static void
 check_color_output (bool is_pipe)
 {
@@ -323,6 +713,9 @@ check_color_output (bool is_pipe)
   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));
 
+  if (colors_enabled)
+    parse_diff_color ();
+
   if (output_is_tty)
     install_signal_handlers ();
 }
@@ -923,12 +1316,27 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE
+  };
+
+static void
+put_indicator (const struct bin_str *ind)
+{
+  fwrite (ind->string, ind->len, 1, outfile);
+}
+
 void
 set_header_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[1m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_HEADER]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -936,7 +1344,11 @@ set_line_numbers_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[36m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_LINE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -944,7 +1356,11 @@ set_add_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[32m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_ADD]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -952,17 +1368,22 @@ set_delete_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[31m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_DELETE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
 reset_color_context (void)
 {
-  static char const reset_sequence[] = "\x1b[0m";
-  if (! colors_enabled)
-    return;
-
-  fputs (reset_sequence, outfile);
+  if (colors_enabled)
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_RESET]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.5.0

[0002-doc-mention-color-and-palette-in-NEWS.patch (text/x-patch, inline)]
From 24a52233fd92f2f34b5050aa7f575b15ed51e0ff Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Mon, 2 Nov 2015 19:03:32 +0000
Subject: [PATCH 2/3] doc: mention --color and --palette in NEWS

---
 NEWS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/NEWS b/NEWS
index 7cdfedd..685dc9d 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,13 @@ GNU diffutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New features
+
+   diff accepts two new options --color and --palette to set a colored
+   output.  --color takes an optional argument specifying when to
+   colorize a line: --color=always, --color=auto, --color=never.
+   --palette is used to change the used colors.
+
 ** Bug fixes
 
   When binary files differ, diff now exits with status 1 as POSIX requires.
-- 
2.5.0

[0003-tests-Add-tests-for-color-and-palette.patch (text/x-patch, inline)]
From 6537a0d21f3bbb17690effcc33db2c5a7fdea795 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan <at> redhat.com>
Date: Mon, 2 Nov 2015 19:05:10 +0000
Subject: [PATCH 3/3] tests: Add tests for --color and --palette

* tests/colors: New file.
* tests/Makefile.am (TESTS): Add colors.
---
 tests/Makefile.am |  3 ++-
 tests/colors      | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)
 create mode 100755 tests/colors

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 438fbdf..805ccc2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,8 @@ TESTS = \
   no-newline-at-eof \
   stdin \
   strcoll-0-names \
-  filename-quoting
+  filename-quoting \
+  colors
 
 EXTRA_DIST = \
   $(TESTS) init.sh t-local.sh
diff --git a/tests/colors b/tests/colors
new file mode 100755
index 0000000..02da181
--- /dev/null
+++ b/tests/colors
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+
+TZ=UTC0
+export TZ
+
+fail=0
+
+echo a > a
+echo b > b
+
+touch --date='1970-01-01 00:00:00' a b
+
+# Compare with some known outputs
+
+diff --color=always a b | sha1sum \
+     | grep dbed959c9975cf761ff4950d93d342d7c271c11f || fail=1
+
+diff --color=auto a b | sha1sum \
+     | grep 90742ce0d628cc2f2067b232404578e000b80cce || fail=1
+
+diff --color=never a b | sha1sum \
+     | grep 90742ce0d628cc2f2067b232404578e000b80cce || fail=1
+
+diff --color a b | sha1sum \
+     | grep 90742ce0d628cc2f2067b232404578e000b80cce || fail=1
+
+diff -u --color=always a b | sha1sum \
+     | grep 5712fbffc94c501eaeec5cb02468ce2bbed6d7c9 || fail=1
+
+diff -c --color=always a b | sha1sum \
+     | grep a9bfa18ea6425db547ac69bc13fb78cf9416ad55 || fail=1
+
+diff -N --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b \
+     | sha1sum | grep 7796a82c2e7bd1f4ee04cb44352d83e1db87c092 || fail=1
+
+Exit $fail
-- 
2.5.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Thu, 26 Nov 2015 17:18:01 GMT) Full text and rfc822 format available.

Message #233 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Thu, 26 Nov 2015 09:17:24 -0800
[Message part 1 (text/plain, inline)]
On Mon, Nov 16, 2015 at 4:19 PM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
> Jim Meyering <jim <at> meyering.net> writes:
>
>> On Tue, Nov 3, 2015 at 9:27 AM, Eric Blake <eblake <at> redhat.com> wrote:
>>> On 11/03/2015 10:05 AM, Giuseppe Scrivano wrote:
>>>
>>>> I have attached the patches that implement --palette, the missing tests
>>>> and update the NEWS file.
>>>>
>>>
>>>> +++ b/doc/diffutils.texi
>>>> @@ -3763,6 +3763,7 @@ Always use color.
>>>>  Specifying @option{--color} and no @var{when} is equivalent to
>>>>  @option{--color=auto}.
>>>>
>>>> +
>>>>  @item -C @var{lines}
>>>
>>> Spurious change?
>>>
>>>>  @itemx --context <at> r{[}=@var{lines}@r{]}
>>>>  Use the context output format, showing @var{lines} (an integer) lines of
>>>> @@ -3890,6 +3891,11 @@ if-then-else format.  @xref{Line Formats}.
>>>>  @itemx --show-c-function
>>>>  Show which C function each change is in.  @xref{C Function Headings}.
>>>>
>>>> +@item --palette=@var{scheme}
>>>> +It allows to specify what colors are used to colorize the output.  It
>>>
>>> Passive voice.  Would sound better as:
>>>
>>> Specify what color palette to use when colored output to use.
>>
>> Thanks for the quick review, Eric.
>> I'll wait for the next iteration.
>
> sorry for taking so long, I hope the attached version is fine.

I have begun reviewing carefully.
I adjusted NEWS.  Here is the modified paragraph:

** New features

   diff accepts two new options --color and --palette to generate
   and configure colored output.  --color takes an optional argument
   specifying when to colorize a line: --color=always, --color=auto,
   --color=never.  --palette is used to configure which colors are used.

I looked at the tests/colors script: we cannot/should not use sha1sum
for two reasons. 1) it is a short cut; better to include the precise expected
output in each case. Using this approach, if/when a test fails, there is
no record of what the expected output was. 2) the "sha1sum" command
is not universally available by that name. On BSD-based systems it is
called "sha1". Thus, I began the conversion, and in so doing, I found
some room for improvement: with the current patches I have, diff -u
emits a pair of identical color-changing escape sequences before each
"+"-prefixed line:

  $ diff -u --color=always a b|cat -A
  ^[[1;39m--- a^I1969-12-31 16:00:00.000000000 -0800$
  +++ b^I1969-12-31 16:00:00.000000000 -0800$
  ^[[0m^[[36m@@ -1 +1 @@^[[0m$
  ^[[31m-a$
  ^[[32m^[[32m+b$
  ^[[0m

Notice also how the final \e[0m is on the final line by itself,
with no following newline. Please adjust so that it appears at the
end of the final line instead. I confirmed that git-diff appears
to do the same thing, but noted that git uses \e[m instead (no
"0" part). Do you know of any pros/cons for one or the other?

I've attached the beginnings of the adjusted tests/colors
script that I used to discover these things. Can you finish the job
of converting it to use "compare" rather than sha1sum?
[colors.sh (application/x-sh, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 27 Nov 2015 15:04:01 GMT) Full text and rfc822 format available.

Message #236 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 27 Nov 2015 16:01:56 +0100
[Message part 1 (text/plain, inline)]
Hi Jim,

thanks for the great advices.

Jim Meyering <jim <at> meyering.net> writes:

> I looked at the tests/colors script: we cannot/should not use sha1sum
> for two reasons. 1) it is a short cut; better to include the precise expected
> output in each case. Using this approach, if/when a test fails, there is
> no record of what the expected output was. 2) the "sha1sum" command
> is not universally available by that name. On BSD-based systems it is
> called "sha1". Thus, I began the conversion, and in so doing, I found
> some room for improvement: with the current patches I have, diff -u
> emits a pair of identical color-changing escape sequences before each
> "+"-prefixed line:
>
>   $ diff -u --color=always a b|cat -A
>   ^[[1;39m--- a^I1969-12-31 16:00:00.000000000 -0800$
>   +++ b^I1969-12-31 16:00:00.000000000 -0800$
>   ^[[0m^[[36m@@ -1 +1 @@^[[0m$
>   ^[[31m-a$
>   ^[[32m^[[32m+b$
>   ^[[0m
>
> Notice also how the final \e[0m is on the final line by itself,
> with no following newline. Please adjust so that it appears at the
> end of the final line instead. I confirmed that git-diff appears
> to do the same thing, but noted that git uses \e[m instead (no
> "0" part). Do you know of any pros/cons for one or the other?

I don't know if there is any difference in practice between \e[m and
\e0[m. I took the implementation in ls as reference which uses \e0[m.


> I've attached the beginnings of the adjusted tests/colors
> script that I used to discover these things. Can you finish the job
> of converting it to use "compare" rather than sha1sum?

Sure.  The new tests helped me to spot two issues in the "diff: add
support for --color" patch.  I also added some extra check to avoid to
enter the same colors context twice.  These fixes are in
0004-fixup-diff-add-support-for-color.patch.

To generate sequences on the same line they belong, I have created a new
patch to facilitate the review.  Probably the patch should get squashed
into 0001-diff-add-support-for-color.patch and 0005-tests-Add-tests-for-color-and-palette.patch
once reviewed.

I have not changed the first two patches of the series, I am including
them just for completeness.

Regards,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 50def324f48ff09501e6f07cecae2a6cd5a16e3c Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH 1/6] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_add_color_context): Add function definition.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(reset_color_context): Likewise.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(output_1_line): Periodically call `process_signals'.
(caught_signals): New sigset_t.
(colors_enabled): New boolean variable.
(interrupt_signal): New sig_atomic_t.
(stop_signal_count): New sig_atomic_t.
(check_color_output): New function.
(install_signal_handlers): Likewise. Copied from coreutils ls.
(process_signals): Likewise.  Copied from coreutils ls.
(reset_color_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(set_header_color_context): Likewise.
(set_line_numbers_color_context): Likewise.
(sighandler): Likewise.  Copied from coreutils ls.
(stophandler): Likewise.  Copied from coreutils ls.
---
 doc/diffutils.texi |  21 ++++
 src/context.c      |  51 +++++++--
 src/diff.c         |  27 ++++-
 src/diff.h         |  21 ++++
 src/normal.c       |  18 ++-
 src/side.c         |  15 +++
 src/util.c         | 316 +++++++++++++++++++++++++++++++++++++++++++++++------
 7 files changed, 421 insertions(+), 48 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 091257f..0944b44 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3742,6 +3742,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is present.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Only use color if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..8e9a74f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_header_color_context ();
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  reset_color_context ();
 }
 
 /* Print an edit script in context format.  */
@@ -215,6 +217,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,12 +228,18 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_delete_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 
@@ -244,6 +253,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +264,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_add_color_context ();
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            reset_color_context ();
 	}
     }
 }
@@ -330,11 +345,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_line_numbers_color_context ();
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  reset_color_context ();
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +377,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_delete_color_context ();
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,9 +400,15 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_add_color_context ();
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
@@ -386,6 +417,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            reset_color_context ();
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index efd7e47..4e0f602 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
+  N_("                             or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..472fa93 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* What kind of changes a hunk contains.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,8 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+extern void set_header_color_context (void);
+extern void set_add_color_context (void);
+extern void set_delete_color_context (void);
+extern void reset_color_context (void);
+extern void set_line_numbers_color_context (void);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..227af10 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_line_numbers_color_context ();
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  reset_color_context ();
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_delete_color_context ();
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      reset_color_context ();
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_add_color_context ();
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      reset_color_context ();
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..b762d31 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_delete_color_context ();
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_add_color_context ();
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    reset_color_context ();
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..6cc1411 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,22 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +159,174 @@ print_message_queue (void)
     }
 }
 
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      reset_color_context ();
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+static void
+install_signal_handlers (void)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = sizeof (sig) / sizeof *(sig) };
+
+#if ! SA_NOCLDSTOP
+  bool caught_sig[nsigs];
+#endif
+  {
+    int j;
+#if SA_NOCLDSTOP
+    struct sigaction act;
+
+    sigemptyset (&caught_signals);
+    for (j = 0; j < nsigs; j++)
+      {
+        sigaction (sig[j], NULL, &act);
+        if (act.sa_handler != SIG_IGN)
+          sigaddset (&caught_signals, sig[j]);
+      }
+
+    act.sa_mask = caught_signals;
+    act.sa_flags = SA_RESTART;
+
+    for (j = 0; j < nsigs; j++)
+      if (sigismember (&caught_signals, sig[j]))
+        {
+          act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+    for (j = 0; j < nsigs; j++)
+      {
+        caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+        if (caught_sig[j])
+          {
+            signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+            siginterrupt (sig[j], 0);
+          }
+      }
+#endif
+    }
+}
+
+static char const *current_name0;
+static char const *current_name1;
+static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (bool is_pipe)
+{
+  bool output_is_tty;
+
+  if (! outfile || colors_style == NEVER)
+    return;
+
+  output_is_tty = !is_pipe && isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -150,10 +334,6 @@ print_message_queue (void)
    we fork off a 'pr' and make OUTFILE a pipe to it.
    'pr' then outputs to our stdout.  */
 
-static char const *current_name0;
-static char const *current_name1;
-static bool currently_recursive;
-
 void
 setup_output (char const *name0, char const *name1, bool recursive)
 {
@@ -313,6 +493,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +501,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +512,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +855,21 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  const size_t MAX_CHUNK = 1024;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t to_write = MIN (left, MAX_CHUNK);
+          size_t written = fwrite (base, sizeof (char), to_write, outfile);
+          if (written < to_write)
+            return;
+          base += written;
+          left -= written;
+          process_signals ();
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,40 +877,92 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t counter_proc_signals = 0;
 
       while (t < limit)
-	switch ((c = *t++))
-	  {
-	  case '\t':
-	    {
-	      size_t spaces = tab_size - column % tab_size;
-	      column += spaces;
-	      do
-		putc (' ', out);
-	      while (--spaces);
-	    }
-	    break;
+        {
+          counter_proc_signals++;
+          if (counter_proc_signals == MAX_CHUNK)
+            {
+              process_signals ();
+              counter_proc_signals = 0;
+            }
+
+          switch ((c = *t++))
+            {
+            case '\t':
+              {
+                size_t spaces = tab_size - column % tab_size;
+                column += spaces;
+                do
+                  putc (' ', out);
+                while (--spaces);
+              }
+              break;
+
+            case '\r':
+              putc (c, out);
+              if (flag_format && t < limit && *t != '\n')
+                fprintf (out, flag_format, line_flag);
+              column = 0;
+              break;
+
+            case '\b':
+              if (column == 0)
+                continue;
+              column--;
+              putc (c, out);
+              break;
+
+            default:
+              column += isprint (c) != 0;
+              putc (c, out);
+              break;
+            }
+        }
+    }
+}
 
-	  case '\r':
-	    putc (c, out);
-	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
-	    column = 0;
-	    break;
+void
+set_header_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[1m", outfile);
+}
 
-	  case '\b':
-	    if (column == 0)
-	      continue;
-	    column--;
-	    putc (c, out);
-	    break;
+void
+set_line_numbers_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[36m", outfile);
+}
 
-	  default:
-	    column += isprint (c) != 0;
-	    putc (c, out);
-	    break;
-	  }
-    }
+void
+set_add_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[32m", outfile);
+}
+
+void
+set_delete_color_context (void)
+{
+  process_signals ();
+  if (colors_enabled)
+    fputs ("\x1B[31m", outfile);
+}
+
+void
+reset_color_context (void)
+{
+  static char const reset_sequence[] = "\x1b[0m";
+  if (! colors_enabled)
+    return;
+
+  fputs (reset_sequence, outfile);
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.5.0

[0002-diff-add-palette.patch (text/x-patch, inline)]
From 0bbbbc9ce6a2b68e2ff20a1a8b4ed69a7e43619b Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 19 Oct 2015 10:29:41 +0200
Subject: [PATCH 2/6] diff: add --palette

* doc/diffutils.texi: Add documentation for --palette
* src/diff.h (set_color_palette): New prototype.
* src/diff.c (set_color_palette): New function.
(color_palette): New variable.
* src/utils.c (struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_header_color_context): Use put_indicator instead of directly
outputting the sequence.
(set_line_numbers_colors_context): Likewise.
(set_add_color_context): Likewise.
(set_delete_color_context): Likewise.
(reset_color_context): Likewise.
---
 doc/diffutils.texi |  34 +++++
 src/diff.c         |   8 +
 src/diff.h         |   1 +
 src/util.c         | 439 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 473 insertions(+), 9 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 0944b44..39ba35d 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3890,6 +3890,40 @@ if-then-else format.  @xref{Line Formats}.
 @itemx --show-c-function
 Show which C function each change is in.  @xref{C Function Headings}.
 
+@item --palette=@var{palette}
+Specify what color palette to use when colored output is enabled.  It
+defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red deleted lines,
+green added lines, cyan line numbers, bold header.
+
+Supported capabilities are as follows.
+
+@table @code
+@item ad=32
+@vindex ad @r{capability}
+
+SGR substring for added lines.
+The default is green foreground.
+
+@item de=31
+@vindex de @r{capability}
+
+SGR substring for deleted lines.
+The default is red foreground.
+
+@item hd=1
+@vindex hd @r{capability}
+
+SGR substring for chunk header.
+The default is bold foreground.
+
+@item ln=36
+@vindex ln @r{capability}
+
+SGR substring for line numbers.
+The default is cyan foreground.
+@end table
+
+
 @item -q
 @itemx --brief
 Report only whether the files differ, not the details of the
diff --git a/src/diff.c b/src/diff.c
index 4e0f602..4c3d29a 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -140,6 +140,7 @@ enum
   CHANGED_GROUP_FORMAT_OPTION,
 
   COLOR_OPTION,
+  COLOR_PALETTE_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -196,6 +197,7 @@ static struct option const longopts[] =
   {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
   {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
   {"paginate", 0, 0, 'l'},
+  {"palette", 1, 0, COLOR_PALETTE_OPTION},
   {"rcs", 0, 0, 'n'},
   {"recursive", 0, 0, 'r'},
   {"report-identical-files", 0, 0, 's'},
@@ -635,6 +637,10 @@ main (int argc, char **argv)
 	  specify_colors_style (optarg);
 	  break;
 
+	case COLOR_PALETTE_OPTION:
+	  set_color_palette (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -950,6 +956,8 @@ static char const * const option_help_msgid[] = {
   N_("    --speed-large-files  assume large files and many scattered small changes"),
   N_("    --color[=WHEN]         colorize the output; WHEN can be 'never', 'always',"),
   N_("                             or 'auto' (the default)"),
+  N_("    --palette=PALETTE    specify the colors to use when --color is active"),
+  N_("                         PALETTE is a colon-separated list terminfo capabilities"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
diff --git a/src/diff.h b/src/diff.h
index 472fa93..5930cd1 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -411,3 +411,4 @@ extern void set_add_color_context (void);
 extern void set_delete_color_context (void);
 extern void reset_color_context (void);
 extern void set_line_numbers_color_context (void);
+extern void set_color_palette (const char *palette);
diff --git a/src/util.c b/src/util.c
index 6cc1411..dedf3b3 100644
--- a/src/util.c
+++ b/src/util.c
@@ -310,6 +310,396 @@ static char const *current_name1;
 static bool currently_recursive;
 static bool colors_enabled;
 
+static struct color_ext_type *color_ext_list = 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 */
+  };
+
+/* Parse a string as part of the --palette argument; 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 bool
+get_funky_string (char **dest, const char **src, bool 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;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+#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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { LEN_STR_PAIR ("1") },		/* hd: Header */
+    { LEN_STR_PAIR ("32") },		/* ad: Add line */
+    { LEN_STR_PAIR ("31") },		/* de: Delete line */
+    { LEN_STR_PAIR ("36") },		/* ln: Line number */
+  };
+
+static const char *const indicator_name[]=
+  {
+    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
+  };
+
+static const char *color_palette;
+
+void
+set_color_palette (const char *palette)
+{
+  color_palette = palette;
+}
+
+static void
+parse_diff_color (void)
+{
+  char *color_buf;
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = color_palette) == NULL || *p == '\0')
+    return;
+
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     --palette 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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), label);
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for --palette"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      colors_enabled = false;
+    }
+}
+
 static void
 check_color_output (bool is_pipe)
 {
@@ -323,6 +713,9 @@ check_color_output (bool is_pipe)
   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));
 
+  if (colors_enabled)
+    parse_diff_color ();
+
   if (output_is_tty)
     install_signal_handlers ();
 }
@@ -923,12 +1316,27 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE
+  };
+
+static void
+put_indicator (const struct bin_str *ind)
+{
+  fwrite (ind->string, ind->len, 1, outfile);
+}
+
 void
 set_header_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[1m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_HEADER]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -936,7 +1344,11 @@ set_line_numbers_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[36m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_LINE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -944,7 +1356,11 @@ set_add_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[32m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_ADD]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
@@ -952,17 +1368,22 @@ set_delete_color_context (void)
 {
   process_signals ();
   if (colors_enabled)
-    fputs ("\x1B[31m", outfile);
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_DELETE]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 void
 reset_color_context (void)
 {
-  static char const reset_sequence[] = "\x1b[0m";
-  if (! colors_enabled)
-    return;
-
-  fputs (reset_sequence, outfile);
+  if (colors_enabled)
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_RESET]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
 }
 
 char const change_letter[] = { 0, 'd', 'a', 'c' };
-- 
2.5.0

[0003-doc-mention-color-and-palette-in-NEWS.patch (text/x-patch, inline)]
From 88b6384539d6d6e5c74e07efa55dbc6f4b12de6d Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 2 Nov 2015 19:03:32 +0000
Subject: [PATCH 3/6] doc: mention --color and --palette in NEWS

---
 NEWS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/NEWS b/NEWS
index 7cdfedd..088f13b 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,13 @@ GNU diffutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New features
+
+   diff accepts two new options --color and --palette to generate
+   and configure colored output.  --color takes an optional argument
+   specifying when to colorize a line: --color=always, --color=auto,
+   --color=never.  --palette is used to configure which colors are used.
+
 ** Bug fixes
 
   When binary files differ, diff now exits with status 1 as POSIX requires.
-- 
2.5.0

[0004-fixup-diff-add-support-for-color.patch (text/x-patch, inline)]
From 49d2d3887f3358874a9e548209b0bd83ec347234 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Fri, 27 Nov 2015 11:43:58 +0000
Subject: [PATCH 4/6] fixup! diff: add support for --color

---
 src/context.c |  5 ++++-
 src/util.c    | 17 ++++++++++++-----
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/src/context.c b/src/context.c
index 8e9a74f..216f547 100644
--- a/src/context.c
+++ b/src/context.c
@@ -207,9 +207,11 @@ pr_context_hunk (struct change *hunk)
   if (function)
     print_context_function (out, function);
 
+  set_line_numbers_color_context ();
   fputs ("\n*** ", out);
   print_context_number_range (&files[0], first0, last0);
   fputs (" ****\n", out);
+  reset_color_context ();
 
   if (changes & OLD)
     {
@@ -243,9 +245,11 @@ pr_context_hunk (struct change *hunk)
 	}
     }
 
+  set_line_numbers_color_context ();
   fputs ("--- ", out);
   print_context_number_range (&files[1], first1, last1);
   fputs (" ----\n", out);
+  reset_color_context ();
 
   if (changes & NEW)
     {
@@ -408,7 +412,6 @@ pr_unidiff_hunk (struct change *hunk)
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
-	      set_add_color_context ();
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
diff --git a/src/util.c b/src/util.c
index dedf3b3..b0e277b 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1327,15 +1327,18 @@ put_indicator (const struct bin_str *ind)
   fwrite (ind->string, ind->len, 1, outfile);
 }
 
+static enum indicator_no last_context = C_RESET;
+
 void
 set_header_color_context (void)
 {
   process_signals ();
-  if (colors_enabled)
+  if (colors_enabled && last_context != C_HEADER)
     {
       put_indicator (&color_indicator[C_LEFT]);
       put_indicator (&color_indicator[C_HEADER]);
       put_indicator (&color_indicator[C_RIGHT]);
+      last_context = C_HEADER;
     }
 }
 
@@ -1343,11 +1346,12 @@ void
 set_line_numbers_color_context (void)
 {
   process_signals ();
-  if (colors_enabled)
+  if (colors_enabled && last_context != C_LINE)
     {
       put_indicator (&color_indicator[C_LEFT]);
       put_indicator (&color_indicator[C_LINE]);
       put_indicator (&color_indicator[C_RIGHT]);
+      last_context = C_LINE;
     }
 }
 
@@ -1355,11 +1359,12 @@ void
 set_add_color_context (void)
 {
   process_signals ();
-  if (colors_enabled)
+  if (colors_enabled && last_context != C_ADD)
     {
       put_indicator (&color_indicator[C_LEFT]);
       put_indicator (&color_indicator[C_ADD]);
       put_indicator (&color_indicator[C_RIGHT]);
+      last_context = C_ADD;
     }
 }
 
@@ -1367,22 +1372,24 @@ void
 set_delete_color_context (void)
 {
   process_signals ();
-  if (colors_enabled)
+  if (colors_enabled && last_context != C_DELETE)
     {
       put_indicator (&color_indicator[C_LEFT]);
       put_indicator (&color_indicator[C_DELETE]);
       put_indicator (&color_indicator[C_RIGHT]);
+      last_context = C_DELETE;
     }
 }
 
 void
 reset_color_context (void)
 {
-  if (colors_enabled)
+  if (colors_enabled && last_context != C_RESET)
     {
       put_indicator (&color_indicator[C_LEFT]);
       put_indicator (&color_indicator[C_RESET]);
       put_indicator (&color_indicator[C_RIGHT]);
+      last_context = C_RESET;
     }
 }
 
-- 
2.5.0

[0005-tests-Add-tests-for-color-and-palette.patch (text/x-patch, inline)]
From a2b6721675cb4d90fc0322a5b2fc9d5c1d4c35df Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 2 Nov 2015 19:05:10 +0000
Subject: [PATCH 5/6] tests: Add tests for --color and --palette

* tests/colors: New file.
* tests/Makefile.am (TESTS): Add colors.
---
 tests/Makefile.am |   3 +-
 tests/colors      | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 1 deletion(-)
 create mode 100755 tests/colors

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 438fbdf..805ccc2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,8 @@ TESTS = \
   no-newline-at-eof \
   stdin \
   strcoll-0-names \
-  filename-quoting
+  filename-quoting \
+  colors
 
 EXTRA_DIST = \
   $(TESTS) init.sh t-local.sh
diff --git a/tests/colors b/tests/colors
new file mode 100755
index 0000000..5a62232
--- /dev/null
+++ b/tests/colors
@@ -0,0 +1,119 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+
+TZ=UTC0
+export TZ
+
+fail=0
+
+echo a > a
+echo b > b
+
+epoch='1970-01-01 00:00:00'
+touch --date="$epoch" a b
+
+gen_exp_u()
+{
+    local tab=$(printf '\t')
+    local epoch_plus="$epoch.000000000 +0000"
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"$hd--- a$tab$epoch_plus
++++ b$tab$epoch_plus
+$rs${ln}@@ -1 +1 @@$rs
+$de-a
+$ad+b
+$rs"
+}
+
+gen_exp_c()
+{
+    local tab=$(printf '\t')
+    local epoch_posix_1003_1_2001="Thu Jan  1 00:00:00 1970"
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"$hd*** a$tab$epoch_posix_1003_1_2001
+--- b$tab$epoch_posix_1003_1_2001
+$rs***************$ln
+*** 1 ****
+$rs$de! a
+$rs$ln--- 1 ----
+$rs$ad! b
+$rs"
+}
+
+gen_exp_default()
+{
+    printf '%s' \
+"1c1
+< a
+---
+> b
+"
+}
+
+gen_exp_default_colors()
+{
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"${ln}1c1
+$rs$de< a
+$rs---
+$ad> b
+$rs"
+}
+
+# Compare with some known outputs
+
+rs=0 hd=1 ad=32 de=31 ln=36
+
+diff --color=auto a b > k
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp k || fail=1
+
+diff --color=never a b > k
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp k || fail=1
+
+diff a b > k
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp k || fail=1
+
+diff --color=always a b > k
+test $? = 1 || fail=1
+gen_exp_default_colors > exp || framework_failure_
+compare exp k || fail=1
+
+diff -u --color=always a b > k
+test $? = 1 || fail=1
+gen_exp_u > exp || framework_failure_
+compare exp k || fail=1
+
+diff -c --color=always a b > k
+test $? = 1 || fail=1
+gen_exp_c > exp || framework_failure_
+compare exp k || fail=1
+
+rs=0 hd=33 ad=34 de=35 ln=36
+diff -u --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b > k
+test $? = 1 || fail=1
+gen_exp_u > exp || framework_failure_
+compare exp k || fail=1
+
+Exit $fail
-- 
2.5.0

[0006-Generate-terminal-sequences-on-the-line-they-belong.patch (text/x-patch, inline)]
From 031a5778004cc492fbf33f647838116780a73938 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Fri, 27 Nov 2015 13:56:05 +0100
Subject: [PATCH 6/6] Generate terminal sequences on the line they belong

* src/diff.h: New function 'print_1_line_nl'.
* src/context.c (pr_context_hunk): Use 'print_1_line_nl'.
Generate terminal sequences on the line they belong.
(pr_unidiff_hunk): Likewise.
* src/normal.c (print_normal_hunk): Likewise.
* src/util.c (print_1_line_nl): New function.
(print_1_line): Become a wrapper of 'print_1_line_nl'.
* tests/colors: Adjust tests.
---
 src/context.c | 64 ++++++++++++++++++++++++++++++++++-------------------------
 src/diff.h    |  1 +
 src/normal.c  | 26 +++++++++++++++++-------
 src/util.c    | 19 ++++++++++++++++--
 tests/colors  | 28 +++++++++++++-------------
 5 files changed, 88 insertions(+), 50 deletions(-)

diff --git a/src/context.c b/src/context.c
index 216f547..0834079 100644
--- a/src/context.c
+++ b/src/context.c
@@ -207,19 +207,23 @@ pr_context_hunk (struct change *hunk)
   if (function)
     print_context_function (out, function);
 
+  putc ('\n', out);
   set_line_numbers_color_context ();
-  fputs ("\n*** ", out);
+  fputs ("*** ", out);
   print_context_number_range (&files[0], first0, last0);
-  fputs (" ****\n", out);
+  fputs (" ****", out);
   reset_color_context ();
+  putc ('\n', out);
 
   if (changes & OLD)
     {
       struct change *next = hunk;
 
+      if (first0 <= last0)
+        set_delete_color_context ();
+
       for (i = first0; i <= last0; i++)
 	{
-          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -231,33 +235,36 @@ pr_context_hunk (struct change *hunk)
 	  prefix = " ";
 	  if (next && next->line0 <= i)
             {
-              reset_context = true;
-              set_delete_color_context ();
               /* The change NEXT covers this line.
                  If lines were inserted here in file 1, this is "changed".
                  Otherwise it is "deleted".  */
               prefix = (next->inserted > 0 ? "!" : "-");
             }
 
-	  print_1_line (prefix, &files[0].linbuf[i]);
-          if (reset_context)
+	  print_1_line_nl (prefix, &files[0].linbuf[i], true);
+          if (i == last0)
             reset_color_context ();
+          if (files[0].linbuf[i + 1][-1] == '\n')
+            putc ('\n', out);
 	}
     }
 
   set_line_numbers_color_context ();
   fputs ("--- ", out);
   print_context_number_range (&files[1], first1, last1);
-  fputs (" ----\n", out);
+  fputs (" ----", out);
   reset_color_context ();
+  putc ('\n', out);
 
   if (changes & NEW)
     {
       struct change *next = hunk;
 
+      if (first1 <= last1)
+        set_add_color_context ();
+
       for (i = first1; i <= last1; i++)
 	{
-          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -269,16 +276,16 @@ pr_context_hunk (struct change *hunk)
 	  prefix = " ";
 	  if (next && next->line1 <= i)
             {
-              reset_context = true;
-              set_add_color_context ();
               /* The change NEXT covers this line.
                  If lines were deleted here in file 0, this is "changed".
                  Otherwise it is "inserted".  */
               prefix = (next->deleted > 0 ? "!" : "+");
             }
-	  print_1_line (prefix, &files[1].linbuf[i]);
-          if (reset_context)
+	  print_1_line_nl (prefix, &files[1].linbuf[i], true);
+          if (i == last1)
             reset_color_context ();
+          if (files[1].linbuf[i + 1][-1] == '\n')
+            putc ('\n', out);
 	}
     }
 }
@@ -381,16 +388,11 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
-          bool reset_context = false;
-
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
           if (k)
-            {
-              reset_context = true;
-              set_delete_color_context ();
-            }
+            set_delete_color_context ();
 
 	  while (k--)
 	    {
@@ -398,30 +400,38 @@ pr_unidiff_hunk (struct change *hunk)
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
-	      print_1_line (NULL, line);
+	      print_1_line_nl (NULL, line, true);
+
+              if (!k)
+                reset_color_context ();
+
+              if (line[1][-1] == '\n')
+                putc ('\n', out);
 	    }
 
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
           if (k)
-            {
-              reset_context = true;
-              set_add_color_context ();
-            }
+            set_add_color_context ();
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
-	      print_1_line (NULL, line);
+	      print_1_line_nl (NULL, line, true);
+
+              if (!k)
+                reset_color_context ();
+
+              if (line[1][-1] == '\n')
+                putc ('\n', out);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
 
-          if (reset_context)
-            reset_color_context ();
 	  next = next->link;
 	}
     }
diff --git a/src/diff.h b/src/diff.h
index 5930cd1..791c29e 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -399,6 +399,7 @@ extern void output_1_line (char const *, char const *, char const *,
 extern void perror_with_name (char const *);
 extern void pfatal_with_name (char const *) __attribute__((noreturn));
 extern void print_1_line (char const *, char const * const *);
+extern void print_1_line_nl (char const *, char const * const *, bool);
 extern void print_message_queue (void);
 extern void print_number_range (char, struct file_data *, lin, lin);
 extern void print_script (struct change *, struct change * (*) (struct change *),
diff --git a/src/normal.c b/src/normal.c
index 227af10..5a1e687 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -53,16 +53,22 @@ print_normal_hunk (struct change *hunk)
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
-  fputc ('\n', outfile);
   reset_color_context ();
+  fputc ('\n', outfile);
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
     {
-      set_delete_color_context ();
+      if (first0 <= last0)
+        set_delete_color_context ();
       for (i = first0; i <= last0; i++)
-        print_1_line ("<", &files[0].linbuf[i]);
-      reset_color_context ();
+        {
+          print_1_line_nl ("<", &files[0].linbuf[i], true);
+          if (i == last0)
+            reset_color_context ();
+          if (files[0].linbuf[i + 1][-1] == '\n')
+            putc ('\n', outfile);
+        }
     }
 
   if (changes == CHANGED)
@@ -71,9 +77,15 @@ print_normal_hunk (struct change *hunk)
   /* Print the lines that the second file has.  */
   if (changes & NEW)
     {
-      set_add_color_context ();
+      if (first1 <= last1)
+        set_add_color_context ();
       for (i = first1; i <= last1; i++)
-        print_1_line (">", &files[1].linbuf[i]);
-      reset_color_context ();
+        {
+          print_1_line_nl (">", &files[1].linbuf[i], true);
+          if (i == last1)
+            reset_color_context ();
+          if (files[1].linbuf[i + 1][-1] == '\n')
+            putc ('\n', outfile);
+        }
     }
 }
diff --git a/src/util.c b/src/util.c
index b0e277b..65b06e9 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1206,6 +1206,18 @@ print_script (struct change *script,
 void
 print_1_line (char const *line_flag, char const *const *line)
 {
+  print_1_line_nl (line_flag, line, false);
+}
+
+/* Print the text of a single line LINE,
+   flagging it with the characters in LINE_FLAG (which say whether
+   the line is inserted, deleted, changed, etc.).  LINE_FLAG must not
+   end in a blank, unless it is a single blank.  If SKIP_NL is set, then
+   the final '\n' is not printed.  */
+
+void
+print_1_line_nl (char const *line_flag, char const *const *line, bool skip_nl)
+{
   char const *base = line[0], *limit = line[1]; /* Help the compiler.  */
   FILE *out = outfile; /* Help the compiler some more.  */
   char const *flag_format = 0;
@@ -1233,10 +1245,13 @@ print_1_line (char const *line_flag, char const *const *line)
       fprintf (out, flag_format_1, line_flag_1);
     }
 
-  output_1_line (base, limit, flag_format, line_flag);
+  output_1_line (base, limit - (skip_nl && limit[-1] == '\n'), flag_format, line_flag);
 
   if ((!line_flag || line_flag[0]) && limit[-1] != '\n')
-    fprintf (out, "\n\\ %s\n", _("No newline at end of file"));
+    {
+      reset_color_context ();
+      fprintf (out, "\n\\ %s\n", _("No newline at end of file"));
+    }
 }
 
 /* Output a line from BASE up to LIMIT.
diff --git a/tests/colors b/tests/colors
index 5a62232..7593ca9 100755
--- a/tests/colors
+++ b/tests/colors
@@ -26,9 +26,9 @@ gen_exp_u()
 "$hd--- a$tab$epoch_plus
 +++ b$tab$epoch_plus
 $rs${ln}@@ -1 +1 @@$rs
-$de-a
-$ad+b
-$rs"
+$de-a$rs
+$ad+b$rs
+"
 }
 
 gen_exp_c()
@@ -43,12 +43,12 @@ gen_exp_c()
     printf '%s' \
 "$hd*** a$tab$epoch_posix_1003_1_2001
 --- b$tab$epoch_posix_1003_1_2001
-$rs***************$ln
-*** 1 ****
-$rs$de! a
-$rs$ln--- 1 ----
-$rs$ad! b
-$rs"
+$rs***************
+$ln*** 1 ****$rs
+$de! a$rs
+$ln--- 1 ----$rs
+$ad! b$rs
+"
 }
 
 gen_exp_default()
@@ -69,11 +69,11 @@ gen_exp_default_colors()
     local de=$(printf "\e[${de}m")
     local ln=$(printf "\e[${ln}m")
     printf '%s' \
-"${ln}1c1
-$rs$de< a
-$rs---
-$ad> b
-$rs"
+"${ln}1c1$rs
+$de< a$rs
+---
+$ad> b$rs
+"
 }
 
 # Compare with some known outputs
-- 
2.5.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Fri, 27 Nov 2015 19:46:01 GMT) Full text and rfc822 format available.

Message #239 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Fri, 27 Nov 2015 11:45:21 -0800
[Message part 1 (text/plain, inline)]
On Fri, Nov 27, 2015 at 7:01 AM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
> Hi Jim,
>
> thanks for the great advices.
>
> Jim Meyering <jim <at> meyering.net> writes:
>
>> I looked at the tests/colors script: we cannot/should not use sha1sum
>> for two reasons. 1) it is a short cut; better to include the precise expected
>> output in each case. Using this approach, if/when a test fails, there is
>> no record of what the expected output was. 2) the "sha1sum" command
>> is not universally available by that name. On BSD-based systems it is
>> called "sha1". Thus, I began the conversion, and in so doing, I found
>> some room for improvement: with the current patches I have, diff -u
>> emits a pair of identical color-changing escape sequences before each
>> "+"-prefixed line:
>>
>>   $ diff -u --color=always a b|cat -A
>>   ^[[1;39m--- a^I1969-12-31 16:00:00.000000000 -0800$
>>   +++ b^I1969-12-31 16:00:00.000000000 -0800$
>>   ^[[0m^[[36m@@ -1 +1 @@^[[0m$
>>   ^[[31m-a$
>>   ^[[32m^[[32m+b$
>>   ^[[0m
>>
>> Notice also how the final \e[0m is on the final line by itself,
>> with no following newline. Please adjust so that it appears at the
>> end of the final line instead. I confirmed that git-diff appears
>> to do the same thing, but noted that git uses \e[m instead (no
>> "0" part). Do you know of any pros/cons for one or the other?
>
> I don't know if there is any difference in practice between \e[m and
> \e0[m. I took the implementation in ls as reference which uses \e0[m.
>
>
>> I've attached the beginnings of the adjusted tests/colors
>> script that I used to discover these things. Can you finish the job
>> of converting it to use "compare" rather than sha1sum?
>
> Sure.  The new tests helped me to spot two issues in the "diff: add
> support for --color" patch.  I also added some extra check to avoid to
> enter the same colors context twice.  These fixes are in
> 0004-fixup-diff-add-support-for-color.patch.
>
> To generate sequences on the same line they belong, I have created a new
> patch to facilitate the review.  Probably the patch should get squashed
> into 0001-diff-add-support-for-color.patch and 0005-tests-Add-tests-for-color-and-palette.patch
> once reviewed.
>
> I have not changed the first two patches of the series, I am including
> them just for completeness.

Thank you for the quick and nice work.
Please adjust the test to use "out" rather than (my fault) "k" as the
output file name.

Your fixup diff highlights the fact that there is too much
duplication in the definitions of the reset_color_context and
the four set_*_color_context functions. Any reason not to use
a single function named e.g., set_color_context, and pass it
the C_* enum constant?

This comment was clearly intended for a different enum:

  +/* What kind of changes a hunk contains.  */
  +enum colors_style

Two nits in the .texi:

  +Do not use color at all.  This is the default when no --color option
  +is present.
  +@item auto
  +@vindex auto @r{color option}
  +@cindex terminal, using color iff
  +Only use color if standard output is a terminal.

s/present/specified/
s/Only use color/Use color only/

In the --help output addition, the spacing is slightly off:

     N_("    --speed-large-files  assume large files and many
scattered small changes"),
  +  N_("    --color[=WHEN]         colorize the output; WHEN can be
'never', 'always',"),
  +  N_("                             or 'auto' (the default)"),
     "",
     N_("    --help               display this help and exit"),
     N_("-v, --version            output version information and exit"),

The "c" in the added description, "colorize the ..." is indented
two spaces too much. Same for the continuation line.

In your palette option description, the continuation line must be
indented by two spaces, so that help2man knows to format it
as such:

  +  N_("    --palette=PALETTE    specify the colors to use when
--color is active"),
  +  N_("                         PALETTE is a colon-separated list
terminfo capabilities"),

Nit: s/const char/char const/, e.g., here:

  +extern void set_color_palette (const char *palette);

For any pair of arrays whose lengths must be aligned, e.g.,

  +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+rs+rc) */
  +    { LEN_STR_PAIR ("0") },        /* rs: Reset to ordinary colors */
  +    { LEN_STR_PAIR ("1") },        /* hd: Header */
  +    { LEN_STR_PAIR ("32") },        /* ad: Add line */
  +    { LEN_STR_PAIR ("31") },        /* de: Delete line */
  +    { LEN_STR_PAIR ("36") },        /* ln: Line number */
  +  };
  +
  +static const char *const indicator_name[]=
  +  {
  +    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
  +  };

Please add the following to ensure that no one adds
an element to one without also adding the matching
element in the other:

  ARGMATCH_VERIFY (indicator_name, color_indicator);

But that would be the first use of argmatch.h and the
argmatch module, so you'll actually have to make two
more changes (include the .h and add the argmatch
module to bootstrap.conf). I've attached a tiny patch.

With that, I think we'll be ready to go.
Thank you for your patience.
[argmatch.patch (text/x-patch, attachment)]

Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sat, 28 Nov 2015 20:38:01 GMT) Full text and rfc822 format available.

Message #242 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Sat, 28 Nov 2015 21:36:44 +0100
[Message part 1 (text/plain, inline)]
Jim Meyering <jim <at> meyering.net> writes:

> diff --git a/bootstrap.conf b/bootstrap.conf
> index 9b2de22..3ab2c8b 100644
> --- a/bootstrap.conf
> +++ b/bootstrap.conf
> @@ -19,6 +19,7 @@
>  # gnulib modules used by this package.
>  gnulib_modules='
>  announce-gen
> +argmatch
>  binary-io
>  c-stack
>  config-h
> diff --git a/src/util.c b/src/util.c
> index 65b06e9..d27d202 100644
> --- a/src/util.c
> +++ b/src/util.c
> @@ -19,6 +19,7 @@
>     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>
>  #include "diff.h"
> +#include "argmatch.h"
>  #include <dirname.h>
>  #include <error.h>
>  #include <system-quote.h>
> @@ -561,6 +562,7 @@ static const char *const indicator_name[]=
>    {
>      "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
>    };
> +ARGMATCH_VERIFY (indicator_name, color_indicator);
>
>  static const char *color_palette;

thanks for the review and the patch.  I had to add this chunk as
reported by "make syntax-check":

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 74fb756..af39427 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,6 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+lib/argmatch.c
 lib/c-stack.c
 lib/error.c
 lib/file-type.c


As it is hopefully getting closer to be accepted, I am attaching the
full series with the amended changes.  I've verified that each patch
passes "make check" and "make syntax-check".

Regards,
Giuseppe

[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 900ab32b3cd9d06a9c002c53eb5c9738047f203f Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH 1/5] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(output_1_line): Periodically call `process_signals'.
(caught_signals): New sigset_t.
(colors_enabled): New boolean variable.
(interrupt_signal): New sig_atomic_t.
(stop_signal_count): New sig_atomic_t.
(check_color_output): New function.
(install_signal_handlers): Likewise. Copied from coreutils ls.
(process_signals): Likewise.  Copied from coreutils ls.
(set_color_context): New function.
(sighandler): Likewise.  Copied from coreutils ls.
(stophandler): Likewise.  Copied from coreutils ls.
---
 doc/diffutils.texi |  21 ++++
 src/context.c      |  54 ++++++++--
 src/diff.c         |  27 ++++-
 src/diff.h         |  27 +++++
 src/normal.c       |  18 +++-
 src/side.c         |  15 +++
 src/util.c         | 308 +++++++++++++++++++++++++++++++++++++++++++++++------
 7 files changed, 423 insertions(+), 47 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 091257f..b2c39da 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3742,6 +3742,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is specified.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Use color only if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..104c90b 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_color_context (HEADER_CONTEXT);
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  set_color_context (RESET_CONTEXT);
 }
 
 /* Print an edit script in context format.  */
@@ -205,9 +207,11 @@ pr_context_hunk (struct change *hunk)
   if (function)
     print_context_function (out, function);
 
+  set_color_context (LINE_NUMBER_CONTEXT);
   fputs ("\n*** ", out);
   print_context_number_range (&files[0], first0, last0);
   fputs (" ****\n", out);
+  set_color_context (RESET_CONTEXT);
 
   if (changes & OLD)
     {
@@ -215,6 +219,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first0; i <= last0; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -225,18 +230,26 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
+            {
+              reset_context = true;
+              set_color_context (DELETE_CONTEXT);
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
 
 	  print_1_line (prefix, &files[0].linbuf[i]);
+          if (reset_context)
+            set_color_context (RESET_CONTEXT);
 	}
     }
 
+  set_color_context (LINE_NUMBER_CONTEXT);
   fputs ("--- ", out);
   print_context_number_range (&files[1], first1, last1);
   fputs (" ----\n", out);
+  set_color_context (RESET_CONTEXT);
 
   if (changes & NEW)
     {
@@ -244,6 +257,7 @@ pr_context_hunk (struct change *hunk)
 
       for (i = first1; i <= last1; i++)
 	{
+          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -254,12 +268,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
+            {
+              reset_context = true;
+              set_color_context (ADD_CONTEXT);
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
 	  print_1_line (prefix, &files[1].linbuf[i]);
+          if (reset_context)
+            set_color_context (RESET_CONTEXT);
 	}
     }
 }
@@ -330,11 +349,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_color_context (LINE_NUMBER_CONTEXT);
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  set_color_context (RESET_CONTEXT);
 
   if (function)
     print_context_function (out, function);
@@ -360,9 +381,17 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
+          bool reset_context = false;
+
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            {
+              reset_context = true;
+              set_color_context (DELETE_CONTEXT);
+            }
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
@@ -375,6 +404,11 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
+          if (k)
+            {
+              reset_context = true;
+              set_color_context (ADD_CONTEXT);
+            }
 	  while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
@@ -386,6 +420,8 @@ pr_unidiff_hunk (struct change *hunk)
 
 	  /* We're done with this hunk, so on to the next! */
 
+          if (reset_context)
+            set_color_context (RESET_CONTEXT);
 	  next = next->link;
 	}
     }
diff --git a/src/diff.c b/src/diff.c
index efd7e47..536f545 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]       colorize the output; WHEN can be 'never', 'always',"),
+  N_("                           or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..3c46042 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* When colors should be used in the output.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -390,3 +406,14 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+
+enum color_context
+{
+  HEADER_CONTEXT,
+  ADD_CONTEXT,
+  DELETE_CONTEXT,
+  RESET_CONTEXT,
+  LINE_NUMBER_CONTEXT,
+};
+
+extern void set_color_context (enum color_context color_context);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..82aee77 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,31 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_color_context (LINE_NUMBER_CONTEXT);
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
   fputc ('\n', outfile);
+  set_color_context (RESET_CONTEXT);
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      set_color_context (DELETE_CONTEXT);
+      for (i = first0; i <= last0; i++)
+        print_1_line ("<", &files[0].linbuf[i]);
+      set_color_context (RESET_CONTEXT);
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      set_color_context (ADD_CONTEXT);
+      for (i = first1; i <= last1; i++)
+        print_1_line (">", &files[1].linbuf[i]);
+      set_color_context (RESET_CONTEXT);
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..a0213c4 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE_CONTEXT);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD_CONTEXT);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET_CONTEXT);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..694f4d9 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,22 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +159,174 @@ print_message_queue (void)
     }
 }
 
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      set_color_context (RESET_CONTEXT);
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+static void
+install_signal_handlers (void)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = sizeof (sig) / sizeof *(sig) };
+
+#if ! SA_NOCLDSTOP
+  bool caught_sig[nsigs];
+#endif
+  {
+    int j;
+#if SA_NOCLDSTOP
+    struct sigaction act;
+
+    sigemptyset (&caught_signals);
+    for (j = 0; j < nsigs; j++)
+      {
+        sigaction (sig[j], NULL, &act);
+        if (act.sa_handler != SIG_IGN)
+          sigaddset (&caught_signals, sig[j]);
+      }
+
+    act.sa_mask = caught_signals;
+    act.sa_flags = SA_RESTART;
+
+    for (j = 0; j < nsigs; j++)
+      if (sigismember (&caught_signals, sig[j]))
+        {
+          act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+    for (j = 0; j < nsigs; j++)
+      {
+        caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+        if (caught_sig[j])
+          {
+            signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+            siginterrupt (sig[j], 0);
+          }
+      }
+#endif
+    }
+}
+
+static char const *current_name0;
+static char const *current_name1;
+static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (bool is_pipe)
+{
+  bool output_is_tty;
+
+  if (! outfile || colors_style == NEVER)
+    return;
+
+  output_is_tty = !is_pipe && isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -150,10 +334,6 @@ print_message_queue (void)
    we fork off a 'pr' and make OUTFILE a pipe to it.
    'pr' then outputs to our stdout.  */
 
-static char const *current_name0;
-static char const *current_name1;
-static bool currently_recursive;
-
 void
 setup_output (char const *name0, char const *name1, bool recursive)
 {
@@ -313,6 +493,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +501,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +512,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -672,8 +855,21 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  const size_t MAX_CHUNK = 1024;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t to_write = MIN (left, MAX_CHUNK);
+          size_t written = fwrite (base, sizeof (char), to_write, outfile);
+          if (written < to_write)
+            return;
+          base += written;
+          left -= written;
+          process_signals ();
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,39 +877,85 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t counter_proc_signals = 0;
 
       while (t < limit)
-	switch ((c = *t++))
-	  {
-	  case '\t':
-	    {
-	      size_t spaces = tab_size - column % tab_size;
-	      column += spaces;
-	      do
-		putc (' ', out);
-	      while (--spaces);
-	    }
-	    break;
+        {
+          counter_proc_signals++;
+          if (counter_proc_signals == MAX_CHUNK)
+            {
+              process_signals ();
+              counter_proc_signals = 0;
+            }
+
+          switch ((c = *t++))
+            {
+            case '\t':
+              {
+                size_t spaces = tab_size - column % tab_size;
+                column += spaces;
+                do
+                  putc (' ', out);
+                while (--spaces);
+              }
+              break;
+
+            case '\r':
+              putc (c, out);
+              if (flag_format && t < limit && *t != '\n')
+                fprintf (out, flag_format, line_flag);
+              column = 0;
+              break;
+
+            case '\b':
+              if (column == 0)
+                continue;
+              column--;
+              putc (c, out);
+              break;
+
+            default:
+              column += isprint (c) != 0;
+              putc (c, out);
+              break;
+            }
+        }
+    }
+}
 
-	  case '\r':
-	    putc (c, out);
-	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
-	    column = 0;
-	    break;
 
-	  case '\b':
-	    if (column == 0)
-	      continue;
-	    column--;
-	    putc (c, out);
-	    break;
+void
+set_color_context (enum color_context color_context)
+{
+  process_signals ();
+  if (colors_enabled)
+    {
+      switch (color_context)
+        {
+        case HEADER_CONTEXT:
+          fputs ("\x1B[1m", outfile);
+          break;
 
-	  default:
-	    column += isprint (c) != 0;
-	    putc (c, out);
-	    break;
-	  }
+        case LINE_NUMBER_CONTEXT:
+          fputs ("\x1B[36m", outfile);
+
+          break;
+
+        case ADD_CONTEXT:
+          fputs ("\x1B[32m", outfile);
+          break;
+
+        case DELETE_CONTEXT:
+          fputs ("\x1B[31m", outfile);
+          break;
+
+        case RESET_CONTEXT:
+          fputs ("\x1b[0m", outfile);
+          break;
+
+        default:
+          abort ();
+        }
     }
 }
 
-- 
2.5.0

[0002-diff-add-palette.patch (text/x-patch, inline)]
From f860a90da16f6d21939611718696347c3506b075 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 19 Oct 2015 10:29:41 +0200
Subject: [PATCH 2/5] diff: add --palette

* bootstrap (gnulib_modules): Add 'argmatch'.
* doc/diffutils.texi: Add documentation for --palette
* src/diff.h (set_color_palette): New prototype.
* src/diff.c (set_color_palette): New function.
(color_palette): New variable.
* src/utils.c: Include "argmatch.h".
(struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_color_context): Use put_indicator instead of directly
outputting the sequence.
* po/POTFILES.in: Add 'lib/argmatch.c'
---
 bootstrap.conf     |   1 +
 doc/diffutils.texi |  34 +++++
 po/POTFILES.in     |   1 +
 src/diff.c         |   8 +
 src/diff.h         |   1 +
 src/util.c         | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 462 insertions(+), 7 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 9b2de22..3ab2c8b 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -19,6 +19,7 @@
 # gnulib modules used by this package.
 gnulib_modules='
 announce-gen
+argmatch
 binary-io
 c-stack
 config-h
diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index b2c39da..4ec9a0b 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3890,6 +3890,40 @@ if-then-else format.  @xref{Line Formats}.
 @itemx --show-c-function
 Show which C function each change is in.  @xref{C Function Headings}.
 
+@item --palette=@var{palette}
+Specify what color palette to use when colored output is enabled.  It
+defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red deleted lines,
+green added lines, cyan line numbers, bold header.
+
+Supported capabilities are as follows.
+
+@table @code
+@item ad=32
+@vindex ad @r{capability}
+
+SGR substring for added lines.
+The default is green foreground.
+
+@item de=31
+@vindex de @r{capability}
+
+SGR substring for deleted lines.
+The default is red foreground.
+
+@item hd=1
+@vindex hd @r{capability}
+
+SGR substring for chunk header.
+The default is bold foreground.
+
+@item ln=36
+@vindex ln @r{capability}
+
+SGR substring for line numbers.
+The default is cyan foreground.
+@end table
+
+
 @item -q
 @itemx --brief
 Report only whether the files differ, not the details of the
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 74fb756..af39427 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,6 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+lib/argmatch.c
 lib/c-stack.c
 lib/error.c
 lib/file-type.c
diff --git a/src/diff.c b/src/diff.c
index 536f545..46ac99d 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -140,6 +140,7 @@ enum
   CHANGED_GROUP_FORMAT_OPTION,
 
   COLOR_OPTION,
+  COLOR_PALETTE_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -196,6 +197,7 @@ static struct option const longopts[] =
   {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
   {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
   {"paginate", 0, 0, 'l'},
+  {"palette", 1, 0, COLOR_PALETTE_OPTION},
   {"rcs", 0, 0, 'n'},
   {"recursive", 0, 0, 'r'},
   {"report-identical-files", 0, 0, 's'},
@@ -635,6 +637,10 @@ main (int argc, char **argv)
 	  specify_colors_style (optarg);
 	  break;
 
+	case COLOR_PALETTE_OPTION:
+	  set_color_palette (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -950,6 +956,8 @@ static char const * const option_help_msgid[] = {
   N_("    --speed-large-files  assume large files and many scattered small changes"),
   N_("    --color[=WHEN]       colorize the output; WHEN can be 'never', 'always',"),
   N_("                           or 'auto' (the default)"),
+  N_("    --palette=PALETTE    specify the colors to use when --color is active"),
+  N_("                           PALETTE is a colon-separated list terminfo capabilities"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
diff --git a/src/diff.h b/src/diff.h
index 3c46042..c5fe4f9 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -417,3 +417,4 @@ enum color_context
 };
 
 extern void set_color_context (enum color_context color_context);
+extern void set_color_palette (char const *palette);
diff --git a/src/util.c b/src/util.c
index 694f4d9..3bd828f 100644
--- a/src/util.c
+++ b/src/util.c
@@ -19,6 +19,7 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "diff.h"
+#include "argmatch.h"
 #include <dirname.h>
 #include <error.h>
 #include <system-quote.h>
@@ -310,6 +311,397 @@ static char const *current_name1;
 static bool currently_recursive;
 static bool colors_enabled;
 
+static struct color_ext_type *color_ext_list = 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 */
+  };
+
+/* Parse a string as part of the --palette argument; 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 bool
+get_funky_string (char **dest, const char **src, bool 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;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+#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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { LEN_STR_PAIR ("1") },		/* hd: Header */
+    { LEN_STR_PAIR ("32") },		/* ad: Add line */
+    { LEN_STR_PAIR ("31") },		/* de: Delete line */
+    { LEN_STR_PAIR ("36") },		/* ln: Line number */
+  };
+
+static const char *const indicator_name[] =
+  {
+    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
+  };
+ARGMATCH_VERIFY (indicator_name, color_indicator);
+
+static char const *color_palette;
+
+void
+set_color_palette (char const *palette)
+{
+  color_palette = palette;
+}
+
+static void
+parse_diff_color (void)
+{
+  char *color_buf;
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = color_palette) == NULL || *p == '\0')
+    return;
+
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     --palette 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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), label);
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for --palette"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      colors_enabled = false;
+    }
+}
+
 static void
 check_color_output (bool is_pipe)
 {
@@ -323,6 +715,9 @@ check_color_output (bool is_pipe)
   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));
 
+  if (colors_enabled)
+    parse_diff_color ();
+
   if (output_is_tty)
     install_signal_handlers ();
 }
@@ -923,42 +1318,57 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE
+  };
+
+static void
+put_indicator (const struct bin_str *ind)
+{
+  fwrite (ind->string, ind->len, 1, outfile);
+}
+
+static enum color_context last_context = RESET_CONTEXT;
 
 void
 set_color_context (enum color_context color_context)
 {
   process_signals ();
-  if (colors_enabled)
+  if (colors_enabled && last_context != color_context)
     {
+      put_indicator (&color_indicator[C_LEFT]);
       switch (color_context)
         {
         case HEADER_CONTEXT:
-          fputs ("\x1B[1m", outfile);
+          put_indicator (&color_indicator[C_HEADER]);
           break;
 
         case LINE_NUMBER_CONTEXT:
-          fputs ("\x1B[36m", outfile);
-
+          put_indicator (&color_indicator[C_LINE]);
           break;
 
         case ADD_CONTEXT:
-          fputs ("\x1B[32m", outfile);
+          put_indicator (&color_indicator[C_ADD]);
           break;
 
         case DELETE_CONTEXT:
-          fputs ("\x1B[31m", outfile);
+          put_indicator (&color_indicator[C_DELETE]);
           break;
 
         case RESET_CONTEXT:
-          fputs ("\x1b[0m", outfile);
+          put_indicator (&color_indicator[C_RESET]);
           break;
 
         default:
           abort ();
         }
+      put_indicator (&color_indicator[C_RIGHT]);
+      last_context = color_context;
     }
 }
 
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.5.0

[0003-doc-mention-color-and-palette-in-NEWS.patch (text/x-patch, inline)]
From bebef1281fdb6d53f725b0eb1d18703e8845d768 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 2 Nov 2015 19:03:32 +0000
Subject: [PATCH 3/5] doc: mention --color and --palette in NEWS

---
 NEWS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/NEWS b/NEWS
index 7cdfedd..088f13b 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,13 @@ GNU diffutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New features
+
+   diff accepts two new options --color and --palette to generate
+   and configure colored output.  --color takes an optional argument
+   specifying when to colorize a line: --color=always, --color=auto,
+   --color=never.  --palette is used to configure which colors are used.
+
 ** Bug fixes
 
   When binary files differ, diff now exits with status 1 as POSIX requires.
-- 
2.5.0

[0004-tests-Add-tests-for-color-and-palette.patch (text/x-patch, inline)]
From e58c36a9062168bf56a8447d1c4287879ef54e9f Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 2 Nov 2015 19:05:10 +0000
Subject: [PATCH 4/5] tests: Add tests for --color and --palette

* tests/colors: New file.
* tests/Makefile.am (TESTS): Add colors.
---
 tests/Makefile.am |   3 +-
 tests/colors      | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 1 deletion(-)
 create mode 100755 tests/colors

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 438fbdf..805ccc2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,8 @@ TESTS = \
   no-newline-at-eof \
   stdin \
   strcoll-0-names \
-  filename-quoting
+  filename-quoting \
+  colors
 
 EXTRA_DIST = \
   $(TESTS) init.sh t-local.sh
diff --git a/tests/colors b/tests/colors
new file mode 100755
index 0000000..92c0452
--- /dev/null
+++ b/tests/colors
@@ -0,0 +1,119 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+
+TZ=UTC0
+export TZ
+
+fail=0
+
+echo a > a
+echo b > b
+
+epoch='1970-01-01 00:00:00'
+touch --date="$epoch" a b
+
+gen_exp_u()
+{
+    local tab=$(printf '\t')
+    local epoch_plus="$epoch.000000000 +0000"
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"$hd--- a$tab$epoch_plus
++++ b$tab$epoch_plus
+$rs${ln}@@ -1 +1 @@$rs
+$de-a
+$ad+b
+$rs"
+}
+
+gen_exp_c()
+{
+    local tab=$(printf '\t')
+    local epoch_posix_1003_1_2001="Thu Jan  1 00:00:00 1970"
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"$hd*** a$tab$epoch_posix_1003_1_2001
+--- b$tab$epoch_posix_1003_1_2001
+$rs***************$ln
+*** 1 ****
+$rs$de! a
+$rs$ln--- 1 ----
+$rs$ad! b
+$rs"
+}
+
+gen_exp_default()
+{
+    printf '%s' \
+"1c1
+< a
+---
+> b
+"
+}
+
+gen_exp_default_colors()
+{
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"${ln}1c1
+$rs$de< a
+$rs---
+$ad> b
+$rs"
+}
+
+# Compare with some known outputs
+
+rs=0 hd=1 ad=32 de=31 ln=36
+
+diff --color=auto a b > out
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp out || fail=1
+
+diff --color=never a b > out
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp out || fail=1
+
+diff a b > out
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp out || fail=1
+
+diff --color=always a b > out
+test $? = 1 || fail=1
+gen_exp_default_colors > exp || framework_failure_
+compare exp out || fail=1
+
+diff -u --color=always a b > out
+test $? = 1 || fail=1
+gen_exp_u > exp || framework_failure_
+compare exp out || fail=1
+
+diff -c --color=always a b > out
+test $? = 1 || fail=1
+gen_exp_c > exp || framework_failure_
+compare exp out || fail=1
+
+rs=0 hd=33 ad=34 de=35 ln=36
+diff -u --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b > out
+test $? = 1 || fail=1
+gen_exp_u > exp || framework_failure_
+compare exp out || fail=1
+
+Exit $fail
-- 
2.5.0

[0005-Generate-terminal-sequences-on-the-line-they-belong.patch (text/x-patch, inline)]
From a61b7dc46b43459656cde44824d23948cc28d5f9 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Fri, 27 Nov 2015 13:56:05 +0100
Subject: [PATCH 5/5] Generate terminal sequences on the line they belong

* src/diff.h: New function 'print_1_line_nl'.
* src/context.c (pr_context_hunk): Use 'print_1_line_nl'.
Generate terminal sequences on the line they belong.
(pr_unidiff_hunk): Likewise.
* src/normal.c (print_normal_hunk): Likewise.
* src/util.c (print_1_line_nl): New function.
(print_1_line): Become a wrapper of 'print_1_line_nl'.
* tests/colors: Adjust tests.
---
 src/context.c | 67 +++++++++++++++++++++++++++++++++--------------------------
 src/diff.h    |  1 +
 src/normal.c  | 26 ++++++++++++++++-------
 src/util.c    | 19 +++++++++++++++--
 tests/colors  | 28 ++++++++++++-------------
 5 files changed, 89 insertions(+), 52 deletions(-)

diff --git a/src/context.c b/src/context.c
index 104c90b..46b5b1f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -207,19 +207,23 @@ pr_context_hunk (struct change *hunk)
   if (function)
     print_context_function (out, function);
 
+  putc ('\n', out);
   set_color_context (LINE_NUMBER_CONTEXT);
-  fputs ("\n*** ", out);
+  fputs ("*** ", out);
   print_context_number_range (&files[0], first0, last0);
-  fputs (" ****\n", out);
+  fputs (" ****", out);
   set_color_context (RESET_CONTEXT);
+  putc ('\n', out);
 
   if (changes & OLD)
     {
       struct change *next = hunk;
 
+      if (first0 <= last0)
+        set_color_context (DELETE_CONTEXT);
+
       for (i = first0; i <= last0; i++)
 	{
-          bool reset_context = false;
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */
 
@@ -231,33 +235,35 @@ pr_context_hunk (struct change *hunk)
 	  prefix = " ";
 	  if (next && next->line0 <= i)
             {
-              reset_context = true;
-              set_color_context (DELETE_CONTEXT);
               /* The change NEXT covers this line.
                  If lines were inserted here in file 1, this is "changed".
                  Otherwise it is "deleted".  */
               prefix = (next->inserted > 0 ? "!" : "-");
             }
-
-	  print_1_line (prefix, &files[0].linbuf[i]);
-          if (reset_context)
+	  print_1_line_nl (prefix, &files[0].linbuf[i], true);
+          if (i == last0)
             set_color_context (RESET_CONTEXT);
+          if (files[0].linbuf[i + 1][-1] == '\n')
+            putc ('\n', out);
 	}
     }
 
   set_color_context (LINE_NUMBER_CONTEXT);
   fputs ("--- ", out);
   print_context_number_range (&files[1], first1, last1);
-  fputs (" ----\n", out);
+  fputs (" ----", out);
   set_color_context (RESET_CONTEXT);
+  putc ('\n', out);
 
   if (changes & NEW)
     {
       struct change *next = hunk;
 
+      if (first1 <= last1)
+        set_color_context (ADD_CONTEXT);
+
       for (i = first1; i <= last1; i++)
 	{
-          bool reset_context = false;
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */
 
@@ -269,16 +275,16 @@ pr_context_hunk (struct change *hunk)
 	  prefix = " ";
 	  if (next && next->line1 <= i)
             {
-              reset_context = true;
-              set_color_context (ADD_CONTEXT);
               /* The change NEXT covers this line.
                  If lines were deleted here in file 0, this is "changed".
                  Otherwise it is "inserted".  */
               prefix = (next->deleted > 0 ? "!" : "+");
             }
-	  print_1_line (prefix, &files[1].linbuf[i]);
-          if (reset_context)
+	  print_1_line_nl (prefix, &files[1].linbuf[i], true);
+          if (i == last1)
             set_color_context (RESET_CONTEXT);
+          if (files[1].linbuf[i + 1][-1] == '\n')
+            putc ('\n', out);
 	}
     }
 }
@@ -381,16 +387,11 @@ pr_unidiff_hunk (struct change *hunk)
 	}
       else
 	{
-          bool reset_context = false;
-
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
           if (k)
-            {
-              reset_context = true;
-              set_color_context (DELETE_CONTEXT);
-            }
+            set_color_context (DELETE_CONTEXT);
 
 	  while (k--)
 	    {
@@ -398,30 +399,38 @@ pr_unidiff_hunk (struct change *hunk)
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
-	      print_1_line (NULL, line);
+	      print_1_line_nl (NULL, line, true);
+
+              if (!k)
+                set_color_context (RESET_CONTEXT);
+
+              if (line[1][-1] == '\n')
+                putc ('\n', out);
 	    }
 
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
           if (k)
-            {
-              reset_context = true;
-              set_color_context (ADD_CONTEXT);
-            }
-	  while (k--)
+            set_color_context (ADD_CONTEXT);
+
+          while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
-	      print_1_line (NULL, line);
+	      print_1_line_nl (NULL, line, true);
+
+              if (!k)
+                set_color_context (RESET_CONTEXT);
+
+              if (line[1][-1] == '\n')
+                putc ('\n', out);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
 
-          if (reset_context)
-            set_color_context (RESET_CONTEXT);
 	  next = next->link;
 	}
     }
diff --git a/src/diff.h b/src/diff.h
index c5fe4f9..6f1bb34 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -399,6 +399,7 @@ extern void output_1_line (char const *, char const *, char const *,
 extern void perror_with_name (char const *);
 extern void pfatal_with_name (char const *) __attribute__((noreturn));
 extern void print_1_line (char const *, char const * const *);
+extern void print_1_line_nl (char const *, char const * const *, bool);
 extern void print_message_queue (void);
 extern void print_number_range (char, struct file_data *, lin, lin);
 extern void print_script (struct change *, struct change * (*) (struct change *),
diff --git a/src/normal.c b/src/normal.c
index 82aee77..e78e8ba 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -53,16 +53,22 @@ print_normal_hunk (struct change *hunk)
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
-  fputc ('\n', outfile);
   set_color_context (RESET_CONTEXT);
+  fputc ('\n', outfile);
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
     {
-      set_color_context (DELETE_CONTEXT);
+      if (first0 <= last0)
+        set_color_context (DELETE_CONTEXT);
       for (i = first0; i <= last0; i++)
-        print_1_line ("<", &files[0].linbuf[i]);
-      set_color_context (RESET_CONTEXT);
+        {
+          print_1_line_nl ("<", &files[0].linbuf[i], true);
+          if (i == last0)
+            set_color_context (RESET_CONTEXT);
+          if (files[0].linbuf[i + 1][-1] == '\n')
+            putc ('\n', outfile);
+        }
     }
 
   if (changes == CHANGED)
@@ -71,9 +77,15 @@ print_normal_hunk (struct change *hunk)
   /* Print the lines that the second file has.  */
   if (changes & NEW)
     {
-      set_color_context (ADD_CONTEXT);
+      if (first1 <= last1)
+        set_color_context (ADD_CONTEXT);
       for (i = first1; i <= last1; i++)
-        print_1_line (">", &files[1].linbuf[i]);
-      set_color_context (RESET_CONTEXT);
+        {
+          print_1_line_nl (">", &files[1].linbuf[i], true);
+          if (i == last1)
+            set_color_context (RESET_CONTEXT);
+          if (files[1].linbuf[i + 1][-1] == '\n')
+            putc ('\n', outfile);
+        }
     }
 }
diff --git a/src/util.c b/src/util.c
index 3bd828f..1fa61fa 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1208,6 +1208,18 @@ print_script (struct change *script,
 void
 print_1_line (char const *line_flag, char const *const *line)
 {
+  print_1_line_nl (line_flag, line, false);
+}
+
+/* Print the text of a single line LINE,
+   flagging it with the characters in LINE_FLAG (which say whether
+   the line is inserted, deleted, changed, etc.).  LINE_FLAG must not
+   end in a blank, unless it is a single blank.  If SKIP_NL is set, then
+   the final '\n' is not printed.  */
+
+void
+print_1_line_nl (char const *line_flag, char const *const *line, bool skip_nl)
+{
   char const *base = line[0], *limit = line[1]; /* Help the compiler.  */
   FILE *out = outfile; /* Help the compiler some more.  */
   char const *flag_format = 0;
@@ -1235,10 +1247,13 @@ print_1_line (char const *line_flag, char const *const *line)
       fprintf (out, flag_format_1, line_flag_1);
     }
 
-  output_1_line (base, limit, flag_format, line_flag);
+  output_1_line (base, limit - (skip_nl && limit[-1] == '\n'), flag_format, line_flag);
 
   if ((!line_flag || line_flag[0]) && limit[-1] != '\n')
-    fprintf (out, "\n\\ %s\n", _("No newline at end of file"));
+    {
+      set_color_context (RESET_CONTEXT);
+      fprintf (out, "\n\\ %s\n", _("No newline at end of file"));
+    }
 }
 
 /* Output a line from BASE up to LIMIT.
diff --git a/tests/colors b/tests/colors
index 92c0452..facfd8d 100755
--- a/tests/colors
+++ b/tests/colors
@@ -26,9 +26,9 @@ gen_exp_u()
 "$hd--- a$tab$epoch_plus
 +++ b$tab$epoch_plus
 $rs${ln}@@ -1 +1 @@$rs
-$de-a
-$ad+b
-$rs"
+$de-a$rs
+$ad+b$rs
+"
 }
 
 gen_exp_c()
@@ -43,12 +43,12 @@ gen_exp_c()
     printf '%s' \
 "$hd*** a$tab$epoch_posix_1003_1_2001
 --- b$tab$epoch_posix_1003_1_2001
-$rs***************$ln
-*** 1 ****
-$rs$de! a
-$rs$ln--- 1 ----
-$rs$ad! b
-$rs"
+$rs***************
+$ln*** 1 ****$rs
+$de! a$rs
+$ln--- 1 ----$rs
+$ad! b$rs
+"
 }
 
 gen_exp_default()
@@ -69,11 +69,11 @@ gen_exp_default_colors()
     local de=$(printf "\e[${de}m")
     local ln=$(printf "\e[${ln}m")
     printf '%s' \
-"${ln}1c1
-$rs$de< a
-$rs---
-$ad> b
-$rs"
+"${ln}1c1$rs
+$de< a$rs
+---
+$ad> b$rs
+"
 }
 
 # Compare with some known outputs
-- 
2.5.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 29 Nov 2015 04:35:01 GMT) Full text and rfc822 format available.

Message #245 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Sat, 28 Nov 2015 20:33:59 -0800
On Sat, Nov 28, 2015 at 12:36 PM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
....
> As it is hopefully getting closer to be accepted, I am attaching the
> full series with the amended changes.  I've verified that each patch
> passes "make check" and "make syntax-check".

Thanks, I see no problem.
I can push as-is, but wondered if you wanted to merge
your 0005 patch into the others? Probably not worth the work.
Either way is fine.




Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 29 Nov 2015 13:46:01 GMT) Full text and rfc822 format available.

Message #248 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Giuseppe Scrivano <gscrivano <at> gnu.org>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Sun, 29 Nov 2015 14:45:19 +0100
[Message part 1 (text/plain, inline)]
Jim Meyering <jim <at> meyering.net> writes:

> On Sat, Nov 28, 2015 at 12:36 PM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
> ....
>> As it is hopefully getting closer to be accepted, I am attaching the
>> full series with the amended changes.  I've verified that each patch
>> passes "make check" and "make syntax-check".
>
> Thanks, I see no problem.
> I can push as-is, but wondered if you wanted to merge
> your 0005 patch into the others? Probably not worth the work.
> Either way is fine.

it was not much work as there were no conflicts to solve, so I splitted
0005 into 0001 and 0004.  I verified again that each step passes all the
tests.

Regards,
Giuseppe


[0001-diff-add-support-for-color.patch (text/x-patch, inline)]
From 4b944653c3c27d99312f77813394a4c965c5c787 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Sun, 8 Mar 2015 22:45:11 +0100
Subject: [PATCH 1/4] diff: add support for --color

* doc/diffutils.texi (diff Options): Add documentation for --color.
Copied from coreutils ls --color.
* src/context.c (pr_unidiff_hunk): Set the color context.
(print_context_header): Likewise.
(pr_context_hunk): Likewise.
* src/diff.h (enum colors_style): New enum to record when to use colors.
(colors_style): New variable to memorize the argument value.
(set_color_context): Add function definition.
* src/diff.c: : Define COLOR_OPTION.
(specify_colors_style): New function.
(longopts): Add --color.
(main): Handle --color argument.
(option_help_msgid): Add usage string for --color.
* src/normal.c (print_normal_hunk): Set the color context.
* src/side.c (print_1sdiff_line): Likewise.
* src/util.c (print_1_line_nl): New function.
(print_1_line): Make it a wrapper of 'print_1_line_nl'.
(colors_enabled): New boolean variable.
(begin_output): Call check_color_output once the output file is
configured.
(output_1_line): Periodically call `process_signals'.
(caught_signals): New sigset_t.
(colors_enabled): New boolean variable.
(interrupt_signal): New sig_atomic_t.
(stop_signal_count): New sig_atomic_t.
(check_color_output): New function.
(install_signal_handlers): Likewise. Copied from coreutils ls.
(process_signals): Likewise.  Copied from coreutils ls.
(set_color_context): New function.
(sighandler): Likewise.  Copied from coreutils ls.
(stophandler): Likewise.  Copied from coreutils ls.
---
 doc/diffutils.texi |  21 ++++
 src/context.c      |  81 ++++++++++---
 src/diff.c         |  27 ++++-
 src/diff.h         |  28 +++++
 src/normal.c       |  30 ++++-
 src/side.c         |  15 +++
 src/util.c         | 327 +++++++++++++++++++++++++++++++++++++++++++++++------
 7 files changed, 471 insertions(+), 58 deletions(-)

diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index 091257f..b2c39da 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3742,6 +3742,27 @@ Read and write data in binary mode.  @xref{Binary}.
 Use the context output format, showing three lines of context.
 @xref{Context Format}.
 
+@item --color [=@var{when}]
+@cindex color, distinguishing different context
+Specify whether to use color for distinguishing different contexts,
+like header, added or removed lines.  @var{when} may be omitted, or
+one of:
+@itemize @bullet
+@item none
+@vindex none @r{color option}
+Do not use color at all.  This is the default when no --color option
+is specified.
+@item auto
+@vindex auto @r{color option}
+@cindex terminal, using color iff
+Use color only if standard output is a terminal.
+@item always
+@vindex always @r{color option}
+Always use color.
+@end itemize
+Specifying @option{--color} and no @var{when} is equivalent to
+@option{--color=auto}.
+
 @item -C @var{lines}
 @itemx --context <at> r{[}=@var{lines}@r{]}
 Use the context output format, showing @var{lines} (an integer) lines of
diff --git a/src/context.c b/src/context.c
index e0f21c4..46b5b1f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -80,6 +80,7 @@ print_context_label (char const *mark,
 void
 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
 {
+  set_color_context (HEADER_CONTEXT);
   if (unidiff)
     {
       print_context_label ("---", &inf[0], names[0], file_label[0]);
@@ -90,6 +91,7 @@ print_context_header (struct file_data inf[], char const *const *names, bool uni
       print_context_label ("***", &inf[0], names[0], file_label[0]);
       print_context_label ("---", &inf[1], names[1], file_label[1]);
     }
+  set_color_context (RESET_CONTEXT);
 }
 
 /* Print an edit script in context format.  */
@@ -205,14 +207,21 @@ pr_context_hunk (struct change *hunk)
   if (function)
     print_context_function (out, function);
 
-  fputs ("\n*** ", out);
+  putc ('\n', out);
+  set_color_context (LINE_NUMBER_CONTEXT);
+  fputs ("*** ", out);
   print_context_number_range (&files[0], first0, last0);
-  fputs (" ****\n", out);
+  fputs (" ****", out);
+  set_color_context (RESET_CONTEXT);
+  putc ('\n', out);
 
   if (changes & OLD)
     {
       struct change *next = hunk;
 
+      if (first0 <= last0)
+        set_color_context (DELETE_CONTEXT);
+
       for (i = first0; i <= last0; i++)
 	{
 	  /* Skip past changes that apply (in file 0)
@@ -225,23 +234,34 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line0 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were inserted here in file 1, this is "changed".
-	       Otherwise it is "deleted".  */
-	    prefix = (next->inserted > 0 ? "!" : "-");
-
-	  print_1_line (prefix, &files[0].linbuf[i]);
+            {
+              /* The change NEXT covers this line.
+                 If lines were inserted here in file 1, this is "changed".
+                 Otherwise it is "deleted".  */
+              prefix = (next->inserted > 0 ? "!" : "-");
+            }
+	  print_1_line_nl (prefix, &files[0].linbuf[i], true);
+          if (i == last0)
+            set_color_context (RESET_CONTEXT);
+          if (files[0].linbuf[i + 1][-1] == '\n')
+            putc ('\n', out);
 	}
     }
 
+  set_color_context (LINE_NUMBER_CONTEXT);
   fputs ("--- ", out);
   print_context_number_range (&files[1], first1, last1);
-  fputs (" ----\n", out);
+  fputs (" ----", out);
+  set_color_context (RESET_CONTEXT);
+  putc ('\n', out);
 
   if (changes & NEW)
     {
       struct change *next = hunk;
 
+      if (first1 <= last1)
+        set_color_context (ADD_CONTEXT);
+
       for (i = first1; i <= last1; i++)
 	{
 	  /* Skip past changes that apply (in file 1)
@@ -254,12 +274,17 @@ pr_context_hunk (struct change *hunk)
 
 	  prefix = " ";
 	  if (next && next->line1 <= i)
-	    /* The change NEXT covers this line.
-	       If lines were deleted here in file 0, this is "changed".
-	       Otherwise it is "inserted".  */
-	    prefix = (next->deleted > 0 ? "!" : "+");
-
-	  print_1_line (prefix, &files[1].linbuf[i]);
+            {
+              /* The change NEXT covers this line.
+                 If lines were deleted here in file 0, this is "changed".
+                 Otherwise it is "inserted".  */
+              prefix = (next->deleted > 0 ? "!" : "+");
+            }
+	  print_1_line_nl (prefix, &files[1].linbuf[i], true);
+          if (i == last1)
+            set_color_context (RESET_CONTEXT);
+          if (files[1].linbuf[i + 1][-1] == '\n')
+            putc ('\n', out);
 	}
     }
 }
@@ -330,11 +355,13 @@ pr_unidiff_hunk (struct change *hunk)
   begin_output ();
   out = outfile;
 
+  set_color_context (LINE_NUMBER_CONTEXT);
   fputs ("@@ -", out);
   print_unidiff_number_range (&files[0], first0, last0);
   fputs (" +", out);
   print_unidiff_number_range (&files[1], first1, last1);
   fputs (" @@", out);
+  set_color_context (RESET_CONTEXT);
 
   if (function)
     print_context_function (out, function);
@@ -363,25 +390,43 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* For each difference, first output the deleted part. */
 
 	  k = next->deleted;
+          if (k)
+            set_color_context (DELETE_CONTEXT);
+
 	  while (k--)
 	    {
 	      char const * const *line = &files[0].linbuf[i++];
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
-	      print_1_line (NULL, line);
+	      print_1_line_nl (NULL, line, true);
+
+              if (!k)
+                set_color_context (RESET_CONTEXT);
+
+              if (line[1][-1] == '\n')
+                putc ('\n', out);
 	    }
 
 	  /* Then output the inserted part. */
 
 	  k = next->inserted;
-	  while (k--)
+          if (k)
+            set_color_context (ADD_CONTEXT);
+
+          while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
-	      print_1_line (NULL, line);
+	      print_1_line_nl (NULL, line, true);
+
+              if (!k)
+                set_color_context (RESET_CONTEXT);
+
+              if (line[1][-1] == '\n')
+                putc ('\n', out);
 	    }
 
 	  /* We're done with this hunk, so on to the next! */
diff --git a/src/diff.c b/src/diff.c
index efd7e47..536f545 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -70,6 +70,7 @@ static void add_regexp (struct regexp_list *, char const *);
 static void summarize_regexp_list (struct regexp_list *);
 static void specify_style (enum output_style);
 static void specify_value (char const **, char const *, char const *);
+static void specify_colors_style (char const *);
 static void try_help (char const *, char const *) __attribute__((noreturn));
 static void check_stdout (void);
 static void usage (void);
@@ -136,7 +137,9 @@ enum
   UNCHANGED_GROUP_FORMAT_OPTION,
   OLD_GROUP_FORMAT_OPTION,
   NEW_GROUP_FORMAT_OPTION,
-  CHANGED_GROUP_FORMAT_OPTION
+  CHANGED_GROUP_FORMAT_OPTION,
+
+  COLOR_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -159,6 +162,7 @@ static struct option const longopts[] =
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"color", 2, 0, COLOR_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -627,6 +631,10 @@ main (int argc, char **argv)
 	  specify_value (&group_format[c], optarg, group_format_option[c]);
 	  break;
 
+	case COLOR_OPTION:
+	  specify_colors_style (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -940,6 +948,8 @@ static char const * const option_help_msgid[] = {
   N_("-d, --minimal            try hard to find a smaller set of changes"),
   N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
   N_("    --speed-large-files  assume large files and many scattered small changes"),
+  N_("    --color[=WHEN]       colorize the output; WHEN can be 'never', 'always',"),
+  N_("                           or 'auto' (the default)"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
@@ -1008,6 +1018,21 @@ specify_style (enum output_style style)
       output_style = style;
     }
 }
+
+/* Set the color mode.  */
+static void
+specify_colors_style (char const *value)
+{
+  if (value == NULL || STREQ (value, "auto"))
+    colors_style = AUTO;
+  else if (STREQ (value, "always"))
+    colors_style = ALWAYS;
+  else if (STREQ (value, "never"))
+    colors_style = NEVER;
+  else
+    try_help ("invalid color '%s'", value);
+}
+
 
 /* Set the last-modified time of *ST to be the current time.  */
 
diff --git a/src/diff.h b/src/diff.h
index 465e4bc..7bf7344 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -38,6 +38,19 @@ enum changes
   /* Both deletes and inserts: a hunk containing both old and new lines.  */
   CHANGED
 };
+
+/* When colors should be used in the output.  */
+enum colors_style
+{
+  /* Never output colors.  */
+  NEVER,
+
+  /* Output colors if the output is a terminal.  */
+  AUTO,
+
+  /* Always output colors.  */
+  ALWAYS,
+};
 
 /* Variables for command line options */
 
@@ -83,6 +96,9 @@ enum output_style
 
 XTERN enum output_style output_style;
 
+/* Define the current color context used to print a line.  */
+XTERN enum colors_style colors_style;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -383,6 +399,7 @@ extern void output_1_line (char const *, char const *, char const *,
 extern void perror_with_name (char const *);
 extern void pfatal_with_name (char const *) __attribute__((noreturn));
 extern void print_1_line (char const *, char const * const *);
+extern void print_1_line_nl (char const *, char const * const *, bool);
 extern void print_message_queue (void);
 extern void print_number_range (char, struct file_data *, lin, lin);
 extern void print_script (struct change *, struct change * (*) (struct change *),
@@ -390,3 +407,14 @@ extern void print_script (struct change *, struct change * (*) (struct change *)
 extern void setup_output (char const *, char const *, bool);
 extern void translate_range (struct file_data const *, lin, lin,
                              long int *, long int *);
+
+enum color_context
+{
+  HEADER_CONTEXT,
+  ADD_CONTEXT,
+  DELETE_CONTEXT,
+  RESET_CONTEXT,
+  LINE_NUMBER_CONTEXT,
+};
+
+extern void set_color_context (enum color_context color_context);
diff --git a/src/normal.c b/src/normal.c
index 721fd1a..e78e8ba 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -49,21 +49,43 @@ print_normal_hunk (struct change *hunk)
   begin_output ();
 
   /* Print out the line number header for this hunk */
+  set_color_context (LINE_NUMBER_CONTEXT);
   print_number_range (',', &files[0], first0, last0);
   fputc (change_letter[changes], outfile);
   print_number_range (',', &files[1], first1, last1);
+  set_color_context (RESET_CONTEXT);
   fputc ('\n', outfile);
 
   /* Print the lines that the first file has.  */
   if (changes & OLD)
-    for (i = first0; i <= last0; i++)
-      print_1_line ("<", &files[0].linbuf[i]);
+    {
+      if (first0 <= last0)
+        set_color_context (DELETE_CONTEXT);
+      for (i = first0; i <= last0; i++)
+        {
+          print_1_line_nl ("<", &files[0].linbuf[i], true);
+          if (i == last0)
+            set_color_context (RESET_CONTEXT);
+          if (files[0].linbuf[i + 1][-1] == '\n')
+            putc ('\n', outfile);
+        }
+    }
 
   if (changes == CHANGED)
     fputs ("---\n", outfile);
 
   /* Print the lines that the second file has.  */
   if (changes & NEW)
-    for (i = first1; i <= last1; i++)
-      print_1_line (">", &files[1].linbuf[i]);
+    {
+      if (first1 <= last1)
+        set_color_context (ADD_CONTEXT);
+      for (i = first1; i <= last1; i++)
+        {
+          print_1_line_nl (">", &files[1].linbuf[i], true);
+          if (i == last1)
+            set_color_context (RESET_CONTEXT);
+          if (files[1].linbuf[i + 1][-1] == '\n')
+            putc ('\n', outfile);
+        }
+    }
 }
diff --git a/src/side.c b/src/side.c
index 155512c..a0213c4 100644
--- a/src/side.c
+++ b/src/side.c
@@ -206,6 +206,18 @@ print_1sdiff_line (char const *const *left, char sep,
   size_t c2o = sdiff_column2_offset;
   size_t col = 0;
   bool put_newline = false;
+  bool color_to_reset = false;
+
+  if (sep == '<')
+    {
+      set_color_context (DELETE_CONTEXT);
+      color_to_reset = true;
+    }
+  else if (sep == '>')
+    {
+      set_color_context (ADD_CONTEXT);
+      color_to_reset = true;
+    }
 
   if (left)
     {
@@ -233,6 +245,9 @@ print_1sdiff_line (char const *const *left, char sep,
 
   if (put_newline)
     putc ('\n', out);
+
+  if (color_to_reset)
+    set_color_context (RESET_CONTEXT);
 }
 
 /* Print lines common to both files in side-by-side format.  */
diff --git a/src/util.c b/src/util.c
index 2d6d3fc..78b1a2d 100644
--- a/src/util.c
+++ b/src/util.c
@@ -24,6 +24,22 @@
 #include <system-quote.h>
 #include <xalloc.h>
 #include "xvasprintf.h"
+#include <signal.h>
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
 
 char const pr_program[] = PR_PROGRAM;
 
@@ -143,6 +159,174 @@ print_message_queue (void)
     }
 }
 
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      set_color_context (RESET_CONTEXT);
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+static void
+install_signal_handlers (void)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = sizeof (sig) / sizeof *(sig) };
+
+#if ! SA_NOCLDSTOP
+  bool caught_sig[nsigs];
+#endif
+  {
+    int j;
+#if SA_NOCLDSTOP
+    struct sigaction act;
+
+    sigemptyset (&caught_signals);
+    for (j = 0; j < nsigs; j++)
+      {
+        sigaction (sig[j], NULL, &act);
+        if (act.sa_handler != SIG_IGN)
+          sigaddset (&caught_signals, sig[j]);
+      }
+
+    act.sa_mask = caught_signals;
+    act.sa_flags = SA_RESTART;
+
+    for (j = 0; j < nsigs; j++)
+      if (sigismember (&caught_signals, sig[j]))
+        {
+          act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+          sigaction (sig[j], &act, NULL);
+        }
+#else
+    for (j = 0; j < nsigs; j++)
+      {
+        caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+        if (caught_sig[j])
+          {
+            signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+            siginterrupt (sig[j], 0);
+          }
+      }
+#endif
+    }
+}
+
+static char const *current_name0;
+static char const *current_name1;
+static bool currently_recursive;
+static bool colors_enabled;
+
+static void
+check_color_output (bool is_pipe)
+{
+  bool output_is_tty;
+
+  if (! outfile || colors_style == NEVER)
+    return;
+
+  output_is_tty = !is_pipe && isatty (fileno (outfile));
+
+  colors_enabled = (colors_style == ALWAYS
+                    || (colors_style == AUTO && output_is_tty));
+
+  if (output_is_tty)
+    install_signal_handlers ();
+}
+
 /* Call before outputting the results of comparing files NAME0 and NAME1
    to set up OUTFILE, the stdio stream for the output to go to.
 
@@ -150,10 +334,6 @@ print_message_queue (void)
    we fork off a 'pr' and make OUTFILE a pipe to it.
    'pr' then outputs to our stdout.  */
 
-static char const *current_name0;
-static char const *current_name1;
-static bool currently_recursive;
-
 void
 setup_output (char const *name0, char const *name1, bool recursive)
 {
@@ -313,6 +493,7 @@ begin_output (void)
 	    outfile = fdopen (pipes[1], "w");
 	    if (!outfile)
 	      pfatal_with_name ("fdopen");
+	    check_color_output (true);
 	  }
 #else
 	char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
@@ -320,6 +501,7 @@ begin_output (void)
 	outfile = popen (command, "w");
 	if (!outfile)
 	  pfatal_with_name (command);
+	check_color_output (true);
 	free (command);
 #endif
       }
@@ -330,6 +512,7 @@ begin_output (void)
       /* If -l was not specified, output the diff straight to 'stdout'.  */
 
       outfile = stdout;
+      check_color_output (false);
 
       /* If handling multiple files (because scanning a directory),
 	 print which files the following output is about.  */
@@ -630,6 +813,18 @@ print_script (struct change *script,
 void
 print_1_line (char const *line_flag, char const *const *line)
 {
+  print_1_line_nl (line_flag, line, false);
+}
+
+/* Print the text of a single line LINE,
+   flagging it with the characters in LINE_FLAG (which say whether
+   the line is inserted, deleted, changed, etc.).  LINE_FLAG must not
+   end in a blank, unless it is a single blank.  If SKIP_NL is set, then
+   the final '\n' is not printed.  */
+
+void
+print_1_line_nl (char const *line_flag, char const *const *line, bool skip_nl)
+{
   char const *base = line[0], *limit = line[1]; /* Help the compiler.  */
   FILE *out = outfile; /* Help the compiler some more.  */
   char const *flag_format = 0;
@@ -657,10 +852,13 @@ print_1_line (char const *line_flag, char const *const *line)
       fprintf (out, flag_format_1, line_flag_1);
     }
 
-  output_1_line (base, limit, flag_format, line_flag);
+  output_1_line (base, limit - (skip_nl && limit[-1] == '\n'), flag_format, line_flag);
 
   if ((!line_flag || line_flag[0]) && limit[-1] != '\n')
-    fprintf (out, "\n\\ %s\n", _("No newline at end of file"));
+    {
+      set_color_context (RESET_CONTEXT);
+      fprintf (out, "\n\\ %s\n", _("No newline at end of file"));
+    }
 }
 
 /* Output a line from BASE up to LIMIT.
@@ -672,8 +870,21 @@ void
 output_1_line (char const *base, char const *limit, char const *flag_format,
 	       char const *line_flag)
 {
+  const size_t MAX_CHUNK = 1024;
   if (!expand_tabs)
-    fwrite (base, sizeof (char), limit - base, outfile);
+    {
+      size_t left = limit - base;
+      while (left)
+        {
+          size_t to_write = MIN (left, MAX_CHUNK);
+          size_t written = fwrite (base, sizeof (char), to_write, outfile);
+          if (written < to_write)
+            return;
+          base += written;
+          left -= written;
+          process_signals ();
+        }
+    }
   else
     {
       register FILE *out = outfile;
@@ -681,39 +892,85 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
       register char const *t = base;
       register size_t column = 0;
       size_t tab_size = tabsize;
+      size_t counter_proc_signals = 0;
 
       while (t < limit)
-	switch ((c = *t++))
-	  {
-	  case '\t':
-	    {
-	      size_t spaces = tab_size - column % tab_size;
-	      column += spaces;
-	      do
-		putc (' ', out);
-	      while (--spaces);
-	    }
-	    break;
+        {
+          counter_proc_signals++;
+          if (counter_proc_signals == MAX_CHUNK)
+            {
+              process_signals ();
+              counter_proc_signals = 0;
+            }
+
+          switch ((c = *t++))
+            {
+            case '\t':
+              {
+                size_t spaces = tab_size - column % tab_size;
+                column += spaces;
+                do
+                  putc (' ', out);
+                while (--spaces);
+              }
+              break;
+
+            case '\r':
+              putc (c, out);
+              if (flag_format && t < limit && *t != '\n')
+                fprintf (out, flag_format, line_flag);
+              column = 0;
+              break;
+
+            case '\b':
+              if (column == 0)
+                continue;
+              column--;
+              putc (c, out);
+              break;
+
+            default:
+              column += isprint (c) != 0;
+              putc (c, out);
+              break;
+            }
+        }
+    }
+}
 
-	  case '\r':
-	    putc (c, out);
-	    if (flag_format && t < limit && *t != '\n')
-	      fprintf (out, flag_format, line_flag);
-	    column = 0;
-	    break;
 
-	  case '\b':
-	    if (column == 0)
-	      continue;
-	    column--;
-	    putc (c, out);
-	    break;
+void
+set_color_context (enum color_context color_context)
+{
+  process_signals ();
+  if (colors_enabled)
+    {
+      switch (color_context)
+        {
+        case HEADER_CONTEXT:
+          fputs ("\x1B[1m", outfile);
+          break;
 
-	  default:
-	    column += isprint (c) != 0;
-	    putc (c, out);
-	    break;
-	  }
+        case LINE_NUMBER_CONTEXT:
+          fputs ("\x1B[36m", outfile);
+
+          break;
+
+        case ADD_CONTEXT:
+          fputs ("\x1B[32m", outfile);
+          break;
+
+        case DELETE_CONTEXT:
+          fputs ("\x1B[31m", outfile);
+          break;
+
+        case RESET_CONTEXT:
+          fputs ("\x1b[0m", outfile);
+          break;
+
+        default:
+          abort ();
+        }
     }
 }
 
-- 
2.5.0

[0002-diff-add-palette.patch (text/x-patch, inline)]
From 8ab5aeabc752dfc746ebd385d1ce569d96d051b5 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 19 Oct 2015 10:29:41 +0200
Subject: [PATCH 2/4] diff: add --palette

* bootstrap (gnulib_modules): Add 'argmatch'.
* doc/diffutils.texi: Add documentation for --palette
* src/diff.h (set_color_palette): New prototype.
* src/diff.c (set_color_palette): New function.
(color_palette): New variable.
* src/utils.c: Include "argmatch.h".
(struct bin_str): New struct.
(struct color_ext_type): New struct.
(color_indicator): New array.
(indicator_name): New array.
(indicator_no): New enum.
(parse_state): New enum.
(put_indicator): New function.
(get_funky_string): New function. Copied from coreutils ls.
(parse_diff_color):  New function. Copied from coreutils ls
"parse_ls_color" function.
(set_color_context): Use put_indicator instead of directly
outputting the sequence.
* po/POTFILES.in: Add 'lib/argmatch.c'
---
 bootstrap.conf     |   1 +
 doc/diffutils.texi |  34 +++++
 po/POTFILES.in     |   1 +
 src/diff.c         |   8 +
 src/diff.h         |   1 +
 src/util.c         | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 462 insertions(+), 7 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 9b2de22..3ab2c8b 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -19,6 +19,7 @@
 # gnulib modules used by this package.
 gnulib_modules='
 announce-gen
+argmatch
 binary-io
 c-stack
 config-h
diff --git a/doc/diffutils.texi b/doc/diffutils.texi
index b2c39da..4ec9a0b 100644
--- a/doc/diffutils.texi
+++ b/doc/diffutils.texi
@@ -3890,6 +3890,40 @@ if-then-else format.  @xref{Line Formats}.
 @itemx --show-c-function
 Show which C function each change is in.  @xref{C Function Headings}.
 
+@item --palette=@var{palette}
+Specify what color palette to use when colored output is enabled.  It
+defaults to @samp{rs=0:hd=1:ad=32:de=31:ln=36} for red deleted lines,
+green added lines, cyan line numbers, bold header.
+
+Supported capabilities are as follows.
+
+@table @code
+@item ad=32
+@vindex ad @r{capability}
+
+SGR substring for added lines.
+The default is green foreground.
+
+@item de=31
+@vindex de @r{capability}
+
+SGR substring for deleted lines.
+The default is red foreground.
+
+@item hd=1
+@vindex hd @r{capability}
+
+SGR substring for chunk header.
+The default is bold foreground.
+
+@item ln=36
+@vindex ln @r{capability}
+
+SGR substring for line numbers.
+The default is cyan foreground.
+@end table
+
+
 @item -q
 @itemx --brief
 Report only whether the files differ, not the details of the
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 74fb756..af39427 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,6 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+lib/argmatch.c
 lib/c-stack.c
 lib/error.c
 lib/file-type.c
diff --git a/src/diff.c b/src/diff.c
index 536f545..46ac99d 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -140,6 +140,7 @@ enum
   CHANGED_GROUP_FORMAT_OPTION,
 
   COLOR_OPTION,
+  COLOR_PALETTE_OPTION,
 };
 
 static char const group_format_option[][sizeof "--unchanged-group-format"] =
@@ -196,6 +197,7 @@ static struct option const longopts[] =
   {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
   {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
   {"paginate", 0, 0, 'l'},
+  {"palette", 1, 0, COLOR_PALETTE_OPTION},
   {"rcs", 0, 0, 'n'},
   {"recursive", 0, 0, 'r'},
   {"report-identical-files", 0, 0, 's'},
@@ -635,6 +637,10 @@ main (int argc, char **argv)
 	  specify_colors_style (optarg);
 	  break;
 
+	case COLOR_PALETTE_OPTION:
+	  set_color_palette (optarg);
+	  break;
+
 	default:
 	  try_help (NULL, NULL);
 	}
@@ -950,6 +956,8 @@ static char const * const option_help_msgid[] = {
   N_("    --speed-large-files  assume large files and many scattered small changes"),
   N_("    --color[=WHEN]       colorize the output; WHEN can be 'never', 'always',"),
   N_("                           or 'auto' (the default)"),
+  N_("    --palette=PALETTE    specify the colors to use when --color is active"),
+  N_("                           PALETTE is a colon-separated list terminfo capabilities"),
   "",
   N_("    --help               display this help and exit"),
   N_("-v, --version            output version information and exit"),
diff --git a/src/diff.h b/src/diff.h
index 7bf7344..6f1bb34 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -418,3 +418,4 @@ enum color_context
 };
 
 extern void set_color_context (enum color_context color_context);
+extern void set_color_palette (char const *palette);
diff --git a/src/util.c b/src/util.c
index 78b1a2d..1fa61fa 100644
--- a/src/util.c
+++ b/src/util.c
@@ -19,6 +19,7 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "diff.h"
+#include "argmatch.h"
 #include <dirname.h>
 #include <error.h>
 #include <system-quote.h>
@@ -310,6 +311,397 @@ static char const *current_name1;
 static bool currently_recursive;
 static bool colors_enabled;
 
+static struct color_ext_type *color_ext_list = 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 */
+  };
+
+/* Parse a string as part of the --palette argument; 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 bool
+get_funky_string (char **dest, const char **src, bool 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;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+#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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { LEN_STR_PAIR ("1") },		/* hd: Header */
+    { LEN_STR_PAIR ("32") },		/* ad: Add line */
+    { LEN_STR_PAIR ("31") },		/* de: Delete line */
+    { LEN_STR_PAIR ("36") },		/* ln: Line number */
+  };
+
+static const char *const indicator_name[] =
+  {
+    "lc", "rc", "ec", "rs", "hd", "ad", "de", "ln", NULL
+  };
+ARGMATCH_VERIFY (indicator_name, color_indicator);
+
+static char const *color_palette;
+
+void
+set_color_palette (char const *palette)
+{
+  color_palette = palette;
+}
+
+static void
+parse_diff_color (void)
+{
+  char *color_buf;
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = color_palette) == NULL || *p == '\0')
+    return;
+
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     --palette 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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), label);
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for --palette"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      colors_enabled = false;
+    }
+}
+
 static void
 check_color_output (bool is_pipe)
 {
@@ -323,6 +715,9 @@ check_color_output (bool is_pipe)
   colors_enabled = (colors_style == ALWAYS
                     || (colors_style == AUTO && output_is_tty));
 
+  if (colors_enabled)
+    parse_diff_color ();
+
   if (output_is_tty)
     install_signal_handlers ();
 }
@@ -938,42 +1333,57 @@ output_1_line (char const *base, char const *limit, char const *flag_format,
     }
 }
 
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_HEADER, C_ADD, C_DELETE, C_LINE
+  };
+
+static void
+put_indicator (const struct bin_str *ind)
+{
+  fwrite (ind->string, ind->len, 1, outfile);
+}
+
+static enum color_context last_context = RESET_CONTEXT;
 
 void
 set_color_context (enum color_context color_context)
 {
   process_signals ();
-  if (colors_enabled)
+  if (colors_enabled && last_context != color_context)
     {
+      put_indicator (&color_indicator[C_LEFT]);
       switch (color_context)
         {
         case HEADER_CONTEXT:
-          fputs ("\x1B[1m", outfile);
+          put_indicator (&color_indicator[C_HEADER]);
           break;
 
         case LINE_NUMBER_CONTEXT:
-          fputs ("\x1B[36m", outfile);
-
+          put_indicator (&color_indicator[C_LINE]);
           break;
 
         case ADD_CONTEXT:
-          fputs ("\x1B[32m", outfile);
+          put_indicator (&color_indicator[C_ADD]);
           break;
 
         case DELETE_CONTEXT:
-          fputs ("\x1B[31m", outfile);
+          put_indicator (&color_indicator[C_DELETE]);
           break;
 
         case RESET_CONTEXT:
-          fputs ("\x1b[0m", outfile);
+          put_indicator (&color_indicator[C_RESET]);
           break;
 
         default:
           abort ();
         }
+      put_indicator (&color_indicator[C_RIGHT]);
+      last_context = color_context;
     }
 }
 
+
 char const change_letter[] = { 0, 'd', 'a', 'c' };
 
 /* Translate an internal line number (an index into diff's table of lines)
-- 
2.5.0

[0003-doc-mention-color-and-palette-in-NEWS.patch (text/x-patch, inline)]
From 773498ed3956af73ae3caff69c62f3b84a632cca Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 2 Nov 2015 19:03:32 +0000
Subject: [PATCH 3/4] doc: mention --color and --palette in NEWS

---
 NEWS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/NEWS b/NEWS
index 7cdfedd..088f13b 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,13 @@ GNU diffutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New features
+
+   diff accepts two new options --color and --palette to generate
+   and configure colored output.  --color takes an optional argument
+   specifying when to colorize a line: --color=always, --color=auto,
+   --color=never.  --palette is used to configure which colors are used.
+
 ** Bug fixes
 
   When binary files differ, diff now exits with status 1 as POSIX requires.
-- 
2.5.0

[0004-tests-Add-tests-for-color-and-palette.patch (text/x-patch, inline)]
From 4a69b46c3d71dd7299cf5d3b1dde3ce6902d3afe Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivano <at> gnu.org>
Date: Mon, 2 Nov 2015 19:05:10 +0000
Subject: [PATCH 4/4] tests: Add tests for --color and --palette

* tests/colors: New file.
* tests/Makefile.am (TESTS): Add colors.
---
 tests/Makefile.am |   3 +-
 tests/colors      | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 1 deletion(-)
 create mode 100755 tests/colors

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 438fbdf..805ccc2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,8 @@ TESTS = \
   no-newline-at-eof \
   stdin \
   strcoll-0-names \
-  filename-quoting
+  filename-quoting \
+  colors
 
 EXTRA_DIST = \
   $(TESTS) init.sh t-local.sh
diff --git a/tests/colors b/tests/colors
new file mode 100755
index 0000000..facfd8d
--- /dev/null
+++ b/tests/colors
@@ -0,0 +1,119 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+
+TZ=UTC0
+export TZ
+
+fail=0
+
+echo a > a
+echo b > b
+
+epoch='1970-01-01 00:00:00'
+touch --date="$epoch" a b
+
+gen_exp_u()
+{
+    local tab=$(printf '\t')
+    local epoch_plus="$epoch.000000000 +0000"
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"$hd--- a$tab$epoch_plus
++++ b$tab$epoch_plus
+$rs${ln}@@ -1 +1 @@$rs
+$de-a$rs
+$ad+b$rs
+"
+}
+
+gen_exp_c()
+{
+    local tab=$(printf '\t')
+    local epoch_posix_1003_1_2001="Thu Jan  1 00:00:00 1970"
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"$hd*** a$tab$epoch_posix_1003_1_2001
+--- b$tab$epoch_posix_1003_1_2001
+$rs***************
+$ln*** 1 ****$rs
+$de! a$rs
+$ln--- 1 ----$rs
+$ad! b$rs
+"
+}
+
+gen_exp_default()
+{
+    printf '%s' \
+"1c1
+< a
+---
+> b
+"
+}
+
+gen_exp_default_colors()
+{
+    local rs=$(printf "\e[${rs}m")
+    local hd=$(printf "\e[${hd}m")
+    local ad=$(printf "\e[${ad}m")
+    local de=$(printf "\e[${de}m")
+    local ln=$(printf "\e[${ln}m")
+    printf '%s' \
+"${ln}1c1$rs
+$de< a$rs
+---
+$ad> b$rs
+"
+}
+
+# Compare with some known outputs
+
+rs=0 hd=1 ad=32 de=31 ln=36
+
+diff --color=auto a b > out
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp out || fail=1
+
+diff --color=never a b > out
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp out || fail=1
+
+diff a b > out
+test $? = 1 || fail=1
+gen_exp_default > exp || framework_failure_
+compare exp out || fail=1
+
+diff --color=always a b > out
+test $? = 1 || fail=1
+gen_exp_default_colors > exp || framework_failure_
+compare exp out || fail=1
+
+diff -u --color=always a b > out
+test $? = 1 || fail=1
+gen_exp_u > exp || framework_failure_
+compare exp out || fail=1
+
+diff -c --color=always a b > out
+test $? = 1 || fail=1
+gen_exp_c > exp || framework_failure_
+compare exp out || fail=1
+
+rs=0 hd=33 ad=34 de=35 ln=36
+diff -u --color=always --palette="rs=0:hd=33:ad=34:de=35:ln=36" a b > out
+test $? = 1 || fail=1
+gen_exp_u > exp || framework_failure_
+compare exp out || fail=1
+
+Exit $fail
-- 
2.5.0


Information forwarded to bug-diffutils <at> gnu.org:
bug#20062; Package diffutils. (Sun, 29 Nov 2015 16:42:02 GMT) Full text and rfc822 format available.

Message #251 received at 20062 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Giuseppe Scrivano <gscrivano <at> gnu.org>
Cc: Eric Blake <eblake <at> redhat.com>, 20062 <at> debbugs.gnu.org
Subject: Re: [bug-diffutils] bug#20062: [PATCH] diff: add support for --color
Date: Sun, 29 Nov 2015 08:40:20 -0800
On Sun, Nov 29, 2015 at 5:45 AM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
> Jim Meyering <jim <at> meyering.net> writes:
>
>> On Sat, Nov 28, 2015 at 12:36 PM, Giuseppe Scrivano <gscrivano <at> gnu.org> wrote:
>> ....
>>> As it is hopefully getting closer to be accepted, I am attaching the
>>> full series with the amended changes.  I've verified that each patch
>>> passes "make check" and "make syntax-check".
>>
>> Thanks, I see no problem.
>> I can push as-is, but wondered if you wanted to merge
>> your 0005 patch into the others? Probably not worth the work.
>> Either way is fine.
>
> it was not much work as there were no conflicts to solve, so I splitted
> 0005 into 0001 and 0004.  I verified again that each step passes all the
> tests.

Good! Worthwhile after all. Thank you.
I have just pushed those commits.




bug closed, send any further explanations to 20062 <at> debbugs.gnu.org and Giuseppe Scrivano <gscrivan <at> redhat.com> Request was from Jim Meyering <jim <at> meyering.net> to control <at> debbugs.gnu.org. (Fri, 05 May 2017 05:48:02 GMT) Full text and rfc822 format available.

bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Fri, 02 Jun 2017 11:24:05 GMT) Full text and rfc822 format available.

This bug report was last modified 6 years and 351 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.