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