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