xref: /openbsd-src/gnu/usr.bin/cvs/diff/context.c (revision b2346922a76a50a89e33beab4ebbc0950de8a8df)
1 /* Context-format output routines for GNU DIFF.
2    Copyright (C) 1988,1989,1991,1992,1993,1994,1998 Free Software Foundation, Inc.
3 
4 This file is part of GNU DIFF.
5 
6 GNU DIFF is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU DIFF is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU DIFF; see the file COPYING.  If not, write to
18 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
19 
20 #include "diff.h"
21 
22 static struct change *find_hunk PARAMS((struct change *));
23 static void find_function PARAMS((struct file_data const *, int, char const **, size_t *));
24 static void mark_ignorable PARAMS((struct change *));
25 static void pr_context_hunk PARAMS((struct change *));
26 static void pr_unidiff_hunk PARAMS((struct change *));
27 static void print_context_label PARAMS ((char const *, struct file_data *, char const *));
28 static void print_context_number_range PARAMS((struct file_data const *, int, int));
29 static void print_unidiff_number_range PARAMS((struct file_data const *, int, int));
30 
31 /* Last place find_function started searching from.  */
32 static int find_function_last_search;
33 
34 /* The value find_function returned when it started searching there.  */
35 static int find_function_last_match;
36 
37 /* Print a label for a context diff, with a file name and date or a label.  */
38 
39 static void
40 print_context_label (mark, inf, label)
41      char const *mark;
42      struct file_data *inf;
43      char const *label;
44 {
45   if (label)
46     printf_output ("%s %s\n", mark, label);
47   else
48     {
49       char const *ct = ctime (&inf->stat.st_mtime);
50       if (!ct)
51 	ct = "?\n";
52       /* See Posix.2 section 4.17.6.1.4 for this format.  */
53       printf_output ("%s %s\t%s", mark, inf->name, ct);
54     }
55 }
56 
57 /* Print a header for a context diff, with the file names and dates.  */
58 
59 void
60 print_context_header (inf, unidiff_flag)
61      struct file_data inf[];
62      int unidiff_flag;
63 {
64   if (unidiff_flag)
65     {
66       print_context_label ("---", &inf[0], file_label[0]);
67       print_context_label ("+++", &inf[1], file_label[1]);
68     }
69   else
70     {
71       print_context_label ("***", &inf[0], file_label[0]);
72       print_context_label ("---", &inf[1], file_label[1]);
73     }
74 }
75 
76 /* Print an edit script in context format.  */
77 
78 void
79 print_context_script (script, unidiff_flag)
80      struct change *script;
81      int unidiff_flag;
82 {
83   if (ignore_blank_lines_flag || ignore_regexp_list)
84     mark_ignorable (script);
85   else
86     {
87       struct change *e;
88       for (e = script; e; e = e->link)
89 	e->ignore = 0;
90     }
91 
92   find_function_last_search = - files[0].prefix_lines;
93   find_function_last_match = find_function_last_search - 1;
94 
95   if (unidiff_flag)
96     print_script (script, find_hunk, pr_unidiff_hunk);
97   else
98     print_script (script, find_hunk, pr_context_hunk);
99 }
100 
101 /* Print a pair of line numbers with a comma, translated for file FILE.
102    If the second number is not greater, use the first in place of it.
103 
104    Args A and B are internal line numbers.
105    We print the translated (real) line numbers.  */
106 
107 static void
108 print_context_number_range (file, a, b)
109      struct file_data const *file;
110      int a, b;
111 {
112   int trans_a, trans_b;
113   translate_range (file, a, b, &trans_a, &trans_b);
114 
115   /* Note: we can have B < A in the case of a range of no lines.
116      In this case, we should print the line number before the range,
117      which is B.  */
118   if (trans_b > trans_a)
119     printf_output ("%d,%d", trans_a, trans_b);
120   else
121     printf_output ("%d", trans_b);
122 }
123 
124 /* Print a portion of an edit script in context format.
125    HUNK is the beginning of the portion to be printed.
126    The end is marked by a `link' that has been nulled out.
127 
128    Prints out lines from both files, and precedes each
129    line with the appropriate flag-character.  */
130 
131 static void
132 pr_context_hunk (hunk)
133      struct change *hunk;
134 {
135   int first0, last0, first1, last1, show_from, show_to, i;
136   struct change *next;
137   char const *prefix;
138   char const *function;
139   size_t function_length;
140 
141   /* Determine range of line numbers involved in each file.  */
142 
143   analyze_hunk (hunk, &first0, &last0, &first1, &last1, &show_from, &show_to);
144 
145   if (!show_from && !show_to)
146     return;
147 
148   /* Include a context's width before and after.  */
149 
150   i = - files[0].prefix_lines;
151   first0 = max (first0 - context, i);
152   first1 = max (first1 - context, i);
153   last0 = min (last0 + context, files[0].valid_lines - 1);
154   last1 = min (last1 + context, files[1].valid_lines - 1);
155 
156   /* If desired, find the preceding function definition line in file 0.  */
157   function = 0;
158   if (function_regexp_list)
159     find_function (&files[0], first0, &function, &function_length);
160 
161   begin_output ();
162 
163   /* If we looked for and found a function this is part of,
164      include its name in the header of the diff section.  */
165   printf_output ("***************");
166 
167   if (function)
168     {
169       printf_output (" ");
170       write_output (function, min (function_length - 1, 40));
171     }
172 
173   printf_output ("\n*** ");
174   print_context_number_range (&files[0], first0, last0);
175   printf_output (" ****\n");
176 
177   if (show_from)
178     {
179       next = hunk;
180 
181       for (i = first0; i <= last0; i++)
182 	{
183 	  /* Skip past changes that apply (in file 0)
184 	     only to lines before line I.  */
185 
186 	  while (next && next->line0 + next->deleted <= i)
187 	    next = next->link;
188 
189 	  /* Compute the marking for line I.  */
190 
191 	  prefix = " ";
192 	  if (next && next->line0 <= i)
193 	    /* The change NEXT covers this line.
194 	       If lines were inserted here in file 1, this is "changed".
195 	       Otherwise it is "deleted".  */
196 	    prefix = (next->inserted > 0 ? "!" : "-");
197 
198 	  print_1_line (prefix, &files[0].linbuf[i]);
199 	}
200     }
201 
202   printf_output ("--- ");
203   print_context_number_range (&files[1], first1, last1);
204   printf_output (" ----\n");
205 
206   if (show_to)
207     {
208       next = hunk;
209 
210       for (i = first1; i <= last1; i++)
211 	{
212 	  /* Skip past changes that apply (in file 1)
213 	     only to lines before line I.  */
214 
215 	  while (next && next->line1 + next->inserted <= i)
216 	    next = next->link;
217 
218 	  /* Compute the marking for line I.  */
219 
220 	  prefix = " ";
221 	  if (next && next->line1 <= i)
222 	    /* The change NEXT covers this line.
223 	       If lines were deleted here in file 0, this is "changed".
224 	       Otherwise it is "inserted".  */
225 	    prefix = (next->deleted > 0 ? "!" : "+");
226 
227 	  print_1_line (prefix, &files[1].linbuf[i]);
228 	}
229     }
230 }
231 
232 /* Print a pair of line numbers with a comma, translated for file FILE.
233    If the second number is smaller, use the first in place of it.
234    If the numbers are equal, print just one number.
235 
236    Args A and B are internal line numbers.
237    We print the translated (real) line numbers.  */
238 
239 static void
240 print_unidiff_number_range (file, a, b)
241      struct file_data const *file;
242      int a, b;
243 {
244   int trans_a, trans_b;
245   translate_range (file, a, b, &trans_a, &trans_b);
246 
247   /* Note: we can have B < A in the case of a range of no lines.
248      In this case, we should print the line number before the range,
249      which is B.  */
250   if (trans_b <= trans_a)
251     printf_output (trans_b == trans_a ? "%d" : "%d,0", trans_b);
252   else
253     printf_output ("%d,%d", trans_a, trans_b - trans_a + 1);
254 }
255 
256 /* Print a portion of an edit script in unidiff format.
257    HUNK is the beginning of the portion to be printed.
258    The end is marked by a `link' that has been nulled out.
259 
260    Prints out lines from both files, and precedes each
261    line with the appropriate flag-character.  */
262 
263 static void
264 pr_unidiff_hunk (hunk)
265      struct change *hunk;
266 {
267   int first0, last0, first1, last1, show_from, show_to, i, j, k;
268   struct change *next;
269   char const *function;
270   size_t function_length;
271 
272   /* Determine range of line numbers involved in each file.  */
273 
274   analyze_hunk (hunk, &first0, &last0, &first1, &last1, &show_from, &show_to);
275 
276   if (!show_from && !show_to)
277     return;
278 
279   /* Include a context's width before and after.  */
280 
281   i = - files[0].prefix_lines;
282   first0 = max (first0 - context, i);
283   first1 = max (first1 - context, i);
284   last0 = min (last0 + context, files[0].valid_lines - 1);
285   last1 = min (last1 + context, files[1].valid_lines - 1);
286 
287   /* If desired, find the preceding function definition line in file 0.  */
288   function = 0;
289   if (function_regexp_list)
290     find_function (&files[0], first0, &function, &function_length);
291 
292   begin_output ();
293 
294   printf_output ("@@ -");
295   print_unidiff_number_range (&files[0], first0, last0);
296   printf_output (" +");
297   print_unidiff_number_range (&files[1], first1, last1);
298   printf_output (" @@");
299 
300   /* If we looked for and found a function this is part of,
301      include its name in the header of the diff section.  */
302 
303   if (function)
304     {
305       write_output (" ", 1);
306       write_output (function, min (function_length - 1, 40));
307     }
308   write_output ("\n", 1);
309 
310   next = hunk;
311   i = first0;
312   j = first1;
313 
314   while (i <= last0 || j <= last1)
315     {
316 
317       /* If the line isn't a difference, output the context from file 0. */
318 
319       if (!next || i < next->line0)
320 	{
321 	  write_output (tab_align_flag ? "\t" : " ", 1);
322 	  print_1_line (0, &files[0].linbuf[i++]);
323 	  j++;
324 	}
325       else
326 	{
327 	  /* For each difference, first output the deleted part. */
328 
329 	  k = next->deleted;
330 	  while (k--)
331 	    {
332 	      write_output ("-", 1);
333 	      if (tab_align_flag)
334 		write_output ("\t", 1);
335 	      print_1_line (0, &files[0].linbuf[i++]);
336 	    }
337 
338 	  /* Then output the inserted part. */
339 
340 	  k = next->inserted;
341 	  while (k--)
342 	    {
343 	      write_output ("+", 1);
344 	      if (tab_align_flag)
345 		write_output ("\t", 1);
346 	      print_1_line (0, &files[1].linbuf[j++]);
347 	    }
348 
349 	  /* We're done with this hunk, so on to the next! */
350 
351 	  next = next->link;
352 	}
353     }
354 }
355 
356 /* Scan a (forward-ordered) edit script for the first place that more than
357    2*CONTEXT unchanged lines appear, and return a pointer
358    to the `struct change' for the last change before those lines.  */
359 
360 static struct change *
361 find_hunk (start)
362      struct change *start;
363 {
364   struct change *prev;
365   int top0, top1;
366   int thresh;
367 
368   do
369     {
370       /* Compute number of first line in each file beyond this changed.  */
371       top0 = start->line0 + start->deleted;
372       top1 = start->line1 + start->inserted;
373       prev = start;
374       start = start->link;
375       /* Threshold distance is 2*CONTEXT between two non-ignorable changes,
376 	 but only CONTEXT if one is ignorable.  */
377       thresh = ((prev->ignore || (start && start->ignore))
378 		? context
379 		: 2 * context + 1);
380       /* It is not supposed to matter which file we check in the end-test.
381 	 If it would matter, crash.  */
382       if (start && start->line0 - top0 != start->line1 - top1)
383 	abort ();
384     } while (start
385 	     /* Keep going if less than THRESH lines
386 		elapse before the affected line.  */
387 	     && start->line0 < top0 + thresh);
388 
389   return prev;
390 }
391 
392 /* Set the `ignore' flag properly in each change in SCRIPT.
393    It should be 1 if all the lines inserted or deleted in that change
394    are ignorable lines.  */
395 
396 static void
397 mark_ignorable (script)
398      struct change *script;
399 {
400   while (script)
401     {
402       struct change *next = script->link;
403       int first0, last0, first1, last1, deletes, inserts;
404 
405       /* Turn this change into a hunk: detach it from the others.  */
406       script->link = 0;
407 
408       /* Determine whether this change is ignorable.  */
409       analyze_hunk (script, &first0, &last0, &first1, &last1, &deletes, &inserts);
410       /* Reconnect the chain as before.  */
411       script->link = next;
412 
413       /* If the change is ignorable, mark it.  */
414       script->ignore = (!deletes && !inserts);
415 
416       /* Advance to the following change.  */
417       script = next;
418     }
419 }
420 
421 /* Find the last function-header line in FILE prior to line number LINENUM.
422    This is a line containing a match for the regexp in `function_regexp'.
423    Store the address of the line text into LINEP and the length of the
424    line into LENP.
425    Do not store anything if no function-header is found.  */
426 
427 static void
428 find_function (file, linenum, linep, lenp)
429      struct file_data const *file;
430      int linenum;
431      char const **linep;
432      size_t *lenp;
433 {
434   int i = linenum;
435   int last = find_function_last_search;
436   find_function_last_search = i;
437 
438   while (--i >= last)
439     {
440       /* See if this line is what we want.  */
441       struct regexp_list *r;
442       char const *line = file->linbuf[i];
443       size_t len = file->linbuf[i + 1] - line;
444 
445       for (r = function_regexp_list; r; r = r->next)
446 	if (0 <= re_search (&r->buf, line, len, 0, len, 0))
447 	  {
448 	    *linep = line;
449 	    *lenp = len;
450 	    find_function_last_match = i;
451 	    return;
452 	  }
453     }
454   /* If we search back to where we started searching the previous time,
455      find the line we found last time.  */
456   if (find_function_last_match >= - file->prefix_lines)
457     {
458       i = find_function_last_match;
459       *linep = file->linbuf[i];
460       *lenp = file->linbuf[i + 1] - *linep;
461       return;
462     }
463   return;
464 }
465