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