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