man page: add documentation for browse mode
[wiggle/upstream.git] / vpatch.c
blob44aa898bba1494b97b55fa001839e4be6d997b70
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>
48 static void term_init(void);
50 /* global attributes */
51 unsigned int a_delete, a_added, a_common, a_sep, a_void,
52 a_unmatched, a_extra, a_already;
53 unsigned int a_has_conflicts, a_has_wiggles, a_no_wiggles;
55 /******************************************************************
56 * Help window
57 * We display help in an insert, leaving 5 columns left and right,
58 * and 2 rows top and bottom, but at most 58x15 plus border
59 * In help mode:
60 * SPC or RTN moves down or to next page
61 * BKSPC goes backwards
62 * 'q' returns to origin screen
63 * '?' show help on help
64 * left and right scroll help view
66 * A help text is an array of lines of text
69 char *help_help[] = {
70 " You are viewing the help page for the help viewer.",
71 "You normally get here by typing '?'",
72 "",
73 "The following keystrokes work in the help viewer:",
74 " ? display this help message",
75 " q return to previous view",
76 " SPC move forward through help document",
77 " RTN same as SPC",
78 " BKSP move backward through help document",
79 " RIGHT scroll help window so text on the right appears",
80 " LEFT scroll help window so text on the left appears",
81 NULL
84 char *help_missing[] = {
85 "The file that this patch applies to appears",
86 "to be missing.",
87 "Please type 'q' to continue",
88 NULL
91 char *help_corrupt[] = {
92 "This patch appears to be corrupt",
93 "Please type 'q' to continue",
94 NULL
97 /* We can give one or two pages to display in the help window.
98 * The first is specific to the current context. The second
99 * is optional and may provide help in a more broad context.
101 static void help_window(char *page1[], char *page2[])
103 int rows, cols;
104 int top, left;
105 int r, c;
106 int ch;
107 char **page = page1;
108 int line = 0;
109 int shift = 0;
111 getmaxyx(stdscr, rows, cols);
113 if (cols < 70) {
114 left = 6;
115 cols = cols-12;
116 } else {
117 left = (cols-58)/2 - 1;
118 cols = 58;
121 if (rows < 21) {
122 top = 3;
123 rows = rows - 6;
124 } else {
125 top = (rows-15)/2 - 1;
126 rows = 15;
129 /* Draw a border around the 'help' area */
130 (void)attrset(A_STANDOUT);
131 for (c = left; c < left+cols; c++) {
132 mvaddch(top-1, c, '-');
133 mvaddch(top+rows, c, '-');
135 for (r = top; r < top + rows ; r++) {
136 mvaddch(r, left-1, '|');
137 mvaddch(r, left+cols, '|');
139 mvaddch(top-1, left-1, '/');
140 mvaddch(top-1, left+cols, '\\');
141 mvaddch(top+rows, left-1, '\\');
142 mvaddch(top+rows, left+cols, '/');
143 mvaddstr(top-1, left + cols/2 - 9, "HELP - 'q' to exit");
144 mvaddstr(top+rows, left+cols/2 - 17, "Press SPACE for more, '?' for help");
145 (void)attrset(A_NORMAL);
147 while (1) {
148 char **lnp = page + line;
150 /* Draw as much of the page at the current offset
151 * as fits.
153 for (r = 0; r < rows; r++) {
154 char *ln = *lnp;
155 int sh = shift;
156 if (ln)
157 lnp++;
158 else
159 ln = "";
161 while (*ln && sh > 0) {
162 ln++;
163 sh--;
165 for (c = 0; c < cols; c++) {
166 int chr = *ln;
167 if (chr)
168 ln++;
169 else
170 chr = ' ';
171 mvaddch(top+r, left+c, chr);
174 move(top+rows-1, left);
175 ch = getch();
177 switch (ch) {
178 case 'q':
179 return;
180 case '?':
181 if (page1 != help_help)
182 help_window(help_help, NULL);
183 break;
184 case ' ':
185 case '\r': /* page-down */
186 for (r = 0; r < rows-2; r++)
187 if (page[line])
188 line++;
189 if (!page[line]) {
190 line = 0;
191 if (page == page1)
192 page = page2;
193 else
194 page = NULL;
195 if (page == NULL)
196 return;
198 break;
200 case '\b': /* page up */
201 if (line > 0) {
202 line -= (rows-2);
203 if (line < 0)
204 line = 0;
205 } else {
206 if (page == page2)
207 page = page1;
208 else
209 page = page2;
210 if (page == NULL)
211 page = page1;
212 line = 0;
214 break;
216 case KEY_LEFT:
217 if (shift > 0)
218 shift--;
219 break;
220 case KEY_RIGHT:
221 shift++;
222 break;
224 case KEY_UP:
225 if (line > 0)
226 line--;
227 break;
228 case KEY_DOWN:
229 if (page[line])
230 line++;
231 break;
236 static char *typenames[] = {
237 [End] = "End",
238 [Unmatched] = "Unmatched",
239 [Unchanged] = "Unchanged",
240 [Extraneous] = "Extraneous",
241 [Changed] = "Changed",
242 [Conflict] = "Conflict",
243 [AlreadyApplied] = "AlreadyApplied",
246 /* When we merge the original and the diff together we need
247 * to keep track of where everything came from.
248 * When we display the different views, we need to be able to
249 * select certain portions of the whole document.
250 * These flags are used to identify what is present, and to
251 * request different parts be extracted. They also help
252 * guide choice of colour.
254 #define BEFORE 1
255 #define AFTER 2
256 #define ORIG 4
257 #define RESULT 8
258 #define CHANGED 16 /* The RESULT is different to ORIG */
259 #define CHANGES 32 /* AFTER is different to BEFORE */
260 #define WIGGLED 64 /* a conflict that was successfully resolved */
261 #define CONFLICTED 128 /* a conflict that was not successfully resolved */
263 /* Displaying a Merge.
264 * The first step is to linearise the merge. The merge in inherently
265 * parallel with before/after streams. However much of the whole document
266 * is linear as normally much of the original in unchanged.
267 * All parallelism comes from the patch. This normally produces two
268 * parallel stream, but in the case of a conflict can produce three.
269 * For browsing the merge we only ever show two alternates in-line.
270 * When there are three we use two panes with 1 or 2 alternates in each.
271 * So to linearise the two streams we find lines that are completely
272 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
273 * a region where there are changes. We include everything between
274 * these twice, in two separate passes. The exact interpretation of the
275 * passes is handled at a higher level but will be one of:
276 * original and result
277 * before and after
278 * original and after (for a conflict)
279 * This is all encoded in the 'struct merge'. An array of these describes
280 * the whole document.
282 * At any position in the merge we can be in one of 3 states:
283 * 0: unchanged section
284 * 1: first pass
285 * 2: second pass
287 * So to walk a merge in display order we need a position in the merge,
288 * a current state, and when in a changed section, we need to know the
289 * bounds of that changed section.
290 * This is all encoded in 'struct mpos'.
292 * Each location may or may not be visible depending on certain
293 * display options.
295 * Also, some locations might be 'invalid' in that they don't need to be displayed.
296 * For example when the patch leaves a section of the original unchanged,
297 * we only need to see the original - the before/after sections are treated
298 * as invalid and are not displayed.
299 * The visibility of newlines is crucial and guides the display. One line
300 * of displayed text is all the visible sections between two visible newlines.
302 * Counting lines is a bit tricky. We only worry about line numbers in the
303 * original (stream 0) as these could compare with line numbers mentioned in
304 * patch chunks.
305 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
306 * That way we don't get a full counted line until we see the first char after the
307 * newline, so '+' lines are counted with the previous line.
310 struct mp {
311 int m; /* merger index */
312 int s; /* stream 0,1,2 for a,b,c */
313 int o; /* offset in that stream */
314 int lineno; /* Counts newlines in stream 0
315 * set lsb when see newline.
316 * add one when not newline and lsb set
319 struct mpos {
320 struct mp p, /* the current point (end of a line) */
321 lo, /* eol for start of the current group */
322 hi; /* eol for end of the current group */
323 int state; /*
324 * 0 if on an unchanged (lo/hi not meaningful)
325 * 1 if on the '-' of a diff,
326 * 2 if on the '+' of a diff
330 struct cursor {
331 struct mp pos; /* where in the document we are (an element) */
332 int offset; /* which char in that element */
333 int target; /* display column - or -1 if we are looking for 'pos' */
334 int col; /* where we found pos or target */
335 int width; /* Size of char, for moving to the right */
336 int alt; /* Cursor is in alternate window */
339 /* used for checking location during search */
340 static int same_mp(struct mp a, struct mp b)
342 return a.m == b.m &&
343 a.s == b.s &&
344 a.o == b.o;
346 static int same_mpos(struct mpos a, struct mpos b)
348 return same_mp(a.p, b.p) &&
349 (a.state == b.state || a.state == 0 || b.state == 0);
352 /* Check if a particular stream is meaningful in a particular merge
353 * section. e.g. in an Unchanged section, only stream 0, the
354 * original, is meaningful. This is used to avoid walking down
355 * pointless paths.
357 static int stream_valid(int s, enum mergetype type)
359 switch (type) {
360 case End:
361 return 1;
362 case Unmatched:
363 return s == 0;
364 case Unchanged:
365 return s == 0;
366 case Extraneous:
367 return s == 2;
368 case Changed:
369 return s != 1;
370 case Conflict:
371 return 1;
372 case AlreadyApplied:
373 return 1;
375 return 0;
379 * Advance the 'pos' in the current mergepos returning the next
380 * element (word).
381 * This walks the merges in sequence, and the streams within
382 * each merge.
384 static struct elmnt next_melmnt(struct mp *pos,
385 struct file fm, struct file fb, struct file fa,
386 struct merge *m)
388 pos->o++;
389 while (1) {
390 int l = 0; /* Length remaining in current merge section */
391 if (pos->m >= 0)
392 switch (pos->s) {
393 case 0:
394 l = m[pos->m].al;
395 break;
396 case 1:
397 l = m[pos->m].bl;
398 break;
399 case 2:
400 l = m[pos->m].cl;
401 break;
403 if (pos->o >= l) {
404 /* Offset has reached length, choose new stream or
405 * new merge */
406 pos->o = 0;
407 do {
408 pos->s++;
409 if (pos->s > 2) {
410 pos->s = 0;
411 pos->m++;
413 } while (!stream_valid(pos->s, m[pos->m].type));
414 } else
415 break;
417 if (pos->m == -1 || m[pos->m].type == End) {
418 struct elmnt e;
419 e.start = NULL; e.len = 0;
420 return e;
422 switch (pos->s) {
423 default: /* keep compiler happy */
424 case 0:
425 if (pos->lineno & 1)
426 pos->lineno++;
427 if (ends_mline(fm.list[m[pos->m].a + pos->o]))
428 pos->lineno++;
429 return fm.list[m[pos->m].a + pos->o];
430 case 1: return fb.list[m[pos->m].b + pos->o];
431 case 2: return fa.list[m[pos->m].c + pos->o];
435 /* step current position.p backwards */
436 static struct elmnt prev_melmnt(struct mp *pos,
437 struct file fm, struct file fb, struct file fa,
438 struct merge *m)
440 if (pos->s == 0) {
441 if (ends_mline(fm.list[m[pos->m].a + pos->o]))
442 pos->lineno--;
443 if (pos->lineno & 1)
444 pos->lineno--;
447 pos->o--;
448 while (pos->m >= 0 && pos->o < 0) {
449 do {
450 pos->s--;
451 if (pos->s < 0) {
452 pos->s = 2;
453 pos->m--;
455 } while (pos->m >= 0 &&
456 !stream_valid(pos->s, m[pos->m].type));
457 if (pos->m >= 0) {
458 switch (pos->s) {
459 case 0:
460 pos->o = m[pos->m].al-1;
461 break;
462 case 1:
463 pos->o = m[pos->m].bl-1;
464 break;
465 case 2:
466 pos->o = m[pos->m].cl-1;
467 break;
471 if (pos->m < 0) {
472 struct elmnt e;
473 e.start = NULL; e.len = 0;
474 return e;
476 switch (pos->s) {
477 default: /* keep compiler happy */
478 case 0: return fm.list[m[pos->m].a + pos->o];
479 case 1: return fb.list[m[pos->m].b + pos->o];
480 case 2: return fa.list[m[pos->m].c + pos->o];
484 /* 'visible' not only checks if this stream in this merge should be
485 * visible in this mode, but also chooses which colour/highlight to use
486 * to display it.
488 static int visible(int mode, enum mergetype type, int stream)
490 if (mode == 0)
491 return -1;
492 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
493 switch (type) {
494 case End: /* The END is always visible */
495 return A_NORMAL;
496 case Unmatched: /* Visible in ORIG and RESULT */
497 if (mode & (ORIG|RESULT))
498 return a_unmatched;
499 break;
500 case Unchanged: /* visible everywhere, but only show stream 0 */
501 if (stream == 0)
502 return a_common;
503 break;
504 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
505 if ((mode & (BEFORE|AFTER))
506 && stream == 2)
507 return a_extra;
508 break;
509 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
510 if (stream == 0 &&
511 (mode & (ORIG|BEFORE)))
512 return a_delete;
513 if (stream == 2 &&
514 (mode & (RESULT|AFTER)))
515 return a_added;
516 break;
517 case Conflict:
518 switch (stream) {
519 case 0:
520 if (mode & ORIG)
521 return a_unmatched | A_REVERSE;
522 break;
523 case 1:
524 if (mode & BEFORE)
525 return a_extra | A_UNDERLINE;
526 break;
527 case 2:
528 if (mode & (AFTER|RESULT))
529 return a_added | A_UNDERLINE;
530 break;
532 break;
533 case AlreadyApplied:
534 switch (stream) {
535 case 0:
536 if (mode & (ORIG|RESULT))
537 return a_already;
538 break;
539 case 1:
540 if (mode & BEFORE)
541 return a_delete | A_UNDERLINE;
542 break;
543 case 2:
544 if (mode & AFTER)
545 return a_added | A_UNDERLINE;
546 break;
548 break;
550 return -1;
553 /* checkline creates a summary of the sort of changes that
554 * are in a line, returning an "or" of
555 * CHANGED
556 * CHANGES
557 * WIGGLED
558 * CONFLICTED
560 static int check_line(struct mpos pos, struct file fm, struct file fb,
561 struct file fa,
562 struct merge *m, int mode)
564 int rv = 0;
565 struct elmnt e;
566 int unmatched = 0;
568 do {
569 if (m[pos.p.m].type == Changed)
570 rv |= CHANGED | CHANGES;
571 else if ((m[pos.p.m].type == AlreadyApplied ||
572 m[pos.p.m].type == Conflict))
573 rv |= CONFLICTED | CHANGES;
574 else if (m[pos.p.m].type == Extraneous &&
575 /* hunk headers don't count as wiggles */
576 fb.list[m[pos.p.m].b].start[0] != '\0')
577 rv |= WIGGLED;
578 else if (m[pos.p.m].type == Unmatched)
579 unmatched = 1;
580 if (m[pos.p.m].in_conflict)
581 rv |= CONFLICTED | CHANGES;
582 e = prev_melmnt(&pos.p, fm, fb, fa, m);
583 } while (e.start != NULL &&
584 (!ends_mline(e)
585 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
587 if (unmatched && (rv & CHANGES))
588 rv |= WIGGLED;
589 return rv;
592 /* Find the next line in the merge which is visible.
593 * If we hit the end of a conflicted set during pass-1
594 * we rewind for pass-2.
595 * 'mode' tells which bits we want to see, possible one of
596 * the 4 parts (before/after/orig/result) or one of the pairs
597 * before+after or orig+result.
599 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
600 struct file fa,
601 struct merge *m, int mode)
603 int mask;
604 do {
605 struct mp prv;
606 int mode2;
608 prv = pos->p;
609 while (1) {
610 struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
611 if (e.start == NULL)
612 break;
613 if (ends_mline(e) &&
614 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
615 break;
617 mode2 = check_line(*pos, fm, fb, fa, m, mode);
619 if ((mode2 & CHANGES) && pos->state == 0) {
620 /* Just entered a diff-set */
621 pos->lo = pos->p;
622 pos->state = 1;
623 } else if (!(mode2 & CHANGES) && pos->state) {
624 /* Come to the end of a diff-set */
625 switch (pos->state) {
626 case 1:
627 /* Need to record the end */
628 pos->hi = prv;
629 /* time for another pass */
630 pos->p = pos->lo;
631 pos->state++;
632 break;
633 case 2:
634 /* finished final pass */
635 pos->state = 0;
636 break;
639 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
640 switch (pos->state) {
641 case 1:
642 mask &= ~(RESULT|AFTER);
643 break;
644 case 2:
645 mask &= ~(ORIG|BEFORE);
646 break;
648 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
652 /* Move to previous line - simply the reverse of next_mline */
653 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
654 struct file fa,
655 struct merge *m, int mode)
657 int mask;
658 do {
659 struct mp prv;
660 int mode2;
662 prv = pos->p;
663 if (pos->p.m < 0)
664 return;
665 while (1) {
666 struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
667 if (e.start == NULL)
668 break;
669 if (ends_mline(e) &&
670 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
671 break;
673 mode2 = check_line(*pos, fm, fb, fa, m, mode);
675 if ((mode2 & CHANGES) && pos->state == 0) {
676 /* Just entered a diff-set */
677 pos->hi = pos->p;
678 pos->state = 2;
679 } else if (!(mode2 & CHANGES) && pos->state) {
680 /* Come to the end (start) of a diff-set */
681 switch (pos->state) {
682 case 1:
683 /* finished final pass */
684 pos->state = 0;
685 break;
686 case 2:
687 /* Need to record the start */
688 pos->lo = prv;
689 /* time for another pass */
690 pos->p = pos->hi;
691 pos->state--;
692 break;
695 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
696 switch (pos->state) {
697 case 1:
698 mask &= ~(RESULT|AFTER);
699 break;
700 case 2:
701 mask &= ~(ORIG|BEFORE);
702 break;
704 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
707 /* blank a whole row of display */
708 static void blank(int row, int start, int cols, unsigned int attr)
710 (void)attrset(attr);
711 move(row, start);
712 while (cols-- > 0)
713 addch(' ');
716 /* search of a string on one display line. If found, update the
717 * cursor.
720 static int mcontains(struct mpos pos,
721 struct file fm, struct file fb, struct file fa,
722 struct merge *m,
723 int mode, char *search, struct cursor *curs,
724 int dir, int ignore_case)
726 /* See if any of the files, between start of this line and here,
727 * contain the search string.
728 * However this is modified by dir:
729 * -2: find last match *before* curs
730 * -1: find last match at-or-before curs
731 * 1: find first match at-or-after curs
732 * 2: find first match *after* curs
734 * We only test for equality with curs, so if it is on a different
735 * line it will not be found and everything is before/after.
736 * As we search from end-of-line to start we find the last
737 * match first.
738 * For a forward search, we stop when we find curs.
739 * For a backward search, we forget anything found when we find curs.
741 struct elmnt e;
742 int found = 0;
743 struct mp mp;
744 int o;
745 int len = strlen(search);
747 do {
748 e = prev_melmnt(&pos.p, fm, fb, fa, m);
749 if (e.start && e.start[0]) {
750 int i;
751 int curs_i;
752 if (same_mp(pos.p, curs->pos))
753 curs_i = curs->offset;
754 else
755 curs_i = -1;
756 for (i = e.len-1; i >= 0; i--) {
757 if (i == curs_i && dir == -1)
758 /* next match is the one we want */
759 found = 0;
760 if (i == curs_i && dir == 2)
761 /* future matches not accepted */
762 goto break_while;
763 if ((!found || dir > 0) &&
764 (ignore_case ? strncasecmp : strncmp)
765 (e.start+i, search, len) == 0) {
766 mp = pos.p;
767 o = i;
768 found = 1;
770 if (i == curs_i && dir == -2)
771 /* next match is the one we want */
772 found = 0;
773 if (i == curs_i && dir == 1)
774 /* future matches not accepted */
775 goto break_while;
778 } while (e.start != NULL &&
779 (!ends_mline(e)
780 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
781 break_while:
782 if (found) {
783 curs->pos = mp;
784 curs->offset = o;
786 return found;
789 /* Drawing the display window.
790 * There are 7 different ways we can display the data, each
791 * of which can be configured by a keystroke:
792 * o original - just show the original file with no changes, but still
793 * with highlights of what is changed or unmatched
794 * r result - show just the result of the merge. Conflicts just show
795 * the original, not the before/after options
796 * b before - show the 'before' stream of the patch
797 * a after - show the 'after' stream of the patch
798 * d diff - show just the patch, both before and after
799 * m merge - show the full merge with -+ sections for changes.
800 * If point is in a wiggled or conflicted section the
801 * window is split horizontally and the diff is shown
802 * in the bottom window
803 * | sidebyside - two panes, left and right. Left holds the merge,
804 * right holds the diff. In the case of a conflict,
805 * left holds orig/after, right holds before/after
807 * The horizontal split for 'merge' mode is managed as follows.
808 * - The window is split when we first visit a line that contains
809 * a wiggle or a conflict, and the second pane is removed when
810 * we next visit a line that contains no changes (is fully Unchanged).
811 * - to display the second pane, we find a visible end-of-line in the
812 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
813 * the we centre that line.
814 * - We need to rewind to an unchanged section, and wind forward again
815 * to make sure that 'lo' and 'hi' are set properly.
816 * - every time we move, we redraw the second pane (see how that goes).
819 /* draw_mside draws one text line or, in the case of sidebyside, one side
820 * of a textline.
821 * The 'mode' tells us what to draw via the 'visible()' function.
822 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
823 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
824 * The desired cursor position is given in 'target' the actual end
825 * cursor position (allowing e.g. for tabs) is returned in *colp.
827 static void draw_mside(int mode, int row, int offset, int start, int cols,
828 struct file fm, struct file fb, struct file fa,
829 struct merge *m,
830 struct mpos pos,
831 struct cursor *curs)
833 struct elmnt e;
834 int col = 0;
835 char tag;
836 unsigned int tag_attr;
838 switch (pos.state) {
839 case 0: /* unchanged line */
840 tag = ' ';
841 tag_attr = A_NORMAL;
842 break;
843 case 1: /* 'before' text */
844 tag = '-';
845 tag_attr = a_delete;
846 if ((mode & ORIG) && (mode & CONFLICTED)) {
847 tag = '|';
848 tag_attr = a_delete;
850 mode &= (ORIG|BEFORE);
851 break;
852 case 2: /* the 'after' part */
853 tag = '+';
854 tag_attr = a_added;
855 mode &= (AFTER|RESULT);
856 break;
859 if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) {
860 /* Not visible, just draw a blank */
861 blank(row, offset, cols, a_void);
862 if (curs) {
863 curs->width = -1;
864 curs->col = 0;
865 curs->pos = pos.p;
866 curs->offset = 0;
868 return;
871 (void)attrset(tag_attr);
872 mvaddch(row, offset, tag);
873 offset++;
874 cols--;
875 (void)attrset(A_NORMAL);
877 /* find previous visible newline, or start of file */
879 e = prev_melmnt(&pos.p, fm, fb, fa, m);
880 while (e.start != NULL &&
881 (!ends_mline(e) ||
882 visible(mode, m[pos.p.m].type, pos.p.s) == -1));
884 while (1) {
885 unsigned char *c;
886 int l;
887 e = next_melmnt(&pos.p, fm, fb, fa, m);
888 if (e.start == NULL ||
889 (ends_mline(e)
890 && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
891 /* We have reached the end of visible line, or end of file */
892 if (curs) {
893 curs->col = col;
894 if (col >= start + cols)
895 curs->width = 0;
896 else
897 curs->width = -1; /* end of line */
898 if (curs->target >= 0) {
899 curs->pos = pos.p;
900 curs->offset = 0;
901 } else if (same_mp(pos.p, curs->pos))
902 curs->target = col;
904 if (col < start)
905 col = start;
906 if (e.start && e.start[0] == 0) {
907 char b[40];
908 struct elmnt e1;
909 if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) {
910 int A, B, C, D, E, F;
911 e1 = fb.list[m[pos.p.m].b + pos.p.o];
912 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
913 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
914 sprintf(b, "@@ -%d,%d +%d,%d @@\n", B, C, E, F);
915 (void)attrset(a_sep);
916 } else {
917 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
918 sprintf(b, "<%.17s>", e.start+1);
920 mvaddstr(row, col-start+offset, b);
921 col += strlen(b);
923 blank(row, col-start+offset, start+cols-col,
924 e.start
925 ? (unsigned)visible(mode, m[pos.p.m].type, pos.p.s)
926 : A_NORMAL);
927 return;
929 if (visible(mode, m[pos.p.m].type, pos.p.s) == -1)
930 continue;
931 if (e.start[0] == 0)
932 continue;
933 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
934 c = (unsigned char *)e.start;
935 for (l = 0; l < e.len; l++) {
936 int scol = col;
937 if (*c >= ' ' && *c != 0x7f) {
938 if (col >= start && col < start+cols)
939 mvaddch(row, col-start+offset, *c);
940 col++;
941 } else if (*c == '\t') {
942 do {
943 if (col >= start && col < start+cols) {
944 mvaddch(row, col-start+offset, ' ');
945 } col++;
946 } while ((col&7) != 0);
947 } else {
948 if (col >= start && col < start+cols)
949 mvaddch(row, col-start+offset, '?');
950 col++;
952 if (curs) {
953 if (curs->target >= 0) {
954 if (curs->target < col) {
955 /* Found target column */
956 curs->pos = pos.p;
957 curs->offset = l;
958 curs->col = scol;
959 if (scol >= start + cols)
960 /* Didn't appear on screen */
961 curs->width = 0;
962 else
963 curs->width = col - scol;
964 curs = NULL;
966 } else if (l == curs->offset &&
967 same_mp(pos.p, curs->pos)) {
968 /* Found the pos */
969 curs->target = scol;
970 curs->col = scol;
971 if (scol >= start + cols)
972 /* Didn't appear on screen */
973 curs->width = 0;
974 else
975 curs->width = col - scol;
976 curs = NULL;
979 c++;
984 /* Draw either 1 or 2 sides depending on the mode. */
986 static void draw_mline(int mode, int row, int start, int cols,
987 struct file fm, struct file fb, struct file fa,
988 struct merge *m,
989 struct mpos pos,
990 struct cursor *curs)
993 * Draw the left and right images of this line
994 * One side might be a_blank depending on the
995 * visibility of this newline
997 int lcols, rcols;
999 mode |= check_line(pos, fm, fb, fa, m, mode);
1001 if ((mode & (BEFORE|AFTER)) &&
1002 (mode & (ORIG|RESULT))) {
1004 lcols = (cols-1)/2;
1005 rcols = cols - lcols - 1;
1007 (void)attrset(A_STANDOUT);
1008 mvaddch(row, lcols, '|');
1010 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1011 fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1013 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1014 fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1015 } else
1016 draw_mside(mode, row, 0, start, cols,
1017 fm, fb, fa, m, pos, curs);
1020 static char *merge_help[] = {
1021 "This view shows the merge of the patch with the",
1022 "original file. It is like a full-context diff showing",
1023 "removed lines with a '-' prefix and added lines with a",
1024 "'+' prefix.",
1025 "In cases where a patch chunk could not be successfully",
1026 "applied, the original text is prefixed with a '|', and",
1027 "the text that the patch wanted to add is prefixed with",
1028 "a '+'.",
1029 "When the cursor is over such a conflict, or over a chunk",
1030 "which required wiggling to apply (i.e. there was unmatched",
1031 "text in the original, or extraneous unchanged text in",
1032 "the patch), the terminal is split and the bottom pane is",
1033 "use to display the part of the patch that applied to",
1034 "this section of the original. This allows you to confirm",
1035 "that a wiggled patch applied correctly, and to see",
1036 "why there was a conflict",
1037 NULL
1039 static char *diff_help[] = {
1040 "This is the 'diff' or 'patch' view. It shows",
1041 "only the patch that is being applied without the",
1042 "original to which it is being applied.",
1043 "Underlined text indicates parts of the patch which",
1044 "resulted in a conflict when applied to the",
1045 "original.",
1046 NULL
1048 static char *orig_help[] = {
1049 "This is the 'original' view which simply shows",
1050 "the original file before applying the patch.",
1051 "Sections of code that would be changed by the patch",
1052 "are highlighted in red.",
1053 NULL
1055 static char *result_help[] = {
1056 "This is the 'result' view which shows just the",
1057 "result of applying the patch. When a conflict",
1058 "occurred this view does not show the full conflict",
1059 "but only the 'after' part of the patch. To see",
1060 "the full conflict, use the 'merge' or 'sidebyside'",
1061 "views.",
1062 NULL
1064 static char *before_help[] = {
1065 "This view shows the 'before' section of a patch.",
1066 "It allows the expected match text to be seen uncluttered",
1067 "by text that is meant to replaced it."
1068 "Red text is text that will be removed by the patch",
1069 NULL
1071 static char *after_help[] = {
1072 "This view shows the 'after' section of a patch.",
1073 "It allows the intended result to be seen uncluttered",
1074 "by text that was meant to be matched and replaced."
1075 "Green text is text that was added by the patch - it",
1076 "was not present in the 'before' part of the patch",
1077 NULL
1079 static char *sidebyside_help[] = {
1080 "This is the Side By Side view of a patched file.",
1081 "The left side shows the original and the result.",
1082 "The right side shows the patch which was applied",
1083 "and lines up with the original/result as much as",
1084 "possible.",
1086 "Where one side has no line which matches the",
1087 "other side it is displayed as a solid colour in the",
1088 "yellow family (depending on your terminal window).",
1089 NULL
1091 static char *merge_window_help[] = {
1092 " Highlight Colours and Keystroke commands",
1094 "In all different views of a merge, highlight colours",
1095 "are used to show which parts of lines were added,",
1096 "removed, already changed, unchanged or in conflict.",
1097 "Colours and their use are:",
1098 " normal unchanged text",
1099 " red text that was removed or changed",
1100 " green text that was added or the result",
1101 " of a change",
1102 " yellow background used in side-by-side for a line",
1103 " which has no match on the other",
1104 " side",
1105 " blue text in the original which did not",
1106 " match anything in the patch",
1107 " cyan text in the patch which did not",
1108 " match anything in the original",
1109 " cyan background already changed text: the result",
1110 " of the patch matches the original",
1111 " underline remove or added text can also be",
1112 " underlined indicating that it",
1113 " was involved in a conflict",
1115 "While viewing a merge various keystroke commands can",
1116 "be used to move around and change the view. Basic",
1117 "movement commands from both 'vi' and 'emacs' are",
1118 "available:",
1120 " p control-p k UP Move to previous line",
1121 " n control-n j DOWN Move to next line",
1122 " l LEFT Move one char to right",
1123 " h RIGHT Move one char to left",
1124 " / control-s Enter incremental search mode",
1125 " control-r Enter reverse-search mode",
1126 " control-g Search again",
1127 " ? Display help message",
1128 " ESC-< 0-G Go to start of file",
1129 " ESC-> G Go to end of file",
1130 " q Return to list of files or exit",
1131 " control-L recenter current line",
1132 " control-V page down",
1133 " ESC-v page up",
1134 " N go to next patch chunk",
1135 " P go to previous patch chunk",
1136 " O move cursor to alternate pane",
1137 " ^ control-A go to start of line",
1138 " $ control-E go to end of line",
1140 " a display 'after' view",
1141 " b display 'before' view",
1142 " o display 'original' view",
1143 " r display 'result' view",
1144 " d display 'diff' or 'patch' view",
1145 " m display 'merge' view",
1146 " | display side-by-side view",
1147 NULL
1150 static void merge_window(struct plist *p, FILE *f, int reverse)
1152 /* Display the merge window in one of the selectable modes,
1153 * starting with the 'merge' mode.
1155 * Newlines are the key to display.
1156 * 'pos' is always a visible newline (or eof).
1157 * In sidebyside mode it might only be visible on one side,
1158 * in which case the other side will be blank.
1159 * Where the newline is visible, we rewind the previous visible
1160 * newline visible and display the stuff in between
1162 * A 'position' is a struct mpos
1165 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1166 struct file fm, fb, fa;
1167 struct csl *csl1, *csl2;
1168 struct ci ci;
1169 int ch; /* count of chunks */
1170 /* Always refresh the current line.
1171 * If refresh == 1, refresh all lines. If == 2, clear first
1173 int refresh = 2;
1174 int rows = 0, cols = 0;
1175 int splitrow = -1; /* screen row for split - diff appears below */
1176 int lastrow = 0; /* end of screen, or just above 'splitrow' */
1177 int i, c, cswitch;
1178 int mode = ORIG|RESULT;
1179 int mmode = mode; /* Mode for moving - used when in 'other' pane */
1180 char *modename = "merge";
1181 char **modehelp = merge_help;
1183 int row, start = 0;
1184 int trow; /* screen-row while searching. If we cannot find,
1185 * we forget this number */
1186 struct cursor curs;
1187 struct mpos pos; /* current point */
1188 struct mpos tpos, /* temp point while drawing lines above and below pos */
1189 toppos, /* pos at top of screen - for page-up */
1190 botpos; /* pos at bottom of screen - for page-down */
1191 struct mpos vpos, tvpos;
1192 int botrow = 0;
1193 int meta = 0, /* mode for multi-key commands- SEARCH or META */
1194 tmeta;
1195 int num = -1, /* numeric arg being typed. */
1196 tnum;
1197 char search[80]; /* string we are searching for */
1198 unsigned int searchlen = 0;
1199 int search_notfound = 0;
1200 int searchdir = 0;
1201 /* ignore_case:
1202 * 0 == no
1203 * 1 == no because there are upper-case chars
1204 * 2 == yes as there are no upper-case chars
1205 * 3 == yes
1207 int ignore_case = 2;
1208 /* We record all the places we find so 'backspace'
1209 * can easily return to the previous one
1211 struct search_anchor {
1212 struct search_anchor *next;
1213 struct mpos pos;
1214 struct cursor curs;
1215 int notfound;
1216 int row, start;
1217 unsigned int searchlen;
1218 } *anchor = NULL;
1220 if (f == NULL) {
1221 /* three separate files */
1222 sm = load_file(p->file);
1223 sb = load_file(p->before);
1224 sa = load_file(p->after);
1225 ch = 0;
1226 } else {
1227 sp = load_segment(f, p->start, p->end);
1228 if (p->is_merge) {
1229 if (reverse)
1230 split_merge(sp, &sm, &sa, &sb);
1231 else
1232 split_merge(sp, &sm, &sb, &sa);
1233 ch = 0;
1234 } else {
1235 if (reverse)
1236 ch = split_patch(sp, &sa, &sb);
1237 else
1238 ch = split_patch(sp, &sb, &sa);
1240 sm = load_file(p->file);
1243 if (!sm.body || !sb.body || !sa.body) {
1244 term_init();
1245 if (!sm.body)
1246 help_window(help_missing, NULL);
1247 else
1248 help_window(help_corrupt, NULL);
1249 return;
1251 /* FIXME check for errors in the stream */
1252 fm = split_stream(sm, ByWord);
1253 fb = split_stream(sb, ByWord);
1254 fa = split_stream(sa, ByWord);
1256 if (ch)
1257 csl1 = pdiff(fm, fb, ch);
1258 else
1259 csl1 = diff(fm, fb);
1260 csl2 = diff(fb, fa);
1262 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
1264 term_init();
1266 row = 1;
1267 pos.p.m = 0; /* merge node */
1268 pos.p.s = 0; /* stream number */
1269 pos.p.o = -1; /* offset */
1270 pos.p.lineno = 1;
1271 pos.state = 0;
1272 next_mline(&pos, fm, fb, fa, ci.merger, mode);
1273 memset(&curs, 0, sizeof(curs));
1274 vpos = pos;
1275 while (1) {
1276 if (refresh >= 2) {
1277 char buf[100];
1278 clear();
1279 snprintf(buf, 100, "File: %s%s Mode: %s\n",
1280 p->file, reverse ? " - reversed" : "", modename);
1281 (void)attrset(A_BOLD);
1282 mvaddstr(0, 0, buf);
1283 clrtoeol();
1284 (void)attrset(A_NORMAL);
1285 refresh = 1;
1287 if (row < 1 || row >= lastrow)
1288 refresh = 1;
1289 if (curs.alt)
1290 refresh = 1;
1292 if (mode == (ORIG|RESULT)) {
1293 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1294 if (cmode & (WIGGLED | CONFLICTED)) {
1295 if (splitrow < 0) {
1296 splitrow = (rows+1)/2;
1297 lastrow = splitrow - 1;
1298 refresh = 1;
1300 } else if (!curs.alt && splitrow >= 0) {
1301 splitrow = -1;
1302 lastrow = rows-1;
1303 refresh = 1;
1305 } else if (splitrow >= 0) {
1306 splitrow = -1;
1307 lastrow = rows-1;
1308 refresh = 1;
1311 if (refresh) {
1312 getmaxyx(stdscr, rows, cols);
1313 rows--; /* keep last row clear */
1314 if (splitrow >= 0) {
1315 splitrow = (rows+1)/2;
1316 lastrow = splitrow - 1;
1317 } else
1318 lastrow = rows - 1;
1320 if (row < -3)
1321 row = lastrow/2+1;
1322 if (row < 1)
1323 row = 1;
1324 if (row > lastrow+3)
1325 row = lastrow/2+1;
1326 if (row >= lastrow)
1327 row = lastrow-1;
1329 if (getenv("WIGGLE_VTRACE")) {
1330 char b[100];
1331 char *e, e2[7];
1332 int i;
1333 switch (vpos.p.s) {
1334 case 0:
1335 e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start;
1336 break;
1337 case 1:
1338 e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start;
1339 break;
1340 case 2:
1341 e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start;
1342 break;
1344 for (i = 0; i < 6; i++) {
1345 e2[i] = e[i];
1346 if (e2[i] < 32 || e2[i] >= 127)
1347 e2[i] = '?';
1349 sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
1350 vpos.p.s, vpos.p.o,
1351 vpos.p.m, typenames[ci.merger[vpos.p.m].type],
1352 ci.merger[vpos.p.m].al,
1353 ci.merger[vpos.p.m].bl,
1354 ci.merger[vpos.p.m].cl,
1355 ci.merger[vpos.p.m].in_conflict,
1358 (void)attrset(A_NORMAL);
1359 mvaddstr(0, 50, b);
1360 clrtoeol();
1363 /* Always refresh the line */
1364 while (start > curs.target) {
1365 start -= 8;
1366 refresh = 1;
1368 if (start < 0)
1369 start = 0;
1370 retry:
1371 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1372 pos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1373 if (curs.width == 0 && start < curs.col) {
1374 /* width == 0 implies it appear after end-of-screen */
1375 start += 8;
1376 refresh = 1;
1377 goto retry;
1379 if (curs.col < start) {
1380 start -= 8;
1381 refresh = 1;
1382 if (start < 0)
1383 start = 0;
1384 goto retry;
1386 if (refresh) {
1387 refresh = 0;
1388 tpos = pos;
1390 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1391 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1392 draw_mline(mode, i--, start, cols,
1393 fm, fb, fa, ci.merger,
1394 tpos, NULL);
1397 if (i) {
1398 row -= (i+1);
1399 refresh = 1;
1400 goto retry;
1402 toppos = tpos;
1403 while (i >= 1)
1404 blank(i--, 0, cols, a_void);
1405 tpos = pos;
1406 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1407 draw_mline(mode, i++, start, cols,
1408 fm, fb, fa, ci.merger,
1409 tpos, NULL);
1410 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1412 botpos = tpos; botrow = i;
1413 while (i <= lastrow)
1414 blank(i++, 0, cols, a_void);
1417 if (splitrow >= 0) {
1418 struct mpos spos = pos;
1419 int smode = BEFORE|AFTER;
1420 int srow = (rows + splitrow)/2;
1421 if (visible(smode, ci.merger[spos.p.m].type,
1422 spos.p.s) < 0)
1423 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1424 /* Now hi/lo might be wrong, so lets fix it. */
1425 tpos = spos;
1426 while (spos.p.m >= 0 && spos.state != 0)
1427 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1428 while (!same_mpos(spos, tpos))
1429 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1431 (void)attrset(a_sep);
1432 for (i = 0; i < cols; i++)
1433 mvaddstr(splitrow, i, "-");
1435 tpos = spos;
1436 for (i = srow-1; i > splitrow; i--) {
1437 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1438 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1439 tpos, NULL);
1441 while (i > splitrow)
1442 blank(i--, 0, cols, a_void);
1443 tpos = spos;
1444 for (i = srow;
1445 i < rows && ci.merger[tpos.p.m].type != End;
1446 i++) {
1447 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1448 tpos,
1449 (i == srow && curs.alt) ? &curs : NULL);
1450 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1452 while (i < rows)
1453 blank(i++, 0, cols, a_void);
1455 /* Now that curs is accurate, report the type */
1457 char lbuf[30];
1458 (void)attrset(A_BOLD);
1459 snprintf(lbuf, 29, "%s ln:%d",
1460 typenames[ci.merger[curs.pos.m].type],
1461 (pos.p.lineno-1)/2);
1462 mvaddstr(0, cols - strlen(lbuf) - 4, " ");
1463 mvaddstr(0, cols - strlen(lbuf) - 1, lbuf);
1465 #define META(c) ((c)|0x1000)
1466 #define SEARCH(c) ((c)|0x2000)
1467 move(rows, 0);
1468 (void)attrset(A_NORMAL);
1469 if (num >= 0) {
1470 char buf[10];
1471 snprintf(buf, 10, "%d ", num);
1472 addstr(buf);
1474 if (meta & META(0))
1475 addstr("ESC...");
1476 if (meta & SEARCH(0)) {
1477 if (searchdir < 0)
1478 addstr("Backwards ");
1479 addstr("Search: ");
1480 addstr(search);
1481 if (search_notfound)
1482 addstr(" - Not Found.");
1483 search_notfound = 0;
1485 clrtoeol();
1486 /* '+1' to skip over the leading +/-/| char */
1487 if (curs.alt && splitrow > 0)
1488 move((rows + splitrow)/2, curs.col - start + 1);
1489 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1490 (mode & (ORIG|RESULT))))
1491 move(row, curs.col-start + (cols-1)/2+2);
1492 else
1493 move(row, curs.col-start+1);
1494 c = getch();
1495 tmeta = meta; meta = 0;
1496 tnum = num; num = -1;
1497 tvpos = vpos; vpos = pos;
1498 cswitch = c | tmeta;
1499 /* Handle some ranges */
1500 /* case '0' ... '9': */
1501 if (cswitch >= '0' && cswitch <= '9')
1502 cswitch = '0';
1503 /* case SEARCH(' ') ... SEARCH('~'): */
1504 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1505 cswitch = SEARCH(' ');
1507 switch (cswitch) {
1508 case 27: /* escape */
1509 case META(27):
1510 meta = META(0);
1511 break;
1512 case META('<'): /* start of file */
1513 start:
1514 tpos = pos; row++;
1515 do {
1516 pos = tpos; row--;
1517 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1518 } while (tpos.p.m >= 0);
1519 if (row <= 0)
1520 row = 0;
1521 break;
1522 case META('>'): /* end of file */
1523 case 'G':
1524 if (tnum >= 0)
1525 goto start;
1526 tpos = pos; row--;
1527 do {
1528 pos = tpos; row++;
1529 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1530 } while (ci.merger[tpos.p.m].type != End);
1531 if (row >= lastrow)
1532 row = lastrow;
1533 break;
1534 case '0': /* actually '0'...'9' */
1535 if (tnum < 0)
1536 tnum = 0;
1537 num = tnum*10 + (c-'0');
1538 break;
1539 case 'q':
1540 return;
1542 case '/':
1543 case 'S'-64:
1544 /* incr search forward */
1545 meta = SEARCH(0);
1546 searchlen = 0;
1547 search[searchlen] = 0;
1548 searchdir = 1;
1549 break;
1550 case '\\':
1551 case 'R'-64:
1552 /* incr search backwards */
1553 meta = SEARCH(0);
1554 searchlen = 0;
1555 search[searchlen] = 0;
1556 searchdir = -1;
1557 break;
1558 case SEARCH('G'-64):
1559 case SEARCH('S'-64):
1560 case SEARCH('R'-64):
1561 /* search again */
1562 if ((c|tmeta) == SEARCH('R'-64))
1563 searchdir = -2;
1564 else if ((c|tmeta) == SEARCH('S'-64))
1565 searchdir = 2;
1566 else
1567 searchdir *= 2;
1568 meta = SEARCH(0);
1569 tpos = pos; trow = row;
1570 goto search_again;
1572 case SEARCH('H'-64):
1573 case SEARCH(KEY_BACKSPACE):
1574 meta = SEARCH(0);
1575 if (anchor) {
1576 struct search_anchor *a;
1577 a = anchor;
1578 anchor = a->next;
1579 free(a);
1581 if (anchor) {
1582 struct search_anchor *a;
1583 a = anchor;
1584 anchor = a->next;
1585 pos = a->pos;
1586 row = a->row;
1587 start = a->start;
1588 curs = a->curs;
1589 curs.target = -1;
1590 search_notfound = a->notfound;
1591 searchlen = a->searchlen;
1592 search[searchlen] = 0;
1593 free(a);
1594 refresh = 1;
1596 break;
1597 case SEARCH(' '): /* actually ' '...'~' */
1598 case SEARCH('\t'):
1599 meta = SEARCH(0);
1600 if (searchlen < sizeof(search)-1)
1601 search[searchlen++] = c & (0x7f);
1602 search[searchlen] = 0;
1603 tpos = pos; trow = row;
1604 search_again:
1605 search_notfound = 1;
1606 if (ignore_case == 1 || ignore_case == 2) {
1607 unsigned int i;
1608 ignore_case = 2;
1609 for (i=0; i < searchlen; i++)
1610 if (isupper(search[i])) {
1611 ignore_case = 1;
1612 break;
1615 do {
1616 if (mcontains(tpos, fm, fb, fa, ci.merger,
1617 mmode, search, &curs, searchdir,
1618 ignore_case >= 2)) {
1619 curs.target = -1;
1620 pos = tpos;
1621 row = trow;
1622 search_notfound = 0;
1623 break;
1625 if (searchdir < 0) {
1626 trow--;
1627 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1628 } else {
1629 trow++;
1630 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1632 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1633 searchdir /= abs(searchdir);
1635 break;
1636 case 'L'-64:
1637 refresh = 2;
1638 row = lastrow / 2;
1639 break;
1641 case 'V'-64: /* page down */
1642 pos = botpos;
1643 if (botrow <= lastrow)
1644 row = botrow;
1645 else
1646 row = 2;
1647 refresh = 1;
1648 break;
1649 case META('v'): /* page up */
1650 pos = toppos;
1651 row = lastrow-1;
1652 refresh = 1;
1653 break;
1655 case 'j':
1656 case 'n':
1657 case 'N'-64:
1658 case KEY_DOWN:
1659 if (tnum < 0)
1660 tnum = 1;
1661 for (; tnum > 0 ; tnum--) {
1662 tpos = pos;
1663 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1664 if (ci.merger[tpos.p.m].type != End) {
1665 pos = tpos;
1666 row++;
1669 break;
1670 case 'N':
1671 /* Next diff */
1672 tpos = pos; row--;
1673 do {
1674 pos = tpos; row++;
1675 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1676 } while (pos.state != 0 && ci.merger[tpos.p.m].type != End);
1677 tpos = pos; row--;
1678 do {
1679 pos = tpos; row++;
1680 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1681 } while (pos.state == 0 && ci.merger[tpos.p.m].type != End);
1683 break;
1684 case 'P':
1685 /* Previous diff */
1686 tpos = pos; row++;
1687 do {
1688 pos = tpos; row--;
1689 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1690 } while (tpos.state == 0 && tpos.p.m >= 0);
1691 tpos = pos; row++;
1692 do {
1693 pos = tpos; row--;
1694 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1695 } while (tpos.state != 0 && tpos.p.m >= 0);
1696 break;
1698 case 'k':
1699 case 'p':
1700 case 'P'-64:
1701 case KEY_UP:
1702 if (tnum < 0)
1703 tnum = 1;
1704 for (; tnum > 0 ; tnum--) {
1705 tpos = pos;
1706 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1707 if (tpos.p.m >= 0) {
1708 pos = tpos;
1709 row--;
1712 break;
1714 case KEY_LEFT:
1715 case 'h':
1716 /* left */
1717 curs.target = curs.col - 1;
1718 if (curs.target < 0) {
1719 /* Try to go to end of previous line */
1720 tpos = pos;
1721 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1722 if (tpos.p.m >= 0) {
1723 pos = tpos;
1724 row--;
1725 curs.pos = pos.p;
1726 curs.target = -1;
1727 } else
1728 curs.target = 0;
1730 break;
1731 case KEY_RIGHT:
1732 case 'l':
1733 /* right */
1734 if (curs.width >= 0)
1735 curs.target = curs.col + curs.width;
1736 else {
1737 /* end of line, go to next */
1738 tpos = pos;
1739 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1740 if (ci.merger[tpos.p.m].type != End) {
1741 pos = tpos;
1742 curs.pos = pos.p;
1743 row++;
1744 curs.target = 0;
1747 break;
1749 case '^':
1750 case 'A'-64:
1751 /* Start of line */
1752 curs.target = 0;
1753 break;
1754 case '$':
1755 case 'E'-64:
1756 /* End of line */
1757 curs.target = 1000;
1758 break;
1760 case 'O':
1761 curs.alt = !curs.alt;
1762 if (curs.alt && mode == (ORIG|RESULT))
1763 mmode = (BEFORE|AFTER);
1764 else
1765 mmode = mode;
1766 break;
1768 case 'a': /* 'after' view in patch window */
1769 mode = AFTER; modename = "after"; modehelp = after_help;
1770 mmode = mode; curs.alt = 0;
1771 refresh = 3;
1772 break;
1773 case 'b': /* 'before' view in patch window */
1774 mode = BEFORE; modename = "before"; modehelp = before_help;
1775 mmode = mode; curs.alt = 0;
1776 refresh = 3;
1777 break;
1778 case 'o': /* 'original' view in the merge window */
1779 mode = ORIG; modename = "original"; modehelp = orig_help;
1780 mmode = mode; curs.alt = 0;
1781 refresh = 3;
1782 break;
1783 case 'r': /* the 'result' view in the merge window */
1784 mode = RESULT; modename = "result"; modehelp = result_help;
1785 mmode = mode; curs.alt = 0;
1786 refresh = 3;
1787 break;
1788 case 'd':
1789 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
1790 mmode = mode; curs.alt = 0;
1791 refresh = 3;
1792 break;
1793 case 'm':
1794 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
1795 mmode = mode; curs.alt = 0;
1796 refresh = 3;
1797 break;
1799 case '|':
1800 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
1801 mmode = mode; curs.alt = 0;
1802 refresh = 3;
1803 break;
1805 case 'H': /* scroll window to the right */
1806 if (start > 0)
1807 start--;
1808 curs.target = start + 1;
1809 refresh = 1;
1810 break;
1811 case 'L': /* scroll window to the left */
1812 if (start < cols)
1813 start++;
1814 curs.target = start + 1;
1815 refresh = 1;
1816 break;
1818 case '<':
1819 prev_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1820 if (tvpos.p.m >= 0)
1821 vpos = tvpos;
1822 break;
1823 case '>':
1824 next_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1825 if (ci.merger[tvpos.p.m].type != End)
1826 vpos = tvpos;
1827 break;
1829 case '?':
1830 help_window(modehelp, merge_window_help);
1831 refresh = 2;
1832 break;
1834 case KEY_RESIZE:
1835 refresh = 2;
1836 break;
1839 if (meta == SEARCH(0)) {
1840 if (anchor == NULL ||
1841 !same_mpos(anchor->pos, pos) ||
1842 anchor->searchlen != searchlen ||
1843 !same_mp(anchor->curs.pos, curs.pos)) {
1844 struct search_anchor *a = xmalloc(sizeof(*a));
1845 a->pos = pos;
1846 a->row = row;
1847 a->start = start;
1848 a->curs = curs;
1849 a->searchlen = searchlen;
1850 a->notfound = search_notfound;
1851 a->next = anchor;
1852 anchor = a;
1854 } else {
1855 while (anchor) {
1856 struct search_anchor *a = anchor;
1857 anchor = a->next;
1858 free(a);
1861 if (refresh == 3) {
1862 /* move backward and forward to make sure we
1863 * are on a visible line
1865 tpos = pos;
1866 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1867 if (tpos.p.m >= 0)
1868 pos = tpos;
1869 tpos = pos;
1870 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1871 if (ci.merger[tpos.p.m].type != End)
1872 pos = tpos;
1877 static void show_merge(char *origname, FILE *patch, int reverse,
1878 int is_merge, char *before, char *after)
1880 struct plist p;
1882 p.file = origname;
1883 if (patch) {
1884 p.start = 0;
1885 fseek(patch, 0, SEEK_END);
1886 p.end = ftell(patch);
1887 fseek(patch, 0, SEEK_SET);
1889 p.calced = 0;
1890 p.is_merge = is_merge;
1891 p.before = before;
1892 p.after = after;
1894 freopen("/dev/null","w",stderr);
1895 merge_window(&p, patch, reverse);
1898 static void calc_one(struct plist *pl, FILE *f, int reverse)
1900 struct stream s1, s2;
1901 struct stream s = load_segment(f, pl->start, pl->end);
1902 struct stream sf;
1903 if (pl->is_merge) {
1904 if (reverse)
1905 split_merge(s, &sf, &s2, &s1);
1906 else
1907 split_merge(s, &sf, &s1, &s2);
1908 pl->chunks = 0;
1909 } else {
1910 sf = load_file(pl->file);
1911 if (reverse)
1912 pl->chunks = split_patch(s, &s2, &s1);
1913 else
1914 pl->chunks = split_patch(s, &s1, &s2);
1916 if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
1917 pl->wiggles = pl->conflicts = -1;
1918 } else {
1919 struct file ff, fp1, fp2;
1920 struct csl *csl1, *csl2;
1921 struct ci ci;
1922 ff = split_stream(sf, ByWord);
1923 fp1 = split_stream(s1, ByWord);
1924 fp2 = split_stream(s2, ByWord);
1925 if (pl->chunks)
1926 csl1 = pdiff(ff, fp1, pl->chunks);
1927 else
1928 csl1 = diff(ff, fp1);
1929 csl2 = diff(fp1, fp2);
1930 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
1931 pl->wiggles = ci.wiggles;
1932 pl->conflicts = ci.conflicts;
1933 free(csl1);
1934 free(csl2);
1935 free(ff.list);
1936 free(fp1.list);
1937 free(fp2.list);
1940 free(s1.body);
1941 free(s2.body);
1942 free(s.body);
1943 free(sf.body);
1944 pl->calced = 1;
1947 static int get_prev(int pos, struct plist *pl, int n, int mode)
1949 int found = 0;
1950 if (pos == -1)
1951 return pos;
1952 do {
1953 if (pl[pos].prev == -1)
1954 return pl[pos].parent;
1955 pos = pl[pos].prev;
1956 while (pl[pos].open &&
1957 pl[pos].last >= 0)
1958 pos = pl[pos].last;
1959 if (pl[pos].last >= 0)
1960 /* always see directories */
1961 found = 1;
1962 else if (mode == 0)
1963 found = 1;
1964 else if (mode <= 1 && pl[pos].wiggles > 0)
1965 found = 1;
1966 else if (mode <= 2 && pl[pos].conflicts > 0)
1967 found = 1;
1968 } while (pos >= 0 && !found);
1969 return pos;
1972 static int get_next(int pos, struct plist *pl, int n, int mode,
1973 FILE *f, int reverse)
1975 int found = 0;
1976 if (pos == -1)
1977 return pos;
1978 do {
1979 if (pl[pos].open) {
1980 if (pos + 1 < n)
1981 pos = pos+1;
1982 else
1983 return -1;
1984 } else {
1985 while (pos >= 0 && pl[pos].next == -1)
1986 pos = pl[pos].parent;
1987 if (pos >= 0)
1988 pos = pl[pos].next;
1990 if (pos < 0)
1991 return -1;
1992 if (pl[pos].calced == 0 && pl[pos].end)
1993 calc_one(pl+pos, f, reverse);
1994 if (pl[pos].last >= 0)
1995 /* always see directories */
1996 found = 1;
1997 else if (mode == 0)
1998 found = 1;
1999 else if (mode <= 1 && pl[pos].wiggles > 0)
2000 found = 1;
2001 else if (mode <= 2 && pl[pos].conflicts > 0)
2002 found = 1;
2003 } while (pos >= 0 && !found);
2004 return pos;
2007 static void draw_one(int row, struct plist *pl, FILE *f, int reverse)
2009 char hdr[12];
2010 hdr[0] = 0;
2012 if (pl == NULL) {
2013 move(row, 0);
2014 clrtoeol();
2015 return;
2017 if (pl->calced == 0 && pl->end)
2018 /* better load the patch and count the chunks */
2019 calc_one(pl, f, reverse);
2020 if (pl->end == 0) {
2021 strcpy(hdr, " ");
2022 } else {
2023 if (pl->chunks > 99)
2024 strcpy(hdr, "XX");
2025 else
2026 sprintf(hdr, "%2d", pl->chunks);
2027 if (pl->wiggles > 99)
2028 strcpy(hdr+2, " XX");
2029 else
2030 sprintf(hdr+2, " %2d", pl->wiggles);
2031 if (pl->conflicts > 99)
2032 strcpy(hdr+5, " XX ");
2033 else
2034 sprintf(hdr+5, " %2d ", pl->conflicts);
2036 if (pl->end)
2037 strcpy(hdr+9, "= ");
2038 else if (pl->open)
2039 strcpy(hdr+9, "+ ");
2040 else
2041 strcpy(hdr+9, "- ");
2043 if (!pl->end)
2044 attrset(0);
2045 else if (pl->conflicts)
2046 attrset(a_has_conflicts);
2047 else if (pl->wiggles)
2048 attrset(a_has_wiggles);
2049 else
2050 attrset(a_no_wiggles);
2052 mvaddstr(row, 0, hdr);
2053 mvaddstr(row, 11, pl->file);
2054 clrtoeol();
2057 static char *main_help[] = {
2058 " You are using the \"browse\" mode of wiggle.",
2059 "This page shows a list of files in a patch together with",
2060 "the directories that contain them.",
2061 "A directory is indicated by a '+' if the contents are",
2062 "listed or a '-' if the contents are hidden. A file is",
2063 "indicated by an '='. Typing <space> or <return> will",
2064 "expose or hide a directory, and will visit a file.",
2066 "The three columns of numbers are:",
2067 " Ch The number of patch chunks which applied to",
2068 " this file",
2069 " Wi The number of chunks that needed to be wiggled",
2070 " in to place",
2071 " Co The number of chunks that created an unresolvable",
2072 " conflict",
2074 "Keystrokes recognised in this page are:",
2075 " ? Display this help",
2076 " SPC On a directory, toggle hiding of contents",
2077 " On file, visit the file",
2078 " RTN Same as SPC",
2079 " q Quit program",
2080 " n,j,DOWN Go to next line",
2081 " p,k,UP Go to previous line",
2083 " A list All files",
2084 " W only list files with a wiggle or a conflict",
2085 " C only list files with a conflict",
2086 NULL
2089 static void main_window(struct plist *pl, int n, FILE *f, int reverse)
2091 /* The main window lists all files together with summary information:
2092 * number of chunks, number of wiggles, number of conflicts.
2093 * The list is scrollable
2094 * When a entry is 'selected', we switch to the 'file' window
2095 * The list can be condensed by removing files with no conflict
2096 * or no wiggles, or removing subdirectories
2098 * We record which file in the list is 'current', and which
2099 * screen line it is on. We try to keep things stable while
2100 * moving.
2102 * Counts are printed before the name using at most 2 digits.
2103 * Numbers greater than 99 are XX
2104 * Ch Wi Co File
2105 * 27 5 1 drivers/md/md.c
2107 * A directory show the sum in all children.
2109 * Commands:
2110 * select: enter, space, mouseclick
2111 * on file, go to file window
2112 * on directory, toggle open
2113 * up: k, p, control-p uparrow
2114 * Move to previous open object
2115 * down: j, n, control-n, downarrow
2116 * Move to next open object
2118 * A W C: select All Wiggles or Conflicts
2119 * mode
2122 int pos = 0; /* position in file */
2123 int row = 1; /* position on screen */
2124 int rows = 0; /* size of screen in rows */
2125 int cols = 0;
2126 int tpos, i;
2127 int refresh = 2;
2128 int c = 0;
2129 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2131 freopen("/dev/null","w",stderr);
2132 term_init();
2133 pl = sort_patches(pl, &n);
2135 while (1) {
2136 if (refresh == 2) {
2137 clear(); (void)attrset(0);
2138 attron(A_BOLD);
2139 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2140 move(2, 0);
2141 attroff(A_BOLD);
2142 refresh = 1;
2144 if (row < 1 || row >= rows)
2145 refresh = 1;
2146 if (refresh) {
2147 refresh = 0;
2148 getmaxyx(stdscr, rows, cols);
2149 cols = cols; /* Silence warning that 'cols' isn't used */
2150 if (row >= rows + 3)
2151 row = (rows+1)/2;
2152 if (row >= rows)
2153 row = rows-1;
2154 tpos = pos;
2155 for (i = row; i > 1; i--) {
2156 tpos = get_prev(tpos, pl, n, mode);
2157 if (tpos == -1) {
2158 row = row - i + 1;
2159 break;
2162 /* Ok, row and pos could be trustworthy now */
2163 tpos = pos;
2164 for (i = row; i >= 1; i--) {
2165 draw_one(i, &pl[tpos], f, reverse);
2166 tpos = get_prev(tpos, pl, n, mode);
2168 tpos = pos;
2169 for (i = row+1; i < rows; i++) {
2170 tpos = get_next(tpos, pl, n, mode, f, reverse);
2171 if (tpos >= 0)
2172 draw_one(i, &pl[tpos], f, reverse);
2173 else
2174 draw_one(i, NULL, f, reverse);
2177 {char bb[20];
2178 sprintf(bb, "%d", c);
2179 mvaddstr(0, 70, bb);
2180 clrtoeol();
2182 move(row, 9);
2183 c = getch();
2184 switch (c) {
2185 case 'j':
2186 case 'n':
2187 case 'N':
2188 case 'N'-64:
2189 case KEY_DOWN:
2190 tpos = get_next(pos, pl, n, mode, f, reverse);
2191 if (tpos >= 0) {
2192 pos = tpos;
2193 row++;
2195 break;
2196 case 'k':
2197 case 'p':
2198 case 'P':
2199 case 'P'-64:
2200 case KEY_UP:
2201 tpos = get_prev(pos, pl, n, mode);
2202 if (tpos >= 0) {
2203 pos = tpos;
2204 row--;
2206 break;
2208 case ' ':
2209 case 13:
2210 if (pl[pos].end == 0) {
2211 pl[pos].open = !pl[pos].open;
2212 refresh = 1;
2213 } else {
2214 /* diff_window(&pl[pos], f); */
2215 merge_window(&pl[pos], f, reverse);
2216 refresh = 2;
2218 break;
2219 case 27: /* escape */
2220 mvaddstr(0, 70, "ESC..."); clrtoeol();
2221 c = getch();
2222 switch (c) {
2224 break;
2225 case 'q':
2226 return;
2228 case 'A':
2229 mode = 0; refresh = 1;
2230 break;
2231 case 'W':
2232 mode = 1; refresh = 1;
2233 break;
2234 case 'C':
2235 mode = 2; refresh = 1;
2236 break;
2238 case '?':
2239 help_window(main_help, NULL);
2240 refresh = 2;
2241 break;
2243 case KEY_RESIZE:
2244 refresh = 2;
2245 break;
2250 static void catch(int sig)
2252 if (sig == SIGINT) {
2253 signal(sig, catch);
2254 return;
2256 nocbreak();
2257 nl();
2258 endwin();
2259 printf("Died on signal %d\n", sig);
2260 exit(2);
2263 static void term_init(void)
2266 static int init_done = 0;
2268 if (init_done)
2269 return;
2270 init_done = 1;
2272 signal(SIGINT, catch);
2273 signal(SIGQUIT, catch);
2274 signal(SIGTERM, catch);
2275 signal(SIGBUS, catch);
2276 signal(SIGSEGV, catch);
2278 initscr(); cbreak(); noecho();
2279 start_color();
2280 use_default_colors();
2281 if (!has_colors()) {
2282 a_delete = A_UNDERLINE;
2283 a_added = A_BOLD;
2284 a_common = A_NORMAL;
2285 a_sep = A_STANDOUT;
2286 a_already = A_STANDOUT;
2287 a_has_conflicts = A_UNDERLINE;
2288 a_has_wiggles = A_BOLD;
2289 a_no_wiggles = A_NORMAL;
2290 } else {
2291 init_pair(1, COLOR_RED, -1);
2292 a_delete = COLOR_PAIR(1);
2293 init_pair(2, COLOR_GREEN, -1);
2294 a_added = COLOR_PAIR(2);
2295 a_common = A_NORMAL;
2296 init_pair(3, COLOR_WHITE, COLOR_GREEN);
2297 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
2298 init_pair(4, -1, COLOR_YELLOW);
2299 a_void = COLOR_PAIR(4);
2300 init_pair(5, COLOR_BLUE, -1);
2301 a_unmatched = COLOR_PAIR(5);
2302 init_pair(6, COLOR_CYAN, -1);
2303 a_extra = COLOR_PAIR(6);
2305 init_pair(7, COLOR_BLACK, COLOR_CYAN);
2306 a_already = COLOR_PAIR(7);
2308 a_has_conflicts = a_delete;
2309 a_has_wiggles = a_added;
2310 a_no_wiggles = a_unmatched;
2312 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
2313 mousemask(ALL_MOUSE_EVENTS, NULL);
2316 int vpatch(int argc, char *argv[], int patch, int strip,
2317 int reverse, int replace)
2319 /* NOTE argv[0] is first arg...
2320 * Behaviour depends on number of args:
2321 * 0: A multi-file patch is read from stdin
2322 * 1: if 'patch', parse it as a multi-file patch and allow
2323 * the files to be browsed.
2324 * if filename ends '.rej', then treat it as a patch again
2325 * a file with the same basename
2326 * Else treat the file as a merge (with conflicts) and view it.
2327 * 2: First file is original, second is patch
2328 * 3: Files are: original previous new. The diff between 'previous' and
2329 * 'new' needs to be applied to 'original'.
2331 * If a multi-file patch is being read, 'strip' tells how many
2332 * path components to strip. If it is -1, we guess based on
2333 * existing files.
2334 * If 'reverse' is given, when we invert any patch or diff
2335 * If 'replace' then we save the resulting merge.
2337 FILE *in;
2338 FILE *f;
2339 struct plist *pl;
2340 int num_patches;
2342 switch (argc) {
2343 default:
2344 fprintf(stderr, "%s: too many file names given.\n", Cmd);
2345 exit(1);
2347 case 0: /* stdin is a patch */
2348 if (lseek(fileno(stdin), 0L, 1) == -1) {
2349 /* cannot seek, so need to copy to a temp file */
2350 f = tmpfile();
2351 if (!f) {
2352 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
2353 exit(1);
2355 pl = parse_patch(stdin, f, &num_patches);
2356 in = f;
2357 } else {
2358 pl = parse_patch(stdin, NULL, &num_patches);
2359 in = fdopen(dup(0), "r");
2361 /* use stderr for keyboard input */
2362 dup2(2, 0);
2363 if (set_prefix(pl, num_patches, strip) == 0) {
2364 fprintf(stderr, "%s: aborting\n", Cmd);
2365 exit(2);
2367 main_window(pl, num_patches, in, reverse);
2368 break;
2370 case 1: /* a patch, a .rej, or a merge file */
2371 f = fopen(argv[0], "r");
2372 if (!f) {
2373 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2374 exit(1);
2376 if (patch) {
2377 pl = parse_patch(f, NULL, &num_patches);
2378 if (set_prefix(pl, num_patches, strip) == 0) {
2379 fprintf(stderr, "%s: aborting\n", Cmd);
2380 exit(2);
2382 main_window(pl, num_patches, f, reverse);
2383 } else if (strlen(argv[0]) > 4 &&
2384 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
2385 char *origname = strdup(argv[0]);
2386 origname[strlen(origname) - 4] = '\0';
2387 show_merge(origname, f, reverse, 0, NULL, NULL);
2388 } else
2389 show_merge(argv[0], f, reverse, 1, NULL, NULL);
2391 break;
2392 case 2: /* an orig and a diff/.ref */
2393 f = fopen(argv[1], "r");
2394 if (!f) {
2395 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2396 exit(1);
2398 show_merge(argv[0], f, reverse, 0, NULL, NULL);
2399 break;
2400 case 3: /* orig, before, after */
2401 show_merge(argv[0], NULL, reverse, 1, argv[1], argv[2]);
2402 break;
2405 nocbreak();
2406 nl();
2407 endwin();
2408 exit(0);