xref: /openbsd-src/gnu/usr.bin/cvs/diff/context.c (revision b2346922a76a50a89e33beab4ebbc0950de8a8df)
12286d8edStholo /* Context-format output routines for GNU DIFF.
2*b2346922Stholo    Copyright (C) 1988,1989,1991,1992,1993,1994,1998 Free Software Foundation, Inc.
32286d8edStholo 
42286d8edStholo This file is part of GNU DIFF.
52286d8edStholo 
62286d8edStholo GNU DIFF is free software; you can redistribute it and/or modify
72286d8edStholo it under the terms of the GNU General Public License as published by
82286d8edStholo the Free Software Foundation; either version 2, or (at your option)
92286d8edStholo any later version.
102286d8edStholo 
112286d8edStholo GNU DIFF is distributed in the hope that it will be useful,
122286d8edStholo but WITHOUT ANY WARRANTY; without even the implied warranty of
132286d8edStholo MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
142286d8edStholo GNU General Public License for more details.
152286d8edStholo 
162286d8edStholo You should have received a copy of the GNU General Public License
172286d8edStholo along with GNU DIFF; see the file COPYING.  If not, write to
182286d8edStholo the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
192286d8edStholo 
202286d8edStholo #include "diff.h"
212286d8edStholo 
222286d8edStholo static struct change *find_hunk PARAMS((struct change *));
232286d8edStholo static void find_function PARAMS((struct file_data const *, int, char const **, size_t *));
242286d8edStholo static void mark_ignorable PARAMS((struct change *));
252286d8edStholo static void pr_context_hunk PARAMS((struct change *));
262286d8edStholo static void pr_unidiff_hunk PARAMS((struct change *));
272286d8edStholo static void print_context_label PARAMS ((char const *, struct file_data *, char const *));
282286d8edStholo static void print_context_number_range PARAMS((struct file_data const *, int, int));
292286d8edStholo static void print_unidiff_number_range PARAMS((struct file_data const *, int, int));
302286d8edStholo 
312286d8edStholo /* Last place find_function started searching from.  */
322286d8edStholo static int find_function_last_search;
332286d8edStholo 
342286d8edStholo /* The value find_function returned when it started searching there.  */
352286d8edStholo static int find_function_last_match;
362286d8edStholo 
372286d8edStholo /* Print a label for a context diff, with a file name and date or a label.  */
382286d8edStholo 
392286d8edStholo static void
402286d8edStholo print_context_label (mark, inf, label)
412286d8edStholo      char const *mark;
422286d8edStholo      struct file_data *inf;
432286d8edStholo      char const *label;
442286d8edStholo {
452286d8edStholo   if (label)
46*b2346922Stholo     printf_output ("%s %s\n", mark, label);
472286d8edStholo   else
482286d8edStholo     {
492286d8edStholo       char const *ct = ctime (&inf->stat.st_mtime);
502286d8edStholo       if (!ct)
512286d8edStholo 	ct = "?\n";
522286d8edStholo       /* See Posix.2 section 4.17.6.1.4 for this format.  */
53*b2346922Stholo       printf_output ("%s %s\t%s", mark, inf->name, ct);
542286d8edStholo     }
552286d8edStholo }
562286d8edStholo 
572286d8edStholo /* Print a header for a context diff, with the file names and dates.  */
582286d8edStholo 
592286d8edStholo void
602286d8edStholo print_context_header (inf, unidiff_flag)
612286d8edStholo      struct file_data inf[];
622286d8edStholo      int unidiff_flag;
632286d8edStholo {
642286d8edStholo   if (unidiff_flag)
652286d8edStholo     {
662286d8edStholo       print_context_label ("---", &inf[0], file_label[0]);
672286d8edStholo       print_context_label ("+++", &inf[1], file_label[1]);
682286d8edStholo     }
692286d8edStholo   else
702286d8edStholo     {
712286d8edStholo       print_context_label ("***", &inf[0], file_label[0]);
722286d8edStholo       print_context_label ("---", &inf[1], file_label[1]);
732286d8edStholo     }
742286d8edStholo }
752286d8edStholo 
762286d8edStholo /* Print an edit script in context format.  */
772286d8edStholo 
782286d8edStholo void
792286d8edStholo print_context_script (script, unidiff_flag)
802286d8edStholo      struct change *script;
812286d8edStholo      int unidiff_flag;
822286d8edStholo {
832286d8edStholo   if (ignore_blank_lines_flag || ignore_regexp_list)
842286d8edStholo     mark_ignorable (script);
852286d8edStholo   else
862286d8edStholo     {
872286d8edStholo       struct change *e;
882286d8edStholo       for (e = script; e; e = e->link)
892286d8edStholo 	e->ignore = 0;
902286d8edStholo     }
912286d8edStholo 
922286d8edStholo   find_function_last_search = - files[0].prefix_lines;
932286d8edStholo   find_function_last_match = find_function_last_search - 1;
942286d8edStholo 
952286d8edStholo   if (unidiff_flag)
962286d8edStholo     print_script (script, find_hunk, pr_unidiff_hunk);
972286d8edStholo   else
982286d8edStholo     print_script (script, find_hunk, pr_context_hunk);
992286d8edStholo }
1002286d8edStholo 
1012286d8edStholo /* Print a pair of line numbers with a comma, translated for file FILE.
1022286d8edStholo    If the second number is not greater, use the first in place of it.
1032286d8edStholo 
1042286d8edStholo    Args A and B are internal line numbers.
1052286d8edStholo    We print the translated (real) line numbers.  */
1062286d8edStholo 
1072286d8edStholo static void
1082286d8edStholo print_context_number_range (file, a, b)
1092286d8edStholo      struct file_data const *file;
1102286d8edStholo      int a, b;
1112286d8edStholo {
1122286d8edStholo   int trans_a, trans_b;
1132286d8edStholo   translate_range (file, a, b, &trans_a, &trans_b);
1142286d8edStholo 
1152286d8edStholo   /* Note: we can have B < A in the case of a range of no lines.
1162286d8edStholo      In this case, we should print the line number before the range,
1172286d8edStholo      which is B.  */
1182286d8edStholo   if (trans_b > trans_a)
119*b2346922Stholo     printf_output ("%d,%d", trans_a, trans_b);
1202286d8edStholo   else
121*b2346922Stholo     printf_output ("%d", trans_b);
1222286d8edStholo }
1232286d8edStholo 
1242286d8edStholo /* Print a portion of an edit script in context format.
1252286d8edStholo    HUNK is the beginning of the portion to be printed.
1262286d8edStholo    The end is marked by a `link' that has been nulled out.
1272286d8edStholo 
1282286d8edStholo    Prints out lines from both files, and precedes each
1292286d8edStholo    line with the appropriate flag-character.  */
1302286d8edStholo 
1312286d8edStholo static void
1322286d8edStholo pr_context_hunk (hunk)
1332286d8edStholo      struct change *hunk;
1342286d8edStholo {
1352286d8edStholo   int first0, last0, first1, last1, show_from, show_to, i;
1362286d8edStholo   struct change *next;
1372286d8edStholo   char const *prefix;
1382286d8edStholo   char const *function;
1392286d8edStholo   size_t function_length;
1402286d8edStholo 
1412286d8edStholo   /* Determine range of line numbers involved in each file.  */
1422286d8edStholo 
1432286d8edStholo   analyze_hunk (hunk, &first0, &last0, &first1, &last1, &show_from, &show_to);
1442286d8edStholo 
1452286d8edStholo   if (!show_from && !show_to)
1462286d8edStholo     return;
1472286d8edStholo 
1482286d8edStholo   /* Include a context's width before and after.  */
1492286d8edStholo 
1502286d8edStholo   i = - files[0].prefix_lines;
1512286d8edStholo   first0 = max (first0 - context, i);
1522286d8edStholo   first1 = max (first1 - context, i);
1532286d8edStholo   last0 = min (last0 + context, files[0].valid_lines - 1);
1542286d8edStholo   last1 = min (last1 + context, files[1].valid_lines - 1);
1552286d8edStholo 
1562286d8edStholo   /* If desired, find the preceding function definition line in file 0.  */
1572286d8edStholo   function = 0;
1582286d8edStholo   if (function_regexp_list)
1592286d8edStholo     find_function (&files[0], first0, &function, &function_length);
1602286d8edStholo 
1612286d8edStholo   begin_output ();
1622286d8edStholo 
1632286d8edStholo   /* If we looked for and found a function this is part of,
1642286d8edStholo      include its name in the header of the diff section.  */
165*b2346922Stholo   printf_output ("***************");
1662286d8edStholo 
1672286d8edStholo   if (function)
1682286d8edStholo     {
169*b2346922Stholo       printf_output (" ");
170*b2346922Stholo       write_output (function, min (function_length - 1, 40));
1712286d8edStholo     }
1722286d8edStholo 
173*b2346922Stholo   printf_output ("\n*** ");
1742286d8edStholo   print_context_number_range (&files[0], first0, last0);
175*b2346922Stholo   printf_output (" ****\n");
1762286d8edStholo 
1772286d8edStholo   if (show_from)
1782286d8edStholo     {
1792286d8edStholo       next = hunk;
1802286d8edStholo 
1812286d8edStholo       for (i = first0; i <= last0; i++)
1822286d8edStholo 	{
1832286d8edStholo 	  /* Skip past changes that apply (in file 0)
1842286d8edStholo 	     only to lines before line I.  */
1852286d8edStholo 
1862286d8edStholo 	  while (next && next->line0 + next->deleted <= i)
1872286d8edStholo 	    next = next->link;
1882286d8edStholo 
1892286d8edStholo 	  /* Compute the marking for line I.  */
1902286d8edStholo 
1912286d8edStholo 	  prefix = " ";
1922286d8edStholo 	  if (next && next->line0 <= i)
1932286d8edStholo 	    /* The change NEXT covers this line.
1942286d8edStholo 	       If lines were inserted here in file 1, this is "changed".
1952286d8edStholo 	       Otherwise it is "deleted".  */
1962286d8edStholo 	    prefix = (next->inserted > 0 ? "!" : "-");
1972286d8edStholo 
1982286d8edStholo 	  print_1_line (prefix, &files[0].linbuf[i]);
1992286d8edStholo 	}
2002286d8edStholo     }
2012286d8edStholo 
202*b2346922Stholo   printf_output ("--- ");
2032286d8edStholo   print_context_number_range (&files[1], first1, last1);
204*b2346922Stholo   printf_output (" ----\n");
2052286d8edStholo 
2062286d8edStholo   if (show_to)
2072286d8edStholo     {
2082286d8edStholo       next = hunk;
2092286d8edStholo 
2102286d8edStholo       for (i = first1; i <= last1; i++)
2112286d8edStholo 	{
2122286d8edStholo 	  /* Skip past changes that apply (in file 1)
2132286d8edStholo 	     only to lines before line I.  */
2142286d8edStholo 
2152286d8edStholo 	  while (next && next->line1 + next->inserted <= i)
2162286d8edStholo 	    next = next->link;
2172286d8edStholo 
2182286d8edStholo 	  /* Compute the marking for line I.  */
2192286d8edStholo 
2202286d8edStholo 	  prefix = " ";
2212286d8edStholo 	  if (next && next->line1 <= i)
2222286d8edStholo 	    /* The change NEXT covers this line.
2232286d8edStholo 	       If lines were deleted here in file 0, this is "changed".
2242286d8edStholo 	       Otherwise it is "inserted".  */
2252286d8edStholo 	    prefix = (next->deleted > 0 ? "!" : "+");
2262286d8edStholo 
2272286d8edStholo 	  print_1_line (prefix, &files[1].linbuf[i]);
2282286d8edStholo 	}
2292286d8edStholo     }
2302286d8edStholo }
2312286d8edStholo 
2322286d8edStholo /* Print a pair of line numbers with a comma, translated for file FILE.
2332286d8edStholo    If the second number is smaller, use the first in place of it.
2342286d8edStholo    If the numbers are equal, print just one number.
2352286d8edStholo 
2362286d8edStholo    Args A and B are internal line numbers.
2372286d8edStholo    We print the translated (real) line numbers.  */
2382286d8edStholo 
2392286d8edStholo static void
2402286d8edStholo print_unidiff_number_range (file, a, b)
2412286d8edStholo      struct file_data const *file;
2422286d8edStholo      int a, b;
2432286d8edStholo {
2442286d8edStholo   int trans_a, trans_b;
2452286d8edStholo   translate_range (file, a, b, &trans_a, &trans_b);
2462286d8edStholo 
2472286d8edStholo   /* Note: we can have B < A in the case of a range of no lines.
2482286d8edStholo      In this case, we should print the line number before the range,
2492286d8edStholo      which is B.  */
2502286d8edStholo   if (trans_b <= trans_a)
251*b2346922Stholo     printf_output (trans_b == trans_a ? "%d" : "%d,0", trans_b);
2522286d8edStholo   else
253*b2346922Stholo     printf_output ("%d,%d", trans_a, trans_b - trans_a + 1);
2542286d8edStholo }
2552286d8edStholo 
2562286d8edStholo /* Print a portion of an edit script in unidiff format.
2572286d8edStholo    HUNK is the beginning of the portion to be printed.
2582286d8edStholo    The end is marked by a `link' that has been nulled out.
2592286d8edStholo 
2602286d8edStholo    Prints out lines from both files, and precedes each
2612286d8edStholo    line with the appropriate flag-character.  */
2622286d8edStholo 
2632286d8edStholo static void
2642286d8edStholo pr_unidiff_hunk (hunk)
2652286d8edStholo      struct change *hunk;
2662286d8edStholo {
2672286d8edStholo   int first0, last0, first1, last1, show_from, show_to, i, j, k;
2682286d8edStholo   struct change *next;
2692286d8edStholo   char const *function;
2702286d8edStholo   size_t function_length;
2712286d8edStholo 
2722286d8edStholo   /* Determine range of line numbers involved in each file.  */
2732286d8edStholo 
2742286d8edStholo   analyze_hunk (hunk, &first0, &last0, &first1, &last1, &show_from, &show_to);
2752286d8edStholo 
2762286d8edStholo   if (!show_from && !show_to)
2772286d8edStholo     return;
2782286d8edStholo 
2792286d8edStholo   /* Include a context's width before and after.  */
2802286d8edStholo 
2812286d8edStholo   i = - files[0].prefix_lines;
2822286d8edStholo   first0 = max (first0 - context, i);
2832286d8edStholo   first1 = max (first1 - context, i);
2842286d8edStholo   last0 = min (last0 + context, files[0].valid_lines - 1);
2852286d8edStholo   last1 = min (last1 + context, files[1].valid_lines - 1);
2862286d8edStholo 
2872286d8edStholo   /* If desired, find the preceding function definition line in file 0.  */
2882286d8edStholo   function = 0;
2892286d8edStholo   if (function_regexp_list)
2902286d8edStholo     find_function (&files[0], first0, &function, &function_length);
2912286d8edStholo 
2922286d8edStholo   begin_output ();
2932286d8edStholo 
294*b2346922Stholo   printf_output ("@@ -");
2952286d8edStholo   print_unidiff_number_range (&files[0], first0, last0);
296*b2346922Stholo   printf_output (" +");
2972286d8edStholo   print_unidiff_number_range (&files[1], first1, last1);
298*b2346922Stholo   printf_output (" @@");
2992286d8edStholo 
3002286d8edStholo   /* If we looked for and found a function this is part of,
3012286d8edStholo      include its name in the header of the diff section.  */
3022286d8edStholo 
3032286d8edStholo   if (function)
3042286d8edStholo     {
305*b2346922Stholo       write_output (" ", 1);
306*b2346922Stholo       write_output (function, min (function_length - 1, 40));
3072286d8edStholo     }
308*b2346922Stholo   write_output ("\n", 1);
3092286d8edStholo 
3102286d8edStholo   next = hunk;
3112286d8edStholo   i = first0;
3122286d8edStholo   j = first1;
3132286d8edStholo 
3142286d8edStholo   while (i <= last0 || j <= last1)
3152286d8edStholo     {
3162286d8edStholo 
3172286d8edStholo       /* If the line isn't a difference, output the context from file 0. */
3182286d8edStholo 
3192286d8edStholo       if (!next || i < next->line0)
3202286d8edStholo 	{
321*b2346922Stholo 	  write_output (tab_align_flag ? "\t" : " ", 1);
3222286d8edStholo 	  print_1_line (0, &files[0].linbuf[i++]);
3232286d8edStholo 	  j++;
3242286d8edStholo 	}
3252286d8edStholo       else
3262286d8edStholo 	{
3272286d8edStholo 	  /* For each difference, first output the deleted part. */
3282286d8edStholo 
3292286d8edStholo 	  k = next->deleted;
3302286d8edStholo 	  while (k--)
3312286d8edStholo 	    {
332*b2346922Stholo 	      write_output ("-", 1);
3332286d8edStholo 	      if (tab_align_flag)
334*b2346922Stholo 		write_output ("\t", 1);
3352286d8edStholo 	      print_1_line (0, &files[0].linbuf[i++]);
3362286d8edStholo 	    }
3372286d8edStholo 
3382286d8edStholo 	  /* Then output the inserted part. */
3392286d8edStholo 
3402286d8edStholo 	  k = next->inserted;
3412286d8edStholo 	  while (k--)
3422286d8edStholo 	    {
343*b2346922Stholo 	      write_output ("+", 1);
3442286d8edStholo 	      if (tab_align_flag)
345*b2346922Stholo 		write_output ("\t", 1);
3462286d8edStholo 	      print_1_line (0, &files[1].linbuf[j++]);
3472286d8edStholo 	    }
3482286d8edStholo 
3492286d8edStholo 	  /* We're done with this hunk, so on to the next! */
3502286d8edStholo 
3512286d8edStholo 	  next = next->link;
3522286d8edStholo 	}
3532286d8edStholo     }
3542286d8edStholo }
3552286d8edStholo 
3562286d8edStholo /* Scan a (forward-ordered) edit script for the first place that more than
3572286d8edStholo    2*CONTEXT unchanged lines appear, and return a pointer
3582286d8edStholo    to the `struct change' for the last change before those lines.  */
3592286d8edStholo 
3602286d8edStholo static struct change *
3612286d8edStholo find_hunk (start)
3622286d8edStholo      struct change *start;
3632286d8edStholo {
3642286d8edStholo   struct change *prev;
3652286d8edStholo   int top0, top1;
3662286d8edStholo   int thresh;
3672286d8edStholo 
3682286d8edStholo   do
3692286d8edStholo     {
3702286d8edStholo       /* Compute number of first line in each file beyond this changed.  */
3712286d8edStholo       top0 = start->line0 + start->deleted;
3722286d8edStholo       top1 = start->line1 + start->inserted;
3732286d8edStholo       prev = start;
3742286d8edStholo       start = start->link;
3752286d8edStholo       /* Threshold distance is 2*CONTEXT between two non-ignorable changes,
3762286d8edStholo 	 but only CONTEXT if one is ignorable.  */
3772286d8edStholo       thresh = ((prev->ignore || (start && start->ignore))
3782286d8edStholo 		? context
3792286d8edStholo 		: 2 * context + 1);
3802286d8edStholo       /* It is not supposed to matter which file we check in the end-test.
3812286d8edStholo 	 If it would matter, crash.  */
3822286d8edStholo       if (start && start->line0 - top0 != start->line1 - top1)
3832286d8edStholo 	abort ();
3842286d8edStholo     } while (start
3852286d8edStholo 	     /* Keep going if less than THRESH lines
3862286d8edStholo 		elapse before the affected line.  */
3872286d8edStholo 	     && start->line0 < top0 + thresh);
3882286d8edStholo 
3892286d8edStholo   return prev;
3902286d8edStholo }
3912286d8edStholo 
3922286d8edStholo /* Set the `ignore' flag properly in each change in SCRIPT.
3932286d8edStholo    It should be 1 if all the lines inserted or deleted in that change
3942286d8edStholo    are ignorable lines.  */
3952286d8edStholo 
3962286d8edStholo static void
3972286d8edStholo mark_ignorable (script)
3982286d8edStholo      struct change *script;
3992286d8edStholo {
4002286d8edStholo   while (script)
4012286d8edStholo     {
4022286d8edStholo       struct change *next = script->link;
4032286d8edStholo       int first0, last0, first1, last1, deletes, inserts;
4042286d8edStholo 
4052286d8edStholo       /* Turn this change into a hunk: detach it from the others.  */
4062286d8edStholo       script->link = 0;
4072286d8edStholo 
4082286d8edStholo       /* Determine whether this change is ignorable.  */
4092286d8edStholo       analyze_hunk (script, &first0, &last0, &first1, &last1, &deletes, &inserts);
4102286d8edStholo       /* Reconnect the chain as before.  */
4112286d8edStholo       script->link = next;
4122286d8edStholo 
4132286d8edStholo       /* If the change is ignorable, mark it.  */
4142286d8edStholo       script->ignore = (!deletes && !inserts);
4152286d8edStholo 
4162286d8edStholo       /* Advance to the following change.  */
4172286d8edStholo       script = next;
4182286d8edStholo     }
4192286d8edStholo }
4202286d8edStholo 
4212286d8edStholo /* Find the last function-header line in FILE prior to line number LINENUM.
4222286d8edStholo    This is a line containing a match for the regexp in `function_regexp'.
4232286d8edStholo    Store the address of the line text into LINEP and the length of the
4242286d8edStholo    line into LENP.
4252286d8edStholo    Do not store anything if no function-header is found.  */
4262286d8edStholo 
4272286d8edStholo static void
4282286d8edStholo find_function (file, linenum, linep, lenp)
4292286d8edStholo      struct file_data const *file;
4302286d8edStholo      int linenum;
4312286d8edStholo      char const **linep;
4322286d8edStholo      size_t *lenp;
4332286d8edStholo {
4342286d8edStholo   int i = linenum;
4352286d8edStholo   int last = find_function_last_search;
4362286d8edStholo   find_function_last_search = i;
4372286d8edStholo 
4382286d8edStholo   while (--i >= last)
4392286d8edStholo     {
4402286d8edStholo       /* See if this line is what we want.  */
4412286d8edStholo       struct regexp_list *r;
4422286d8edStholo       char const *line = file->linbuf[i];
4432286d8edStholo       size_t len = file->linbuf[i + 1] - line;
4442286d8edStholo 
4452286d8edStholo       for (r = function_regexp_list; r; r = r->next)
4462286d8edStholo 	if (0 <= re_search (&r->buf, line, len, 0, len, 0))
4472286d8edStholo 	  {
4482286d8edStholo 	    *linep = line;
4492286d8edStholo 	    *lenp = len;
4502286d8edStholo 	    find_function_last_match = i;
4512286d8edStholo 	    return;
4522286d8edStholo 	  }
4532286d8edStholo     }
4542286d8edStholo   /* If we search back to where we started searching the previous time,
4552286d8edStholo      find the line we found last time.  */
4562286d8edStholo   if (find_function_last_match >= - file->prefix_lines)
4572286d8edStholo     {
4582286d8edStholo       i = find_function_last_match;
4592286d8edStholo       *linep = file->linbuf[i];
4602286d8edStholo       *lenp = file->linbuf[i + 1] - *linep;
4612286d8edStholo       return;
4622286d8edStholo     }
4632286d8edStholo   return;
4642286d8edStholo }
465