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