From ad65591d8442f0d7941fa7dbdfdba834c87439f6 Mon Sep 17 00:00:00 2001
From: Dennis Lambe Jr <malsyned@malsyned.net>
Date: Sat, 29 Dec 2018 13:04:10 -0800
Subject: [PATCH] diff: adjust ANSI escapes for compatibility with less -R

GNU less can display ANSI-colored text with the -R flag, but this
support has some limitations. One of them is that if an escape
sequence starts on one line and ends on a different line, only the
first line will be colored in less.

As a result, when diff creates colored output with multi-line deletes
or adds, less will only color the first line.

This change resets ANSI color to the default at the end of
each line and restarts it at the beginning of the next. It patches
normal and context mode. Side-by-side already worked in my testing.
* src/context.c (print_context_label, pr_context_hunk): As above.
(pr_unidiff_hunk, print_context_header): Likewise.
* src/normal.c (print_normal_hunk): Likewise.
* tests/colors: Adjust existing tests to accommodate this.
* NEWS (Improvements): Mention it.
Proposed in http://bugs.gnu.org/31105
---
 NEWS          |  4 ++++
 src/context.c | 37 +++++++++++++++----------------------
 src/normal.c  | 12 ++++--------
 tests/colors  | 12 ++++++------
 4 files changed, 29 insertions(+), 36 deletions(-)

diff --git a/NEWS b/NEWS
index 7d115a3..db47c4a 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,10 @@ GNU diffutils NEWS                                    -*- outline -*-
     diff -a --strip-trailing-cr <(printf '\r') <(echo a)
   [bug introduced in 2.8 with addition of the --strip-trailing-cr option]

+** Improvements
+
+  diff --color now produces output compatible with less -R.
+

 * Noteworthy changes in release 3.6 (2017-05-21) [stable]

diff --git a/src/context.c b/src/context.c
index 4dd882d..d1930d9 100644
--- a/src/context.c
+++ b/src/context.c
@@ -43,8 +43,9 @@ print_context_label (char const *mark,
 		     char const *name,
 		     char const *label)
 {
+  set_color_context (HEADER_CONTEXT);
   if (label)
-    fprintf (outfile, "%s %s\n", mark, label);
+    fprintf (outfile, "%s %s", mark, label);
   else
     {
       char buf[MAX (INT_STRLEN_BOUND (int) + 32,
@@ -71,8 +72,10 @@ print_context_label (char const *mark,
 	      sprintf (buf, "%"PRIuMAX".%.9d", sec, nsec);
 	    }
 	}
-      fprintf (outfile, "%s %s\t%s\n", mark, name, buf);
+      fprintf (outfile, "%s %s\t%s", mark, name, buf);
     }
+  set_color_context (RESET_CONTEXT);
+  putc ('\n', outfile);
 }

 /* Print a header for a context diff, with the file names and dates.  */
@@ -80,7 +83,6 @@ 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]);
@@ -91,7 +93,6 @@ 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.  */
@@ -219,11 +220,10 @@ pr_context_hunk (struct change *hunk)
     {
       struct change *next = hunk;

-      if (first0 <= last0)
-        set_color_context (DELETE_CONTEXT);
-
       for (i = first0; i <= last0; i++)
 	{
+	  set_color_context (DELETE_CONTEXT);
+
 	  /* Skip past changes that apply (in file 0)
 	     only to lines before line I.  */

@@ -241,8 +241,7 @@ pr_context_hunk (struct change *hunk)
               prefix = (next->inserted > 0 ? "!" : "-");
             }
 	  print_1_line_nl (prefix, &files[0].linbuf[i], true);
-          if (i == last0)
-            set_color_context (RESET_CONTEXT);
+          set_color_context (RESET_CONTEXT);
           if (files[0].linbuf[i + 1][-1] == '\n')
             putc ('\n', out);
 	}
@@ -259,11 +258,10 @@ pr_context_hunk (struct change *hunk)
     {
       struct change *next = hunk;

-      if (first1 <= last1)
-        set_color_context (ADD_CONTEXT);
-
       for (i = first1; i <= last1; i++)
 	{
+	  set_color_context (ADD_CONTEXT);
+
 	  /* Skip past changes that apply (in file 1)
 	     only to lines before line I.  */

@@ -281,8 +279,7 @@ pr_context_hunk (struct change *hunk)
               prefix = (next->deleted > 0 ? "!" : "+");
             }
 	  print_1_line_nl (prefix, &files[1].linbuf[i], true);
-          if (i == last1)
-            set_color_context (RESET_CONTEXT);
+          set_color_context (RESET_CONTEXT);
           if (files[1].linbuf[i + 1][-1] == '\n')
             putc ('\n', out);
 	}
@@ -390,19 +387,17 @@ 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++];
+	      set_color_context (DELETE_CONTEXT);
 	      putc ('-', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line_nl (NULL, line, true);

-              if (!k)
-                set_color_context (RESET_CONTEXT);
+	      set_color_context (RESET_CONTEXT);

               if (line[1][-1] == '\n')
                 putc ('\n', out);
@@ -411,19 +406,17 @@ pr_unidiff_hunk (struct change *hunk)
 	  /* Then output the inserted part. */

 	  k = next->inserted;
-          if (k)
-            set_color_context (ADD_CONTEXT);

           while (k--)
 	    {
 	      char const * const *line = &files[1].linbuf[j++];
+	      set_color_context (ADD_CONTEXT);
 	      putc ('+', out);
 	      if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
 		putc ('\t', out);
 	      print_1_line_nl (NULL, line, true);

-              if (!k)
-                set_color_context (RESET_CONTEXT);
+              set_color_context (RESET_CONTEXT);

               if (line[1][-1] == '\n')
                 putc ('\n', out);
diff --git a/src/normal.c b/src/normal.c
index c6aac07..bdb718b 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -59,13 +59,11 @@ print_normal_hunk (struct change *hunk)
   /* Print the lines that the first file has.  */
   if (changes & OLD)
     {
-      if (first0 <= last0)
-        set_color_context (DELETE_CONTEXT);
       for (i = first0; i <= last0; i++)
         {
+          set_color_context (DELETE_CONTEXT);
           print_1_line_nl ("<", &files[0].linbuf[i], true);
-          if (i == last0)
-            set_color_context (RESET_CONTEXT);
+          set_color_context (RESET_CONTEXT);
           if (files[0].linbuf[i + 1][-1] == '\n')
             putc ('\n', outfile);
         }
@@ -77,13 +75,11 @@ print_normal_hunk (struct change *hunk)
   /* Print the lines that the second file has.  */
   if (changes & NEW)
     {
-      if (first1 <= last1)
-        set_color_context (ADD_CONTEXT);
       for (i = first1; i <= last1; i++)
         {
+          set_color_context (ADD_CONTEXT);
           print_1_line_nl (">", &files[1].linbuf[i], true);
-          if (i == last1)
-            set_color_context (RESET_CONTEXT);
+          set_color_context (RESET_CONTEXT);
           if (files[1].linbuf[i + 1][-1] == '\n')
             putc ('\n', outfile);
         }
diff --git a/tests/colors b/tests/colors
index e5b5f36..d28a61c 100755
--- a/tests/colors
+++ b/tests/colors
@@ -29,9 +29,9 @@ gen_exp_u()
     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
+"$hd--- a$tab$epoch_plus$rs
+$hd+++ b$tab$epoch_plus$rs
+${ln}@@ -1 +1 @@$rs
 $de-a$rs
 $ad+b$rs
 "
@@ -46,9 +46,9 @@ gen_exp_c()
     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***************
+"$hd*** a$tab$epoch_posix_1003_1_2001$rs
+$hd--- b$tab$epoch_posix_1003_1_2001$rs
+***************
 $ln*** 1 ****$rs
 $de! a$rs
 $ln--- 1 ----$rs
-- 
2.20.1.2.gb21ebb671b

