]> git.neil.brown.name Git - wiggle.git/blob - vpatch.c
Disable *all* backups when --no-backups used
[wiggle.git] / vpatch.c
1 /*
2  * wiggle - apply rejected patches
3  *
4  * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5  * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
6  * Copyright (C) 2014-2020 Neil Brown <neil@brown.name>
7  *
8  *
9  *    This program is free software; you can redistribute it and/or modify
10  *    it under the terms of the GNU General Public License as published by
11  *    the Free Software Foundation; either version 2 of the License, or
12  *    (at your option) any later version.
13  *
14  *    This program is distributed in the hope that it will be useful,
15  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *    GNU General Public License for more details.
18  *
19  *    You should have received a copy of the GNU General Public License
20  *    along with this program.
21  *
22  *    Author: Neil Brown
23  *    Email: <neil@brown.name>
24  */
25
26 /*
27  * vpatch - visual front end for wiggle - aka Browse mode.
28  *
29  * "files" display, lists all files with statistics
30  *    - can hide various lines including subdirectories
31  *      and files without wiggles or conflicts
32  * "merge" display shows various views of  merged file with different
33  *  parts in different colours.
34  *
35  *  The window can be split horizontally to show the original and result
36  *  beside the diff, and each different branch can be shown alone.
37  *
38  */
39
40 #include "wiggle.h"
41 #include <curses.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <signal.h>
45 #include <fcntl.h>
46 #include <ctype.h>
47 #include <sys/wait.h>
48
49 static void term_init(int raw);
50
51 static int intr_kills = 0;
52
53 /* global attributes */
54 static unsigned int a_delete, a_added, a_common, a_sep, a_void,
55         a_unmatched, a_extra, a_already;
56 static unsigned int a_has_conflicts, a_has_wiggles, a_no_wiggles, a_saved;
57
58 /******************************************************************
59  * Help window
60  * We display help in an insert, leaving 5 columns left and right,
61  * and 2 rows top and bottom, but at most 58x15 plus border
62  * In help mode:
63  *   SPC or RTN moves down or to next page
64  *   BKSPC goes backwards
65  *   'q' returns to origin screen
66  *   '?' show help on help
67  *   left and right scroll help view
68  *
69  * A help text is an array of lines of text
70  */
71
72 static char *help_help[] = {
73         "   You are viewing the help page for the help viewer.",
74         "You normally get here by typing '?'",
75         "",
76         "The following keystrokes work in the help viewer:",
77         "  ?     display this help message",
78         "  q     return to previous view",
79         "  SPC   move forward through help document",
80         "  RTN   same as SPC",
81         "  BKSP  move backward through help document",
82         "  RIGHT scroll help window so text on the right appears",
83         "  LEFT  scroll help window so text on the left appears",
84         NULL
85 };
86
87 static char *help_missing[] = {
88         "The file that this patch applies to appears",
89         "to be missing.",
90         "Please type 'q' to continue",
91         NULL
92 };
93
94 static char *help_corrupt[] = {
95         "This patch appears to be corrupt",
96         "Please type 'q' to continue",
97         NULL
98 };
99
100 /* We can give one or two pages to display in the help window.
101  * The first is specific to the current context.  The second
102  * is optional and  may provide help in a more broad context.
103  */
104 static int help_window(char *page1[], char *page2[], int query)
105 {
106         int rows, cols;
107         int top, left;
108         int r, c;
109         int ch;
110         char **page = page1;
111         int line = 0;
112         int shift = 0;
113
114         getmaxyx(stdscr, rows, cols);
115
116         if (cols < 70) {
117                 left = 6;
118                 cols = cols-12;
119         } else {
120                 left = (cols-58)/2 - 1;
121                 cols = 58;
122         }
123
124         if (rows < 21) {
125                 top = 3;
126                 rows = rows - 6;
127         } else {
128                 top = (rows-15)/2 - 1;
129                 rows = 15;
130         }
131
132         /* Draw a border around the 'help' area */
133         (void)attrset(A_STANDOUT);
134         for (c = left; c < left+cols; c++) {
135                 mvaddch(top-1, c, '-');
136                 mvaddch(top+rows, c, '-');
137         }
138         for (r = top; r < top + rows ; r++) {
139                 mvaddch(r, left-1, '|');
140                 mvaddch(r, left+cols, '|');
141         }
142         mvaddch(top-1, left-1, '/');
143         mvaddch(top-1, left+cols,  '\\');
144         mvaddch(top+rows, left-1, '\\');
145         mvaddch(top+rows, left+cols, '/');
146         if (query) {
147                 mvaddstr(top-1, left + cols/2 - 4, "Question");
148                 mvaddstr(top+rows, left + cols/2 - 9,
149                          "Answer Y, N, or Q.");
150         } else {
151                 mvaddstr(top-1, left + cols/2 - 9,
152                          "HELP - 'q' to exit");
153                 mvaddstr(top+rows, left+cols/2 - 17,
154                          "Press SPACE for more, '?' for help");
155         }
156         (void)attrset(A_NORMAL);
157
158         while (1) {
159                 char **lnp = page + line;
160
161                 /* Draw as much of the page at the current offset
162                  * as fits.
163                  */
164                 for (r = 0; r < rows; r++) {
165                         char *ln = *lnp;
166                         int sh = shift;
167                         if (ln)
168                                 lnp++;
169                         else
170                                 ln = "";
171
172                         while (*ln && sh > 0) {
173                                 ln++;
174                                 sh--;
175                         }
176                         for (c = 0; c < cols; c++) {
177                                 int chr = *ln;
178                                 if (chr)
179                                         ln++;
180                                 else
181                                         chr = ' ';
182                                 mvaddch(top+r, left+c, chr);
183                         }
184                 }
185                 move(top+rows-1, left);
186                 ch = getch();
187
188                 switch (ch) {
189                 case 'C' - 64:
190                 case 'Q':
191                 case 'q':
192                         return -1;
193                         break;
194                 case 'Y':
195                 case 'y':
196                         if (query)
197                                 return 1;
198                         break;
199                 case 'N':
200                 case 'n':
201                         if (query)
202                                 return 0;
203                         break;
204
205                 case '?':
206                         if (page1 != help_help)
207                                 help_window(help_help, NULL, 0);
208                         break;
209                 case ' ':
210                 case '\r': /* page-down */
211                         for (r = 0; r < rows-2; r++)
212                                 if (page[line])
213                                         line++;
214                         if (!page[line] && !query) {
215                                 line = 0;
216                                 if (page == page1)
217                                         page = page2;
218                                 else
219                                         page = NULL;
220                                 if (page == NULL)
221                                         return -1;
222                         }
223                         break;
224
225                 case '\b': /* page up */
226                         if (line > 0) {
227                                 line -= (rows-2);
228                                 if (line < 0)
229                                         line = 0;
230                         } else {
231                                 if (page == page2)
232                                         page = page1;
233                                 else
234                                         page = page2;
235                                 if (page == NULL)
236                                         page = page1;
237                                 line = 0;
238                         }
239                         break;
240
241                 case KEY_LEFT:
242                         if (shift > 0)
243                                 shift--;
244                         break;
245                 case KEY_RIGHT:
246                         shift++;
247                         break;
248
249                 case KEY_UP:
250                         if (line > 0)
251                                 line--;
252                         break;
253                 case KEY_DOWN:
254                         if (page[line])
255                                 line++;
256                         break;
257                 }
258         }
259 }
260
261 static char *typenames[] = {
262         [End] = "End",
263         [Unmatched] = "Unmatched",
264         [Unchanged] = "Unchanged",
265         [Extraneous] = "Extraneous",
266         [Changed] = "Changed",
267         [Conflict] = "Conflict",
268         [AlreadyApplied] = "AlreadyApplied",
269 };
270
271 /* When we merge the original and the diff together we need
272  * to keep track of where everything came from.
273  * When we display the different views, we need to be able to
274  * select certain portions of the whole document.
275  * These flags are used to identify what is present, and to
276  * request different parts be extracted.  They also help
277  * guide choice of colour.
278  */
279 #define BEFORE  1
280 #define AFTER   2
281 #define ORIG    4
282 #define RESULT  8
283 #define CHANGES 16 /* A change is visible here,
284                     * so 2 streams need to be shown */
285 #define WIGGLED 32 /* a conflict that was successfully resolved */
286 #define CONFLICTED 64 /* a conflict that was not successfully resolved */
287
288 /* Displaying a Merge.
289  * The first step is to linearise the merge.  The merge in inherently
290  * parallel with before/after streams.  However much of the whole document
291  * is linear as normally much of the original in unchanged.
292  * All parallelism comes from the patch.  This normally produces two
293  * parallel stream, but in the case of a conflict can produce three.
294  * For browsing the merge we only ever show two alternates in-line.
295  * When there are three we use two panes with 1 or 2 alternates in each.
296  * So to linearise the two streams we find lines that are completely
297  * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
298  * a region where there are changes.  We include everything between
299  * these twice, in two separate passes.  The exact interpretation of the
300  * passes is handled at a higher level but will be one of:
301  *  original and result
302  *  before and after
303  *  original and after (for a conflict)
304  * This is all encoded in the 'struct merge'.  An array of these describes
305  * the whole document.
306  *
307  * At any position in the merge we can be in one of 3 states:
308  *  0: unchanged section
309  *  1: first pass
310  *  2: second pass
311  *
312  * So to walk a merge in display order we need a position in the merge,
313  * a current state, and when in a changed section, we need to know the
314  * bounds of that changed section.
315  * This is all encoded in 'struct mpos'.
316  *
317  * Each location may or may not be visible depending on certain
318  * display options.
319  *
320  * Also, some locations might be 'invalid' in that they don't need to be displayed.
321  * For example when the patch leaves a section of the original unchanged,
322  * we only need to see the original - the before/after sections are treated
323  * as invalid and are not displayed.
324  * The visibility of newlines is crucial and guides the display.  One line
325  * of displayed text is all the visible sections between two visible newlines.
326  *
327  * Counting lines is a bit tricky.  We only worry about line numbers in the
328  * original (stream 0) as these could compare with line numbers mentioned in
329  * patch chunks.
330  * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
331  * That way we don't get a full counted line until we see the first char after the
332  * newline, so '+' lines are counted with the previous line.
333  *
334  */
335 struct mp {
336         int m; /* merger index */
337         int s; /* stream 0,1,2 for a,b,c */
338         int o; /* offset in that stream */
339         int lineno; /* Counts newlines in stream 0
340                      * set lsb when see newline.
341                      * add one when not newline and lsb set
342                      */
343 };
344 struct mpos {
345         struct mp p, /* the current point (end of a line) */
346                 lo, /* eol for start of the current group */
347                 hi; /* eol for end of the current group */
348         int state; /*
349                     * 0 if on an unchanged (lo/hi not meaningful)
350                     * 1 if on the '-' of a diff,
351                     * 2 if on the '+' of a diff
352                     */
353 };
354
355 struct cursor {
356         struct mp pos;  /* where in the document we are (an element)  */
357         int offset;     /* which char in that element */
358         int target;     /* display column - or -1 if we are looking for 'pos' */
359         int col;        /* where we found pos or target */
360         int width;      /* Size of char, for moving to the right */
361         int alt;        /* Cursor is in alternate window */
362 };
363
364 /* used for checking location during search */
365 static int same_mp(struct mp a, struct mp b)
366 {
367         return a.m == b.m &&
368                 a.s == b.s &&
369                 a.o == b.o;
370 }
371 static int same_mpos(struct mpos a, struct mpos b)
372 {
373         return same_mp(a.p, b.p) &&
374                 (a.state == b.state || a.state == 0 || b.state == 0);
375 }
376
377 /* Check if a particular stream is meaningful in a particular merge
378  * section.  e.g. in an Unchanged section, only stream 0, the
379  * original, is meaningful.  This is used to avoid walking down
380  * pointless paths.
381  */
382 static int stream_valid(int s, enum mergetype type)
383 {
384         switch (type) {
385         case End:
386                 return 1;
387         case Unmatched:
388                 return s == 0;
389         case Unchanged:
390                 return s == 0;
391         case Extraneous:
392                 return s == 2;
393         case Changed:
394                 return s != 1;
395         case Conflict:
396                 return 1;
397         case AlreadyApplied:
398                 return 1;
399         }
400         return 0;
401 }
402
403 /*
404  * Advance the 'pos' in the current mergepos returning the next
405  * element (word).
406  * This walks the merges in sequence, and the streams within
407  * each merge.
408  */
409 static struct elmnt next_melmnt(struct mp *pos,
410                                 struct file fm, struct file fb, struct file fa,
411                                 struct merge *m)
412 {
413         pos->o++;
414         while (pos->m < 0 || m[pos->m].type != End) {
415                 int l = 0; /* Length remaining in current merge section */
416                 if (pos->m >= 0)
417                         switch (pos->s) {
418                         case 0:
419                                 l = m[pos->m].al;
420                                 break;
421                         case 1:
422                                 l = m[pos->m].bl;
423                                 break;
424                         case 2:
425                                 l = m[pos->m].cl;
426                                 break;
427                         }
428                 if (pos->o >= l) {
429                         /* Offset has reached length, choose new stream or
430                          * new merge */
431                         pos->o = 0;
432                         do {
433                                 pos->s++;
434                                 if (pos->s > 2) {
435                                         pos->s = 0;
436                                         pos->m++;
437                                 }
438                         } while (!stream_valid(pos->s, m[pos->m].oldtype));
439                 } else
440                         break;
441         }
442         if (pos->m == -1 || m[pos->m].type == End) {
443                 struct elmnt e;
444                 e.start = NULL; e.hash = 0; e.len = 0;
445                 return e;
446         }
447         switch (pos->s) {
448         default: /* keep compiler happy */
449         case 0:
450                 if (pos->lineno & 1)
451                         pos->lineno++;
452                 if (ends_line(fm.list[m[pos->m].a + pos->o]))
453                         pos->lineno++;
454                 return fm.list[m[pos->m].a + pos->o];
455         case 1: return fb.list[m[pos->m].b + pos->o];
456         case 2: return fa.list[m[pos->m].c + pos->o];
457         }
458 }
459
460 /* step current position.p backwards */
461 static struct elmnt prev_melmnt(struct mp *pos,
462                                 struct file fm, struct file fb, struct file fa,
463                                 struct merge *m)
464 {
465         if (pos->s == 0) {
466                 if (m[pos->m].a + pos->o < fm.elcnt &&
467                     ends_line(fm.list[m[pos->m].a + pos->o]))
468                         pos->lineno--;
469                 if (pos->lineno & 1)
470                         pos->lineno--;
471         }
472
473         pos->o--;
474         while (pos->m >= 0 && pos->o < 0) {
475                 do {
476                         pos->s--;
477                         if (pos->s < 0) {
478                                 pos->s = 2;
479                                 pos->m--;
480                         }
481                 } while (pos->m >= 0 &&
482                          !stream_valid(pos->s, m[pos->m].oldtype));
483                 if (pos->m >= 0) {
484                         switch (pos->s) {
485                         case 0:
486                                 pos->o = m[pos->m].al-1;
487                                 break;
488                         case 1:
489                                 pos->o = m[pos->m].bl-1;
490                                 break;
491                         case 2:
492                                 pos->o = m[pos->m].cl-1;
493                                 break;
494                         }
495                 }
496         }
497         if (pos->m < 0 || m[pos->m].type == End) {
498                 struct elmnt e;
499                 e.start = NULL; e.hash = 0; e.len = 0;
500                 return e;
501         }
502         switch (pos->s) {
503         default: /* keep compiler happy */
504         case 0: return fm.list[m[pos->m].a + pos->o];
505         case 1: return fb.list[m[pos->m].b + pos->o];
506         case 2: return fa.list[m[pos->m].c + pos->o];
507         }
508 }
509
510 /* 'visible' not only checks if this stream in this merge should be
511  * visible in this mode, but also chooses which colour/highlight to use
512  * to display it.
513  */
514 static int visible(int mode, struct merge *m, struct mpos *pos)
515 {
516         enum mergetype type;
517         int stream = pos->p.s;
518
519         if (mode == 0)
520                 return -1;
521         if (pos->p.m < 0)
522                 type = End;
523         else if (mode & RESULT)
524                 type = m[pos->p.m].type;
525         else
526                 type = m[pos->p.m].oldtype;
527         /* mode can be any combination of ORIG RESULT BEFORE AFTER */
528         switch (type) {
529         case End: /* The END is always visible */
530                 return A_NORMAL;
531         case Unmatched: /* Visible in ORIG and RESULT */
532                 if (mode & (ORIG|RESULT))
533                         return a_unmatched;
534                 break;
535         case Unchanged: /* visible everywhere, but only show stream 0 */
536                 if (stream == 0)
537                         return a_common;
538                 break;
539         case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
540                 if ((mode & (BEFORE|AFTER))
541                     && stream == 2)
542                         return a_extra;
543                 break;
544         case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
545                 if (stream == 0 &&
546                     (mode & (ORIG|BEFORE)))
547                         return a_delete;
548                 if (stream == 2 &&
549                     (mode & (RESULT|AFTER)))
550                         return a_added;
551                 break;
552         case Conflict:
553                 switch (stream) {
554                 case 0:
555                         if (mode & ORIG)
556                                 return a_unmatched | A_REVERSE;
557                         break;
558                 case 1:
559                         if (mode & BEFORE)
560                                 return a_extra | A_UNDERLINE;
561                         break;
562                 case 2:
563                         if (mode & (AFTER|RESULT))
564                                 return a_added | A_UNDERLINE;
565                         break;
566                 }
567                 break;
568         case AlreadyApplied:
569                 switch (stream) {
570                 case 0:
571                         if (mode & (ORIG|RESULT))
572                                 return a_already;
573                         break;
574                 case 1:
575                         if (mode & BEFORE)
576                                 return a_delete | A_UNDERLINE;
577                         break;
578                 case 2:
579                         if (mode & AFTER)
580                                 return a_added | A_UNDERLINE;
581                         break;
582                 }
583                 break;
584         }
585         return -1;
586 }
587
588 /* checkline creates a summary of the sort of changes that
589  * are in a line, returning an "or" of
590  *  CHANGES
591  *  WIGGLED
592  *  CONFLICTED
593  */
594 static int check_line(struct mpos pos, struct file fm, struct file fb,
595                       struct file fa,
596                       struct merge *m, int mode)
597 {
598         int rv = 0;
599         struct elmnt e;
600         int unmatched = 0;
601
602         if (pos.p.m < 0)
603                 return 0;
604         do {
605                 int type = m[pos.p.m].oldtype;
606                 if (mode & RESULT)
607                         type = m[pos.p.m].type;
608                 if (type == Changed)
609                         rv |= CHANGES;
610                 else if (type == Conflict) {
611                         rv |= CONFLICTED | CHANGES;
612                 } else if (type == AlreadyApplied) {
613                         rv |= CONFLICTED;
614                         if (mode & (BEFORE|AFTER))
615                                 rv |= CHANGES;
616                 } else if (type == Extraneous) {
617                         if (fb.list[m[pos.p.m].b].start[0] == '\0') {
618                                 /* hunk headers don't count as wiggles
619                                  * and nothing before a hunk header
620                                  * can possibly be part of this 'line' */
621                                 e.start = NULL;
622                                 break;
623                         } else
624                                 rv |= WIGGLED;
625                 } else if (type == Unmatched)
626                         unmatched = 1;
627
628                 if (m[pos.p.m].in_conflict > 1)
629                         rv |= CONFLICTED | CHANGES;
630                 if (m[pos.p.m].in_conflict == 1 &&
631                     (pos.p.o < m[pos.p.m].lo ||
632                      pos.p.o > m[pos.p.m].hi))
633                         rv |= CONFLICTED | CHANGES;
634                 e = prev_melmnt(&pos.p, fm, fb, fa, m);
635         } while (e.start != NULL &&
636                  (!ends_line(e)
637                   || visible(mode, m, &pos) == -1));
638         /* This is a bit of a hack...  If the end-of-line just
639          * before this line was changed, then quite possibly this
640          * line is part of a change too.  This is particularly important
641          * when --ignore-blanks is in effect as newlines are not separate
642          * from other words.  It could be that this test needs to be
643          * strengthened when I have examined more cases.
644          */
645         if (e.start && m[pos.p.m].oldtype == Changed)
646                 rv |= CHANGES;
647
648         if (unmatched && (rv & CHANGES))
649                 rv |= WIGGLED;
650         return rv;
651 }
652
653 /* Find the next line in the merge which is visible.
654  * If we hit the end of a conflicted set during pass-1
655  * we rewind for pass-2.
656  * 'mode' tells which bits we want to see, possible one of
657  * the 4 parts (before/after/orig/result) or one of the pairs
658  * before+after or orig+result.
659  */
660 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
661                        struct file fa,
662                        struct merge *m, int mode)
663 {
664         int mask;
665         do {
666                 struct mp prv;
667                 int mode2;
668
669                 prv = pos->p;
670                 while (1) {
671                         struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
672                         if (e.start == NULL)
673                                 break;
674                         if (ends_line(e) &&
675                             visible(mode, m, pos) >= 0)
676                                 break;
677                 }
678                 mode2 = check_line(*pos, fm, fb, fa, m, mode);
679
680                 if ((mode2 & CHANGES) && pos->state == 0) {
681                         /* Just entered a diff-set */
682                         pos->lo = pos->p;
683                         pos->state = 1;
684                 } else if (!(mode2 & CHANGES) && pos->state) {
685                         /* Come to the end of a diff-set */
686                         switch (pos->state) {
687                         case 1:
688                                 /* Need to record the end */
689                                 pos->hi = prv;
690                                 /* time for another pass */
691                                 pos->p = pos->lo;
692                                 pos->state++;
693                                 break;
694                         case 2:
695                                 /* finished final pass */
696                                 pos->state = 0;
697                                 break;
698                         }
699                 }
700                 mask = ORIG|RESULT|BEFORE|AFTER;
701                 switch (pos->state) {
702                 case 1:
703                         mask &= ~(RESULT|AFTER);
704                         break;
705                 case 2:
706                         mask &= ~(ORIG|BEFORE);
707                         break;
708                 }
709         } while (visible(mode&mask, m, pos) < 0);
710
711 }
712
713 /* Move to previous line - simply the reverse of next_mline */
714 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
715                        struct file fa,
716                        struct merge *m, int mode)
717 {
718         int mask;
719         do {
720                 struct mp prv;
721                 int mode2;
722
723                 prv = pos->p;
724                 if (pos->p.m < 0)
725                         return;
726                 while (1) {
727                         struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
728                         if (e.start == NULL)
729                                 break;
730                         if (ends_line(e) &&
731                             visible(mode, m, pos) >= 0)
732                                 break;
733                 }
734                 mode2 = check_line(*pos, fm, fb, fa, m, mode);
735
736                 if ((mode2 & CHANGES) && pos->state == 0) {
737                         /* Just entered a diff-set */
738                         pos->hi = pos->p;
739                         pos->state = 2;
740                 } else if (!(mode2 & CHANGES) && pos->state) {
741                         /* Come to the end (start) of a diff-set */
742                         switch (pos->state) {
743                         case 1:
744                                 /* finished final pass */
745                                 pos->state = 0;
746                                 break;
747                         case 2:
748                                 /* Need to record the start */
749                                 pos->lo = prv;
750                                 /* time for another pass */
751                                 pos->p = pos->hi;
752                                 pos->state--;
753                                 break;
754                         }
755                 }
756                 mask = ORIG|RESULT|BEFORE|AFTER;
757                 switch (pos->state) {
758                 case 1:
759                         mask &= ~(RESULT|AFTER);
760                         break;
761                 case 2:
762                         mask &= ~(ORIG|BEFORE);
763                         break;
764                 }
765         } while (visible(mode&mask, m, pos) < 0);
766 }
767
768 /* blank a whole row of display */
769 static void blank(int row, int start, int cols, unsigned int attr)
770 {
771         (void)attrset(attr);
772         move(row, start);
773         while (cols-- > 0)
774                 addch(' ');
775 }
776
777 /* search of a string on one display line.  If found, update the
778  * cursor.
779  */
780
781 static int mcontains(struct mpos pos,
782                      struct file fm, struct file fb, struct file fa,
783                      struct merge *m,
784                      int mode, char *search, struct cursor *curs,
785                      int dir, int ignore_case)
786 {
787         /* See if any of the files, between start of this line and here,
788          * contain the search string.
789          * However this is modified by dir:
790          *  -2: find last match *before* curs
791          *  -1: find last match at-or-before curs
792          *   1: find first match at-or-after curs
793          *   2: find first match *after* curs
794          *
795          * We only test for equality with curs, so if it is on a different
796          * line it will not be found and everything is before/after.
797          * As we search from end-of-line to start we find the last
798          * match first.
799          * For a forward search, we stop when we find curs.
800          * For a backward search, we forget anything found when we find curs.
801          */
802         struct elmnt e;
803         int found = 0;
804         struct mp mp;
805         int o = 0;
806         int len = strlen(search);
807
808         do {
809                 e = prev_melmnt(&pos.p, fm, fb, fa, m);
810                 if (e.start && e.start[0]) {
811                         int i;
812                         int curs_i;
813                         if (same_mp(pos.p, curs->pos))
814                                 curs_i = curs->offset;
815                         else
816                                 curs_i = -1;
817                         for (i = e.len-1; i >= 0; i--) {
818                                 if (i == curs_i && dir == -1)
819                                         /* next match is the one we want */
820                                         found = 0;
821                                 if (i == curs_i && dir == 2)
822                                         /* future matches not accepted */
823                                         goto break_while;
824                                 if ((!found || dir > 0) &&
825                                     (ignore_case ? strncasecmp : strncmp)
826                                     (e.start+i, search, len) == 0) {
827                                         mp = pos.p;
828                                         o = i;
829                                         found = 1;
830                                 }
831                                 if (i == curs_i && dir == -2)
832                                         /* next match is the one we want */
833                                         found = 0;
834                                 if (i == curs_i && dir == 1)
835                                         /* future matches not accepted */
836                                         goto break_while;
837                         }
838                 }
839         } while (e.start != NULL &&
840                  (!ends_line(e)
841                   || visible(mode, m, &pos) == -1));
842 break_while:
843         if (found) {
844                 curs->pos = mp;
845                 curs->offset = o;
846         }
847         return found;
848 }
849
850 /* Drawing the display window.
851  * There are 7 different ways we can display the data, each
852  * of which can be configured by a keystroke:
853  *  o   original - just show the original file with no changes, but still
854  *                 with highlights of what is changed or unmatched
855  *  r   result   - show just the result of the merge.  Conflicts just show
856  *                 the original, not the before/after options
857  *  b   before   - show the 'before' stream of the patch
858  *  a   after    - show the 'after' stream of the patch
859  *  d   diff     - show just the patch, both before and after
860  *  m   merge    - show the full merge with -+ sections for changes.
861  *                 If point is in a wiggled or conflicted section the
862  *                 window is split horizontally and the diff is shown
863  *                 in the bottom window
864  *  | sidebyside - two panes, left and right.  Left holds the merge,
865  *                 right holds the diff.  In the case of a conflict,
866  *                 left holds orig/after, right holds before/after
867  *
868  * The horizontal split for 'merge' mode is managed as follows.
869  * - The window is split when we first visit a line that contains
870  *   a wiggle or a conflict, and the second pane is removed when
871  *   we next visit a line that contains no changes (is fully Unchanged).
872  * - to display the second pane, we find a visible end-of-line in the
873  *   (BEFORE|AFTER) mode at-or-before the current end-of-line and
874  *   the we centre that line.
875  * - We need to rewind to an unchanged section, and wind forward again
876  *   to make sure that 'lo' and 'hi' are set properly.
877  * - every time we move, we redraw the second pane (see how that goes).
878  */
879
880 /* draw_mside draws one text line or, in the case of sidebyside, one side
881  * of a textline.
882  * The 'mode' tells us what to draw via the 'visible()' function.
883  * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
884  * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
885  * The desired cursor position is given in 'target' the actual end
886  * cursor position (allowing e.g. for tabs) is returned in *colp.
887  */
888 static void draw_mside(int mode, int row, int offset, int start, int cols,
889                        struct file fm, struct file fb, struct file fa,
890                        struct merge *m,
891                        struct mpos pos,
892                        struct cursor *curs)
893 {
894         struct elmnt e;
895         int col = 0;
896         char tag;
897         unsigned int tag_attr;
898         int changed = 0;
899
900         switch (pos.state) {
901         default: /* keep compiler happy */
902         case 0: /* unchanged line */
903                 tag = ' ';
904                 tag_attr = A_NORMAL;
905                 break;
906         case 1: /* 'before' text */
907                 tag = '-';
908                 tag_attr = a_delete;
909                 if ((mode & ORIG) && (mode & CONFLICTED)) {
910                         tag = '|';
911                         tag_attr = a_delete | A_REVERSE;
912                 }
913                 mode &= (ORIG|BEFORE);
914                 break;
915         case 2: /* the 'after' part */
916                 tag = '+';
917                 tag_attr = a_added;
918                 mode &= (AFTER|RESULT);
919                 break;
920         }
921
922         if (visible(mode, m, &pos) < 0) {
923                 /* Not visible, just draw a blank */
924                 blank(row, offset, cols, a_void);
925                 if (curs) {
926                         curs->width = -1;
927                         curs->col = 0;
928                         curs->pos = pos.p;
929                         curs->offset = 0;
930                 }
931                 return;
932         }
933
934         (void)attrset(tag_attr);
935         mvaddch(row, offset, tag);
936         offset++;
937         cols--;
938         (void)attrset(A_NORMAL);
939
940         if (check_line(pos, fm, fb, fa, m, mode))
941                 changed = 1;
942
943         /* find previous visible newline, or start of file */
944         do
945                 e = prev_melmnt(&pos.p, fm, fb, fa, m);
946         while (e.start != NULL &&
947                (!ends_line(e) ||
948                 visible(mode, m, &pos) == -1));
949
950         while (1) {
951                 unsigned char *c;
952                 unsigned int attr;
953                 int highlight_space;
954                 int l;
955                 e = next_melmnt(&pos.p, fm, fb, fa, m);
956                 if (!e.start)
957                         break;
958
959                 if (visible(mode, m, &pos) == -1)
960                         continue;
961                 if (e.start[0] == 0)
962                         break;
963                 c = (unsigned char *)e.start - e.prefix;
964                 highlight_space = 0;
965                 attr = visible(mode, m, &pos);
966                 if ((attr == a_unmatched || attr == a_extra) &&
967                     changed)
968                         /* Only highlight spaces if there is a tab nearby */
969                         for (l = 0; l < e.plen + e.prefix; l++)
970                                 if (c[l] == '\t')
971                                         highlight_space = 1;
972                 if (!highlight_space && (c[0] == ' ' || c[0] == '\t')) {
973                         /* always highlight space/tab at end-of-line */
974                         struct mp nxt = pos.p;
975                         struct elmnt nxte = next_melmnt(&nxt, fm, fb, fa, m);
976                         if (nxte.start[0] == '\n')
977                                 highlight_space = 1;
978                 }
979                 for (l = 0; l < e.plen + e.prefix; l++) {
980                         int scol = col;
981                         if (*c == '\n')
982                                 break;
983                         (void)attrset(attr);
984                         if (*c >= ' ' && *c != 0x7f) {
985                                 if (highlight_space)
986                                         (void)attrset(attr|A_REVERSE);
987                                 if (col >= start && col < start+cols)
988                                         mvaddch(row, col-start+offset, *c);
989                                 col++;
990                         } else if (*c == '\t') {
991                                 if (highlight_space)
992                                         (void)attrset(attr|A_UNDERLINE);
993                                 do {
994                                         if (col >= start && col < start+cols) {
995                                                 mvaddch(row, col-start+offset, ' ');
996                                         } col++;
997                                 } while ((col&7) != 0);
998                         } else {
999                                 if (col >= start && col < start+cols)
1000                                         mvaddch(row, col-start+offset, '?');
1001                                 col++;
1002                         }
1003                         if (curs) {
1004                                 if (curs->target >= 0) {
1005                                         if (curs->target < col) {
1006                                                 /* Found target column */
1007                                                 curs->pos = pos.p;
1008                                                 curs->offset = l;
1009                                                 curs->col = scol;
1010                                                 if (scol >= start + cols)
1011                                                         /* Didn't appear on screen */
1012                                                         curs->width = 0;
1013                                                 else
1014                                                         curs->width = col - scol;
1015                                                 curs = NULL;
1016                                         }
1017                                 } else if (l == curs->offset &&
1018                                            same_mp(pos.p, curs->pos)) {
1019                                         /* Found the pos */
1020                                         curs->target = scol;
1021                                         curs->col = scol;
1022                                         if (scol >= start + cols)
1023                                                 /* Didn't appear on screen */
1024                                                 curs->width = 0;
1025                                         else
1026                                                 curs->width = col - scol;
1027                                         curs = NULL;
1028                                 }
1029                         }
1030                         c++;
1031                 }
1032                 if ((ends_line(e)
1033                      && visible(mode, m, &pos) != -1))
1034                         break;
1035         }
1036
1037         /* We have reached the end of visible line, or end of file */
1038         if (curs) {
1039                 curs->col = col;
1040                 if (col >= start + cols)
1041                         curs->width = 0;
1042                 else
1043                         curs->width = -1; /* end of line */
1044                 if (curs->target >= 0) {
1045                         curs->pos = pos.p;
1046                         curs->offset = 0;
1047                 } else if (same_mp(pos.p, curs->pos))
1048                         curs->target = col;
1049         }
1050         if (col < start)
1051                 col = start;
1052         if (e.start && e.start[0] == 0) {
1053                 char b[100];
1054                 struct elmnt e1;
1055                 int A, B, C, D, E, F;
1056                 e1 = fb.list[m[pos.p.m].b + pos.p.o];
1057                 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
1058                 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
1059                 snprintf(b, sizeof(b), "@@ -%d,%d +%d,%d @@%s", B, C, E, F, e1.start+18);
1060                 (void)attrset(a_sep);
1061
1062                 mvaddstr(row, col-start+offset, b);
1063                 col += strlen(b);
1064         }
1065         blank(row, col-start+offset, start+cols-col,
1066               e.start
1067               ? (unsigned)visible(mode, m, &pos)
1068               : A_NORMAL);
1069 }
1070
1071 /* Draw either 1 or 2 sides depending on the mode. */
1072
1073 static void draw_mline(int mode, int row, int start, int cols,
1074                        struct file fm, struct file fb, struct file fa,
1075                        struct merge *m,
1076                        struct mpos pos,
1077                        struct cursor *curs)
1078 {
1079         /*
1080          * Draw the left and right images of this line
1081          * One side might be a_blank depending on the
1082          * visibility of this newline
1083          */
1084         int lcols, rcols;
1085
1086         mode |= check_line(pos, fm, fb, fa, m, mode);
1087
1088         if ((mode & (BEFORE|AFTER)) &&
1089             (mode & (ORIG|RESULT))) {
1090
1091                 lcols = (cols-1)/2;
1092                 rcols = cols - lcols - 1;
1093
1094                 (void)attrset(A_STANDOUT);
1095                 mvaddch(row, lcols, '|');
1096
1097                 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1098                            fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1099
1100                 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1101                            fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1102         } else
1103                 draw_mside(mode, row, 0, start, cols,
1104                            fm, fb, fa, m, pos, curs);
1105 }
1106
1107 static char *merge_help[] = {
1108         "This view shows the merge of the patch with the",
1109         "original file.  It is like a full-context diff showing",
1110         "removed lines with a '-' prefix and added lines with a",
1111         "'+' prefix.",
1112         "In cases where a patch chunk could not be successfully",
1113         "applied, the original text is prefixed with a '|', and",
1114         "the text that the patch wanted to add is prefixed with",
1115         "a '+'.",
1116         "When the cursor is over such a conflict, or over a chunk",
1117         "which required wiggling to apply (i.e. there was unmatched",
1118         "text in the original, or extraneous unchanged text in",
1119         "the patch), the terminal is split and the bottom pane is",
1120         "use to display the part of the patch that applied to",
1121         "this section of the original.  This allows you to confirm",
1122         "that a wiggled patch applied correctly, and to see",
1123         "why there was a conflict",
1124         NULL
1125 };
1126 static char *diff_help[] = {
1127         "This is the 'diff' or 'patch' view.  It shows",
1128         "only the patch that is being applied without the",
1129         "original to which it is being applied.",
1130         "Underlined text indicates parts of the patch which",
1131         "resulted in a conflict when applied to the",
1132         "original.",
1133         NULL
1134 };
1135 static char *orig_help[] = {
1136         "This is the 'original' view which simply shows",
1137         "the original file before applying the patch.",
1138         "Sections of code that would be changed by the patch",
1139         "are highlighted in red.",
1140         NULL
1141 };
1142 static char *result_help[] = {
1143         "This is the 'result' view which shows just the",
1144         "result of applying the patch.  When a conflict",
1145         "occurred this view does not show the full conflict",
1146         "but only the 'after' part of the patch.  To see",
1147         "the full conflict, use the 'merge' or 'sidebyside'",
1148         "views.",
1149         NULL
1150 };
1151 static char *before_help[] = {
1152         "This view shows the 'before' section of a patch.",
1153         "It allows the expected match text to be seen uncluttered",
1154         "by text that is meant to replaced it.",
1155         "Red text is text that will be removed by the patch",
1156         NULL
1157 };
1158 static char *after_help[] = {
1159         "This view shows the 'after' section of a patch.",
1160         "It allows the intended result to be seen uncluttered",
1161         "by text that was meant to be matched and replaced.",
1162         "Green text is text that was added by the patch - it",
1163         "was not present in the 'before' part of the patch",
1164         NULL
1165 };
1166 static char *sidebyside_help[] = {
1167         "This is the Side By Side view of a patched file.",
1168         "The left side shows the original and the result.",
1169         "The right side shows the patch which was applied",
1170         "and lines up with the original/result as much as",
1171         "possible.",
1172         "",
1173         "Where one side has no line which matches the",
1174         "other side it is displayed as a solid colour in the",
1175         "yellow family (depending on your terminal window).",
1176         NULL
1177 };
1178 static char *merge_window_help[] = {
1179         "  Highlight Colours and Keystroke commands",
1180         "",
1181         "In all different views of a merge, highlight colours",
1182         "are used to show which parts of lines were added,",
1183         "removed, already changed, unchanged or in conflict.",
1184         "Colours and their use are:",
1185         " normal              unchanged text",
1186         " red                 text that was removed or changed",
1187         " green               text that was added or the result",
1188         "                     of a change",
1189         " yellow background   used in side-by-side for a line",
1190         "                     which has no match on the other",
1191         "                     side",
1192         " blue                text in the original which did not",
1193         "                     match anything in the patch",
1194         " cyan                text in the patch which did not",
1195         "                     match anything in the original",
1196         " cyan background     already changed text: the result",
1197         "                     of the patch matches the original",
1198         " underline           remove or added text can also be",
1199         "                     underlined indicating that it",
1200         "                     was involved in a conflict",
1201         "",
1202         "While viewing a merge various keystroke commands can",
1203         "be used to move around and change the view.  Basic",
1204         "movement commands from both 'vi' and 'emacs' are",
1205         "available:",
1206         "",
1207         " p control-p k UP    Move to previous line",
1208         " n control-n j DOWN  Move to next line",
1209         " l LEFT              Move one char to right",
1210         " h RIGHT             Move one char to left",
1211         " / control-s         Enter incremental search mode",
1212         " control-r           Enter reverse-search mode",
1213         " control-g           Search again",
1214         " ?                   Display help message",
1215         " ESC-<  0-G          Go to start of file",
1216         " ESC->  G            Go to end of file",
1217         " q                   Return to list of files or exit",
1218         " S                   Arrange for merge to be saved on exit",
1219         " control-C           Disable auto-save-on-exit",
1220         " control-L           recenter current line",
1221         " control-V SPACE     page down",
1222         " ESC-v   BACKSPC     page up",
1223         " N                   go to next patch chunk",
1224         " P                   go to previous patch chunk",
1225         " C                   go to next conflicted chunk",
1226         " C-X-o   O           move cursor to alternate pane",
1227         " ^ control-A         go to start of line",
1228         " $ control-E         go to end of line",
1229         "",
1230         " a                   display 'after' view",
1231         " b                   display 'before' view",
1232         " o                   display 'original' view",
1233         " r                   display 'result' view",
1234         " d                   display 'diff' or 'patch' view",
1235         " m                   display 'merge' view",
1236         " |                   display side-by-side view",
1237         "",
1238         " I                   toggle whether spaces are ignored",
1239         "                     when matching text.",
1240         " x                   toggle ignoring of current Changed,",
1241         "                     Conflict, or Unmatched item",
1242         " c                   toggle accepting of result of conflict",
1243         " X                   Revert 'c' and 'x' changes on this line",
1244         " v                   Save the current merge and run the",
1245         "                     default editor on the file.",
1246         NULL
1247 };
1248 static char *save_query[] = {
1249         "",
1250         "You have modified the merge.",
1251         "Would you like to save it?",
1252         " Y = save the modified merge",
1253         " N = discard modifications, don't save",
1254         " Q = return to viewing modified merge",
1255         NULL
1256 };
1257
1258 static char *toggle_ignore[] = {
1259         "",
1260         "You have modified the merge.",
1261         "Toggling ignoring of spaces will discard changes.",
1262         "Do you want to proceed?",
1263         " Y = discard changes and toggle ignoring of spaces",
1264         " N = keep changes, don't toggle",
1265         NULL
1266 };
1267
1268 static void do_edit(char *file, int line)
1269 {
1270         char *ed = getenv("VISUAL");
1271         char linebuf[20];
1272         if (!ed)
1273                 ed = getenv("EDITOR");
1274         if (!ed)
1275                 ed = "/usr/bin/edit";
1276         snprintf(linebuf, sizeof(linebuf), "+%d", line);
1277         switch(fork()) {
1278         case 0:
1279                 execlp(ed, ed, linebuf, file, NULL);
1280                 exit(2);
1281         case -1:
1282                 break;
1283         default:
1284                 wait(NULL);
1285         }
1286 }
1287
1288 static void *memdup(void *a, int len)
1289 {
1290         char *r = malloc(len);
1291         memcpy(r, a, len);
1292         return r;
1293 }
1294
1295
1296 static int save_merge(struct file a, struct file b, struct file c,
1297                       struct merge *merger, char *file, int backup)
1298 {
1299         char *replacename = wiggle_xmalloc(strlen(file) + 20);
1300         char *orignew = wiggle_xmalloc(strlen(file) + 20);
1301         int fd;
1302         FILE *outfile;
1303         int err = 0;
1304         int lineno = 0;
1305         strcpy(replacename, file);
1306         strcat(replacename, "XXXXXX");
1307         strcpy(orignew, file);
1308         strcat(orignew, ".porig");
1309
1310         fd = mkstemp(replacename);
1311         if (fd < 0) {
1312                 err = -1;
1313                 goto out;
1314         }
1315         outfile = fdopen(fd, "w");
1316         lineno = wiggle_print_merge(outfile, &a, &b, &c, 0, merger,
1317                                     NULL, 0, 0);
1318         fclose(outfile);
1319         if (backup && rename(file, orignew) != 0)
1320                 err = -2;
1321         else if (rename(replacename, file) != 0)
1322                 err = -2;
1323
1324 out:
1325         free(replacename);
1326         free(orignew);
1327         return err < 0 ? err : lineno;
1328 }
1329
1330 static int save_tmp_merge(struct file a, struct file b, struct file c,
1331                           struct merge *merger, char **filep,
1332                           struct merge *mpos, int streampos, int offsetpos)
1333 {
1334         int fd;
1335         FILE *outfile;
1336         char *dir, *fname;
1337         int lineno;
1338         int suffix = 0;
1339
1340         if (!*filep) {
1341                 dir = getenv("TMPDIR");
1342                 if (!dir)
1343                         dir = "/tmp";
1344
1345                 asprintf(&fname, "%s/wiggle-tmp-XXXXXX", dir);
1346         } else {
1347                 char *base;
1348                 dir = *filep;
1349                 base = strrchr(dir, '/');
1350                 if (base)
1351                         base++;
1352                 else
1353                         base = dir;
1354                 asprintf(&fname, "%.*stmp-XXXXXX-%s", (int)(base-dir), dir, base);
1355                 suffix = strlen(base)+1;
1356         }
1357         fd = mkstemps(fname, suffix);
1358
1359         if (fd < 0) {
1360                 free(fname);
1361                 *filep = NULL;
1362                 return -1;
1363         }
1364         outfile = fdopen(fd, "w");
1365         lineno = wiggle_print_merge(outfile, &a, &b, &c, 0, merger,
1366                                     mpos, streampos, offsetpos);
1367         fclose(outfile);
1368         *filep = fname;
1369         return lineno;
1370 }
1371
1372 static int merge_window(struct plist *p, FILE *f, int reverse, int replace,
1373                         int selftest, int ignore_blanks, int just_diff, int backup)
1374 {
1375         /* Display the merge window in one of the selectable modes,
1376          * starting with the 'merge' mode.
1377          *
1378          * Newlines are the key to display.
1379          * 'pos' is always a visible newline (or eof).
1380          * In sidebyside mode it might only be visible on one side,
1381          * in which case the other side will be blank.
1382          * Where the newline is visible, we rewind the previous visible
1383          * newline visible and display the stuff in between
1384          *
1385          * A 'position' is a struct mpos
1386          */
1387
1388         struct stream sm, sb, sa, sp; /* main, before, after, patch */
1389         struct file fm, fb, fa;
1390         struct csl *csl1, *csl2;
1391         struct ci ci;
1392         int ch; /* count of chunks */
1393         /* Always refresh the current line.
1394          * If refresh == 1, refresh all lines.  If == 2, clear first
1395          */
1396         int refresh = 2;
1397         int rows = 0, cols = 0;
1398         int splitrow = -1; /* screen row for split - diff appears below */
1399         int lastrow = 0; /* end of screen, or just above 'splitrow' */
1400         int i, c, cswitch;
1401         MEVENT mevent;
1402         int mode = just_diff ? (BEFORE|AFTER) : (ORIG|RESULT);
1403         int mmode = mode; /* Mode for moving - used when in 'other' pane */
1404         char *modename = just_diff ? "diff" : "merge";
1405         char **modehelp = just_diff ? diff_help : merge_help;
1406         char *mesg = NULL;
1407
1408         int row, start = 0;
1409         int trow; /* screen-row while searching.  If we cannot find,
1410                    * we forget this number */
1411         struct cursor curs;
1412         struct mpos pos;  /* current point */
1413         struct mpos tpos, /* temp point while drawing lines above and below pos */
1414                 toppos,   /* pos at top of screen - for page-up */
1415                 botpos;   /* pos at bottom of screen - for page-down */
1416         struct mpos vispos;
1417         int botrow = 0;
1418         int meta = 0,     /* mode for multi-key commands- SEARCH or META */
1419                 tmeta;
1420         int num = -1,     /* numeric arg being typed. */
1421                 tnum;
1422         int lineno;
1423         int changes = 0; /* If any edits have been made to the merge */
1424         int answer;     /* answer to 'save changes?' question */
1425         char *tempname;
1426         struct elmnt e;
1427         char search[80];  /* string we are searching for */
1428         unsigned int searchlen = 0;
1429         int search_notfound = 0;
1430         int searchdir = 0;
1431         /* ignore_case:
1432          *  0 == no
1433          *  1 == no because there are upper-case chars
1434          *  2 == yes as there are no upper-case chars
1435          *  3 == yes
1436          */
1437         int ignore_case = 2;
1438         /* We record all the places we find so 'backspace'
1439          * can easily return to the previous one
1440          */
1441         struct search_anchor {
1442                 struct search_anchor *next;
1443                 struct mpos pos;
1444                 struct cursor curs;
1445                 int notfound;
1446                 int row, start;
1447                 unsigned int searchlen;
1448         } *anchor = NULL;
1449
1450         #define free_stuff(none) \
1451         do { \
1452                 free(fm.list); \
1453                 free(fb.list); \
1454                 free(fa.list); \
1455                 free(csl1); \
1456                 free(csl2); \
1457                 free(ci.merger); \
1458         } while(0)
1459
1460         #define find_line(ln) \
1461         do { \
1462                 pos.p.m = 0; /* merge node */ \
1463                 pos.p.s = 0; /* stream number */ \
1464                 pos.p.o = -1; /* offset */ \
1465                 pos.p.lineno = 1; \
1466                 pos.state = 0; \
1467                 memset(&curs, 0, sizeof(curs)); \
1468                 do \
1469                         next_mline(&pos, fm, fb, fa, ci.merger, mode); \
1470                 while (pos.p.lineno < ln && ci.merger[pos.p.m].type != End); \
1471         } while(0)
1472
1473         #define prepare_merge(ch) \
1474         do { \
1475                 /* FIXME check for errors in the stream */ \
1476                 fm = wiggle_split_stream(sm, ByWord | ignore_blanks); \
1477                 fb = wiggle_split_stream(sb, ByWord | ignore_blanks); \
1478                 fa = wiggle_split_stream(sa, ByWord | ignore_blanks); \
1479 \
1480                 if (ch && !just_diff) \
1481                         csl1 = wiggle_pdiff(fm, fb, ch); \
1482                 else \
1483                         csl1 = wiggle_diff(fm, fb, 1); \
1484                 csl2 = wiggle_diff_patch(fb, fa, 1); \
1485 \
1486                 ci = wiggle_make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0); \
1487                 for (i = 0; ci.merger[i].type != End; i++) \
1488                         ci.merger[i].oldtype = ci.merger[i].type; \
1489         } while(0)
1490
1491         if (selftest) {
1492                 intr_kills = 1;
1493                 selftest = 1;
1494         }
1495
1496         if (f == NULL) {
1497                 if (!p->is_merge) {
1498                         /* three separate files */
1499                         sb = wiggle_load_file(p->before);
1500                         sa = wiggle_load_file(p->after);
1501                         if (just_diff)
1502                                 sm = sb;
1503                         else
1504                                 sm = wiggle_load_file(p->file);
1505                 } else {
1506                         /* One merge file */
1507                         sp = wiggle_load_file(p->file);
1508                         if (reverse)
1509                                 wiggle_split_merge(sp, &sm, &sa, &sb);
1510                         else
1511                                 wiggle_split_merge(sp, &sm, &sb, &sa);
1512                         free(sp.body);
1513                 }
1514                 ch = 0;
1515         } else {
1516                 sp = wiggle_load_segment(f, p->start, p->end);
1517                 if (p->is_merge) {
1518                         if (reverse)
1519                                 wiggle_split_merge(sp, &sm, &sa, &sb);
1520                         else
1521                                 wiggle_split_merge(sp, &sm, &sb, &sa);
1522                         ch = 0;
1523                 } else {
1524                         if (reverse)
1525                                 ch = wiggle_split_patch(sp, &sa, &sb);
1526                         else
1527                                 ch = wiggle_split_patch(sp, &sb, &sa);
1528                         if (just_diff)
1529                                 sm = sb;
1530                         else
1531                                 sm = wiggle_load_file(p->file);
1532                 }
1533                 free(sp.body);
1534         }
1535         if (!sm.body || !sb.body || !sa.body) {
1536                 if (!just_diff)
1537                         free(sm.body);
1538                 free(sb.body);
1539                 free(sa.body);
1540                 term_init(1);
1541                 if (!sm.body)
1542                         help_window(help_missing, NULL, 0);
1543                 else
1544                         help_window(help_corrupt, NULL, 0);
1545                 endwin();
1546                 return 0;
1547         }
1548         prepare_merge(ch);
1549         term_init(!selftest);
1550
1551         row = 1;
1552         find_line(1);
1553
1554         while (1) {
1555                 unsigned int next;
1556                 if (refresh >= 2) {
1557                         clear();
1558                         refresh = 1;
1559                 }
1560                 if (row < 1 || row >= lastrow)
1561                         refresh = 1;
1562                 if (curs.alt)
1563                         refresh = 1;
1564
1565                 if (mode == (ORIG|RESULT)) {
1566                         int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1567                         if (cmode & (WIGGLED | CONFLICTED)) {
1568                                 if (splitrow < 0) {
1569                                         splitrow = (rows+1)/2;
1570                                         lastrow = splitrow - 1;
1571                                         refresh = 1;
1572                                 }
1573                         } else if (!curs.alt && splitrow >= 0) {
1574                                 splitrow = -1;
1575                                 lastrow = rows-1;
1576                                 refresh = 1;
1577                         }
1578                 } else if (splitrow >= 0) {
1579                         splitrow = -1;
1580                         lastrow = rows-1;
1581                         refresh = 1;
1582                 }
1583
1584                 if (refresh) {
1585                         getmaxyx(stdscr, rows, cols);
1586                         rows--; /* keep last row clear */
1587                         if (splitrow >= 0) {
1588                                 splitrow = (rows+1)/2;
1589                                 lastrow = splitrow - 1;
1590                         } else
1591                                 lastrow =  rows - 1;
1592
1593                         if (row < -3)
1594                                 row = lastrow/2+1;
1595                         if (row < 1)
1596                                 row = 1;
1597                         if (row > lastrow+3)
1598                                 row = lastrow/2+1;
1599                         if (row >= lastrow)
1600                                 row = lastrow-1;
1601                 }
1602
1603                 /* Always refresh the line */
1604                 while (start > curs.target) {
1605                         start -= 8;
1606                         refresh = 1;
1607                 }
1608                 if (start < 0)
1609                         start = 0;
1610                 vispos = pos; /* visible position - if cursor is in
1611                                * alternate pane, pos might not be visible
1612                                * in main pane. */
1613                 if (check_line(vispos, fm, fb, fa, ci.merger, mode)
1614                     & CHANGES) {
1615                         if (vispos.state == 0) {
1616                                 vispos.state = 1;
1617                                 vispos.lo = vispos.p;
1618                                 vispos.hi = vispos.p;
1619                         }
1620                 } else {
1621                         vispos.state = 0;
1622                 }
1623
1624                 if (visible(mode, ci.merger, &vispos) < 0)
1625                         prev_mline(&vispos, fm, fb, fa, ci.merger, mode);
1626                 if (!curs.alt)
1627                         pos= vispos;
1628         retry:
1629                 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1630                            vispos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1631                 if (curs.width == 0 && start < curs.col) {
1632                         /* width == 0 implies it appear after end-of-screen */
1633                         start += 8;
1634                         refresh = 1;
1635                         goto retry;
1636                 }
1637                 if (curs.col < start) {
1638                         start -= 8;
1639                         refresh = 1;
1640                         if (start < 0)
1641                                 start = 0;
1642                         goto retry;
1643                 }
1644                 if (refresh) {
1645                         refresh = 0;
1646
1647                         tpos = vispos;
1648
1649                         for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1650                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1651                                 draw_mline(mode, i--, start, cols,
1652                                            fm, fb, fa, ci.merger,
1653                                            tpos, NULL);
1654
1655                         }
1656                         if (i > 0) {
1657                                 row -= (i+1);
1658                                 refresh = 1;
1659                                 goto retry;
1660                         }
1661                         toppos = tpos;
1662                         while (i >= 1)
1663                                 blank(i--, 0, cols, a_void);
1664                         tpos = vispos;
1665                         for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1666                                 draw_mline(mode, i++, start, cols,
1667                                            fm, fb, fa, ci.merger,
1668                                            tpos, NULL);
1669                                 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1670                         }
1671                         botpos = tpos; botrow = i;
1672                         while (i <= lastrow)
1673                                 blank(i++, 0, cols, a_void);
1674                 }
1675
1676                 if (splitrow >= 0) {
1677                         struct mpos spos = pos;
1678                         int smode = BEFORE|AFTER;
1679                         int srow = (rows + splitrow)/2;
1680                         if (check_line(spos, fm, fb, fa, ci.merger, smode)
1681                             & CHANGES) {
1682                                 if (spos.state == 0)
1683                                         spos.state = 1;
1684                         } else {
1685                                 spos.state = 0;
1686                         }
1687                         if (visible(smode, ci.merger, &spos) < 0)
1688                                 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1689                         /* Now hi/lo might be wrong, so lets fix it. */
1690                         tpos = spos;
1691                         if (spos.state)
1692                                 /* 'hi' might be wrong so we mustn't depend
1693                                  * on it while walking back.  So set state
1694                                  * to 1 to avoid ever testing it.
1695                                  */
1696                                 spos.state = 1;
1697                         while (spos.p.m >= 0 && spos.state != 0)
1698                                 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1699                         while (!same_mpos(spos, tpos) &&
1700                                spos.p.m >= 0 &&
1701                                ci.merger[spos.p.m].type != End)
1702                                 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1703
1704                         (void)attrset(a_sep);
1705                         for (i = 0; i < cols; i++)
1706                                 mvaddstr(splitrow, i, "-");
1707
1708                         tpos = spos;
1709                         for (i = srow-1; i > splitrow; i--) {
1710                                 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1711                                 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1712                                            tpos, NULL);
1713                         }
1714                         while (i > splitrow)
1715                                 blank(i--, 0, cols, a_void);
1716                         tpos = spos;
1717                         for (i = srow;
1718                              i < rows && tpos.p.m >= 0 &&
1719                              ci.merger[tpos.p.m].type != End;
1720                              i++) {
1721                                 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1722                                            tpos,
1723                                            (i == srow && curs.alt) ? &curs : NULL);
1724                                 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1725                         }
1726                         while (i < rows)
1727                                 blank(i++, 0, cols, a_void);
1728                 }
1729                 /* Now that curs is accurate, report the type */
1730                 {
1731                         int l = 0;
1732                         char buf[100];
1733                         if (p->file)
1734                                 l = snprintf(buf, 100, "File: %s%s ",
1735                                 p->file, reverse ? " - reversed" : "");
1736                         snprintf(buf+l, 100-l, "Mode: %s", modename);
1737                         (void)attrset(A_BOLD);
1738                         mvaddstr(0, 0, buf);
1739                         (void)attrset(A_NORMAL);
1740                         if (ignore_blanks)
1741                                 addstr(" (ignoring blanks)");
1742                         clrtoeol();
1743                         (void)attrset(A_BOLD);
1744                         if (ci.merger[curs.pos.m].type != ci.merger[curs.pos.m].oldtype)
1745                                 l = snprintf(buf, sizeof(buf), "%s->", typenames[ci.merger[curs.pos.m].oldtype]);
1746                         snprintf(buf+l, sizeof(buf)-l, "%s ln:%d",
1747                                  typenames[ci.merger[curs.pos.m].type],
1748                                  (pos.p.lineno-1)/2);
1749                         mvaddstr(0, cols - strlen(buf) - 1, buf);
1750                 }
1751 #define META(c) ((c)|0x1000)
1752 #define SEARCH(c) ((c)|0x2000)
1753 #define CTRLX(c) ((c)|0x4000)
1754                 move(rows, 0);
1755                 (void)attrset(A_NORMAL);
1756                 if (mesg) {
1757                         attrset(A_REVERSE);
1758                         addstr(mesg);
1759                         mesg = NULL;
1760                         attrset(A_NORMAL);
1761                 }
1762                 if (num >= 0) {
1763                         char buf[12+1];
1764                         snprintf(buf, sizeof(buf), "%d ", num);
1765                         addstr(buf);
1766                 }
1767                 if (meta & META(0))
1768                         addstr("ESC...");
1769                 if (meta & CTRLX(0))
1770                         addstr("C-x ");
1771                 if (meta & SEARCH(0)) {
1772                         if (searchdir < 0)
1773                                 addstr("Backwards ");
1774                         addstr("Search: ");
1775                         addstr(search);
1776                         if (search_notfound)
1777                                 addstr(" - Not Found.");
1778                         search_notfound = 0;
1779                 }
1780                 clrtoeol();
1781                 /* '+1' to skip over the leading +/-/| char */
1782                 if (curs.alt && splitrow > 0)
1783                         move((rows + splitrow)/2, curs.col - start + 1);
1784                 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1785                                       (mode & (ORIG|RESULT))))
1786                         move(row, curs.col-start + (cols-1)/2+2);
1787                 else
1788                         move(row, curs.col-start+1);
1789                 switch (selftest) {
1790                 case 0:
1791                         c = getch(); break;
1792                 case 1:
1793                         c = 'n'; break;
1794                 case 2:
1795                         c = 'q'; break;
1796                 }
1797                 tmeta = meta; meta = 0;
1798                 tnum = num; num = -1;
1799                 cswitch = c | tmeta;
1800                 /* Handle some ranges */
1801                 /* case '0' ... '9': */
1802                 if (cswitch >= '0' && cswitch <= '9')
1803                         cswitch = '0';
1804                 /* case SEARCH(' ') ... SEARCH('~'): */
1805                 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1806                         cswitch = SEARCH(' ');
1807
1808                 switch (cswitch) {
1809                 case 27: /* escape */
1810                 case META(27):
1811                         meta = META(0);
1812                         break;
1813
1814                 case 'X'-64:
1815                 case META('X'-64):
1816                         meta = CTRLX(0);
1817                         break;
1818
1819                 case META('<'): /* start of file */
1820                 start:
1821                         tpos = pos; row++;
1822                         do {
1823                                 pos = tpos; row--;
1824                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1825                         } while (tpos.p.m >= 0);
1826                         if (row <= 0)
1827                                 row = 0;
1828                         break;
1829                 case META('>'): /* end of file */
1830                 case 'G':
1831                         if (tnum >= 0)
1832                                 goto start;
1833                         tpos = pos; row--;
1834                         do {
1835                                 pos = tpos; row++;
1836                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1837                         } while (ci.merger[tpos.p.m].type != End);
1838                         if (row >= lastrow)
1839                                 row = lastrow;
1840                         break;
1841                 case '0': /* actually '0'...'9' */
1842                         if (tnum < 0)
1843                                 tnum = 0;
1844                         num = tnum*10 + (c-'0');
1845                         break;
1846                 case 'C'-64:
1847                         if (replace)
1848                                 mesg = "Autosave disabled";
1849                         else
1850                                 mesg = "Use 'q' to quit";
1851                         replace = 0;
1852                         break;
1853                 case 'S':
1854                         mesg = "Will auto-save on exit, using Ctrl-C to cancel";
1855                         replace = 1;
1856                         break;
1857                 case 'q':
1858                         refresh = 2;
1859                         answer = 0;
1860                         if (replace)
1861                                 answer = 1;
1862                         else if (changes)
1863                                 answer = help_window(save_query, NULL, 1);
1864                         if (answer < 0)
1865                                 break;
1866                         if (answer) {
1867                                 p->wiggles = 0;
1868                                 p->conflicts = wiggle_isolate_conflicts(
1869                                         fm, fb, fa, csl1, csl2, 0,
1870                                         ci.merger, 0, &p->wiggles);
1871                                 p->chunks = p->conflicts;
1872                                 save_merge(fm, fb, fa, ci.merger,
1873                                            p->outfile ? p->outfile : p->file,
1874                                            backup && (p->outfile ? 0 : !p->is_merge));
1875                         }
1876                         if (!just_diff)
1877                                 free(sm.body);
1878                         free(sb.body);
1879                         free(sa.body);
1880                         free_stuff();
1881                         endwin();
1882                         return answer;
1883
1884                 case 'I': /* Toggle ignoring of spaces */
1885                         if (changes) {
1886                                 refresh = 2;
1887                                 answer = help_window(toggle_ignore, NULL, 1);
1888                                 if (answer <= 0)
1889                                         break;
1890                                 changes = 0;
1891                         }
1892                         free_stuff();
1893                         ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
1894                         prepare_merge(ch);
1895                         find_line(pos.p.lineno);
1896
1897                         refresh = 2;
1898                         break;
1899
1900                 case 'v':
1901                         if (!p->file || just_diff) {
1902                                 mesg = "Cannot run editor when diffing";
1903                                 break;
1904                         }
1905                         tempname = p->file;
1906                         lineno = save_tmp_merge(fm, fb, fa, ci.merger,
1907                                                 &tempname,
1908                                                 ci.merger + pos.p.m,
1909                                                 pos.p.s,
1910                                                 pos.p.o);
1911                         endwin();
1912                         free_stuff();
1913                         do_edit(tempname, lineno);
1914                         sp = wiggle_load_file(tempname);
1915                         unlink(tempname);
1916                         wiggle_split_merge(sp, &sm, &sb, &sa);
1917                         if (sp.len == sm.len &&
1918                             memcmp(sp.body, sm.body, sm.len) == 0 &&
1919                                 !p->is_merge) {
1920                                 /* no conflicts left, so display diff */
1921                                 free(sm.body);
1922                                 sm = wiggle_load_file(p->file);
1923                                 free(sb.body);
1924                                 sb = sm;
1925                                 sb.body = memdup(sm.body, sm.len);
1926                         }
1927                         free(sp.body);
1928                         ignore_blanks = 0;
1929                         prepare_merge(0);
1930                         refresh = 2;
1931                         changes = 1;
1932
1933                         find_line(pos.p.lineno);
1934
1935                         doupdate();
1936                         break;
1937
1938                 case '/':
1939                 case 'S'-64:
1940                         /* incr search forward */
1941                         meta = SEARCH(0);
1942                         searchlen = 0;
1943                         search[searchlen] = 0;
1944                         searchdir = 1;
1945                         break;
1946                 case '\\':
1947                 case 'R'-64:
1948                         /* incr search backwards */
1949                         meta = SEARCH(0);
1950                         searchlen = 0;
1951                         search[searchlen] = 0;
1952                         searchdir = -1;
1953                         break;
1954                 case SEARCH('G'-64):
1955                 case SEARCH('S'-64):
1956                 case SEARCH('R'-64):
1957                         /* search again */
1958                         if ((c|tmeta) == SEARCH('R'-64))
1959                                 searchdir = -2;
1960                         else
1961                                 searchdir = 2;
1962                         meta = SEARCH(0);
1963                         tpos = pos; trow = row;
1964                         goto search_again;
1965
1966                 case SEARCH('H'-64):
1967                 case SEARCH(KEY_BACKSPACE):
1968                         meta = SEARCH(0);
1969                         if (anchor) {
1970                                 struct search_anchor *a;
1971                                 a = anchor;
1972                                 anchor = a->next;
1973                                 free(a);
1974                         }
1975                         if (anchor) {
1976                                 struct search_anchor *a;
1977                                 a = anchor;
1978                                 anchor = a->next;
1979                                 pos = a->pos;
1980                                 row = a->row;
1981                                 start = a->start;
1982                                 curs = a->curs;
1983                                 curs.target = -1;
1984                                 search_notfound = a->notfound;
1985                                 searchlen = a->searchlen;
1986                                 search[searchlen] = 0;
1987                                 free(a);
1988                                 refresh = 1;
1989                         }
1990                         break;
1991                 case SEARCH(' '): /* actually ' '...'~' */
1992                 case SEARCH('\t'):
1993                         meta = SEARCH(0);
1994                         if (searchlen < sizeof(search)-1)
1995                                 search[searchlen++] = c & (0x7f);
1996                         search[searchlen] = 0;
1997                         tpos = pos; trow = row;
1998                 search_again:
1999                         search_notfound = 1;
2000                         if (ignore_case == 1 || ignore_case == 2) {
2001                                 unsigned int i;
2002                                 ignore_case = 2;
2003                                 for (i=0; i < searchlen; i++)
2004                                         if (isupper(search[i])) {
2005                                                 ignore_case = 1;
2006                                                 break;
2007                                         }
2008                         }
2009                         do {
2010                                 if (mcontains(tpos, fm, fb, fa, ci.merger,
2011                                               mmode, search, &curs, searchdir,
2012                                               ignore_case >= 2)) {
2013                                         curs.target = -1;
2014                                         pos = tpos;
2015                                         row = trow;
2016                                         search_notfound = 0;
2017                                         break;
2018                                 }
2019                                 if (searchdir < 0) {
2020                                         trow--;
2021                                         prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2022                                 } else {
2023                                         trow++;
2024                                         next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2025                                 }
2026                         } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
2027                         searchdir /= abs(searchdir);
2028
2029                         break;
2030                 case 'L'-64:
2031                         refresh = 2;
2032                         row = lastrow / 2;
2033                         break;
2034
2035                 case KEY_NPAGE:
2036                 case ' ':
2037                 case 'V'-64: /* page down */
2038                         pos = botpos;
2039                         if (botrow <= lastrow) {
2040                                 row = botrow;
2041                                 if (selftest == 1)
2042                                         selftest = 2;
2043                         } else
2044                                 row = 2;
2045                         refresh = 1;
2046                         break;
2047                 case KEY_PPAGE:
2048                 case KEY_BACKSPACE:
2049                 case META('v'): /* page up */
2050                         pos = toppos;
2051                         row = lastrow-1;
2052                         refresh = 1;
2053                         break;
2054
2055                 case KEY_MOUSE:
2056                         if (getmouse(&mevent) != OK)
2057                                 break;
2058                         /* First see if this is on the 'other' pane */
2059                         if (splitrow > 0) {
2060                                 /* merge mode, top and bottom */
2061                                 if ((curs.alt && mevent.y < splitrow) ||
2062                                     (!curs.alt && mevent.y > splitrow)) {
2063                                         goto other_pane;
2064                                 }
2065                         } else if (mode == (ORIG|RESULT|BEFORE|AFTER)) {
2066                                 /* side-by-side mode */
2067                                 if ((curs.alt && mevent.x < cols/2) ||
2068                                     (!curs.alt && mevent.x > cols/2)) {
2069                                         goto other_pane;
2070                                 }
2071                         }
2072                         /* Now try to find the right line */
2073                         if (splitrow < 0 || !curs.alt)
2074                                 trow = row;
2075                         else
2076                                 trow = (rows + splitrow)/2;
2077                         while (trow > mevent.y) {
2078                                 tpos = pos;
2079                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2080                                 if (tpos.p.m >= 0) {
2081                                         pos = tpos;
2082                                         trow--;
2083                                 } else
2084                                         break;
2085                         }
2086                         while (trow < mevent.y) {
2087                                 tpos = pos;
2088                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2089                                 if (ci.merger[tpos.p.m].type != End) {
2090                                         pos = tpos;
2091                                         trow++;
2092                                 } else
2093                                         break;
2094                         }
2095                         if (splitrow < 0 || !curs.alt)
2096                                 /* it is OK to change the row */
2097                                 row = trow;
2098
2099                         /* Now set the target column */
2100                         if (mode == (ORIG|RESULT|BEFORE|AFTER) &&
2101                             curs.alt)
2102                                 curs.target = start + mevent.x - cols / 2 - 1;
2103                         else
2104                                 curs.target = start + mevent.x - 1;
2105                         break;
2106                 case 'j':
2107                 case 'n':
2108                 case 'N'-64:
2109                 case KEY_DOWN:
2110                         if (tnum < 0)
2111                                 tnum = 1;
2112                         for (; tnum > 0 ; tnum--) {
2113                                 tpos = pos;
2114                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2115                                 if (ci.merger[tpos.p.m].type != End) {
2116                                         pos = tpos;
2117                                         row++;
2118                                 } else {
2119                                         if (selftest == 1)
2120                                                 selftest = 2;
2121                                         break;
2122                                 }
2123                         }
2124                         break;
2125                 case 'N':
2126                         /* Next diff */
2127                         tpos = pos; row--;
2128                         do {
2129                                 pos = tpos; row++;
2130                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2131                         } while (!(pos.state == 0
2132                                    && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2133                                        & (CONFLICTED|WIGGLED)) == 0)
2134                                  && ci.merger[tpos.p.m].type != End);
2135                         tpos = pos; row--;
2136                         do {
2137                                 pos = tpos; row++;
2138                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2139                         } while (pos.state == 0
2140                                  && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2141                                      & (CONFLICTED|WIGGLED)) == 0
2142                                  && ci.merger[tpos.p.m].type != End);
2143
2144                         break;
2145                 case 'C':
2146                         /* Next conflict */
2147                         tpos = pos; row--;
2148                         do {
2149                                 pos = tpos; row++;
2150                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2151                         } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2152                                   & CONFLICTED) != 0
2153                                  && ci.merger[tpos.p.m].type != End);
2154                         tpos = pos; row--;
2155                         do {
2156                                 pos = tpos; row++;
2157                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2158                         } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2159                                   & CONFLICTED) == 0
2160                                  && ci.merger[tpos.p.m].type != End);
2161
2162                         break;
2163
2164                 case 'P':
2165                         /* Previous diff */
2166                         tpos = pos; row++;
2167                         do {
2168                                 pos = tpos; row--;
2169                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2170                         } while (tpos.state == 0
2171                                  && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2172                                      & (CONFLICTED|WIGGLED)) == 0
2173                                  && tpos.p.m >= 0);
2174                         tpos = pos; row++;
2175                         do {
2176                                 pos = tpos; row--;
2177                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2178                         } while (!(tpos.state == 0
2179                                    && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2180                                        & (CONFLICTED|WIGGLED)) == 0)
2181                                  && tpos.p.m >= 0);
2182                         break;
2183
2184                 case 'k':
2185                 case 'p':
2186                 case 'P'-64:
2187                 case KEY_UP:
2188                         if (tnum < 0)
2189                                 tnum = 1;
2190                         for (; tnum > 0 ; tnum--) {
2191                                 tpos = pos;
2192                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2193                                 if (tpos.p.m >= 0) {
2194                                         pos = tpos;
2195                                         row--;
2196                                 } else
2197                                         break;
2198                         }
2199                         break;
2200
2201                 case KEY_LEFT:
2202                 case 'h':
2203                         /* left */
2204                         curs.target = curs.col - 1;
2205                         if (curs.target < 0) {
2206                                 /* Try to go to end of previous line */
2207                                 tpos = pos;
2208                                 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2209                                 if (tpos.p.m >= 0) {
2210                                         pos = tpos;
2211                                         row--;
2212                                         curs.pos = pos.p;
2213                                         curs.target = -1;
2214                                 } else
2215                                         curs.target = 0;
2216                         }
2217                         break;
2218                 case KEY_RIGHT:
2219                 case 'l':
2220                         /* right */
2221                         if (curs.width >= 0)
2222                                 curs.target = curs.col + curs.width;
2223                         else {
2224                                 /* end of line, go to next */
2225                                 tpos = pos;
2226                                 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2227                                 if (ci.merger[tpos.p.m].type != End) {
2228                                         pos = tpos;
2229                                         curs.pos = pos.p;
2230                                         row++;
2231                                         curs.target = 0;
2232                                 }
2233                         }
2234                         break;
2235
2236                 case '^':
2237                 case 'A'-64:
2238                         /* Start of line */
2239                         curs.target = 0;
2240                         break;
2241                 case '$':
2242                 case 'E'-64:
2243                         /* End of line */
2244                         curs.target = 1000;
2245                         break;
2246
2247                 case CTRLX('o'):
2248                 case 'O':
2249                 other_pane:
2250                         curs.alt = !curs.alt;
2251                         if (curs.alt && mode == (ORIG|RESULT))
2252                                 mmode = (BEFORE|AFTER);
2253                         else
2254                                 mmode = mode;
2255                         break;
2256
2257                 case 'a': /* 'after' view in patch window */
2258                         if (mode == AFTER)
2259                                 goto set_merge;
2260                         mode = AFTER; modename = "after"; modehelp = after_help;
2261                         mmode = mode; curs.alt = 0;
2262                         refresh = 3;
2263                         break;
2264                 case 'b': /* 'before' view in patch window */
2265                         if (mode == BEFORE)
2266                                 goto set_merge;
2267                         mode = BEFORE; modename = "before"; modehelp = before_help;
2268                         mmode = mode; curs.alt = 0;
2269                         refresh = 3;
2270                         break;
2271                 case 'o': /* 'original' view in the merge window */
2272                         if (mode == ORIG)
2273                                 goto set_merge;
2274                         mode = ORIG; modename = "original"; modehelp = orig_help;
2275                         mmode = mode; curs.alt = 0;
2276                         refresh = 3;
2277                         break;
2278                 case 'r': /* the 'result' view in the merge window */
2279                         if (mode == RESULT)
2280                                 goto set_merge;
2281                         mode = RESULT; modename = "result"; modehelp = result_help;
2282                         mmode = mode; curs.alt = 0;
2283                         refresh = 3;
2284                         break;
2285                 case 'd':
2286                         if (mode == (BEFORE|AFTER))
2287                                 goto set_merge;
2288                         mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
2289                         mmode = mode; curs.alt = 0;
2290                         refresh = 3;
2291                         break;
2292                 case 'm':
2293                 set_merge:
2294                         mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
2295                         mmode = mode; curs.alt = 0;
2296                         refresh = 3;
2297                         break;
2298
2299                 case '|':
2300                         if (mode == (ORIG|RESULT|BEFORE|AFTER))
2301                                 goto set_merge;
2302                         mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
2303                         mmode = mode; curs.alt = 0;
2304                         refresh = 3;
2305                         break;
2306
2307                 case 'H': /* scroll window to the right */
2308                         if (start > 0)
2309                                 start--;
2310                         curs.target = start + 1;
2311                         refresh = 1;
2312                         break;
2313                 case 'L': /* scroll window to the left */
2314                         if (start < cols)
2315                                 start++;
2316                         curs.target = start + 1;
2317                         refresh = 1;
2318                         break;
2319
2320                 case 'x': /* Toggle rejecting of conflict.
2321                            * A 'Conflict' or 'Changed' becomes 'Unchanged'
2322                            * 'Unmatched' becomes 'Changed'
2323                            */
2324                         if (ci.merger[curs.pos.m].oldtype == Conflict ||
2325                             ci.merger[curs.pos.m].oldtype == Changed)
2326                                 next = Unchanged;
2327                         else if (ci.merger[curs.pos.m].oldtype == Unmatched)
2328                                 next = Changed;
2329                         else
2330                                 break;
2331
2332                         if (ci.merger[curs.pos.m].type == next)
2333                                 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2334                         else
2335                                 ci.merger[curs.pos.m].type = next;
2336                         p->conflicts = wiggle_isolate_conflicts(
2337                                 fm, fb, fa, csl1, csl2, 0,
2338                                 ci.merger, 0, &p->wiggles);
2339                         refresh = 1;
2340                         changes = 1;
2341                         break;
2342
2343                 case 'c': /* Toggle accepting of conflict.
2344                            * A 'Conflict' or 'Extraneous' becomes 'Changed'
2345                            */
2346                         if (ci.merger[curs.pos.m].oldtype != Conflict &&
2347                             ci.merger[curs.pos.m].oldtype != Extraneous)
2348                                 break;
2349
2350                         if (ci.merger[curs.pos.m].type == Changed)
2351                                 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2352                         else
2353                                 ci.merger[curs.pos.m].type = Changed;
2354                         p->conflicts = wiggle_isolate_conflicts(
2355                                 fm, fb, fa, csl1, csl2, 0,
2356                                 ci.merger, 0, &p->wiggles);
2357                         refresh = 1;
2358                         changes = 1;
2359                         break;
2360
2361                 case 'X': /* Reset all changes on the current line */
2362                         tpos = pos;
2363                         do {
2364                                 ci.merger[tpos.p.m].type =
2365                                         ci.merger[tpos.p.m].oldtype;
2366                                 e = prev_melmnt(&tpos.p, fm, fb, fa, ci.merger);
2367                                 if (tpos.p.m < 0)
2368                                         break;
2369                         } while (!ends_line(e) ||
2370                                  visible(mode & (RESULT|AFTER), ci.merger, &tpos) < 0);
2371                         p->conflicts = wiggle_isolate_conflicts(
2372                                 fm, fb, fa, csl1, csl2, 0,
2373                                 ci.merger, 0, &p->wiggles);
2374                         refresh = 1;
2375                         changes = 1;
2376                         break;
2377
2378                 case '?':
2379                         help_window(modehelp, merge_window_help, 0);
2380                         refresh = 2;
2381                         break;
2382
2383                 case KEY_RESIZE:
2384                         refresh = 2;
2385                         break;
2386                 }
2387
2388                 if (meta == SEARCH(0)) {
2389                         if (anchor == NULL ||
2390                             !same_mpos(anchor->pos, pos) ||
2391                             anchor->searchlen != searchlen ||
2392                             !same_mp(anchor->curs.pos, curs.pos)) {
2393                                 struct search_anchor *a = wiggle_xmalloc(sizeof(*a));
2394                                 a->pos = pos;
2395                                 a->row = row;
2396                                 a->start = start;
2397                                 a->curs = curs;
2398                                 a->searchlen = searchlen;
2399                                 a->notfound = search_notfound;
2400                                 a->next = anchor;
2401                                 anchor = a;
2402                         }
2403                 } else {
2404                         while (anchor) {
2405                                 struct search_anchor *a = anchor;
2406                                 anchor = a->next;
2407                                 free(a);
2408                         }
2409                 }
2410                 if (refresh == 3) {
2411                         /* move backward and forward to make sure we
2412                          * are on a visible line
2413                          */
2414                         tpos = pos;
2415                         prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2416                         if (tpos.p.m >= 0)
2417                                 pos = tpos;
2418                         tpos = pos;
2419                         next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2420                         if (ci.merger[tpos.p.m].type != End)
2421                                 pos = tpos;
2422                 }
2423         }
2424 }
2425
2426 static int show_merge(char *origname, FILE *patch, int reverse,
2427                       int is_merge, char *before, char *after,
2428                       int replace, char *outfile,
2429                       int selftest, int ignore_blanks,
2430                       int just_diff, int backup)
2431 {
2432         struct plist p = {0};
2433
2434         p.file = origname;
2435         p.outfile = replace ? outfile : NULL;
2436         if (patch) {
2437                 p.start = 0;
2438                 fseek(patch, 0, SEEK_END);
2439                 p.end = ftell(patch);
2440                 fseek(patch, 0, SEEK_SET);
2441         }
2442         p.calced = 0;
2443         p.is_merge = is_merge;
2444         p.before = before;
2445         p.after = after;
2446
2447         freopen("/dev/null","w",stderr);
2448         return merge_window(&p, patch, reverse, replace, selftest,
2449                             ignore_blanks, just_diff, backup);
2450 }
2451
2452 static void calc_one(struct plist *pl, FILE *f, int reverse,
2453                      int ignore_blanks, int just_diff)
2454 {
2455         struct stream s1, s2;
2456         struct stream s = wiggle_load_segment(f, pl->start, pl->end);
2457         struct stream sf;
2458         if (pl->is_merge) {
2459                 if (reverse)
2460                         wiggle_split_merge(s, &sf, &s2, &s1);
2461                 else
2462                         wiggle_split_merge(s, &sf, &s1, &s2);
2463                 pl->chunks = 0;
2464         } else {
2465                 if (reverse)
2466                         pl->chunks = wiggle_split_patch(s, &s2, &s1);
2467                 else
2468                         pl->chunks = wiggle_split_patch(s, &s1, &s2);
2469                 if (just_diff)
2470                         sf = s1;
2471                 else
2472                         sf = wiggle_load_file(pl->file);
2473         }
2474         if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
2475                 pl->wiggles = pl->conflicts = -1;
2476         } else {
2477                 struct file ff, fp1, fp2;
2478                 struct csl *csl1, *csl2;
2479                 struct ci ci;
2480                 ff = wiggle_split_stream(sf, ByWord | ignore_blanks);
2481                 fp1 = wiggle_split_stream(s1, ByWord | ignore_blanks);
2482                 fp2 = wiggle_split_stream(s2, ByWord | ignore_blanks);
2483                 if (pl->chunks && !just_diff)
2484                         csl1 = wiggle_pdiff(ff, fp1, pl->chunks);
2485                 else
2486                         csl1 = wiggle_diff(ff, fp1, 1);
2487                 csl2 = wiggle_diff_patch(fp1, fp2, 1);
2488                 ci = wiggle_make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
2489                 pl->wiggles = ci.wiggles;
2490                 pl->conflicts = ci.conflicts;
2491                 free(ci.merger);
2492                 free(csl1);
2493                 free(csl2);
2494                 free(ff.list);
2495                 free(fp1.list);
2496                 free(fp2.list);
2497         }
2498
2499         free(s1.body);
2500         free(s2.body);
2501         free(s.body);
2502         if (!just_diff)
2503                 free(sf.body);
2504         pl->calced = 1;
2505 }
2506
2507 static int get_prev(int pos, struct plist *pl, int n, int mode)
2508 {
2509         int found = 0;
2510         if (pos == -1 || pl == NULL)
2511                 return pos;
2512         do {
2513                 if (pl[pos].prev == -1)
2514                         return pl[pos].parent;
2515                 pos = pl[pos].prev;
2516                 while (pl[pos].open &&
2517                        pl[pos].last >= 0)
2518                         pos = pl[pos].last;
2519                 if (pl[pos].last >= 0)
2520                         /* always see directories */
2521                         found = 1;
2522                 else if (mode == 0)
2523                         found = 1;
2524                 else if (mode <= 1 && pl[pos].wiggles > 0)
2525                         found = 1;
2526                 else if (mode <= 2 && pl[pos].conflicts > 0)
2527                         found = 1;
2528         } while (pos >= 0 && !found);
2529         return pos;
2530 }
2531
2532 static int get_next(int pos, struct plist *pl, int n, int mode,
2533                     FILE *f, int reverse, int ignore_blanks, int just_diff)
2534 {
2535         int found = 0;
2536         if (pos == -1)
2537                 return pos;
2538         do {
2539                 if (pl[pos].open) {
2540                         if (pos + 1 < n)
2541                                 pos =  pos+1;
2542                         else
2543                                 return -1;
2544                 } else {
2545                         while (pos >= 0 && pl[pos].next == -1)
2546                                 pos = pl[pos].parent;
2547                         if (pos >= 0)
2548                                 pos = pl[pos].next;
2549                 }
2550                 if (pos < 0)
2551                         return -1;
2552                 if (pl[pos].calced == 0 && pl[pos].end)
2553                         calc_one(pl+pos, f, reverse, ignore_blanks, just_diff);
2554                 if (pl[pos].last >= 0)
2555                         /* always see directories */
2556                         found = 1;
2557                 else if (mode == 0)
2558                         found = 1;
2559                 else if (mode <= 1 && pl[pos].wiggles > 0)
2560                         found = 1;
2561                 else if (mode <= 2 && pl[pos].conflicts > 0)
2562                         found = 1;
2563         } while (pos >= 0 && !found);
2564         return pos;
2565 }
2566
2567 static void draw_one(int row, struct plist *pl, FILE *f, int reverse,
2568                      int ignore_blanks, int just_diff)
2569 {
2570         char hdr[2*12];
2571         hdr[0] = 0;
2572
2573         if (pl == NULL) {
2574                 move(row, 0);
2575                 clrtoeol();
2576                 return;
2577         }
2578         if (pl->calced == 0 && pl->end)
2579                 /* better load the patch and count the chunks */
2580                 calc_one(pl, f, reverse, ignore_blanks, just_diff);
2581         if (pl->end == 0) {
2582                 strcpy(hdr, "         ");
2583         } else {
2584                 if (pl->chunks > 99)
2585                         strcpy(hdr, "XX");
2586                 else
2587                         sprintf(hdr, "%2d", pl->chunks);
2588                 if (pl->wiggles > 99)
2589                         strcpy(hdr+2, " XX");
2590                 else
2591                         sprintf(hdr+2, " %2d", pl->wiggles);
2592                 if (pl->conflicts > 99)
2593                         strcpy(hdr+5, " XX ");
2594                 else
2595                         sprintf(hdr+5, " %2d ", pl->conflicts);
2596         }
2597         if (pl->end)
2598                 strcpy(hdr+9, "= ");
2599         else if (pl->open)
2600                 strcpy(hdr+9, "+ ");
2601         else
2602                 strcpy(hdr+9, "- ");
2603
2604         if (!pl->end)
2605                 attrset(0);
2606         else if (pl->is_merge)
2607                 attrset(a_saved);
2608         else if (pl->conflicts)
2609                 attrset(a_has_conflicts);
2610         else if (pl->wiggles)
2611                 attrset(a_has_wiggles);
2612         else
2613                 attrset(a_no_wiggles);
2614
2615         mvaddstr(row, 0, hdr);
2616         mvaddstr(row, 11, pl->file);
2617         clrtoeol();
2618 }
2619
2620 static int save_one(FILE *f, struct plist *pl, int reverse,
2621                     int ignore_blanks, int backup)
2622 {
2623         struct stream sp, sa, sb, sm;
2624         struct file fa, fb, fm;
2625         struct csl *csl1, *csl2;
2626         struct ci ci;
2627         int chunks;
2628         sp = wiggle_load_segment(f, pl->start,
2629                                  pl->end);
2630         if (reverse)
2631                 chunks = wiggle_split_patch(sp, &sa, &sb);
2632         else
2633                 chunks = wiggle_split_patch(sp, &sb, &sa);
2634         fb = wiggle_split_stream(sb, ByWord | ignore_blanks);
2635         fa = wiggle_split_stream(sa, ByWord | ignore_blanks);
2636         sm = wiggle_load_file(pl->file);
2637         fm = wiggle_split_stream(sm, ByWord | ignore_blanks);
2638         csl1 = wiggle_pdiff(fm, fb, chunks);
2639         csl2 = wiggle_diff_patch(fb, fa, 1);
2640         ci = wiggle_make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
2641         return save_merge(fm, fb, fa, ci.merger,
2642                           pl->file, backup);
2643 }
2644
2645 static char *main_help[] = {
2646         "   You are using the \"browse\" mode of wiggle.",
2647         "This page shows a list of files in a patch together with",
2648         "the directories that contain them.",
2649         "A directory is indicated by a '+' if the contents are",
2650         "listed or a '-' if the contents are hidden.  A file is",
2651         "indicated by an '='.  Typing <space> or <return> will",
2652         "expose or hide a directory, and will visit a file.",
2653         "",
2654         "The three columns of numbers are:",
2655         "  Ch   The number of patch chunks which applied to",
2656         "       this file",
2657         "  Wi   The number of chunks that needed to be wiggled",
2658         "       in to place",
2659         "  Co   The number of chunks that created an unresolvable",
2660         "       conflict",
2661         "",
2662         "Keystrokes recognised in this page are:",
2663         "  ?          Display this help",
2664         "  SPC        On a directory, toggle hiding of contents",
2665         "             On file, visit the file",
2666         "  RTN        Same as SPC",
2667         "  q          Quit program",
2668         "  control-C  Disable auto-save-on-exit",
2669         "  n,j,DOWN   Go to next line",
2670         "  p,k,UP     Go to previous line",
2671         "",
2672         "  A          list All files",
2673         "  W          only list files with a wiggle or a conflict",
2674         "  C          only list files with a conflict",
2675         "",
2676         "  S          Save this file with changes applied.  If",
2677         "             some but not all files are saved, wiggle will",
2678         "             prompt on exit to save the rest.",
2679         "  R          Revert the current saved file to its original",
2680         "             content",
2681         "  I          toggle whether spaces are ignored",
2682         "             when matching text.",
2683         NULL
2684 };
2685 static char *saveall_msg = " %d file%s (of %d) have not been saved.";
2686 static char saveall_buf[200];
2687 static char *saveall_query[] = {
2688         "",
2689         saveall_buf,
2690         " Would you like to save them?",
2691         "  Y = yes, save them all",
2692         "  N = no, exit without saving anything else",
2693         "  Q = Don't quit just yet",
2694         NULL
2695 };
2696 static void main_window(struct plist *pl, int np, FILE *f, int reverse,
2697                         int replace, int ignore_blanks, int just_diff, int backup)
2698 {
2699         /* The main window lists all files together with summary information:
2700          * number of chunks, number of wiggles, number of conflicts.
2701          * The list is scrollable
2702          * When a entry is 'selected', we switch to the 'file' window
2703          * The list can be condensed by removing files with no conflict
2704          * or no wiggles, or removing subdirectories
2705          *
2706          * We record which file in the list is 'current', and which
2707          * screen line it is on.  We try to keep things stable while
2708          * moving.
2709          *
2710          * Counts are printed before the name using at most 2 digits.
2711          * Numbers greater than 99 are XX
2712          * Ch Wi Co File
2713          * 27 5   1 drivers/md/md.c
2714          *
2715          * A directory show the sum in all children.
2716          *
2717          * Commands:
2718          *  select:  enter, space, mouseclick
2719          *      on file, go to file window
2720          *      on directory, toggle open
2721          *  up:  k, p, control-p uparrow
2722          *      Move to previous open object
2723          *  down: j, n, control-n, downarrow
2724          *      Move to next open object
2725          *
2726          *  A W C: select All Wiggles or Conflicts
2727          *         mode
2728          *
2729          */
2730         char *mesg = NULL;
2731         char mesg_buf[1024];
2732         int last_mesg_len = 0;
2733         int pos = 0; /* position in file */
2734         int row = 1; /* position on screen */
2735         int rows = 0; /* size of screen in rows */
2736         int cols = 0;
2737         int tpos, i;
2738         int refresh = 2;
2739         int c = 0;
2740         int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2741         int cnt; /* count of files that need saving */
2742         int any; /* count of files that have been save*/
2743         int ans;
2744         MEVENT mevent;
2745         char *debug = getenv("WIGGLE_DEBUG");
2746
2747         if (debug && !*debug)
2748                 debug = NULL;
2749
2750         freopen("/dev/null","w",stderr);
2751         term_init(1);
2752
2753         while (1) {
2754                 if (refresh == 2) {
2755                         clear(); (void)attrset(0);
2756                         attron(A_BOLD);
2757                         mvaddstr(0, 0, "Ch Wi Co Patched Files");
2758                         attroff(A_BOLD);
2759                         if (ignore_blanks)
2760                                 addstr(" (ignoring blanks)");
2761                         move(2, 0);
2762                         refresh = 1;
2763                 }
2764                 if (row < 1  || row >= rows)
2765                         refresh = 1;
2766                 if (refresh) {
2767                         refresh = 0;
2768                         getmaxyx(stdscr, rows, cols);
2769
2770                         if (row >= rows + 3)
2771                                 row = (rows+1)/2;
2772                         if (row >= rows)
2773                                 row = rows-1;
2774                         tpos = pos;
2775                         for (i = row; i > 1; i--) {
2776                                 tpos = get_prev(tpos, pl, np, mode);
2777                                 if (tpos == -1) {
2778                                         row = row - i + 1;
2779                                         break;
2780                                 }
2781                         }
2782                         /* Ok, row and pos could be trustworthy now */
2783                         tpos = pos;
2784                         for (i = row; i >= 1; i--) {
2785                                 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2786                                 tpos = get_prev(tpos, pl, np, mode);
2787                         }
2788                         tpos = pos;
2789                         for (i = row+1; i < rows; i++) {
2790                                 tpos = get_next(tpos, pl, np, mode, f, reverse,ignore_blanks, just_diff);
2791                                 if (tpos >= 0)
2792                                         draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2793                                 else
2794                                         draw_one(i, NULL, f, reverse, ignore_blanks, just_diff);
2795                         }
2796                 }
2797                 attrset(0);
2798                 if (last_mesg_len) {
2799                         move(0, cols - last_mesg_len);
2800                         clrtoeol();
2801                         last_mesg_len = 0;
2802                 }
2803                 if (mesg) {
2804                         last_mesg_len = strlen(mesg);
2805                         move(0, cols - last_mesg_len);
2806                         addstr(mesg);
2807                         mesg = NULL;
2808                 } else if (debug) {
2809                         /* debugging help: report last keystroke */
2810                         char bb[30];
2811                         sprintf(bb, "last-key = 0%o", c);
2812                         attrset(0);
2813                         last_mesg_len = strlen(bb);
2814                         mvaddstr(0, cols - last_mesg_len, bb);
2815                 }
2816                 move(row, 9);
2817                 c = getch();
2818                 switch (c) {
2819                 case 'j':
2820                 case 'n':
2821                 case 'N':
2822                 case 'N'-64:
2823                 case KEY_DOWN:
2824                         tpos = get_next(pos, pl, np, mode, f, reverse, ignore_blanks, just_diff);
2825                         if (tpos >= 0) {
2826                                 pos = tpos;
2827                                 row++;
2828                         }
2829                         break;
2830                 case 'k':
2831                 case 'p':
2832                 case 'P':
2833                 case 'P'-64:
2834                 case KEY_UP:
2835                         tpos = get_prev(pos, pl, np, mode);
2836                         if (tpos >= 0) {
2837                                 pos = tpos;
2838                                 row--;
2839                         }
2840                         break;
2841
2842                 case KEY_MOUSE:
2843                         if (getmouse(&mevent) != OK)
2844                                 break;
2845                         while (row < mevent.y &&
2846                                (tpos = get_next(pos, pl, np, mode, f, reverse, ignore_blanks, just_diff))
2847                                >= 0) {
2848                                 pos = tpos;
2849                                 row++;
2850                         }
2851                         while (row > mevent.y &&
2852                                (tpos = get_prev(pos, pl, np, mode)) >= 0) {
2853                                 pos = tpos;
2854                                 row--;
2855                         }
2856                         if (row != mevent.y)
2857                                 /* couldn't find the line */
2858                                 break;
2859                         /* FALL THROUGH */
2860                 case ' ':
2861                 case 13:
2862                         if (pl[pos].end == 0) {
2863                                 pl[pos].open = !pl[pos].open;
2864                                 refresh = 1;
2865                                 if (pl[pos].open)
2866                                         mesg = "Opened folder";
2867                                 else
2868                                         mesg = "Closed folder";
2869                         } else {
2870                                 int c;
2871                                 if (pl[pos].is_merge)
2872                                         c = merge_window(&pl[pos], NULL, reverse, 0, 0,
2873                                                          ignore_blanks, just_diff, backup);
2874                                 else
2875                                         c = merge_window(&pl[pos], f, reverse, 0, 0,
2876                                                          ignore_blanks, just_diff, backup);
2877                                 refresh = 2;
2878                                 if (c) {
2879                                         pl[pos].is_merge = 1;
2880                                         snprintf(mesg_buf, cols,
2881                                                  "Saved file %s.",
2882                                                  pl[pos].file);
2883                                         mesg = mesg_buf;
2884                                 }
2885                         }
2886                         break;
2887                 case 27: /* escape */
2888                         attrset(0);
2889                         mvaddstr(0, cols-10, "ESC..."); clrtoeol();
2890                         c = getch();
2891                         switch (c) {
2892                         }
2893                         move(0, cols-10); clrtoeol();
2894                         break;
2895                 case 'C'-64:
2896                         if (replace)
2897                                 mesg = "Save-on-exit disabled. Use 'q' to quit.";
2898                         else
2899                                 mesg = "Use 'q' to quit.";
2900                         replace = 0;
2901                         break;
2902
2903                 case 'q':
2904                         cnt = 0;
2905                         any = 0;
2906                         for (i = 0; i < np; i++)
2907                                 if (pl[i].end && !pl[i].is_merge)
2908                                         cnt++;
2909                                 else if (pl[i].end)
2910                                         any++;
2911                         if (!cnt) {
2912                                 endwin();
2913                                 return;
2914                         }
2915                         refresh = 2;
2916                         if (replace)
2917                                 ans = 1;
2918                         else if (any) {
2919                                 sprintf(saveall_buf, saveall_msg,
2920                                         cnt, cnt == 1 ? "" : "s", cnt+any);
2921                                 ans = help_window(saveall_query, NULL, 1);
2922                         } else
2923                                 ans = 0;
2924                         if (ans < 0)
2925                                 break;
2926                         if (ans) {
2927                                 for (i = 0; i < np; i++) {
2928                                         if (pl[i].end
2929                                             && !pl[i].is_merge)
2930                                                 save_one(f, &pl[i],
2931                                                          reverse,
2932                                                          ignore_blanks, backup);
2933                                 }
2934                         } else
2935                                 cnt = 0;
2936                         endwin();
2937                         if (cnt)
2938                                 printf("%d file%s saved\n", cnt,
2939                                        cnt == 1 ? "" : "s");
2940                         return;
2941
2942                 case 'A':
2943                         mode = 0; refresh = 1;
2944                         mesg = "Showing ALL files";
2945                         break;
2946                 case 'W':
2947                         mode = 1; refresh = 1;
2948                         mesg = "Showing Wiggled files";
2949                         break;
2950                 case 'C':
2951                         mode = 2; refresh = 1;
2952                         mesg = "Showing Conflicted files";
2953                         break;
2954
2955                 case 'S': /* Save updated file */
2956                         if (pl[pos].end == 0) {
2957                                 /* directory */
2958                                 mesg = "Cannot save a folder.";
2959                         } else if (pl[pos].is_merge) {
2960                                 /* Already saved */
2961                                 mesg = "File is already saved.";
2962                         } else {
2963                                 if (save_one(f, &pl[pos], reverse, ignore_blanks, backup) == 0) {
2964                                         pl[pos].is_merge = 1;
2965                                         snprintf(mesg_buf, cols,
2966                                                  "Saved file %s.",
2967                                                  pl[pos].file);
2968                                         pl[pos].chunks = pl[pos].conflicts;
2969                                         pl[pos].wiggles = 0;
2970                                 } else
2971                                         snprintf(mesg_buf, cols,
2972                                                  "Failed to save file %s.",
2973                                                  pl[pos].file);
2974                                 mesg = mesg_buf;
2975                                 refresh = 1;
2976                         }
2977                         break;
2978
2979                 case 'R': /* Restore updated file */
2980                         if (pl[pos].end == 0)
2981                                 mesg = "Cannot restore a folder.";
2982                         else if (!pl[pos].is_merge)
2983                                 mesg = "File has not been saved, cannot restore.";
2984                         else if (!backup)
2985                                 mesg = "Backups are disabled, nothing to restore!";
2986                         else {
2987                                 /* rename foo.porig to foo, and clear is_merge */
2988                                 char *file = pl[pos].file;
2989                                 char *orignew = wiggle_xmalloc(strlen(file) + 20);
2990                                 strcpy(orignew, file);
2991                                 strcat(orignew, ".porig");
2992                                 if (rename(orignew, file) == 0) {
2993                                         mesg = "File has been restored.";
2994                                         pl[pos].is_merge = 0;
2995                                         refresh = 1;
2996                                         calc_one(&pl[pos], f, reverse, ignore_blanks, just_diff);
2997                                 } else
2998                                         mesg = "Could not restore file!";
2999                         }
3000                         break;
3001
3002                 case 'I': /* Toggle ignoring blanks */
3003                         ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
3004                         refresh = 2;
3005                         for (i = 0; i < np; i++)
3006                                 pl[i].calced = 0;
3007                         break;
3008
3009                 case '?':
3010                         help_window(main_help, NULL, 0);
3011                         refresh = 2;
3012                         break;
3013
3014                 case KEY_RESIZE:
3015                         refresh = 2;
3016                         break;
3017                 }
3018         }
3019 }
3020
3021 static void catch(int sig)
3022 {
3023         if (sig == SIGINT && !intr_kills) {
3024                 signal(sig, catch);
3025                 return;
3026         }
3027         noraw();
3028         nl();
3029         endwin();
3030         printf("Died on signal %d\n", sig);
3031         fflush(stdout);
3032         if (sig != SIGBUS && sig != SIGSEGV)
3033                 exit(2);
3034         else
3035                 /* Otherwise return and wiggle_die */
3036                 signal(sig, NULL);
3037 }
3038
3039 static void term_init(int doraw)
3040 {
3041
3042         static int init_done = 0;
3043
3044         if (init_done)
3045                 return;
3046         init_done = 1;
3047
3048         signal(SIGINT, catch);
3049         signal(SIGQUIT, catch);
3050         signal(SIGTERM, catch);
3051         signal(SIGBUS, catch);
3052         signal(SIGSEGV, catch);
3053
3054         initscr();
3055         if (doraw)
3056                 raw();
3057         else
3058                 cbreak();
3059         noecho();
3060         start_color();
3061         use_default_colors();
3062         if (!has_colors()) {
3063                 a_delete = A_UNDERLINE;
3064                 a_added = A_BOLD;
3065                 a_common = A_NORMAL;
3066                 a_sep = A_STANDOUT;
3067                 a_already = A_STANDOUT;
3068                 a_has_conflicts = A_UNDERLINE;
3069                 a_has_wiggles = A_BOLD;
3070                 a_no_wiggles = A_NORMAL;
3071         } else {
3072                 init_pair(1, COLOR_RED, -1);
3073                 a_delete = COLOR_PAIR(1);
3074                 init_pair(2, COLOR_GREEN, -1);
3075                 a_added = COLOR_PAIR(2);
3076                 a_common = A_NORMAL;
3077                 init_pair(3, COLOR_WHITE, COLOR_GREEN);
3078                 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
3079                 init_pair(4, -1, COLOR_YELLOW);
3080                 a_void = COLOR_PAIR(4);
3081                 init_pair(5, COLOR_BLUE, -1);
3082                 a_unmatched = COLOR_PAIR(5);
3083                 init_pair(6, COLOR_CYAN, -1);
3084                 a_extra = COLOR_PAIR(6);
3085
3086                 init_pair(7, COLOR_BLACK, COLOR_CYAN);
3087                 a_already = COLOR_PAIR(7);
3088
3089                 a_has_conflicts = a_delete;
3090                 a_has_wiggles = a_added;
3091                 a_no_wiggles = a_unmatched;
3092                 a_saved = a_extra;
3093         }
3094         nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
3095         mousemask(ALL_MOUSE_EVENTS, NULL);
3096 }
3097
3098 int vpatch(int argc, char *argv[], int patch, int strip,
3099            int reverse, int replace, char *outfilename,
3100            int selftest, int ignore_blanks, int backup)
3101 {
3102         /* NOTE argv[0] is first arg...
3103          * Behaviour depends on number of args and 'patch'.
3104          * If 'patch' is '1', assume a patch. if '2', assume a diff.
3105          * 0: A multi-file patch or diff is read from stdin.
3106          *    A 'patch' is applies to relevant files. A 'diff' is just
3107          *    displayed.
3108          * 1: if 'patch', parse it as a multi-file patch/diff and allow
3109          *    the files to be browsed.
3110          *    if filename ends '.rej', then treat it as a patch/diff again
3111          *    a file with the same basename
3112          *    Else treat the file as a merge (with conflicts) and view it.
3113          *
3114          * 2: First file is original, second is patch unless patch==2,
3115          *    then two files need to be diffed.
3116          * 3: Files are: original previous new.  The diff between 'previous' and
3117          *    'new' needs to be applied to 'original'.
3118          *
3119          * If a multi-file patch is being read, 'strip' tells how many
3120          * path components to strip.  If it is -1, we guess based on
3121          * existing files.
3122          * If 'reverse' is given, when we invert any patch or diff
3123          * If 'replace' then we save the resulting merge.
3124          */
3125         FILE *in;
3126         FILE *f;
3127         struct plist *pl;
3128         int num_patches;
3129         int just_diff = (patch == 2);
3130
3131         switch (argc) {
3132         default:
3133                 fprintf(stderr, "%s: too many file names given.\n", wiggle_Cmd);
3134                 exit(1);
3135
3136         case 0: /* stdin is a patch or diff */
3137                 if (lseek(fileno(stdin), 0L, 1) == -1) {
3138                         /* cannot seek, so need to copy to a temp file */
3139                         f = tmpfile();
3140                         if (!f) {
3141                                 fprintf(stderr, "%s: Cannot create temp file\n", wiggle_Cmd);
3142                                 exit(1);
3143                         }
3144                         pl = wiggle_parse_patch(stdin, f, &num_patches);
3145                         in = f;
3146                 } else {
3147                         pl = wiggle_parse_patch(stdin, NULL, &num_patches);
3148                         in = fdopen(dup(0), "r");
3149                 }
3150                 /* use stderr for keyboard input */
3151                 dup2(2, 0);
3152                 if (!just_diff &&
3153                     wiggle_set_prefix(pl, num_patches, strip) == 0) {
3154                         fprintf(stderr, "%s: aborting\n", wiggle_Cmd);
3155                         exit(2);
3156                 }
3157                 pl = wiggle_sort_patches(pl, &num_patches);
3158                 main_window(pl, num_patches, in, reverse, replace, ignore_blanks,
3159                             just_diff, backup);
3160                 wiggle_plist_free(pl, num_patches);
3161                 fclose(in);
3162                 break;
3163
3164         case 1: /* a patch/diff, a .rej, or a merge file */
3165                 f = fopen(argv[0], "r");
3166                 if (!f) {
3167                         fprintf(stderr, "%s: cannot open %s\n", wiggle_Cmd, argv[0]);
3168                         exit(1);
3169                 }
3170                 wiggle_check_dir(argv[0], fileno(f));
3171                 if (patch) {
3172                         pl = wiggle_parse_patch(f, NULL, &num_patches);
3173                         if (!just_diff && wiggle_set_prefix(pl, num_patches, strip) == 0) {
3174                                 fprintf(stderr, "%s: aborting\n", wiggle_Cmd);
3175                                 exit(2);
3176                         }
3177                         pl = wiggle_sort_patches(pl, &num_patches);
3178                         main_window(pl, num_patches, f, reverse, replace,
3179                                     ignore_blanks, just_diff, backup);
3180                         wiggle_plist_free(pl, num_patches);
3181                 } else if (strlen(argv[0]) > 4 &&
3182                            strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
3183                         char *origname = strdup(argv[0]);
3184                         origname[strlen(origname) - 4] = '\0';
3185                         show_merge(origname, f, reverse, 0, NULL, NULL,
3186                                    replace, outfilename,
3187                                    selftest, ignore_blanks, just_diff, backup);
3188                 } else
3189                         show_merge(argv[0], f, reverse, 1, NULL, NULL,
3190                                    replace, outfilename,
3191                                    selftest, ignore_blanks, just_diff, backup);
3192
3193                 break;
3194         case 2: /* an orig and a diff/.rej  or two files */
3195                 if (just_diff) {
3196                         show_merge(NULL, NULL, reverse, 0, argv[0], argv[1],
3197                                    replace, outfilename,
3198                                    selftest, ignore_blanks, just_diff, backup);
3199                         break;
3200                 }
3201                 f = fopen(argv[1], "r");
3202                 if (!f) {
3203                         fprintf(stderr, "%s: cannot open %s\n", wiggle_Cmd, argv[0]);
3204                         exit(1);
3205                 }
3206                 wiggle_check_dir(argv[1], fileno(f));
3207                 show_merge(argv[0], f, reverse, 0, NULL, NULL,
3208                            replace, outfilename,
3209                            selftest, ignore_blanks, just_diff, backup);
3210                 break;
3211         case 3: /* orig, before, after */
3212                 show_merge(argv[0], NULL, reverse, 0, argv[1], argv[2],
3213                            replace, outfilename,
3214                            selftest, ignore_blanks, just_diff, backup);
3215                 break;
3216         }
3217
3218         noraw();
3219         nl();
3220         endwin();
3221         exit(0);
3222 }