Release wiggle v1.0
[wiggle/upstream.git] / vpatch.c
blob2ec31d56ed370b70eed325368890468cc6d1d249
1 /*
2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
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.
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.
18 * You should have received a copy of the GNU General Public License
19 * along with this program.
21 * Author: Neil Brown
22 * Email: <neilb@suse.de>
26 * vpatch - visual front end for wiggle - aka Browse mode.
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.
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.
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>
48 static void term_init(int raw);
50 static int intr_kills = 0;
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;
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
68 * A help text is an array of lines of text
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
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
93 char *help_corrupt[] = {
94 "This patch appears to be corrupt",
95 "Please type 'q' to continue",
96 NULL
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.
103 static int help_window(char *page1[], char *page2[], int query)
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;
113 getmaxyx(stdscr, rows, cols);
115 if (cols < 70) {
116 left = 6;
117 cols = cols-12;
118 } else {
119 left = (cols-58)/2 - 1;
120 cols = 58;
123 if (rows < 21) {
124 top = 3;
125 rows = rows - 6;
126 } else {
127 top = (rows-15)/2 - 1;
128 rows = 15;
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, '-');
137 for (r = top; r < top + rows ; r++) {
138 mvaddch(r, left-1, '|');
139 mvaddch(r, left+cols, '|');
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");
155 (void)attrset(A_NORMAL);
157 while (1) {
158 char **lnp = page + line;
160 /* Draw as much of the page at the current offset
161 * as fits.
163 for (r = 0; r < rows; r++) {
164 char *ln = *lnp;
165 int sh = shift;
166 if (ln)
167 lnp++;
168 else
169 ln = "";
171 while (*ln && sh > 0) {
172 ln++;
173 sh--;
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);
184 move(top+rows-1, left);
185 ch = getch();
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;
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;
222 break;
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;
238 break;
240 case KEY_LEFT:
241 if (shift > 0)
242 shift--;
243 break;
244 case KEY_RIGHT:
245 shift++;
246 break;
248 case KEY_UP:
249 if (line > 0)
250 line--;
251 break;
252 case KEY_DOWN:
253 if (page[line])
254 line++;
255 break;
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",
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.
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 */
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.
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
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'.
316 * Each location may or may not be visible depending on certain
317 * display options.
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.
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.
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
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
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 */
363 /* used for checking location during search */
364 static int same_mp(struct mp a, struct mp b)
366 return a.m == b.m &&
367 a.s == b.s &&
368 a.o == b.o;
370 static int same_mpos(struct mpos a, struct mpos b)
372 return same_mp(a.p, b.p) &&
373 (a.state == b.state || a.state == 0 || b.state == 0);
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.
381 static int stream_valid(int s, enum mergetype type)
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;
399 return 0;
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.
408 static struct elmnt next_melmnt(struct mp *pos,
409 struct file fm, struct file fb, struct file fa,
410 struct merge *m)
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;
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++;
437 } while (!stream_valid(pos->s, m[pos->m].oldtype));
438 } else
439 break;
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;
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];
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)
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--;
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--;
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;
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;
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];
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.
513 static int visible(int mode, struct merge *m, struct mpos *pos)
515 enum mergetype type;
516 int stream = pos->p.s;
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;
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;
582 break;
584 return -1;
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
593 static int check_line(struct mpos pos, struct file fm, struct file fb,
594 struct file fa,
595 struct merge *m, int mode)
597 int rv = 0;
598 struct elmnt e;
599 int unmatched = 0;
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;
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.
644 if (e.start && m[pos.p.m].oldtype == Changed)
645 rv |= CHANGES;
647 if (unmatched && (rv & CHANGES))
648 rv |= WIGGLED;
649 return rv;
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.
659 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
660 struct file fa,
661 struct merge *m, int mode)
663 int mask;
664 do {
665 struct mp prv;
666 int mode2;
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;
677 mode2 = check_line(*pos, fm, fb, fa, m, mode);
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;
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;
708 } while (visible(mode&mask, m, pos) < 0);
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)
717 int mask;
718 do {
719 struct mp prv;
720 int mode2;
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;
733 mode2 = check_line(*pos, fm, fb, fa, m, mode);
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;
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;
764 } while (visible(mode&mask, m, pos) < 0);
767 /* blank a whole row of display */
768 static void blank(int row, int start, int cols, unsigned int attr)
770 (void)attrset(attr);
771 move(row, start);
772 while (cols-- > 0)
773 addch(' ');
776 /* search of a string on one display line. If found, update the
777 * cursor.
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)
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
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.
801 struct elmnt e;
802 int found = 0;
803 struct mp mp;
804 int o;
805 int len = strlen(search);
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;
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;
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;
846 return found;
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
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).
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.
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)
893 struct elmnt e;
894 int col = 0;
895 char tag;
896 unsigned int tag_attr;
897 int changed = 0;
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;
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;
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;
930 return;
933 (void)attrset(tag_attr);
934 mvaddch(row, offset, tag);
935 offset++;
936 cols--;
937 (void)attrset(A_NORMAL);
939 if (check_line(pos, fm, fb, fa, m, mode))
940 changed = 1;
942 /* find previous visible newline, or start of file */
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));
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;
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;
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++;
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;
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;
1029 c++;
1031 if ((ends_line(e)
1032 && visible(mode, m, &pos) != -1))
1033 break;
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;
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);
1061 mvaddstr(row, col-start+offset, b);
1062 col += strlen(b);
1064 blank(row, col-start+offset, start+cols-col,
1065 e.start
1066 ? (unsigned)visible(mode, m, &pos)
1067 : A_NORMAL);
1070 /* Draw either 1 or 2 sides depending on the mode. */
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)
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
1083 int lcols, rcols;
1085 mode |= check_line(pos, fm, fb, fa, m, mode);
1087 if ((mode & (BEFORE|AFTER)) &&
1088 (mode & (ORIG|RESULT))) {
1090 lcols = (cols-1)/2;
1091 rcols = cols - lcols - 1;
1093 (void)attrset(A_STANDOUT);
1094 mvaddch(row, lcols, '|');
1096 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1097 fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
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);
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
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
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
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
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
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
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.",
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
1177 static char *merge_window_help[] = {
1178 " Highlight Colours and Keystroke commands",
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",
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:",
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",
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",
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
1247 static char *save_query[] = {
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
1257 static char *toggle_ignore[] = {
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
1267 static void do_edit(char *file, int line)
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);
1287 static void *memdup(void *a, int len)
1289 char *r = malloc(len);
1290 memcpy(r, a, len);
1291 return r;
1294 static int merge_window(struct plist *p, FILE *f, int reverse, int replace,
1295 int selftest, int ignore_blanks, int just_diff)
1297 /* Display the merge window in one of the selectable modes,
1298 * starting with the 'merge' mode.
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
1307 * A 'position' is a struct mpos
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
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;
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
1359 int ignore_case = 2;
1360 /* We record all the places we find so 'backspace'
1361 * can easily return to the previous one
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;
1372 void free_stuff(void)
1374 free(fm.list);
1375 free(fb.list);
1376 free(fa.list);
1377 free(csl1);
1378 free(csl2);
1379 free(ci.merger);
1381 void find_line(int ln)
1383 pos.p.m = 0; /* merge node */
1384 pos.p.s = 0; /* stream number */
1385 pos.p.o = -1; /* offset */
1386 pos.p.lineno = 1;
1387 pos.state = 0;
1388 memset(&curs, 0, sizeof(curs));
1390 next_mline(&pos, fm, fb, fa, ci.merger, mode);
1391 while (pos.p.lineno < ln && ci.merger[pos.p.m].type != End);
1393 void prepare_merge(int ch)
1395 /* FIXME check for errors in the stream */
1396 fm = split_stream(sm, ByWord | ignore_blanks);
1397 fb = split_stream(sb, ByWord | ignore_blanks);
1398 fa = split_stream(sa, ByWord | ignore_blanks);
1400 if (ch && !just_diff)
1401 csl1 = pdiff(fm, fb, ch);
1402 else
1403 csl1 = diff(fm, fb);
1404 csl2 = diff_patch(fb, fa);
1406 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
1407 for (i = 0; ci.merger[i].type != End; i++)
1408 ci.merger[i].oldtype = ci.merger[i].type;
1411 if (selftest) {
1412 intr_kills = 1;
1413 selftest = 1;
1416 if (f == NULL) {
1417 if (!p->is_merge) {
1418 /* three separate files */
1419 sb = load_file(p->before);
1420 sa = load_file(p->after);
1421 if (just_diff)
1422 sm = sb;
1423 else
1424 sm = load_file(p->file);
1425 } else {
1426 /* One merge file */
1427 sp = load_file(p->file);
1428 if (reverse)
1429 split_merge(sp, &sm, &sa, &sb);
1430 else
1431 split_merge(sp, &sm, &sb, &sa);
1432 free(sp.body);
1434 ch = 0;
1435 } else {
1436 sp = load_segment(f, p->start, p->end);
1437 if (p->is_merge) {
1438 if (reverse)
1439 split_merge(sp, &sm, &sa, &sb);
1440 else
1441 split_merge(sp, &sm, &sb, &sa);
1442 ch = 0;
1443 } else {
1444 if (reverse)
1445 ch = split_patch(sp, &sa, &sb);
1446 else
1447 ch = split_patch(sp, &sb, &sa);
1448 if (just_diff)
1449 sm = sb;
1450 else
1451 sm = load_file(p->file);
1453 free(sp.body);
1455 if (!sm.body || !sb.body || !sa.body) {
1456 if (!just_diff)
1457 free(sm.body);
1458 free(sb.body);
1459 free(sa.body);
1460 term_init(1);
1461 if (!sm.body)
1462 help_window(help_missing, NULL, 0);
1463 else
1464 help_window(help_corrupt, NULL, 0);
1465 endwin();
1466 return 0;
1468 prepare_merge(ch);
1469 term_init(!selftest);
1471 row = 1;
1472 find_line(1);
1474 while (1) {
1475 unsigned int next;
1476 if (refresh >= 2) {
1477 clear();
1478 refresh = 1;
1480 if (row < 1 || row >= lastrow)
1481 refresh = 1;
1482 if (curs.alt)
1483 refresh = 1;
1485 if (mode == (ORIG|RESULT)) {
1486 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1487 if (cmode & (WIGGLED | CONFLICTED)) {
1488 if (splitrow < 0) {
1489 splitrow = (rows+1)/2;
1490 lastrow = splitrow - 1;
1491 refresh = 1;
1493 } else if (!curs.alt && splitrow >= 0) {
1494 splitrow = -1;
1495 lastrow = rows-1;
1496 refresh = 1;
1498 } else if (splitrow >= 0) {
1499 splitrow = -1;
1500 lastrow = rows-1;
1501 refresh = 1;
1504 if (refresh) {
1505 getmaxyx(stdscr, rows, cols);
1506 rows--; /* keep last row clear */
1507 if (splitrow >= 0) {
1508 splitrow = (rows+1)/2;
1509 lastrow = splitrow - 1;
1510 } else
1511 lastrow = rows - 1;
1513 if (row < -3)
1514 row = lastrow/2+1;
1515 if (row < 1)
1516 row = 1;
1517 if (row > lastrow+3)
1518 row = lastrow/2+1;
1519 if (row >= lastrow)
1520 row = lastrow-1;
1523 /* Always refresh the line */
1524 while (start > curs.target) {
1525 start -= 8;
1526 refresh = 1;
1528 if (start < 0)
1529 start = 0;
1530 vispos = pos; /* visible position - if cursor is in
1531 * alternate pane, pos might not be visible
1532 * in main pane. */
1533 if (check_line(vispos, fm, fb, fa, ci.merger, mode)
1534 & CHANGES) {
1535 if (vispos.state == 0) {
1536 vispos.state = 1;
1537 vispos.lo = vispos.p;
1538 vispos.hi = vispos.p;
1540 } else {
1541 vispos.state = 0;
1544 if (visible(mode, ci.merger, &vispos) < 0)
1545 prev_mline(&vispos, fm, fb, fa, ci.merger, mode);
1546 if (!curs.alt)
1547 pos= vispos;
1548 retry:
1549 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1550 vispos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1551 if (curs.width == 0 && start < curs.col) {
1552 /* width == 0 implies it appear after end-of-screen */
1553 start += 8;
1554 refresh = 1;
1555 goto retry;
1557 if (curs.col < start) {
1558 start -= 8;
1559 refresh = 1;
1560 if (start < 0)
1561 start = 0;
1562 goto retry;
1564 if (refresh) {
1565 refresh = 0;
1567 tpos = vispos;
1569 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1570 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1571 draw_mline(mode, i--, start, cols,
1572 fm, fb, fa, ci.merger,
1573 tpos, NULL);
1576 if (i > 0) {
1577 row -= (i+1);
1578 refresh = 1;
1579 goto retry;
1581 toppos = tpos;
1582 while (i >= 1)
1583 blank(i--, 0, cols, a_void);
1584 tpos = vispos;
1585 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1586 draw_mline(mode, i++, start, cols,
1587 fm, fb, fa, ci.merger,
1588 tpos, NULL);
1589 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1591 botpos = tpos; botrow = i;
1592 while (i <= lastrow)
1593 blank(i++, 0, cols, a_void);
1596 if (splitrow >= 0) {
1597 struct mpos spos = pos;
1598 int smode = BEFORE|AFTER;
1599 int srow = (rows + splitrow)/2;
1600 if (check_line(spos, fm, fb, fa, ci.merger, smode)
1601 & CHANGES) {
1602 if (spos.state == 0)
1603 spos.state = 1;
1604 } else {
1605 spos.state = 0;
1607 if (visible(smode, ci.merger, &spos) < 0)
1608 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1609 /* Now hi/lo might be wrong, so lets fix it. */
1610 tpos = spos;
1611 if (spos.state)
1612 /* 'hi' might be wrong so we mustn't depend
1613 * on it while walking back. So set state
1614 * to 1 to avoid ever testing it.
1616 spos.state = 1;
1617 while (spos.p.m >= 0 && spos.state != 0)
1618 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1619 while (!same_mpos(spos, tpos) &&
1620 ci.merger[spos.p.m].type != End)
1621 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1623 (void)attrset(a_sep);
1624 for (i = 0; i < cols; i++)
1625 mvaddstr(splitrow, i, "-");
1627 tpos = spos;
1628 for (i = srow-1; i > splitrow; i--) {
1629 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1630 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1631 tpos, NULL);
1633 while (i > splitrow)
1634 blank(i--, 0, cols, a_void);
1635 tpos = spos;
1636 for (i = srow;
1637 i < rows && ci.merger[tpos.p.m].type != End;
1638 i++) {
1639 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1640 tpos,
1641 (i == srow && curs.alt) ? &curs : NULL);
1642 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1644 while (i < rows)
1645 blank(i++, 0, cols, a_void);
1647 /* Now that curs is accurate, report the type */
1649 int l = 0;
1650 char buf[100];
1651 if (p->file)
1652 l = snprintf(buf, 100, "File: %s%s ",
1653 p->file, reverse ? " - reversed" : "");
1654 snprintf(buf+l, 100-l, "Mode: %s", modename);
1655 (void)attrset(A_BOLD);
1656 mvaddstr(0, 0, buf);
1657 (void)attrset(A_NORMAL);
1658 if (ignore_blanks)
1659 addstr(" (ignoring blanks)");
1660 clrtoeol();
1661 (void)attrset(A_BOLD);
1662 if (ci.merger[curs.pos.m].type != ci.merger[curs.pos.m].oldtype)
1663 l = snprintf(buf, sizeof(buf), "%s->", typenames[ci.merger[curs.pos.m].oldtype]);
1664 snprintf(buf+l, sizeof(buf)-l, "%s ln:%d",
1665 typenames[ci.merger[curs.pos.m].type],
1666 (pos.p.lineno-1)/2);
1667 mvaddstr(0, cols - strlen(buf) - 1, buf);
1669 #define META(c) ((c)|0x1000)
1670 #define SEARCH(c) ((c)|0x2000)
1671 #define CTRLX(c) ((c)|0x4000)
1672 move(rows, 0);
1673 (void)attrset(A_NORMAL);
1674 if (mesg) {
1675 attrset(A_REVERSE);
1676 addstr(mesg);
1677 mesg = NULL;
1678 attrset(A_NORMAL);
1680 if (num >= 0) {
1681 char buf[10];
1682 snprintf(buf, 10, "%d ", num);
1683 addstr(buf);
1685 if (meta & META(0))
1686 addstr("ESC...");
1687 if (meta & CTRLX(0))
1688 addstr("C-x ");
1689 if (meta & SEARCH(0)) {
1690 if (searchdir < 0)
1691 addstr("Backwards ");
1692 addstr("Search: ");
1693 addstr(search);
1694 if (search_notfound)
1695 addstr(" - Not Found.");
1696 search_notfound = 0;
1698 clrtoeol();
1699 /* '+1' to skip over the leading +/-/| char */
1700 if (curs.alt && splitrow > 0)
1701 move((rows + splitrow)/2, curs.col - start + 1);
1702 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1703 (mode & (ORIG|RESULT))))
1704 move(row, curs.col-start + (cols-1)/2+2);
1705 else
1706 move(row, curs.col-start+1);
1707 switch (selftest) {
1708 case 0:
1709 c = getch(); break;
1710 case 1:
1711 c = 'n'; break;
1712 case 2:
1713 c = 'q'; break;
1715 tmeta = meta; meta = 0;
1716 tnum = num; num = -1;
1717 cswitch = c | tmeta;
1718 /* Handle some ranges */
1719 /* case '0' ... '9': */
1720 if (cswitch >= '0' && cswitch <= '9')
1721 cswitch = '0';
1722 /* case SEARCH(' ') ... SEARCH('~'): */
1723 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1724 cswitch = SEARCH(' ');
1726 switch (cswitch) {
1727 case 27: /* escape */
1728 case META(27):
1729 meta = META(0);
1730 break;
1732 case 'X'-64:
1733 case META('X'-64):
1734 meta = CTRLX(0);
1735 break;
1737 case META('<'): /* start of file */
1738 start:
1739 tpos = pos; row++;
1740 do {
1741 pos = tpos; row--;
1742 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1743 } while (tpos.p.m >= 0);
1744 if (row <= 0)
1745 row = 0;
1746 break;
1747 case META('>'): /* end of file */
1748 case 'G':
1749 if (tnum >= 0)
1750 goto start;
1751 tpos = pos; row--;
1752 do {
1753 pos = tpos; row++;
1754 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1755 } while (ci.merger[tpos.p.m].type != End);
1756 if (row >= lastrow)
1757 row = lastrow;
1758 break;
1759 case '0': /* actually '0'...'9' */
1760 if (tnum < 0)
1761 tnum = 0;
1762 num = tnum*10 + (c-'0');
1763 break;
1764 case 'C'-64:
1765 if (replace)
1766 mesg = "Autosave disabled";
1767 else
1768 mesg = "Use 'q' to quit";
1769 replace = 0;
1770 break;
1771 case 'S':
1772 mesg = "Will auto-save on exit, using Ctrl-C to cancel";
1773 replace = 1;
1774 break;
1775 case 'q':
1776 refresh = 2;
1777 answer = 0;
1778 if (replace)
1779 answer = 1;
1780 else if (changes)
1781 answer = help_window(save_query, NULL, 1);
1782 if (answer < 0)
1783 break;
1784 if (answer) {
1785 p->wiggles = 0;
1786 p->conflicts = isolate_conflicts(
1787 fm, fb, fa, csl1, csl2, 0,
1788 ci.merger, 0, &p->wiggles);
1789 p->chunks = p->conflicts;
1790 save_merge(fm, fb, fa, ci.merger,
1791 p->outfile ?: p->file,
1792 p->outfile ? 0 : !p->is_merge);
1794 if (!just_diff)
1795 free(sm.body);
1796 free(sb.body);
1797 free(sa.body);
1798 free_stuff();
1799 endwin();
1800 return answer;
1802 case 'I': /* Toggle ignoring of spaces */
1803 if (changes) {
1804 refresh = 2;
1805 answer = help_window(toggle_ignore, NULL, 1);
1806 if (answer <= 0)
1807 break;
1808 changes = 0;
1810 free_stuff();
1811 ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
1812 prepare_merge(ch);
1813 find_line(pos.p.lineno);
1815 refresh = 2;
1816 break;
1818 case 'v':
1819 if (!p->file || just_diff) {
1820 mesg = "Cannot run editor when diffing";
1821 break;
1823 tempname = p->file;
1824 lineno = save_tmp_merge(fm, fb, fa, ci.merger,
1825 &tempname,
1826 ci.merger + pos.p.m,
1827 pos.p.s,
1828 pos.p.o);
1829 endwin();
1830 free_stuff();
1831 do_edit(tempname, lineno);
1832 sp = load_file(tempname);
1833 unlink(tempname);
1834 split_merge(sp, &sm, &sb, &sa);
1835 if (sp.len == sm.len &&
1836 memcmp(sp.body, sm.body, sm.len) == 0 &&
1837 !p->is_merge) {
1838 /* no conflicts left, so display diff */
1839 free(sm.body);
1840 sm = load_file(p->file);
1841 free(sb.body);
1842 sb = sm;
1843 sb.body = memdup(sm.body, sm.len);
1845 free(sp.body);
1846 ignore_blanks = 0;
1847 prepare_merge(0);
1848 refresh = 2;
1849 changes = 1;
1851 find_line(pos.p.lineno);
1853 doupdate();
1854 break;
1856 case '/':
1857 case 'S'-64:
1858 /* incr search forward */
1859 meta = SEARCH(0);
1860 searchlen = 0;
1861 search[searchlen] = 0;
1862 searchdir = 1;
1863 break;
1864 case '\\':
1865 case 'R'-64:
1866 /* incr search backwards */
1867 meta = SEARCH(0);
1868 searchlen = 0;
1869 search[searchlen] = 0;
1870 searchdir = -1;
1871 break;
1872 case SEARCH('G'-64):
1873 case SEARCH('S'-64):
1874 case SEARCH('R'-64):
1875 /* search again */
1876 if ((c|tmeta) == SEARCH('R'-64))
1877 searchdir = -2;
1878 else
1879 searchdir = 2;
1880 meta = SEARCH(0);
1881 tpos = pos; trow = row;
1882 goto search_again;
1884 case SEARCH('H'-64):
1885 case SEARCH(KEY_BACKSPACE):
1886 meta = SEARCH(0);
1887 if (anchor) {
1888 struct search_anchor *a;
1889 a = anchor;
1890 anchor = a->next;
1891 free(a);
1893 if (anchor) {
1894 struct search_anchor *a;
1895 a = anchor;
1896 anchor = a->next;
1897 pos = a->pos;
1898 row = a->row;
1899 start = a->start;
1900 curs = a->curs;
1901 curs.target = -1;
1902 search_notfound = a->notfound;
1903 searchlen = a->searchlen;
1904 search[searchlen] = 0;
1905 free(a);
1906 refresh = 1;
1908 break;
1909 case SEARCH(' '): /* actually ' '...'~' */
1910 case SEARCH('\t'):
1911 meta = SEARCH(0);
1912 if (searchlen < sizeof(search)-1)
1913 search[searchlen++] = c & (0x7f);
1914 search[searchlen] = 0;
1915 tpos = pos; trow = row;
1916 search_again:
1917 search_notfound = 1;
1918 if (ignore_case == 1 || ignore_case == 2) {
1919 unsigned int i;
1920 ignore_case = 2;
1921 for (i=0; i < searchlen; i++)
1922 if (isupper(search[i])) {
1923 ignore_case = 1;
1924 break;
1927 do {
1928 if (mcontains(tpos, fm, fb, fa, ci.merger,
1929 mmode, search, &curs, searchdir,
1930 ignore_case >= 2)) {
1931 curs.target = -1;
1932 pos = tpos;
1933 row = trow;
1934 search_notfound = 0;
1935 break;
1937 if (searchdir < 0) {
1938 trow--;
1939 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1940 } else {
1941 trow++;
1942 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1944 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1945 searchdir /= abs(searchdir);
1947 break;
1948 case 'L'-64:
1949 refresh = 2;
1950 row = lastrow / 2;
1951 break;
1953 case KEY_NPAGE:
1954 case ' ':
1955 case 'V'-64: /* page down */
1956 pos = botpos;
1957 if (botrow <= lastrow) {
1958 row = botrow;
1959 if (selftest == 1)
1960 selftest = 2;
1961 } else
1962 row = 2;
1963 refresh = 1;
1964 break;
1965 case KEY_PPAGE:
1966 case KEY_BACKSPACE:
1967 case META('v'): /* page up */
1968 pos = toppos;
1969 row = lastrow-1;
1970 refresh = 1;
1971 break;
1973 case KEY_MOUSE:
1974 if (getmouse(&mevent) != OK)
1975 break;
1976 /* First see if this is on the 'other' pane */
1977 if (splitrow > 0) {
1978 /* merge mode, top and bottom */
1979 if ((curs.alt && mevent.y < splitrow) ||
1980 (!curs.alt && mevent.y > splitrow)) {
1981 goto other_pane;
1983 } else if (mode == (ORIG|RESULT|BEFORE|AFTER)) {
1984 /* side-by-side mode */
1985 if ((curs.alt && mevent.x < cols/2) ||
1986 (!curs.alt && mevent.x > cols/2)) {
1987 goto other_pane;
1990 /* Now try to find the right line */
1991 if (splitrow < 0 || !curs.alt)
1992 trow = row;
1993 else
1994 trow = (rows + splitrow)/2;
1995 while (trow > mevent.y) {
1996 tpos = pos;
1997 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1998 if (tpos.p.m >= 0) {
1999 pos = tpos;
2000 trow--;
2001 } else
2002 break;
2004 while (trow < mevent.y) {
2005 tpos = pos;
2006 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2007 if (ci.merger[tpos.p.m].type != End) {
2008 pos = tpos;
2009 trow++;
2010 } else
2011 break;
2013 if (splitrow < 0 || !curs.alt)
2014 /* it is OK to change the row */
2015 row = trow;
2017 /* Now set the target column */
2018 if (mode == (ORIG|RESULT|BEFORE|AFTER) &&
2019 curs.alt)
2020 curs.target = start + mevent.x - cols / 2 - 1;
2021 else
2022 curs.target = start + mevent.x - 1;
2023 break;
2024 case 'j':
2025 case 'n':
2026 case 'N'-64:
2027 case KEY_DOWN:
2028 if (tnum < 0)
2029 tnum = 1;
2030 for (; tnum > 0 ; tnum--) {
2031 tpos = pos;
2032 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2033 if (ci.merger[tpos.p.m].type != End) {
2034 pos = tpos;
2035 row++;
2036 } else {
2037 if (selftest == 1)
2038 selftest = 2;
2039 break;
2042 break;
2043 case 'N':
2044 /* Next diff */
2045 tpos = pos; row--;
2046 do {
2047 pos = tpos; row++;
2048 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2049 } while (!(pos.state == 0
2050 && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2051 & (CONFLICTED|WIGGLED)) == 0)
2052 && ci.merger[tpos.p.m].type != End);
2053 tpos = pos; row--;
2054 do {
2055 pos = tpos; row++;
2056 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2057 } while (pos.state == 0
2058 && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2059 & (CONFLICTED|WIGGLED)) == 0
2060 && ci.merger[tpos.p.m].type != End);
2062 break;
2063 case 'C':
2064 /* Next conflict */
2065 tpos = pos; row--;
2066 do {
2067 pos = tpos; row++;
2068 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2069 } while (!(check_line(pos, fm, fb, fa, ci.merger, mmode)
2070 & CONFLICTED) == 0
2071 && ci.merger[tpos.p.m].type != End);
2072 tpos = pos; row--;
2073 do {
2074 pos = tpos; row++;
2075 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2076 } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2077 & CONFLICTED) == 0
2078 && ci.merger[tpos.p.m].type != End);
2080 break;
2082 case 'P':
2083 /* Previous diff */
2084 tpos = pos; row++;
2085 do {
2086 pos = tpos; row--;
2087 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2088 } while (tpos.state == 0
2089 && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2090 & (CONFLICTED|WIGGLED)) == 0
2091 && tpos.p.m >= 0);
2092 tpos = pos; row++;
2093 do {
2094 pos = tpos; row--;
2095 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2096 } while (!(tpos.state == 0
2097 && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2098 & (CONFLICTED|WIGGLED)) == 0)
2099 && tpos.p.m >= 0);
2100 break;
2102 case 'k':
2103 case 'p':
2104 case 'P'-64:
2105 case KEY_UP:
2106 if (tnum < 0)
2107 tnum = 1;
2108 for (; tnum > 0 ; tnum--) {
2109 tpos = pos;
2110 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2111 if (tpos.p.m >= 0) {
2112 pos = tpos;
2113 row--;
2114 } else
2115 break;
2117 break;
2119 case KEY_LEFT:
2120 case 'h':
2121 /* left */
2122 curs.target = curs.col - 1;
2123 if (curs.target < 0) {
2124 /* Try to go to end of previous line */
2125 tpos = pos;
2126 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2127 if (tpos.p.m >= 0) {
2128 pos = tpos;
2129 row--;
2130 curs.pos = pos.p;
2131 curs.target = -1;
2132 } else
2133 curs.target = 0;
2135 break;
2136 case KEY_RIGHT:
2137 case 'l':
2138 /* right */
2139 if (curs.width >= 0)
2140 curs.target = curs.col + curs.width;
2141 else {
2142 /* end of line, go to next */
2143 tpos = pos;
2144 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2145 if (ci.merger[tpos.p.m].type != End) {
2146 pos = tpos;
2147 curs.pos = pos.p;
2148 row++;
2149 curs.target = 0;
2152 break;
2154 case '^':
2155 case 'A'-64:
2156 /* Start of line */
2157 curs.target = 0;
2158 break;
2159 case '$':
2160 case 'E'-64:
2161 /* End of line */
2162 curs.target = 1000;
2163 break;
2165 case CTRLX('o'):
2166 case 'O':
2167 other_pane:
2168 curs.alt = !curs.alt;
2169 if (curs.alt && mode == (ORIG|RESULT))
2170 mmode = (BEFORE|AFTER);
2171 else
2172 mmode = mode;
2173 break;
2175 case 'a': /* 'after' view in patch window */
2176 if (mode == AFTER)
2177 goto set_merge;
2178 mode = AFTER; modename = "after"; modehelp = after_help;
2179 mmode = mode; curs.alt = 0;
2180 refresh = 3;
2181 break;
2182 case 'b': /* 'before' view in patch window */
2183 if (mode == BEFORE)
2184 goto set_merge;
2185 mode = BEFORE; modename = "before"; modehelp = before_help;
2186 mmode = mode; curs.alt = 0;
2187 refresh = 3;
2188 break;
2189 case 'o': /* 'original' view in the merge window */
2190 if (mode == ORIG)
2191 goto set_merge;
2192 mode = ORIG; modename = "original"; modehelp = orig_help;
2193 mmode = mode; curs.alt = 0;
2194 refresh = 3;
2195 break;
2196 case 'r': /* the 'result' view in the merge window */
2197 if (mode == RESULT)
2198 goto set_merge;
2199 mode = RESULT; modename = "result"; modehelp = result_help;
2200 mmode = mode; curs.alt = 0;
2201 refresh = 3;
2202 break;
2203 case 'd':
2204 if (mode == (BEFORE|AFTER))
2205 goto set_merge;
2206 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
2207 mmode = mode; curs.alt = 0;
2208 refresh = 3;
2209 break;
2210 case 'm':
2211 set_merge:
2212 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
2213 mmode = mode; curs.alt = 0;
2214 refresh = 3;
2215 break;
2217 case '|':
2218 if (mode == (ORIG|RESULT|BEFORE|AFTER))
2219 goto set_merge;
2220 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
2221 mmode = mode; curs.alt = 0;
2222 refresh = 3;
2223 break;
2225 case 'H': /* scroll window to the right */
2226 if (start > 0)
2227 start--;
2228 curs.target = start + 1;
2229 refresh = 1;
2230 break;
2231 case 'L': /* scroll window to the left */
2232 if (start < cols)
2233 start++;
2234 curs.target = start + 1;
2235 refresh = 1;
2236 break;
2238 case 'x': /* Toggle rejecting of conflict.
2239 * A 'Conflict' or 'Changed' becomes 'Unchanged'
2240 * 'Unmatched' becomes 'Changed'
2242 if (ci.merger[curs.pos.m].oldtype == Conflict ||
2243 ci.merger[curs.pos.m].oldtype == Changed)
2244 next = Unchanged;
2245 else if (ci.merger[curs.pos.m].oldtype == Unmatched)
2246 next = Changed;
2247 else
2248 break;
2250 if (ci.merger[curs.pos.m].type == next)
2251 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2252 else
2253 ci.merger[curs.pos.m].type = next;
2254 p->conflicts = isolate_conflicts(
2255 fm, fb, fa, csl1, csl2, 0,
2256 ci.merger, 0, &p->wiggles);
2257 refresh = 1;
2258 changes = 1;
2259 break;
2261 case 'c': /* Toggle accepting of conflict.
2262 * A 'Conflict' or 'Extraneous' becomes 'Changed'
2264 if (ci.merger[curs.pos.m].oldtype != Conflict &&
2265 ci.merger[curs.pos.m].oldtype != Extraneous)
2266 break;
2268 if (ci.merger[curs.pos.m].type == Changed)
2269 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2270 else
2271 ci.merger[curs.pos.m].type = Changed;
2272 p->conflicts = isolate_conflicts(
2273 fm, fb, fa, csl1, csl2, 0,
2274 ci.merger, 0, &p->wiggles);
2275 refresh = 1;
2276 changes = 1;
2277 break;
2279 case 'X': /* Reset all changes on the current line */
2280 tpos = pos;
2281 do {
2282 ci.merger[tpos.p.m].type =
2283 ci.merger[tpos.p.m].oldtype;
2284 e = prev_melmnt(&tpos.p, fm, fb, fa, ci.merger);
2285 if (tpos.p.m < 0)
2286 break;
2287 } while (!ends_line(e) ||
2288 visible(mode & (RESULT|AFTER), ci.merger, &tpos) < 0);
2289 p->conflicts = isolate_conflicts(
2290 fm, fb, fa, csl1, csl2, 0,
2291 ci.merger, 0, &p->wiggles);
2292 refresh = 1;
2293 changes = 1;
2294 break;
2296 case '?':
2297 help_window(modehelp, merge_window_help, 0);
2298 refresh = 2;
2299 break;
2301 case KEY_RESIZE:
2302 refresh = 2;
2303 break;
2306 if (meta == SEARCH(0)) {
2307 if (anchor == NULL ||
2308 !same_mpos(anchor->pos, pos) ||
2309 anchor->searchlen != searchlen ||
2310 !same_mp(anchor->curs.pos, curs.pos)) {
2311 struct search_anchor *a = xmalloc(sizeof(*a));
2312 a->pos = pos;
2313 a->row = row;
2314 a->start = start;
2315 a->curs = curs;
2316 a->searchlen = searchlen;
2317 a->notfound = search_notfound;
2318 a->next = anchor;
2319 anchor = a;
2321 } else {
2322 while (anchor) {
2323 struct search_anchor *a = anchor;
2324 anchor = a->next;
2325 free(a);
2328 if (refresh == 3) {
2329 /* move backward and forward to make sure we
2330 * are on a visible line
2332 tpos = pos;
2333 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2334 if (tpos.p.m >= 0)
2335 pos = tpos;
2336 tpos = pos;
2337 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2338 if (ci.merger[tpos.p.m].type != End)
2339 pos = tpos;
2344 static int show_merge(char *origname, FILE *patch, int reverse,
2345 int is_merge, char *before, char *after,
2346 int replace, char *outfile,
2347 int selftest, int ignore_blanks,
2348 int just_diff)
2350 struct plist p = {0};
2352 p.file = origname;
2353 if (replace)
2354 p.outfile = outfile;
2355 if (patch) {
2356 p.start = 0;
2357 fseek(patch, 0, SEEK_END);
2358 p.end = ftell(patch);
2359 fseek(patch, 0, SEEK_SET);
2361 p.calced = 0;
2362 p.is_merge = is_merge;
2363 p.before = before;
2364 p.after = after;
2366 freopen("/dev/null","w",stderr);
2367 return merge_window(&p, patch, reverse, replace, selftest,
2368 ignore_blanks, just_diff);
2371 static void calc_one(struct plist *pl, FILE *f, int reverse,
2372 int ignore_blanks, int just_diff)
2374 struct stream s1, s2;
2375 struct stream s = load_segment(f, pl->start, pl->end);
2376 struct stream sf;
2377 if (pl->is_merge) {
2378 if (reverse)
2379 split_merge(s, &sf, &s2, &s1);
2380 else
2381 split_merge(s, &sf, &s1, &s2);
2382 pl->chunks = 0;
2383 } else {
2384 if (reverse)
2385 pl->chunks = split_patch(s, &s2, &s1);
2386 else
2387 pl->chunks = split_patch(s, &s1, &s2);
2388 if (just_diff)
2389 sf = s1;
2390 else
2391 sf = load_file(pl->file);
2393 if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
2394 pl->wiggles = pl->conflicts = -1;
2395 } else {
2396 struct file ff, fp1, fp2;
2397 struct csl *csl1, *csl2;
2398 struct ci ci;
2399 ff = split_stream(sf, ByWord | ignore_blanks);
2400 fp1 = split_stream(s1, ByWord | ignore_blanks);
2401 fp2 = split_stream(s2, ByWord | ignore_blanks);
2402 if (pl->chunks && !just_diff)
2403 csl1 = pdiff(ff, fp1, pl->chunks);
2404 else
2405 csl1 = diff(ff, fp1);
2406 csl2 = diff_patch(fp1, fp2);
2407 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
2408 pl->wiggles = ci.wiggles;
2409 pl->conflicts = ci.conflicts;
2410 free(ci.merger);
2411 free(csl1);
2412 free(csl2);
2413 free(ff.list);
2414 free(fp1.list);
2415 free(fp2.list);
2418 free(s1.body);
2419 free(s2.body);
2420 free(s.body);
2421 if (!just_diff)
2422 free(sf.body);
2423 pl->calced = 1;
2426 static int get_prev(int pos, struct plist *pl, int n, int mode)
2428 int found = 0;
2429 if (pos == -1)
2430 return pos;
2431 do {
2432 if (pl[pos].prev == -1)
2433 return pl[pos].parent;
2434 pos = pl[pos].prev;
2435 while (pl[pos].open &&
2436 pl[pos].last >= 0)
2437 pos = pl[pos].last;
2438 if (pl[pos].last >= 0)
2439 /* always see directories */
2440 found = 1;
2441 else if (mode == 0)
2442 found = 1;
2443 else if (mode <= 1 && pl[pos].wiggles > 0)
2444 found = 1;
2445 else if (mode <= 2 && pl[pos].conflicts > 0)
2446 found = 1;
2447 } while (pos >= 0 && !found);
2448 return pos;
2451 static int get_next(int pos, struct plist *pl, int n, int mode,
2452 FILE *f, int reverse, int ignore_blanks, int just_diff)
2454 int found = 0;
2455 if (pos == -1)
2456 return pos;
2457 do {
2458 if (pl[pos].open) {
2459 if (pos + 1 < n)
2460 pos = pos+1;
2461 else
2462 return -1;
2463 } else {
2464 while (pos >= 0 && pl[pos].next == -1)
2465 pos = pl[pos].parent;
2466 if (pos >= 0)
2467 pos = pl[pos].next;
2469 if (pos < 0)
2470 return -1;
2471 if (pl[pos].calced == 0 && pl[pos].end)
2472 calc_one(pl+pos, f, reverse, ignore_blanks, just_diff);
2473 if (pl[pos].last >= 0)
2474 /* always see directories */
2475 found = 1;
2476 else if (mode == 0)
2477 found = 1;
2478 else if (mode <= 1 && pl[pos].wiggles > 0)
2479 found = 1;
2480 else if (mode <= 2 && pl[pos].conflicts > 0)
2481 found = 1;
2482 } while (pos >= 0 && !found);
2483 return pos;
2486 static void draw_one(int row, struct plist *pl, FILE *f, int reverse,
2487 int ignore_blanks, int just_diff)
2489 char hdr[12];
2490 hdr[0] = 0;
2492 if (pl == NULL) {
2493 move(row, 0);
2494 clrtoeol();
2495 return;
2497 if (pl->calced == 0 && pl->end)
2498 /* better load the patch and count the chunks */
2499 calc_one(pl, f, reverse, ignore_blanks, just_diff);
2500 if (pl->end == 0) {
2501 strcpy(hdr, " ");
2502 } else {
2503 if (pl->chunks > 99)
2504 strcpy(hdr, "XX");
2505 else
2506 sprintf(hdr, "%2d", pl->chunks);
2507 if (pl->wiggles > 99)
2508 strcpy(hdr+2, " XX");
2509 else
2510 sprintf(hdr+2, " %2d", pl->wiggles);
2511 if (pl->conflicts > 99)
2512 strcpy(hdr+5, " XX ");
2513 else
2514 sprintf(hdr+5, " %2d ", pl->conflicts);
2516 if (pl->end)
2517 strcpy(hdr+9, "= ");
2518 else if (pl->open)
2519 strcpy(hdr+9, "+ ");
2520 else
2521 strcpy(hdr+9, "- ");
2523 if (!pl->end)
2524 attrset(0);
2525 else if (pl->is_merge)
2526 attrset(a_saved);
2527 else if (pl->conflicts)
2528 attrset(a_has_conflicts);
2529 else if (pl->wiggles)
2530 attrset(a_has_wiggles);
2531 else
2532 attrset(a_no_wiggles);
2534 mvaddstr(row, 0, hdr);
2535 mvaddstr(row, 11, pl->file);
2536 clrtoeol();
2539 static int save_one(FILE *f, struct plist *pl, int reverse,
2540 int ignore_blanks)
2542 struct stream sp, sa, sb, sm;
2543 struct file fa, fb, fm;
2544 struct csl *csl1, *csl2;
2545 struct ci ci;
2546 int chunks;
2547 sp = load_segment(f, pl->start,
2548 pl->end);
2549 if (reverse)
2550 chunks = split_patch(sp, &sa, &sb);
2551 else
2552 chunks = split_patch(sp, &sb, &sa);
2553 fb = split_stream(sb, ByWord | ignore_blanks);
2554 fa = split_stream(sa, ByWord | ignore_blanks);
2555 sm = load_file(pl->file);
2556 fm = split_stream(sm, ByWord | ignore_blanks);
2557 csl1 = pdiff(fm, fb, chunks);
2558 csl2 = diff_patch(fb, fa);
2559 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
2560 return save_merge(fm, fb, fa, ci.merger,
2561 pl->file, 1);
2564 static char *main_help[] = {
2565 " You are using the \"browse\" mode of wiggle.",
2566 "This page shows a list of files in a patch together with",
2567 "the directories that contain them.",
2568 "A directory is indicated by a '+' if the contents are",
2569 "listed or a '-' if the contents are hidden. A file is",
2570 "indicated by an '='. Typing <space> or <return> will",
2571 "expose or hide a directory, and will visit a file.",
2573 "The three columns of numbers are:",
2574 " Ch The number of patch chunks which applied to",
2575 " this file",
2576 " Wi The number of chunks that needed to be wiggled",
2577 " in to place",
2578 " Co The number of chunks that created an unresolvable",
2579 " conflict",
2581 "Keystrokes recognised in this page are:",
2582 " ? Display this help",
2583 " SPC On a directory, toggle hiding of contents",
2584 " On file, visit the file",
2585 " RTN Same as SPC",
2586 " q Quit program",
2587 " control-C Disable auto-save-on-exit",
2588 " n,j,DOWN Go to next line",
2589 " p,k,UP Go to previous line",
2591 " A list All files",
2592 " W only list files with a wiggle or a conflict",
2593 " C only list files with a conflict",
2595 " S Save this file with changes applied. If",
2596 " some but not all files are saved, wiggle will",
2597 " prompt on exit to save the rest.",
2598 " R Revert the current saved file to its original",
2599 " content",
2600 " I toggle whether spaces are ignored",
2601 " when matching text.",
2602 NULL
2604 static char *saveall_msg = " %d file%s (of %d) have not been saved.";
2605 static char saveall_buf[200];
2606 static char *saveall_query[] = {
2608 saveall_buf,
2609 " Would you like to save them?",
2610 " Y = yes, save them all",
2611 " N = no, exit without saving anything else",
2612 " Q = Don't quit just yet",
2613 NULL
2615 static void main_window(struct plist *pl, int *np, FILE *f, int reverse,
2616 int replace, int ignore_blanks, int just_diff)
2618 /* The main window lists all files together with summary information:
2619 * number of chunks, number of wiggles, number of conflicts.
2620 * The list is scrollable
2621 * When a entry is 'selected', we switch to the 'file' window
2622 * The list can be condensed by removing files with no conflict
2623 * or no wiggles, or removing subdirectories
2625 * We record which file in the list is 'current', and which
2626 * screen line it is on. We try to keep things stable while
2627 * moving.
2629 * Counts are printed before the name using at most 2 digits.
2630 * Numbers greater than 99 are XX
2631 * Ch Wi Co File
2632 * 27 5 1 drivers/md/md.c
2634 * A directory show the sum in all children.
2636 * Commands:
2637 * select: enter, space, mouseclick
2638 * on file, go to file window
2639 * on directory, toggle open
2640 * up: k, p, control-p uparrow
2641 * Move to previous open object
2642 * down: j, n, control-n, downarrow
2643 * Move to next open object
2645 * A W C: select All Wiggles or Conflicts
2646 * mode
2649 char *mesg = NULL;
2650 char mesg_buf[1024];
2651 int last_mesg_len = 0;
2652 int pos = 0; /* position in file */
2653 int row = 1; /* position on screen */
2654 int rows = 0; /* size of screen in rows */
2655 int cols = 0;
2656 int tpos, i;
2657 int refresh = 2;
2658 int c = 0;
2659 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2660 int cnt; /* count of files that need saving */
2661 int any; /* count of files that have been save*/
2662 int ans;
2663 MEVENT mevent;
2664 char *debug = getenv("WIGGLE_DEBUG");
2666 if (debug && !*debug)
2667 debug = NULL;
2669 freopen("/dev/null","w",stderr);
2670 term_init(1);
2671 pl = sort_patches(pl, np);
2673 while (1) {
2674 if (refresh == 2) {
2675 clear(); (void)attrset(0);
2676 attron(A_BOLD);
2677 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2678 attroff(A_BOLD);
2679 if (ignore_blanks)
2680 addstr(" (ignoring blanks)");
2681 move(2, 0);
2682 refresh = 1;
2684 if (row < 1 || row >= rows)
2685 refresh = 1;
2686 if (refresh) {
2687 refresh = 0;
2688 getmaxyx(stdscr, rows, cols);
2690 if (row >= rows + 3)
2691 row = (rows+1)/2;
2692 if (row >= rows)
2693 row = rows-1;
2694 tpos = pos;
2695 for (i = row; i > 1; i--) {
2696 tpos = get_prev(tpos, pl, *np, mode);
2697 if (tpos == -1) {
2698 row = row - i + 1;
2699 break;
2702 /* Ok, row and pos could be trustworthy now */
2703 tpos = pos;
2704 for (i = row; i >= 1; i--) {
2705 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2706 tpos = get_prev(tpos, pl, *np, mode);
2708 tpos = pos;
2709 for (i = row+1; i < rows; i++) {
2710 tpos = get_next(tpos, pl, *np, mode, f, reverse,ignore_blanks, just_diff);
2711 if (tpos >= 0)
2712 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2713 else
2714 draw_one(i, NULL, f, reverse, ignore_blanks, just_diff);
2717 attrset(0);
2718 if (last_mesg_len) {
2719 move(0, cols - last_mesg_len);
2720 clrtoeol();
2721 last_mesg_len = 0;
2723 if (mesg) {
2724 last_mesg_len = strlen(mesg);
2725 move(0, cols - last_mesg_len);
2726 addstr(mesg);
2727 mesg = NULL;
2728 } else if (debug) {
2729 /* debugging help: report last keystroke */
2730 char bb[30];
2731 sprintf(bb, "last-key = 0%o", c);
2732 attrset(0);
2733 last_mesg_len = strlen(bb);
2734 mvaddstr(0, cols - last_mesg_len, bb);
2736 move(row, 9);
2737 c = getch();
2738 switch (c) {
2739 case 'j':
2740 case 'n':
2741 case 'N':
2742 case 'N'-64:
2743 case KEY_DOWN:
2744 tpos = get_next(pos, pl, *np, mode, f, reverse, ignore_blanks, just_diff);
2745 if (tpos >= 0) {
2746 pos = tpos;
2747 row++;
2749 break;
2750 case 'k':
2751 case 'p':
2752 case 'P':
2753 case 'P'-64:
2754 case KEY_UP:
2755 tpos = get_prev(pos, pl, *np, mode);
2756 if (tpos >= 0) {
2757 pos = tpos;
2758 row--;
2760 break;
2762 case KEY_MOUSE:
2763 if (getmouse(&mevent) != OK)
2764 break;
2765 while (row < mevent.y &&
2766 (tpos = get_next(pos, pl, *np, mode, f, reverse, ignore_blanks, just_diff))
2767 >= 0) {
2768 pos = tpos;
2769 row++;
2771 while (row > mevent.y &&
2772 (tpos = get_prev(pos, pl, *np, mode)) >= 0) {
2773 pos = tpos;
2774 row--;
2776 if (row != mevent.y)
2777 /* couldn't find the line */
2778 break;
2779 /* FALL THROUGH */
2780 case ' ':
2781 case 13:
2782 if (pl[pos].end == 0) {
2783 pl[pos].open = !pl[pos].open;
2784 refresh = 1;
2785 if (pl[pos].open)
2786 mesg = "Opened folder";
2787 else
2788 mesg = "Closed folder";
2789 } else {
2790 int c;
2791 if (pl[pos].is_merge)
2792 c = merge_window(&pl[pos], NULL, reverse, 0, 0, ignore_blanks, just_diff);
2793 else
2794 c = merge_window(&pl[pos], f, reverse, 0, 0, ignore_blanks, just_diff);
2795 refresh = 2;
2796 if (c) {
2797 pl[pos].is_merge = 1;
2798 snprintf(mesg_buf, cols,
2799 "Saved file %s.",
2800 pl[pos].file);
2801 mesg = mesg_buf;
2804 break;
2805 case 27: /* escape */
2806 attrset(0);
2807 mvaddstr(0, cols-10, "ESC..."); clrtoeol();
2808 c = getch();
2809 switch (c) {
2811 move(0, cols-10); clrtoeol();
2812 break;
2813 case 'C'-64:
2814 if (replace)
2815 mesg = "Save-on-exit disabled. Use 'q' to quit.";
2816 else
2817 mesg = "Use 'q' to quit.";
2818 replace = 0;
2819 break;
2821 case 'q':
2822 cnt = 0;
2823 any = 0;
2824 for (i = 0; i < *np; i++)
2825 if (pl[i].end && !pl[i].is_merge)
2826 cnt++;
2827 else if (pl[i].end)
2828 any++;
2829 if (!cnt) {
2830 endwin();
2831 return;
2833 refresh = 2;
2834 if (replace)
2835 ans = 1;
2836 else if (any) {
2837 sprintf(saveall_buf, saveall_msg,
2838 cnt, cnt == 1 ? "" : "s", cnt+any);
2839 ans = help_window(saveall_query, NULL, 1);
2840 } else
2841 ans = 0;
2842 if (ans < 0)
2843 break;
2844 if (ans) {
2845 for (i = 0; i < *np; i++) {
2846 if (pl[i].end
2847 && !pl[i].is_merge)
2848 save_one(f, &pl[i],
2849 reverse,
2850 ignore_blanks);
2852 } else
2853 cnt = 0;
2854 endwin();
2855 if (cnt)
2856 printf("%d file%s saved\n", cnt,
2857 cnt == 1 ? "" : "s");
2858 return;
2860 case 'A':
2861 mode = 0; refresh = 1;
2862 mesg = "Showing ALL files";
2863 break;
2864 case 'W':
2865 mode = 1; refresh = 1;
2866 mesg = "Showing Wiggled files";
2867 break;
2868 case 'C':
2869 mode = 2; refresh = 1;
2870 mesg = "Showing Conflicted files";
2871 break;
2873 case 'S': /* Save updated file */
2874 if (pl[pos].end == 0) {
2875 /* directory */
2876 mesg = "Cannot save a folder.";
2877 } else if (pl[pos].is_merge) {
2878 /* Already saved */
2879 mesg = "File is already saved.";
2880 } else {
2881 if (save_one(f, &pl[pos], reverse, ignore_blanks) == 0) {
2882 pl[pos].is_merge = 1;
2883 snprintf(mesg_buf, cols,
2884 "Saved file %s.",
2885 pl[pos].file);
2886 pl[pos].chunks = pl[pos].conflicts;
2887 pl[pos].wiggles = 0;
2888 } else
2889 snprintf(mesg_buf, cols,
2890 "Failed to save file %s.",
2891 pl[pos].file);
2892 mesg = mesg_buf;
2893 refresh = 1;
2895 break;
2897 case 'R': /* Restore updated file */
2898 if (pl[pos].end == 0)
2899 mesg = "Cannot restore a folder.";
2900 else if (!pl[pos].is_merge)
2901 mesg = "File has not been saved, cannot restore.";
2902 else {
2903 /* rename foo.porig to foo, and clear is_merge */
2904 char *file = pl[pos].file;
2905 char *orignew = xmalloc(strlen(file) + 20);
2906 strcpy(orignew, file);
2907 strcat(orignew, ".porig");
2908 if (rename(orignew, file) == 0) {
2909 mesg = "File has been restored.";
2910 pl[pos].is_merge = 0;
2911 refresh = 1;
2912 calc_one(&pl[pos], f, reverse, ignore_blanks, just_diff);
2913 } else
2914 mesg = "Could not restore file!";
2916 break;
2918 case 'I': /* Toggle ignoring blanks */
2919 ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
2920 refresh = 2;
2921 for (i = 0; i < *np; i++)
2922 pl[i].calced = 0;
2923 break;
2925 case '?':
2926 help_window(main_help, NULL, 0);
2927 refresh = 2;
2928 break;
2930 case KEY_RESIZE:
2931 refresh = 2;
2932 break;
2937 static void catch(int sig)
2939 if (sig == SIGINT && !intr_kills) {
2940 signal(sig, catch);
2941 return;
2943 noraw();
2944 nl();
2945 endwin();
2946 printf("Died on signal %d\n", sig);
2947 fflush(stdout);
2948 if (sig != SIGBUS && sig != SIGSEGV)
2949 exit(2);
2950 else
2951 /* Otherwise return and die */
2952 signal(sig, NULL);
2955 static void term_init(int doraw)
2958 static int init_done = 0;
2960 if (init_done)
2961 return;
2962 init_done = 1;
2964 signal(SIGINT, catch);
2965 signal(SIGQUIT, catch);
2966 signal(SIGTERM, catch);
2967 signal(SIGBUS, catch);
2968 signal(SIGSEGV, catch);
2970 initscr();
2971 if (doraw)
2972 raw();
2973 else
2974 cbreak();
2975 noecho();
2976 start_color();
2977 use_default_colors();
2978 if (!has_colors()) {
2979 a_delete = A_UNDERLINE;
2980 a_added = A_BOLD;
2981 a_common = A_NORMAL;
2982 a_sep = A_STANDOUT;
2983 a_already = A_STANDOUT;
2984 a_has_conflicts = A_UNDERLINE;
2985 a_has_wiggles = A_BOLD;
2986 a_no_wiggles = A_NORMAL;
2987 } else {
2988 init_pair(1, COLOR_RED, -1);
2989 a_delete = COLOR_PAIR(1);
2990 init_pair(2, COLOR_GREEN, -1);
2991 a_added = COLOR_PAIR(2);
2992 a_common = A_NORMAL;
2993 init_pair(3, COLOR_WHITE, COLOR_GREEN);
2994 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
2995 init_pair(4, -1, COLOR_YELLOW);
2996 a_void = COLOR_PAIR(4);
2997 init_pair(5, COLOR_BLUE, -1);
2998 a_unmatched = COLOR_PAIR(5);
2999 init_pair(6, COLOR_CYAN, -1);
3000 a_extra = COLOR_PAIR(6);
3002 init_pair(7, COLOR_BLACK, COLOR_CYAN);
3003 a_already = COLOR_PAIR(7);
3005 a_has_conflicts = a_delete;
3006 a_has_wiggles = a_added;
3007 a_no_wiggles = a_unmatched;
3008 a_saved = a_extra;
3010 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
3011 mousemask(ALL_MOUSE_EVENTS, NULL);
3014 int vpatch(int argc, char *argv[], int patch, int strip,
3015 int reverse, int replace, char *outfilename,
3016 int selftest, int ignore_blanks)
3018 /* NOTE argv[0] is first arg...
3019 * Behaviour depends on number of args and 'patch'.
3020 * If 'patch' is '1', assume a patch. if '2', assume a diff.
3021 * 0: A multi-file patch or diff is read from stdin.
3022 * A 'patch' is applies to relevant files. A 'diff' is just
3023 * displayed.
3024 * 1: if 'patch', parse it as a multi-file patch/diff and allow
3025 * the files to be browsed.
3026 * if filename ends '.rej', then treat it as a patch/diff again
3027 * a file with the same basename
3028 * Else treat the file as a merge (with conflicts) and view it.
3030 * 2: First file is original, second is patch unless patch==2,
3031 * then two files need to be diffed.
3032 * 3: Files are: original previous new. The diff between 'previous' and
3033 * 'new' needs to be applied to 'original'.
3035 * If a multi-file patch is being read, 'strip' tells how many
3036 * path components to strip. If it is -1, we guess based on
3037 * existing files.
3038 * If 'reverse' is given, when we invert any patch or diff
3039 * If 'replace' then we save the resulting merge.
3041 FILE *in;
3042 FILE *f;
3043 struct plist *pl;
3044 int num_patches;
3045 int just_diff = (patch == 2);
3047 switch (argc) {
3048 default:
3049 fprintf(stderr, "%s: too many file names given.\n", Cmd);
3050 exit(1);
3052 case 0: /* stdin is a patch or diff */
3053 if (lseek(fileno(stdin), 0L, 1) == -1) {
3054 /* cannot seek, so need to copy to a temp file */
3055 f = tmpfile();
3056 if (!f) {
3057 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
3058 exit(1);
3060 pl = parse_patch(stdin, f, &num_patches);
3061 in = f;
3062 } else {
3063 pl = parse_patch(stdin, NULL, &num_patches);
3064 in = fdopen(dup(0), "r");
3066 /* use stderr for keyboard input */
3067 dup2(2, 0);
3068 if (!just_diff &&
3069 set_prefix(pl, num_patches, strip) == 0) {
3070 fprintf(stderr, "%s: aborting\n", Cmd);
3071 exit(2);
3073 main_window(pl, &num_patches, in, reverse, replace, ignore_blanks, just_diff);
3074 plist_free(pl, num_patches);
3075 fclose(in);
3076 break;
3078 case 1: /* a patch/diff, a .rej, or a merge file */
3079 f = fopen(argv[0], "r");
3080 if (!f) {
3081 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
3082 exit(1);
3084 check_dir(argv[0], fileno(f));
3085 if (patch) {
3086 pl = parse_patch(f, NULL, &num_patches);
3087 if (!just_diff && set_prefix(pl, num_patches, strip) == 0) {
3088 fprintf(stderr, "%s: aborting\n", Cmd);
3089 exit(2);
3091 main_window(pl, &num_patches, f, reverse, replace,ignore_blanks, just_diff);
3092 plist_free(pl, num_patches);
3093 } else if (strlen(argv[0]) > 4 &&
3094 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
3095 char *origname = strdup(argv[0]);
3096 origname[strlen(origname) - 4] = '\0';
3097 show_merge(origname, f, reverse, 0, NULL, NULL,
3098 replace, outfilename,
3099 selftest, ignore_blanks, just_diff);
3100 } else
3101 show_merge(argv[0], f, reverse, 1, NULL, NULL,
3102 replace, outfilename,
3103 selftest, ignore_blanks, just_diff);
3105 break;
3106 case 2: /* an orig and a diff/.rej or two files */
3107 if (just_diff) {
3108 show_merge(NULL, NULL, reverse, 0, argv[0], argv[1],
3109 replace, outfilename,
3110 selftest, ignore_blanks, just_diff);
3111 break;
3113 f = fopen(argv[1], "r");
3114 check_dir(argv[1], fileno(f));
3115 if (!f) {
3116 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
3117 exit(1);
3119 show_merge(argv[0], f, reverse, 0, NULL, NULL,
3120 replace, outfilename,
3121 selftest, ignore_blanks, just_diff);
3122 break;
3123 case 3: /* orig, before, after */
3124 show_merge(argv[0], NULL, reverse, 0, argv[1], argv[2],
3125 replace, outfilename,
3126 selftest, ignore_blanks, just_diff);
3127 break;
3130 noraw();
3131 nl();
3132 endwin();
3133 exit(0);