1/* Context-format output routines for GNU DIFF.
2   Copyright (C) 1988,1989,1991,1992,1993,1994,1998 Free Software Foundation, Inc.
3
4This file is part of GNU DIFF.
5
6GNU DIFF is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 2, or (at your option)
9any later version.
10
11GNU DIFF is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14GNU General Public License for more details.
15
16*/
17
18#include "diff.h"
19
20static struct change *find_hunk PARAMS((struct change *));
21static void find_function PARAMS((struct file_data const *, int, char const **, size_t *));
22static void mark_ignorable PARAMS((struct change *));
23static void pr_context_hunk PARAMS((struct change *));
24static void pr_unidiff_hunk PARAMS((struct change *));
25static void print_context_label PARAMS ((char const *, struct file_data *, char const *));
26static void print_context_number_range PARAMS((struct file_data const *, int, int));
27static void print_unidiff_number_range PARAMS((struct file_data const *, int, int));
28
29/* Last place find_function started searching from.  */
30static int find_function_last_search;
31
32/* The value find_function returned when it started searching there.  */
33static int find_function_last_match;
34
35/* Print a label for a context diff, with a file name and date or a label.  */
36
37static void
38print_context_label (mark, inf, label)
39     char const *mark;
40     struct file_data *inf;
41     char const *label;
42{
43  if (label)
44    printf_output ("%s %s\n", mark, label);
45  else
46    {
47      char const *ct = ctime (&inf->stat.st_mtime);
48      if (!ct)
49	ct = "?\n";
50      /* See Posix.2 section 4.17.6.1.4 for this format.  */
51      printf_output ("%s %s\t%s", mark, inf->name, ct);
52    }
53}
54
55/* Print a header for a context diff, with the file names and dates.  */
56
57void
58print_context_header (inf, unidiff_flag)
59     struct file_data inf[];
60     int unidiff_flag;
61{
62  if (unidiff_flag)
63    {
64      print_context_label ("---", &inf[0], file_label[0]);
65      print_context_label ("+++", &inf[1], file_label[1]);
66    }
67  else
68    {
69      print_context_label ("***", &inf[0], file_label[0]);
70      print_context_label ("---", &inf[1], file_label[1]);
71    }
72}
73
74/* Print an edit script in context format.  */
75
76void
77print_context_script (script, unidiff_flag)
78     struct change *script;
79     int unidiff_flag;
80{
81  if (ignore_blank_lines_flag || ignore_regexp_list)
82    mark_ignorable (script);
83  else
84    {
85      struct change *e;
86      for (e = script; e; e = e->link)
87	e->ignore = 0;
88    }
89
90  find_function_last_search = - files[0].prefix_lines;
91  find_function_last_match = find_function_last_search - 1;
92
93  if (unidiff_flag)
94    print_script (script, find_hunk, pr_unidiff_hunk);
95  else
96    print_script (script, find_hunk, pr_context_hunk);
97}
98
99/* Print a pair of line numbers with a comma, translated for file FILE.
100   If the second number is not greater, use the first in place of it.
101
102   Args A and B are internal line numbers.
103   We print the translated (real) line numbers.  */
104
105static void
106print_context_number_range (file, a, b)
107     struct file_data const *file;
108     int a, b;
109{
110  int trans_a, trans_b;
111  translate_range (file, a, b, &trans_a, &trans_b);
112
113  /* Note: we can have B < A in the case of a range of no lines.
114     In this case, we should print the line number before the range,
115     which is B.  */
116  if (trans_b > trans_a)
117    printf_output ("%d,%d", trans_a, trans_b);
118  else
119    printf_output ("%d", trans_b);
120}
121
122/* Print a portion of an edit script in context format.
123   HUNK is the beginning of the portion to be printed.
124   The end is marked by a `link' that has been nulled out.
125
126   Prints out lines from both files, and precedes each
127   line with the appropriate flag-character.  */
128
129static void
130pr_context_hunk (hunk)
131     struct change *hunk;
132{
133  int first0, last0, first1, last1, show_from, show_to, i;
134  struct change *next;
135  char const *prefix;
136  char const *function;
137  size_t function_length;
138
139  /* Determine range of line numbers involved in each file.  */
140
141  analyze_hunk (hunk, &first0, &last0, &first1, &last1, &show_from, &show_to);
142
143  if (!show_from && !show_to)
144    return;
145
146  /* Include a context's width before and after.  */
147
148  i = - files[0].prefix_lines;
149  first0 = max (first0 - context, i);
150  first1 = max (first1 - context, i);
151  last0 = min (last0 + context, files[0].valid_lines - 1);
152  last1 = min (last1 + context, files[1].valid_lines - 1);
153
154  /* If desired, find the preceding function definition line in file 0.  */
155  function = 0;
156  if (function_regexp_list)
157    find_function (&files[0], first0, &function, &function_length);
158
159  begin_output ();
160
161  /* If we looked for and found a function this is part of,
162     include its name in the header of the diff section.  */
163  printf_output ("***************");
164
165  if (function)
166    {
167      printf_output (" ");
168      write_output (function, min (function_length - 1, 40));
169    }
170
171  printf_output ("\n*** ");
172  print_context_number_range (&files[0], first0, last0);
173  printf_output (" ****\n");
174
175  if (show_from)
176    {
177      next = hunk;
178
179      for (i = first0; i <= last0; i++)
180	{
181	  /* Skip past changes that apply (in file 0)
182	     only to lines before line I.  */
183
184	  while (next && next->line0 + next->deleted <= i)
185	    next = next->link;
186
187	  /* Compute the marking for line I.  */
188
189	  prefix = " ";
190	  if (next && next->line0 <= i)
191	    /* The change NEXT covers this line.
192	       If lines were inserted here in file 1, this is "changed".
193	       Otherwise it is "deleted".  */
194	    prefix = (next->inserted > 0 ? "!" : "-");
195
196	  print_1_line (prefix, &files[0].linbuf[i]);
197	}
198    }
199
200  printf_output ("--- ");
201  print_context_number_range (&files[1], first1, last1);
202  printf_output (" ----\n");
203
204  if (show_to)
205    {
206      next = hunk;
207
208      for (i = first1; i <= last1; i++)
209	{
210	  /* Skip past changes that apply (in file 1)
211	     only to lines before line I.  */
212
213	  while (next && next->line1 + next->inserted <= i)
214	    next = next->link;
215
216	  /* Compute the marking for line I.  */
217
218	  prefix = " ";
219	  if (next && next->line1 <= i)
220	    /* The change NEXT covers this line.
221	       If lines were deleted here in file 0, this is "changed".
222	       Otherwise it is "inserted".  */
223	    prefix = (next->deleted > 0 ? "!" : "+");
224
225	  print_1_line (prefix, &files[1].linbuf[i]);
226	}
227    }
228}
229
230/* Print a pair of line numbers with a comma, translated for file FILE.
231   If the second number is smaller, use the first in place of it.
232   If the numbers are equal, print just one number.
233
234   Args A and B are internal line numbers.
235   We print the translated (real) line numbers.  */
236
237static void
238print_unidiff_number_range (file, a, b)
239     struct file_data const *file;
240     int a, b;
241{
242  int trans_a, trans_b;
243  translate_range (file, a, b, &trans_a, &trans_b);
244
245  /* Note: we can have B < A in the case of a range of no lines.
246     In this case, we should print the line number before the range,
247     which is B.  */
248  if (trans_b <= trans_a)
249    printf_output (trans_b == trans_a ? "%d" : "%d,0", trans_b);
250  else
251    printf_output ("%d,%d", trans_a, trans_b - trans_a + 1);
252}
253
254/* Print a portion of an edit script in unidiff format.
255   HUNK is the beginning of the portion to be printed.
256   The end is marked by a `link' that has been nulled out.
257
258   Prints out lines from both files, and precedes each
259   line with the appropriate flag-character.  */
260
261static void
262pr_unidiff_hunk (hunk)
263     struct change *hunk;
264{
265  int first0, last0, first1, last1, show_from, show_to, i, j, k;
266  struct change *next;
267  char const *function;
268  size_t function_length;
269
270  /* Determine range of line numbers involved in each file.  */
271
272  analyze_hunk (hunk, &first0, &last0, &first1, &last1, &show_from, &show_to);
273
274  if (!show_from && !show_to)
275    return;
276
277  /* Include a context's width before and after.  */
278
279  i = - files[0].prefix_lines;
280  first0 = max (first0 - context, i);
281  first1 = max (first1 - context, i);
282  last0 = min (last0 + context, files[0].valid_lines - 1);
283  last1 = min (last1 + context, files[1].valid_lines - 1);
284
285  /* If desired, find the preceding function definition line in file 0.  */
286  function = 0;
287  if (function_regexp_list)
288    find_function (&files[0], first0, &function, &function_length);
289
290  begin_output ();
291
292  printf_output ("@@ -");
293  print_unidiff_number_range (&files[0], first0, last0);
294  printf_output (" +");
295  print_unidiff_number_range (&files[1], first1, last1);
296  printf_output (" @@");
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      write_output (" ", 1);
304      write_output (function, min (function_length - 1, 40));
305    }
306  write_output ("\n", 1);
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	  write_output (tab_align_flag ? "\t" : " ", 1);
320	  print_1_line (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	      write_output ("-", 1);
331	      if (tab_align_flag)
332		write_output ("\t", 1);
333	      print_1_line (0, &files[0].linbuf[i++]);
334	    }
335
336	  /* Then output the inserted part. */
337
338	  k = next->inserted;
339	  while (k--)
340	    {
341	      write_output ("+", 1);
342	      if (tab_align_flag)
343		write_output ("\t", 1);
344	      print_1_line (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
358static struct change *
359find_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
394static void
395mark_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
425static void
426find_function (file, linenum, linep, lenp)
427     struct file_data const *file;
428     int linenum;
429     char const **linep;
430     size_t *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      char const *line = file->linbuf[i];
441      size_t 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