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