1/* sdiff-format output routines for GNU DIFF.
2
3   Copyright (C) 1991, 1992, 1993, 1998, 2001, 2002 Free Software
4   Foundation, Inc.
5
6   This file is part of GNU DIFF.
7
8   GNU DIFF is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY.  No author or distributor
10   accepts responsibility to anyone for the consequences of using it
11   or for whether it serves any particular purpose or works at all,
12   unless he says so in writing.  Refer to the GNU DIFF General Public
13   License for full details.
14
15   Everyone is granted permission to copy, modify and redistribute
16   GNU DIFF, but only under the conditions described in the
17   GNU DIFF General Public License.   A copy of this license is
18   supposed to have been given to you along with GNU DIFF so you
19   can know your rights and responsibilities.  It should be in a
20   file named COPYING.  Among other things, the copyright notice
21   and this notice must be preserved on all copies.  */
22
23#include "diff.h"
24
25static void print_sdiff_common_lines (lin, lin);
26static void print_sdiff_hunk (struct change *);
27
28/* Next line number to be printed in the two input files.  */
29static lin next0, next1;
30
31/* Print the edit-script SCRIPT as a sdiff style output.  */
32
33void
34print_sdiff_script (struct change *script)
35{
36  begin_output ();
37
38  next0 = next1 = - files[0].prefix_lines;
39  print_script (script, find_change, print_sdiff_hunk);
40
41  print_sdiff_common_lines (files[0].valid_lines, files[1].valid_lines);
42}
43
44/* Tab from column FROM to column TO, where FROM <= TO.  Yield TO.  */
45
46static unsigned int
47tab_from_to (unsigned int from, unsigned int to)
48{
49  FILE *out = outfile;
50  unsigned int tab;
51
52  if (!expand_tabs)
53    for (tab = from + TAB_WIDTH - from % TAB_WIDTH;  tab <= to;  tab += TAB_WIDTH)
54      {
55	putc ('\t', out);
56	from = tab;
57      }
58  while (from++ < to)
59    putc (' ', out);
60  return to;
61}
62
63/*
64 * Print the text for half an sdiff line.  This means truncate to width
65 * observing tabs, and trim a trailing newline.  Returns the last column
66 * written (not the number of chars).
67 */
68static unsigned int
69print_half_line (char const *const *line, unsigned int indent,
70		 unsigned int out_bound)
71{
72  FILE *out = outfile;
73  register unsigned int in_position = 0;
74  register unsigned int out_position = 0;
75  register char const *text_pointer = line[0];
76  register char const *text_limit = line[1];
77
78  while (text_pointer < text_limit)
79    {
80      register unsigned char c = *text_pointer++;
81
82      switch (c)
83	{
84	case '\t':
85	  {
86	    unsigned int spaces = TAB_WIDTH - in_position % TAB_WIDTH;
87	    if (in_position == out_position)
88	      {
89		unsigned int tabstop = out_position + spaces;
90		if (expand_tabs)
91		  {
92		    if (out_bound < tabstop)
93		      tabstop = out_bound;
94		    for (;  out_position < tabstop;  out_position++)
95		      putc (' ', out);
96		  }
97		else
98		  if (tabstop < out_bound)
99		    {
100		      out_position = tabstop;
101		      putc (c, out);
102		    }
103	      }
104	    in_position += spaces;
105	  }
106	  break;
107
108	case '\r':
109	  {
110	    putc (c, out);
111	    tab_from_to (0, indent);
112	    in_position = out_position = 0;
113	  }
114	  break;
115
116	case '\b':
117	  if (in_position != 0 && --in_position < out_bound)
118	    {
119	      if (out_position <= in_position)
120		/* Add spaces to make up for suppressed tab past out_bound.  */
121		for (;  out_position < in_position;  out_position++)
122		  putc (' ', out);
123	      else
124		{
125		  out_position = in_position;
126		  putc (c, out);
127		}
128	    }
129	  break;
130
131	case '\f':
132	case '\v':
133	control_char:
134	  if (in_position < out_bound)
135	    putc (c, out);
136	  break;
137
138	default:
139	  if (! ISPRINT (c))
140	    goto control_char;
141	  /* falls through */
142	case ' ':
143	  if (in_position++ < out_bound)
144	    {
145	      out_position = in_position;
146	      putc (c, out);
147	    }
148	  break;
149
150	case '\n':
151	  return out_position;
152	}
153    }
154
155  return out_position;
156}
157
158/*
159 * Print side by side lines with a separator in the middle.
160 * 0 parameters are taken to indicate white space text.
161 * Blank lines that can easily be caught are reduced to a single newline.
162 */
163
164static void
165print_1sdiff_line (char const *const *left, char sep,
166		   char const *const *right)
167{
168  FILE *out = outfile;
169  unsigned int hw = sdiff_half_width, c2o = sdiff_column2_offset;
170  unsigned int col = 0;
171  bool put_newline = 0;
172
173  if (left)
174    {
175      put_newline |= left[1][-1] == '\n';
176      col = print_half_line (left, 0, hw);
177    }
178
179  if (sep != ' ')
180    {
181      col = tab_from_to (col, (hw + c2o - 1) / 2) + 1;
182      if (sep == '|' && put_newline != (right[1][-1] == '\n'))
183	sep = put_newline ? '/' : '\\';
184      putc (sep, out);
185    }
186
187  if (right)
188    {
189      put_newline |= right[1][-1] == '\n';
190      if (**right != '\n')
191	{
192	  col = tab_from_to (col, c2o);
193	  print_half_line (right, col, hw);
194	}
195    }
196
197  if (put_newline)
198    putc ('\n', out);
199}
200
201/* Print lines common to both files in side-by-side format.  */
202static void
203print_sdiff_common_lines (lin limit0, lin limit1)
204{
205  lin i0 = next0, i1 = next1;
206
207  if (!suppress_common_lines && (i0 != limit0 || i1 != limit1))
208    {
209      if (sdiff_merge_assist)
210	{
211	  long len0 = limit0 - i0;
212	  long len1 = limit1 - i1;
213	  fprintf (outfile, "i%ld,%ld\n", len0, len1);
214	}
215
216      if (!left_column)
217	{
218	  while (i0 != limit0 && i1 != limit1)
219	    print_1sdiff_line (&files[0].linbuf[i0++], ' ',
220			       &files[1].linbuf[i1++]);
221	  while (i1 != limit1)
222	    print_1sdiff_line (0, ')', &files[1].linbuf[i1++]);
223	}
224      while (i0 != limit0)
225	print_1sdiff_line (&files[0].linbuf[i0++], '(', 0);
226    }
227
228  next0 = limit0;
229  next1 = limit1;
230}
231
232/* Print a hunk of an sdiff diff.
233   This is a contiguous portion of a complete edit script,
234   describing changes in consecutive lines.  */
235
236static void
237print_sdiff_hunk (struct change *hunk)
238{
239  lin first0, last0, first1, last1;
240  register lin i, j;
241
242  /* Determine range of line numbers involved in each file.  */
243  enum changes changes =
244    analyze_hunk (hunk, &first0, &last0, &first1, &last1);
245  if (!changes)
246    return;
247
248  /* Print out lines up to this change.  */
249  print_sdiff_common_lines (first0, first1);
250
251  if (sdiff_merge_assist)
252    {
253      long len0 = last0 - first0 + 1;
254      long len1 = last1 - first1 + 1;
255      fprintf (outfile, "c%ld,%ld\n", len0, len1);
256    }
257
258  /* Print ``xxx  |  xxx '' lines */
259  if (changes == CHANGED)
260    {
261      for (i = first0, j = first1;  i <= last0 && j <= last1;  i++, j++)
262	print_1sdiff_line (&files[0].linbuf[i], '|', &files[1].linbuf[j]);
263      changes = (i <= last0 ? OLD : 0) + (j <= last1 ? NEW : 0);
264      next0 = first0 = i;
265      next1 = first1 = j;
266    }
267
268  /* Print ``     >  xxx '' lines */
269  if (changes & NEW)
270    {
271      for (j = first1; j <= last1; ++j)
272	print_1sdiff_line (0, '>', &files[1].linbuf[j]);
273      next1 = j;
274    }
275
276  /* Print ``xxx  <     '' lines */
277  if (changes & OLD)
278    {
279      for (i = first0; i <= last0; ++i)
280	print_1sdiff_line (&files[0].linbuf[i], '<', 0);
281      next0 = i;
282    }
283}
284