From b88d3090a64918afc3d0b191b74e25588f08cd3f 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.
http://bugs.gnu.org/31105
---
 src/context.c | 39 +++++++++++++++++----------------------
 src/normal.c  | 12 ++++--------
 tests/colors  | 12 ++++++------
 3 files changed, 27 insertions(+), 36 deletions(-)

diff --git a/src/context.c b/src/context.c
index 4dd882d..c8de512 100644
--- a/src/context.c
+++ b/src/context.c
@@ -43,8 +43,11 @@ 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 +74,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);
+  fprintf (outfile, "\n");
 }

 /* Print a header for a context diff, with the file names and dates.  */
@@ -80,7 +85,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 +95,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 +222,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 +243,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 +260,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 +281,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 +389,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 +408,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

