extract: removed unused variable: lineno
[wiggle/upstream.git] / vpatch.c
blob93102bc0ec9eb20399108309422438a4aee62f7f
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 (pos.p.o < m[pos.p.m].lo ||
582 pos.p.o > m[pos.p.m].hi))
583 rv |= CONFLICTED | CHANGES;
584 e = prev_melmnt(&pos.p, fm, fb, fa, m);
585 } while (e.start != NULL &&
586 (!ends_mline(e)
587 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
589 if (unmatched && (rv & CHANGES))
590 rv |= WIGGLED;
591 return rv;
594 /* Find the next line in the merge which is visible.
595 * If we hit the end of a conflicted set during pass-1
596 * we rewind for pass-2.
597 * 'mode' tells which bits we want to see, possible one of
598 * the 4 parts (before/after/orig/result) or one of the pairs
599 * before+after or orig+result.
601 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
602 struct file fa,
603 struct merge *m, int mode)
605 int mask;
606 do {
607 struct mp prv;
608 int mode2;
610 prv = pos->p;
611 while (1) {
612 struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
613 if (e.start == NULL)
614 break;
615 if (ends_mline(e) &&
616 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
617 break;
619 mode2 = check_line(*pos, fm, fb, fa, m, mode);
621 if ((mode2 & CHANGES) && pos->state == 0) {
622 /* Just entered a diff-set */
623 pos->lo = pos->p;
624 pos->state = 1;
625 } else if (!(mode2 & CHANGES) && pos->state) {
626 /* Come to the end of a diff-set */
627 switch (pos->state) {
628 case 1:
629 /* Need to record the end */
630 pos->hi = prv;
631 /* time for another pass */
632 pos->p = pos->lo;
633 pos->state++;
634 break;
635 case 2:
636 /* finished final pass */
637 pos->state = 0;
638 break;
641 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
642 switch (pos->state) {
643 case 1:
644 mask &= ~(RESULT|AFTER);
645 break;
646 case 2:
647 mask &= ~(ORIG|BEFORE);
648 break;
650 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
654 /* Move to previous line - simply the reverse of next_mline */
655 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
656 struct file fa,
657 struct merge *m, int mode)
659 int mask;
660 do {
661 struct mp prv;
662 int mode2;
664 prv = pos->p;
665 if (pos->p.m < 0)
666 return;
667 while (1) {
668 struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
669 if (e.start == NULL)
670 break;
671 if (ends_mline(e) &&
672 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
673 break;
675 mode2 = check_line(*pos, fm, fb, fa, m, mode);
677 if ((mode2 & CHANGES) && pos->state == 0) {
678 /* Just entered a diff-set */
679 pos->hi = pos->p;
680 pos->state = 2;
681 } else if (!(mode2 & CHANGES) && pos->state) {
682 /* Come to the end (start) of a diff-set */
683 switch (pos->state) {
684 case 1:
685 /* finished final pass */
686 pos->state = 0;
687 break;
688 case 2:
689 /* Need to record the start */
690 pos->lo = prv;
691 /* time for another pass */
692 pos->p = pos->hi;
693 pos->state--;
694 break;
697 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
698 switch (pos->state) {
699 case 1:
700 mask &= ~(RESULT|AFTER);
701 break;
702 case 2:
703 mask &= ~(ORIG|BEFORE);
704 break;
706 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
709 /* blank a whole row of display */
710 static void blank(int row, int start, int cols, unsigned int attr)
712 (void)attrset(attr);
713 move(row, start);
714 while (cols-- > 0)
715 addch(' ');
718 /* search of a string on one display line. If found, update the
719 * cursor.
722 static int mcontains(struct mpos pos,
723 struct file fm, struct file fb, struct file fa,
724 struct merge *m,
725 int mode, char *search, struct cursor *curs,
726 int dir, int ignore_case)
728 /* See if any of the files, between start of this line and here,
729 * contain the search string.
730 * However this is modified by dir:
731 * -2: find last match *before* curs
732 * -1: find last match at-or-before curs
733 * 1: find first match at-or-after curs
734 * 2: find first match *after* curs
736 * We only test for equality with curs, so if it is on a different
737 * line it will not be found and everything is before/after.
738 * As we search from end-of-line to start we find the last
739 * match first.
740 * For a forward search, we stop when we find curs.
741 * For a backward search, we forget anything found when we find curs.
743 struct elmnt e;
744 int found = 0;
745 struct mp mp;
746 int o;
747 int len = strlen(search);
749 do {
750 e = prev_melmnt(&pos.p, fm, fb, fa, m);
751 if (e.start && e.start[0]) {
752 int i;
753 int curs_i;
754 if (same_mp(pos.p, curs->pos))
755 curs_i = curs->offset;
756 else
757 curs_i = -1;
758 for (i = e.len-1; i >= 0; i--) {
759 if (i == curs_i && dir == -1)
760 /* next match is the one we want */
761 found = 0;
762 if (i == curs_i && dir == 2)
763 /* future matches not accepted */
764 goto break_while;
765 if ((!found || dir > 0) &&
766 (ignore_case ? strncasecmp : strncmp)
767 (e.start+i, search, len) == 0) {
768 mp = pos.p;
769 o = i;
770 found = 1;
772 if (i == curs_i && dir == -2)
773 /* next match is the one we want */
774 found = 0;
775 if (i == curs_i && dir == 1)
776 /* future matches not accepted */
777 goto break_while;
780 } while (e.start != NULL &&
781 (!ends_mline(e)
782 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
783 break_while:
784 if (found) {
785 curs->pos = mp;
786 curs->offset = o;
788 return found;
791 /* Drawing the display window.
792 * There are 7 different ways we can display the data, each
793 * of which can be configured by a keystroke:
794 * o original - just show the original file with no changes, but still
795 * with highlights of what is changed or unmatched
796 * r result - show just the result of the merge. Conflicts just show
797 * the original, not the before/after options
798 * b before - show the 'before' stream of the patch
799 * a after - show the 'after' stream of the patch
800 * d diff - show just the patch, both before and after
801 * m merge - show the full merge with -+ sections for changes.
802 * If point is in a wiggled or conflicted section the
803 * window is split horizontally and the diff is shown
804 * in the bottom window
805 * | sidebyside - two panes, left and right. Left holds the merge,
806 * right holds the diff. In the case of a conflict,
807 * left holds orig/after, right holds before/after
809 * The horizontal split for 'merge' mode is managed as follows.
810 * - The window is split when we first visit a line that contains
811 * a wiggle or a conflict, and the second pane is removed when
812 * we next visit a line that contains no changes (is fully Unchanged).
813 * - to display the second pane, we find a visible end-of-line in the
814 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
815 * the we centre that line.
816 * - We need to rewind to an unchanged section, and wind forward again
817 * to make sure that 'lo' and 'hi' are set properly.
818 * - every time we move, we redraw the second pane (see how that goes).
821 /* draw_mside draws one text line or, in the case of sidebyside, one side
822 * of a textline.
823 * The 'mode' tells us what to draw via the 'visible()' function.
824 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
825 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
826 * The desired cursor position is given in 'target' the actual end
827 * cursor position (allowing e.g. for tabs) is returned in *colp.
829 static void draw_mside(int mode, int row, int offset, int start, int cols,
830 struct file fm, struct file fb, struct file fa,
831 struct merge *m,
832 struct mpos pos,
833 struct cursor *curs)
835 struct elmnt e;
836 int col = 0;
837 char tag;
838 unsigned int tag_attr;
840 switch (pos.state) {
841 case 0: /* unchanged line */
842 tag = ' ';
843 tag_attr = A_NORMAL;
844 break;
845 case 1: /* 'before' text */
846 tag = '-';
847 tag_attr = a_delete;
848 if ((mode & ORIG) && (mode & CONFLICTED)) {
849 tag = '|';
850 tag_attr = a_delete;
852 mode &= (ORIG|BEFORE);
853 break;
854 case 2: /* the 'after' part */
855 tag = '+';
856 tag_attr = a_added;
857 mode &= (AFTER|RESULT);
858 break;
861 if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) {
862 /* Not visible, just draw a blank */
863 blank(row, offset, cols, a_void);
864 if (curs) {
865 curs->width = -1;
866 curs->col = 0;
867 curs->pos = pos.p;
868 curs->offset = 0;
870 return;
873 (void)attrset(tag_attr);
874 mvaddch(row, offset, tag);
875 offset++;
876 cols--;
877 (void)attrset(A_NORMAL);
879 /* find previous visible newline, or start of file */
881 e = prev_melmnt(&pos.p, fm, fb, fa, m);
882 while (e.start != NULL &&
883 (!ends_mline(e) ||
884 visible(mode, m[pos.p.m].type, pos.p.s) == -1));
886 while (1) {
887 unsigned char *c;
888 int l;
889 e = next_melmnt(&pos.p, fm, fb, fa, m);
890 if (e.start == NULL ||
891 (ends_mline(e)
892 && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
893 /* We have reached the end of visible line, or end of file */
894 if (curs) {
895 curs->col = col;
896 if (col >= start + cols)
897 curs->width = 0;
898 else
899 curs->width = -1; /* end of line */
900 if (curs->target >= 0) {
901 curs->pos = pos.p;
902 curs->offset = 0;
903 } else if (same_mp(pos.p, curs->pos))
904 curs->target = col;
906 if (col < start)
907 col = start;
908 if (e.start && e.start[0] == 0) {
909 char b[40];
910 struct elmnt e1;
911 if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) {
912 int A, B, C, D, E, F;
913 e1 = fb.list[m[pos.p.m].b + pos.p.o];
914 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
915 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
916 sprintf(b, "@@ -%d,%d +%d,%d @@\n", B, C, E, F);
917 (void)attrset(a_sep);
918 } else {
919 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
920 sprintf(b, "<%.17s>", e.start+1);
922 mvaddstr(row, col-start+offset, b);
923 col += strlen(b);
925 blank(row, col-start+offset, start+cols-col,
926 e.start
927 ? (unsigned)visible(mode, m[pos.p.m].type, pos.p.s)
928 : A_NORMAL);
929 return;
931 if (visible(mode, m[pos.p.m].type, pos.p.s) == -1)
932 continue;
933 if (e.start[0] == 0)
934 continue;
935 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
936 c = (unsigned char *)e.start;
937 for (l = 0; l < e.len; l++) {
938 int scol = col;
939 if (*c >= ' ' && *c != 0x7f) {
940 if (col >= start && col < start+cols)
941 mvaddch(row, col-start+offset, *c);
942 col++;
943 } else if (*c == '\t') {
944 do {
945 if (col >= start && col < start+cols) {
946 mvaddch(row, col-start+offset, ' ');
947 } col++;
948 } while ((col&7) != 0);
949 } else {
950 if (col >= start && col < start+cols)
951 mvaddch(row, col-start+offset, '?');
952 col++;
954 if (curs) {
955 if (curs->target >= 0) {
956 if (curs->target < col) {
957 /* Found target column */
958 curs->pos = pos.p;
959 curs->offset = l;
960 curs->col = scol;
961 if (scol >= start + cols)
962 /* Didn't appear on screen */
963 curs->width = 0;
964 else
965 curs->width = col - scol;
966 curs = NULL;
968 } else if (l == curs->offset &&
969 same_mp(pos.p, curs->pos)) {
970 /* Found the pos */
971 curs->target = scol;
972 curs->col = scol;
973 if (scol >= start + cols)
974 /* Didn't appear on screen */
975 curs->width = 0;
976 else
977 curs->width = col - scol;
978 curs = NULL;
981 c++;
986 /* Draw either 1 or 2 sides depending on the mode. */
988 static void draw_mline(int mode, int row, int start, int cols,
989 struct file fm, struct file fb, struct file fa,
990 struct merge *m,
991 struct mpos pos,
992 struct cursor *curs)
995 * Draw the left and right images of this line
996 * One side might be a_blank depending on the
997 * visibility of this newline
999 int lcols, rcols;
1001 mode |= check_line(pos, fm, fb, fa, m, mode);
1003 if ((mode & (BEFORE|AFTER)) &&
1004 (mode & (ORIG|RESULT))) {
1006 lcols = (cols-1)/2;
1007 rcols = cols - lcols - 1;
1009 (void)attrset(A_STANDOUT);
1010 mvaddch(row, lcols, '|');
1012 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1013 fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1015 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1016 fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1017 } else
1018 draw_mside(mode, row, 0, start, cols,
1019 fm, fb, fa, m, pos, curs);
1022 static char *merge_help[] = {
1023 "This view shows the merge of the patch with the",
1024 "original file. It is like a full-context diff showing",
1025 "removed lines with a '-' prefix and added lines with a",
1026 "'+' prefix.",
1027 "In cases where a patch chunk could not be successfully",
1028 "applied, the original text is prefixed with a '|', and",
1029 "the text that the patch wanted to add is prefixed with",
1030 "a '+'.",
1031 "When the cursor is over such a conflict, or over a chunk",
1032 "which required wiggling to apply (i.e. there was unmatched",
1033 "text in the original, or extraneous unchanged text in",
1034 "the patch), the terminal is split and the bottom pane is",
1035 "use to display the part of the patch that applied to",
1036 "this section of the original. This allows you to confirm",
1037 "that a wiggled patch applied correctly, and to see",
1038 "why there was a conflict",
1039 NULL
1041 static char *diff_help[] = {
1042 "This is the 'diff' or 'patch' view. It shows",
1043 "only the patch that is being applied without the",
1044 "original to which it is being applied.",
1045 "Underlined text indicates parts of the patch which",
1046 "resulted in a conflict when applied to the",
1047 "original.",
1048 NULL
1050 static char *orig_help[] = {
1051 "This is the 'original' view which simply shows",
1052 "the original file before applying the patch.",
1053 "Sections of code that would be changed by the patch",
1054 "are highlighted in red.",
1055 NULL
1057 static char *result_help[] = {
1058 "This is the 'result' view which shows just the",
1059 "result of applying the patch. When a conflict",
1060 "occurred this view does not show the full conflict",
1061 "but only the 'after' part of the patch. To see",
1062 "the full conflict, use the 'merge' or 'sidebyside'",
1063 "views.",
1064 NULL
1066 static char *before_help[] = {
1067 "This view shows the 'before' section of a patch.",
1068 "It allows the expected match text to be seen uncluttered",
1069 "by text that is meant to replaced it."
1070 "Red text is text that will be removed by the patch",
1071 NULL
1073 static char *after_help[] = {
1074 "This view shows the 'after' section of a patch.",
1075 "It allows the intended result to be seen uncluttered",
1076 "by text that was meant to be matched and replaced."
1077 "Green text is text that was added by the patch - it",
1078 "was not present in the 'before' part of the patch",
1079 NULL
1081 static char *sidebyside_help[] = {
1082 "This is the Side By Side view of a patched file.",
1083 "The left side shows the original and the result.",
1084 "The right side shows the patch which was applied",
1085 "and lines up with the original/result as much as",
1086 "possible.",
1088 "Where one side has no line which matches the",
1089 "other side it is displayed as a solid colour in the",
1090 "yellow family (depending on your terminal window).",
1091 NULL
1093 static char *merge_window_help[] = {
1094 " Highlight Colours and Keystroke commands",
1096 "In all different views of a merge, highlight colours",
1097 "are used to show which parts of lines were added,",
1098 "removed, already changed, unchanged or in conflict.",
1099 "Colours and their use are:",
1100 " normal unchanged text",
1101 " red text that was removed or changed",
1102 " green text that was added or the result",
1103 " of a change",
1104 " yellow background used in side-by-side for a line",
1105 " which has no match on the other",
1106 " side",
1107 " blue text in the original which did not",
1108 " match anything in the patch",
1109 " cyan text in the patch which did not",
1110 " match anything in the original",
1111 " cyan background already changed text: the result",
1112 " of the patch matches the original",
1113 " underline remove or added text can also be",
1114 " underlined indicating that it",
1115 " was involved in a conflict",
1117 "While viewing a merge various keystroke commands can",
1118 "be used to move around and change the view. Basic",
1119 "movement commands from both 'vi' and 'emacs' are",
1120 "available:",
1122 " p control-p k UP Move to previous line",
1123 " n control-n j DOWN Move to next line",
1124 " l LEFT Move one char to right",
1125 " h RIGHT Move one char to left",
1126 " / control-s Enter incremental search mode",
1127 " control-r Enter reverse-search mode",
1128 " control-g Search again",
1129 " ? Display help message",
1130 " ESC-< 0-G Go to start of file",
1131 " ESC-> G Go to end of file",
1132 " q Return to list of files or exit",
1133 " control-L recenter current line",
1134 " control-V page down",
1135 " ESC-v page up",
1136 " N go to next patch chunk",
1137 " P go to previous patch chunk",
1138 " O move cursor to alternate pane",
1139 " ^ control-A go to start of line",
1140 " $ control-E go to end of line",
1142 " a display 'after' view",
1143 " b display 'before' view",
1144 " o display 'original' view",
1145 " r display 'result' view",
1146 " d display 'diff' or 'patch' view",
1147 " m display 'merge' view",
1148 " | display side-by-side view",
1149 NULL
1152 static void merge_window(struct plist *p, FILE *f, int reverse)
1154 /* Display the merge window in one of the selectable modes,
1155 * starting with the 'merge' mode.
1157 * Newlines are the key to display.
1158 * 'pos' is always a visible newline (or eof).
1159 * In sidebyside mode it might only be visible on one side,
1160 * in which case the other side will be blank.
1161 * Where the newline is visible, we rewind the previous visible
1162 * newline visible and display the stuff in between
1164 * A 'position' is a struct mpos
1167 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1168 struct file fm, fb, fa;
1169 struct csl *csl1, *csl2;
1170 struct ci ci;
1171 int ch; /* count of chunks */
1172 /* Always refresh the current line.
1173 * If refresh == 1, refresh all lines. If == 2, clear first
1175 int refresh = 2;
1176 int rows = 0, cols = 0;
1177 int splitrow = -1; /* screen row for split - diff appears below */
1178 int lastrow = 0; /* end of screen, or just above 'splitrow' */
1179 int i, c, cswitch;
1180 int mode = ORIG|RESULT;
1181 int mmode = mode; /* Mode for moving - used when in 'other' pane */
1182 char *modename = "merge";
1183 char **modehelp = merge_help;
1185 int row, start = 0;
1186 int trow; /* screen-row while searching. If we cannot find,
1187 * we forget this number */
1188 struct cursor curs;
1189 struct mpos pos; /* current point */
1190 struct mpos tpos, /* temp point while drawing lines above and below pos */
1191 toppos, /* pos at top of screen - for page-up */
1192 botpos; /* pos at bottom of screen - for page-down */
1193 struct mpos vpos, tvpos;
1194 int botrow = 0;
1195 int meta = 0, /* mode for multi-key commands- SEARCH or META */
1196 tmeta;
1197 int num = -1, /* numeric arg being typed. */
1198 tnum;
1199 char search[80]; /* string we are searching for */
1200 unsigned int searchlen = 0;
1201 int search_notfound = 0;
1202 int searchdir = 0;
1203 /* ignore_case:
1204 * 0 == no
1205 * 1 == no because there are upper-case chars
1206 * 2 == yes as there are no upper-case chars
1207 * 3 == yes
1209 int ignore_case = 2;
1210 /* We record all the places we find so 'backspace'
1211 * can easily return to the previous one
1213 struct search_anchor {
1214 struct search_anchor *next;
1215 struct mpos pos;
1216 struct cursor curs;
1217 int notfound;
1218 int row, start;
1219 unsigned int searchlen;
1220 } *anchor = NULL;
1222 if (f == NULL) {
1223 /* three separate files */
1224 sm = load_file(p->file);
1225 sb = load_file(p->before);
1226 sa = load_file(p->after);
1227 ch = 0;
1228 } else {
1229 sp = load_segment(f, p->start, p->end);
1230 if (p->is_merge) {
1231 if (reverse)
1232 split_merge(sp, &sm, &sa, &sb);
1233 else
1234 split_merge(sp, &sm, &sb, &sa);
1235 ch = 0;
1236 } else {
1237 if (reverse)
1238 ch = split_patch(sp, &sa, &sb);
1239 else
1240 ch = split_patch(sp, &sb, &sa);
1242 sm = load_file(p->file);
1245 if (!sm.body || !sb.body || !sa.body) {
1246 term_init();
1247 if (!sm.body)
1248 help_window(help_missing, NULL);
1249 else
1250 help_window(help_corrupt, NULL);
1251 return;
1253 /* FIXME check for errors in the stream */
1254 fm = split_stream(sm, ByWord);
1255 fb = split_stream(sb, ByWord);
1256 fa = split_stream(sa, ByWord);
1258 if (ch)
1259 csl1 = pdiff(fm, fb, ch);
1260 else
1261 csl1 = diff(fm, fb);
1262 csl2 = diff(fb, fa);
1264 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
1266 term_init();
1268 row = 1;
1269 pos.p.m = 0; /* merge node */
1270 pos.p.s = 0; /* stream number */
1271 pos.p.o = -1; /* offset */
1272 pos.p.lineno = 1;
1273 pos.state = 0;
1274 next_mline(&pos, fm, fb, fa, ci.merger, mode);
1275 memset(&curs, 0, sizeof(curs));
1276 vpos = pos;
1277 while (1) {
1278 if (refresh >= 2) {
1279 char buf[100];
1280 clear();
1281 snprintf(buf, 100, "File: %s%s Mode: %s\n",
1282 p->file, reverse ? " - reversed" : "", modename);
1283 (void)attrset(A_BOLD);
1284 mvaddstr(0, 0, buf);
1285 clrtoeol();
1286 (void)attrset(A_NORMAL);
1287 refresh = 1;
1289 if (row < 1 || row >= lastrow)
1290 refresh = 1;
1291 if (curs.alt)
1292 refresh = 1;
1294 if (mode == (ORIG|RESULT)) {
1295 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1296 if (cmode & (WIGGLED | CONFLICTED)) {
1297 if (splitrow < 0) {
1298 splitrow = (rows+1)/2;
1299 lastrow = splitrow - 1;
1300 refresh = 1;
1302 } else if (!curs.alt && splitrow >= 0) {
1303 splitrow = -1;
1304 lastrow = rows-1;
1305 refresh = 1;
1307 } else if (splitrow >= 0) {
1308 splitrow = -1;
1309 lastrow = rows-1;
1310 refresh = 1;
1313 if (refresh) {
1314 getmaxyx(stdscr, rows, cols);
1315 rows--; /* keep last row clear */
1316 if (splitrow >= 0) {
1317 splitrow = (rows+1)/2;
1318 lastrow = splitrow - 1;
1319 } else
1320 lastrow = rows - 1;
1322 if (row < -3)
1323 row = lastrow/2+1;
1324 if (row < 1)
1325 row = 1;
1326 if (row > lastrow+3)
1327 row = lastrow/2+1;
1328 if (row >= lastrow)
1329 row = lastrow-1;
1331 if (getenv("WIGGLE_VTRACE")) {
1332 char b[100];
1333 char *e, e2[7];
1334 int i;
1335 switch (vpos.p.s) {
1336 case 0:
1337 e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start;
1338 break;
1339 case 1:
1340 e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start;
1341 break;
1342 case 2:
1343 e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start;
1344 break;
1346 for (i = 0; i < 6; i++) {
1347 e2[i] = e[i];
1348 if (e2[i] < 32 || e2[i] >= 127)
1349 e2[i] = '?';
1351 sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
1352 vpos.p.s, vpos.p.o,
1353 vpos.p.m, typenames[ci.merger[vpos.p.m].type],
1354 ci.merger[vpos.p.m].al,
1355 ci.merger[vpos.p.m].bl,
1356 ci.merger[vpos.p.m].cl,
1357 ci.merger[vpos.p.m].in_conflict,
1360 (void)attrset(A_NORMAL);
1361 mvaddstr(0, 50, b);
1362 clrtoeol();
1365 /* Always refresh the line */
1366 while (start > curs.target) {
1367 start -= 8;
1368 refresh = 1;
1370 if (start < 0)
1371 start = 0;
1372 retry:
1373 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1374 pos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1375 if (curs.width == 0 && start < curs.col) {
1376 /* width == 0 implies it appear after end-of-screen */
1377 start += 8;
1378 refresh = 1;
1379 goto retry;
1381 if (curs.col < start) {
1382 start -= 8;
1383 refresh = 1;
1384 if (start < 0)
1385 start = 0;
1386 goto retry;
1388 if (refresh) {
1389 refresh = 0;
1390 tpos = pos;
1392 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1393 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1394 draw_mline(mode, i--, start, cols,
1395 fm, fb, fa, ci.merger,
1396 tpos, NULL);
1399 if (i) {
1400 row -= (i+1);
1401 refresh = 1;
1402 goto retry;
1404 toppos = tpos;
1405 while (i >= 1)
1406 blank(i--, 0, cols, a_void);
1407 tpos = pos;
1408 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1409 draw_mline(mode, i++, start, cols,
1410 fm, fb, fa, ci.merger,
1411 tpos, NULL);
1412 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1414 botpos = tpos; botrow = i;
1415 while (i <= lastrow)
1416 blank(i++, 0, cols, a_void);
1419 if (splitrow >= 0) {
1420 struct mpos spos = pos;
1421 int smode = BEFORE|AFTER;
1422 int srow = (rows + splitrow)/2;
1423 if (visible(smode, ci.merger[spos.p.m].type,
1424 spos.p.s) < 0)
1425 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1426 /* Now hi/lo might be wrong, so lets fix it. */
1427 tpos = spos;
1428 while (spos.p.m >= 0 && spos.state != 0)
1429 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1430 while (!same_mpos(spos, tpos))
1431 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1433 (void)attrset(a_sep);
1434 for (i = 0; i < cols; i++)
1435 mvaddstr(splitrow, i, "-");
1437 tpos = spos;
1438 for (i = srow-1; i > splitrow; i--) {
1439 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1440 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1441 tpos, NULL);
1443 while (i > splitrow)
1444 blank(i--, 0, cols, a_void);
1445 tpos = spos;
1446 for (i = srow;
1447 i < rows && ci.merger[tpos.p.m].type != End;
1448 i++) {
1449 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1450 tpos,
1451 (i == srow && curs.alt) ? &curs : NULL);
1452 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1454 while (i < rows)
1455 blank(i++, 0, cols, a_void);
1457 /* Now that curs is accurate, report the type */
1459 char lbuf[30];
1460 (void)attrset(A_BOLD);
1461 snprintf(lbuf, 29, "%s ln:%d",
1462 typenames[ci.merger[curs.pos.m].type],
1463 (pos.p.lineno-1)/2);
1464 mvaddstr(0, cols - strlen(lbuf) - 4, " ");
1465 mvaddstr(0, cols - strlen(lbuf) - 1, lbuf);
1467 #define META(c) ((c)|0x1000)
1468 #define SEARCH(c) ((c)|0x2000)
1469 move(rows, 0);
1470 (void)attrset(A_NORMAL);
1471 if (num >= 0) {
1472 char buf[10];
1473 snprintf(buf, 10, "%d ", num);
1474 addstr(buf);
1476 if (meta & META(0))
1477 addstr("ESC...");
1478 if (meta & SEARCH(0)) {
1479 if (searchdir < 0)
1480 addstr("Backwards ");
1481 addstr("Search: ");
1482 addstr(search);
1483 if (search_notfound)
1484 addstr(" - Not Found.");
1485 search_notfound = 0;
1487 clrtoeol();
1488 /* '+1' to skip over the leading +/-/| char */
1489 if (curs.alt && splitrow > 0)
1490 move((rows + splitrow)/2, curs.col - start + 1);
1491 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1492 (mode & (ORIG|RESULT))))
1493 move(row, curs.col-start + (cols-1)/2+2);
1494 else
1495 move(row, curs.col-start+1);
1496 c = getch();
1497 tmeta = meta; meta = 0;
1498 tnum = num; num = -1;
1499 tvpos = vpos; vpos = pos;
1500 cswitch = c | tmeta;
1501 /* Handle some ranges */
1502 /* case '0' ... '9': */
1503 if (cswitch >= '0' && cswitch <= '9')
1504 cswitch = '0';
1505 /* case SEARCH(' ') ... SEARCH('~'): */
1506 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1507 cswitch = SEARCH(' ');
1509 switch (cswitch) {
1510 case 27: /* escape */
1511 case META(27):
1512 meta = META(0);
1513 break;
1514 case META('<'): /* start of file */
1515 start:
1516 tpos = pos; row++;
1517 do {
1518 pos = tpos; row--;
1519 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1520 } while (tpos.p.m >= 0);
1521 if (row <= 0)
1522 row = 0;
1523 break;
1524 case META('>'): /* end of file */
1525 case 'G':
1526 if (tnum >= 0)
1527 goto start;
1528 tpos = pos; row--;
1529 do {
1530 pos = tpos; row++;
1531 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1532 } while (ci.merger[tpos.p.m].type != End);
1533 if (row >= lastrow)
1534 row = lastrow;
1535 break;
1536 case '0': /* actually '0'...'9' */
1537 if (tnum < 0)
1538 tnum = 0;
1539 num = tnum*10 + (c-'0');
1540 break;
1541 case 'q':
1542 return;
1544 case '/':
1545 case 'S'-64:
1546 /* incr search forward */
1547 meta = SEARCH(0);
1548 searchlen = 0;
1549 search[searchlen] = 0;
1550 searchdir = 1;
1551 break;
1552 case '\\':
1553 case 'R'-64:
1554 /* incr search backwards */
1555 meta = SEARCH(0);
1556 searchlen = 0;
1557 search[searchlen] = 0;
1558 searchdir = -1;
1559 break;
1560 case SEARCH('G'-64):
1561 case SEARCH('S'-64):
1562 case SEARCH('R'-64):
1563 /* search again */
1564 if ((c|tmeta) == SEARCH('R'-64))
1565 searchdir = -2;
1566 else if ((c|tmeta) == SEARCH('S'-64))
1567 searchdir = 2;
1568 else
1569 searchdir *= 2;
1570 meta = SEARCH(0);
1571 tpos = pos; trow = row;
1572 goto search_again;
1574 case SEARCH('H'-64):
1575 case SEARCH(KEY_BACKSPACE):
1576 meta = SEARCH(0);
1577 if (anchor) {
1578 struct search_anchor *a;
1579 a = anchor;
1580 anchor = a->next;
1581 free(a);
1583 if (anchor) {
1584 struct search_anchor *a;
1585 a = anchor;
1586 anchor = a->next;
1587 pos = a->pos;
1588 row = a->row;
1589 start = a->start;
1590 curs = a->curs;
1591 curs.target = -1;
1592 search_notfound = a->notfound;
1593 searchlen = a->searchlen;
1594 search[searchlen] = 0;
1595 free(a);
1596 refresh = 1;
1598 break;
1599 case SEARCH(' '): /* actually ' '...'~' */
1600 case SEARCH('\t'):
1601 meta = SEARCH(0);
1602 if (searchlen < sizeof(search)-1)
1603 search[searchlen++] = c & (0x7f);
1604 search[searchlen] = 0;
1605 tpos = pos; trow = row;
1606 search_again:
1607 search_notfound = 1;
1608 if (ignore_case == 1 || ignore_case == 2) {
1609 unsigned int i;
1610 ignore_case = 2;
1611 for (i=0; i < searchlen; i++)
1612 if (isupper(search[i])) {
1613 ignore_case = 1;
1614 break;
1617 do {
1618 if (mcontains(tpos, fm, fb, fa, ci.merger,
1619 mmode, search, &curs, searchdir,
1620 ignore_case >= 2)) {
1621 curs.target = -1;
1622 pos = tpos;
1623 row = trow;
1624 search_notfound = 0;
1625 break;
1627 if (searchdir < 0) {
1628 trow--;
1629 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1630 } else {
1631 trow++;
1632 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1634 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1635 searchdir /= abs(searchdir);
1637 break;
1638 case 'L'-64:
1639 refresh = 2;
1640 row = lastrow / 2;
1641 break;
1643 case 'V'-64: /* page down */
1644 pos = botpos;
1645 if (botrow <= lastrow)
1646 row = botrow;
1647 else
1648 row = 2;
1649 refresh = 1;
1650 break;
1651 case META('v'): /* page up */
1652 pos = toppos;
1653 row = lastrow-1;
1654 refresh = 1;
1655 break;
1657 case 'j':
1658 case 'n':
1659 case 'N'-64:
1660 case KEY_DOWN:
1661 if (tnum < 0)
1662 tnum = 1;
1663 for (; tnum > 0 ; tnum--) {
1664 tpos = pos;
1665 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1666 if (ci.merger[tpos.p.m].type != End) {
1667 pos = tpos;
1668 row++;
1671 break;
1672 case 'N':
1673 /* Next diff */
1674 tpos = pos; row--;
1675 do {
1676 pos = tpos; row++;
1677 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1678 } while (pos.state != 0 && ci.merger[tpos.p.m].type != End);
1679 tpos = pos; row--;
1680 do {
1681 pos = tpos; row++;
1682 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1683 } while (pos.state == 0 && ci.merger[tpos.p.m].type != End);
1685 break;
1686 case 'P':
1687 /* Previous diff */
1688 tpos = pos; row++;
1689 do {
1690 pos = tpos; row--;
1691 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1692 } while (tpos.state == 0 && tpos.p.m >= 0);
1693 tpos = pos; row++;
1694 do {
1695 pos = tpos; row--;
1696 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1697 } while (tpos.state != 0 && tpos.p.m >= 0);
1698 break;
1700 case 'k':
1701 case 'p':
1702 case 'P'-64:
1703 case KEY_UP:
1704 if (tnum < 0)
1705 tnum = 1;
1706 for (; tnum > 0 ; tnum--) {
1707 tpos = pos;
1708 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1709 if (tpos.p.m >= 0) {
1710 pos = tpos;
1711 row--;
1714 break;
1716 case KEY_LEFT:
1717 case 'h':
1718 /* left */
1719 curs.target = curs.col - 1;
1720 if (curs.target < 0) {
1721 /* Try to go to end of previous line */
1722 tpos = pos;
1723 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1724 if (tpos.p.m >= 0) {
1725 pos = tpos;
1726 row--;
1727 curs.pos = pos.p;
1728 curs.target = -1;
1729 } else
1730 curs.target = 0;
1732 break;
1733 case KEY_RIGHT:
1734 case 'l':
1735 /* right */
1736 if (curs.width >= 0)
1737 curs.target = curs.col + curs.width;
1738 else {
1739 /* end of line, go to next */
1740 tpos = pos;
1741 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1742 if (ci.merger[tpos.p.m].type != End) {
1743 pos = tpos;
1744 curs.pos = pos.p;
1745 row++;
1746 curs.target = 0;
1749 break;
1751 case '^':
1752 case 'A'-64:
1753 /* Start of line */
1754 curs.target = 0;
1755 break;
1756 case '$':
1757 case 'E'-64:
1758 /* End of line */
1759 curs.target = 1000;
1760 break;
1762 case 'O':
1763 curs.alt = !curs.alt;
1764 if (curs.alt && mode == (ORIG|RESULT))
1765 mmode = (BEFORE|AFTER);
1766 else
1767 mmode = mode;
1768 break;
1770 case 'a': /* 'after' view in patch window */
1771 mode = AFTER; modename = "after"; modehelp = after_help;
1772 mmode = mode; curs.alt = 0;
1773 refresh = 3;
1774 break;
1775 case 'b': /* 'before' view in patch window */
1776 mode = BEFORE; modename = "before"; modehelp = before_help;
1777 mmode = mode; curs.alt = 0;
1778 refresh = 3;
1779 break;
1780 case 'o': /* 'original' view in the merge window */
1781 mode = ORIG; modename = "original"; modehelp = orig_help;
1782 mmode = mode; curs.alt = 0;
1783 refresh = 3;
1784 break;
1785 case 'r': /* the 'result' view in the merge window */
1786 mode = RESULT; modename = "result"; modehelp = result_help;
1787 mmode = mode; curs.alt = 0;
1788 refresh = 3;
1789 break;
1790 case 'd':
1791 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
1792 mmode = mode; curs.alt = 0;
1793 refresh = 3;
1794 break;
1795 case 'm':
1796 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
1797 mmode = mode; curs.alt = 0;
1798 refresh = 3;
1799 break;
1801 case '|':
1802 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
1803 mmode = mode; curs.alt = 0;
1804 refresh = 3;
1805 break;
1807 case 'H': /* scroll window to the right */
1808 if (start > 0)
1809 start--;
1810 curs.target = start + 1;
1811 refresh = 1;
1812 break;
1813 case 'L': /* scroll window to the left */
1814 if (start < cols)
1815 start++;
1816 curs.target = start + 1;
1817 refresh = 1;
1818 break;
1820 case '<':
1821 prev_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1822 if (tvpos.p.m >= 0)
1823 vpos = tvpos;
1824 break;
1825 case '>':
1826 next_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1827 if (ci.merger[tvpos.p.m].type != End)
1828 vpos = tvpos;
1829 break;
1831 case '?':
1832 help_window(modehelp, merge_window_help);
1833 refresh = 2;
1834 break;
1836 case KEY_RESIZE:
1837 refresh = 2;
1838 break;
1841 if (meta == SEARCH(0)) {
1842 if (anchor == NULL ||
1843 !same_mpos(anchor->pos, pos) ||
1844 anchor->searchlen != searchlen ||
1845 !same_mp(anchor->curs.pos, curs.pos)) {
1846 struct search_anchor *a = xmalloc(sizeof(*a));
1847 a->pos = pos;
1848 a->row = row;
1849 a->start = start;
1850 a->curs = curs;
1851 a->searchlen = searchlen;
1852 a->notfound = search_notfound;
1853 a->next = anchor;
1854 anchor = a;
1856 } else {
1857 while (anchor) {
1858 struct search_anchor *a = anchor;
1859 anchor = a->next;
1860 free(a);
1863 if (refresh == 3) {
1864 /* move backward and forward to make sure we
1865 * are on a visible line
1867 tpos = pos;
1868 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1869 if (tpos.p.m >= 0)
1870 pos = tpos;
1871 tpos = pos;
1872 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1873 if (ci.merger[tpos.p.m].type != End)
1874 pos = tpos;
1879 static void show_merge(char *origname, FILE *patch, int reverse,
1880 int is_merge, char *before, char *after)
1882 struct plist p;
1884 p.file = origname;
1885 if (patch) {
1886 p.start = 0;
1887 fseek(patch, 0, SEEK_END);
1888 p.end = ftell(patch);
1889 fseek(patch, 0, SEEK_SET);
1891 p.calced = 0;
1892 p.is_merge = is_merge;
1893 p.before = before;
1894 p.after = after;
1896 freopen("/dev/null","w",stderr);
1897 merge_window(&p, patch, reverse);
1900 static void calc_one(struct plist *pl, FILE *f, int reverse)
1902 struct stream s1, s2;
1903 struct stream s = load_segment(f, pl->start, pl->end);
1904 struct stream sf;
1905 if (pl->is_merge) {
1906 if (reverse)
1907 split_merge(s, &sf, &s2, &s1);
1908 else
1909 split_merge(s, &sf, &s1, &s2);
1910 pl->chunks = 0;
1911 } else {
1912 sf = load_file(pl->file);
1913 if (reverse)
1914 pl->chunks = split_patch(s, &s2, &s1);
1915 else
1916 pl->chunks = split_patch(s, &s1, &s2);
1918 if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
1919 pl->wiggles = pl->conflicts = -1;
1920 } else {
1921 struct file ff, fp1, fp2;
1922 struct csl *csl1, *csl2;
1923 struct ci ci;
1924 ff = split_stream(sf, ByWord);
1925 fp1 = split_stream(s1, ByWord);
1926 fp2 = split_stream(s2, ByWord);
1927 if (pl->chunks)
1928 csl1 = pdiff(ff, fp1, pl->chunks);
1929 else
1930 csl1 = diff(ff, fp1);
1931 csl2 = diff(fp1, fp2);
1932 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
1933 pl->wiggles = ci.wiggles;
1934 pl->conflicts = ci.conflicts;
1935 free(csl1);
1936 free(csl2);
1937 free(ff.list);
1938 free(fp1.list);
1939 free(fp2.list);
1942 free(s1.body);
1943 free(s2.body);
1944 free(s.body);
1945 free(sf.body);
1946 pl->calced = 1;
1949 static int get_prev(int pos, struct plist *pl, int n, int mode)
1951 int found = 0;
1952 if (pos == -1)
1953 return pos;
1954 do {
1955 if (pl[pos].prev == -1)
1956 return pl[pos].parent;
1957 pos = pl[pos].prev;
1958 while (pl[pos].open &&
1959 pl[pos].last >= 0)
1960 pos = pl[pos].last;
1961 if (pl[pos].last >= 0)
1962 /* always see directories */
1963 found = 1;
1964 else if (mode == 0)
1965 found = 1;
1966 else if (mode <= 1 && pl[pos].wiggles > 0)
1967 found = 1;
1968 else if (mode <= 2 && pl[pos].conflicts > 0)
1969 found = 1;
1970 } while (pos >= 0 && !found);
1971 return pos;
1974 static int get_next(int pos, struct plist *pl, int n, int mode,
1975 FILE *f, int reverse)
1977 int found = 0;
1978 if (pos == -1)
1979 return pos;
1980 do {
1981 if (pl[pos].open) {
1982 if (pos + 1 < n)
1983 pos = pos+1;
1984 else
1985 return -1;
1986 } else {
1987 while (pos >= 0 && pl[pos].next == -1)
1988 pos = pl[pos].parent;
1989 if (pos >= 0)
1990 pos = pl[pos].next;
1992 if (pos < 0)
1993 return -1;
1994 if (pl[pos].calced == 0 && pl[pos].end)
1995 calc_one(pl+pos, f, reverse);
1996 if (pl[pos].last >= 0)
1997 /* always see directories */
1998 found = 1;
1999 else if (mode == 0)
2000 found = 1;
2001 else if (mode <= 1 && pl[pos].wiggles > 0)
2002 found = 1;
2003 else if (mode <= 2 && pl[pos].conflicts > 0)
2004 found = 1;
2005 } while (pos >= 0 && !found);
2006 return pos;
2009 static void draw_one(int row, struct plist *pl, FILE *f, int reverse)
2011 char hdr[12];
2012 hdr[0] = 0;
2014 if (pl == NULL) {
2015 move(row, 0);
2016 clrtoeol();
2017 return;
2019 if (pl->calced == 0 && pl->end)
2020 /* better load the patch and count the chunks */
2021 calc_one(pl, f, reverse);
2022 if (pl->end == 0) {
2023 strcpy(hdr, " ");
2024 } else {
2025 if (pl->chunks > 99)
2026 strcpy(hdr, "XX");
2027 else
2028 sprintf(hdr, "%2d", pl->chunks);
2029 if (pl->wiggles > 99)
2030 strcpy(hdr+2, " XX");
2031 else
2032 sprintf(hdr+2, " %2d", pl->wiggles);
2033 if (pl->conflicts > 99)
2034 strcpy(hdr+5, " XX ");
2035 else
2036 sprintf(hdr+5, " %2d ", pl->conflicts);
2038 if (pl->end)
2039 strcpy(hdr+9, "= ");
2040 else if (pl->open)
2041 strcpy(hdr+9, "+ ");
2042 else
2043 strcpy(hdr+9, "- ");
2045 if (!pl->end)
2046 attrset(0);
2047 else if (pl->conflicts)
2048 attrset(a_has_conflicts);
2049 else if (pl->wiggles)
2050 attrset(a_has_wiggles);
2051 else
2052 attrset(a_no_wiggles);
2054 mvaddstr(row, 0, hdr);
2055 mvaddstr(row, 11, pl->file);
2056 clrtoeol();
2059 static char *main_help[] = {
2060 " You are using the \"browse\" mode of wiggle.",
2061 "This page shows a list of files in a patch together with",
2062 "the directories that contain them.",
2063 "A directory is indicated by a '+' if the contents are",
2064 "listed or a '-' if the contents are hidden. A file is",
2065 "indicated by an '='. Typing <space> or <return> will",
2066 "expose or hide a directory, and will visit a file.",
2068 "The three columns of numbers are:",
2069 " Ch The number of patch chunks which applied to",
2070 " this file",
2071 " Wi The number of chunks that needed to be wiggled",
2072 " in to place",
2073 " Co The number of chunks that created an unresolvable",
2074 " conflict",
2076 "Keystrokes recognised in this page are:",
2077 " ? Display this help",
2078 " SPC On a directory, toggle hiding of contents",
2079 " On file, visit the file",
2080 " RTN Same as SPC",
2081 " q Quit program",
2082 " n,j,DOWN Go to next line",
2083 " p,k,UP Go to previous line",
2085 " A list All files",
2086 " W only list files with a wiggle or a conflict",
2087 " C only list files with a conflict",
2088 NULL
2091 static void main_window(struct plist *pl, int n, FILE *f, int reverse)
2093 /* The main window lists all files together with summary information:
2094 * number of chunks, number of wiggles, number of conflicts.
2095 * The list is scrollable
2096 * When a entry is 'selected', we switch to the 'file' window
2097 * The list can be condensed by removing files with no conflict
2098 * or no wiggles, or removing subdirectories
2100 * We record which file in the list is 'current', and which
2101 * screen line it is on. We try to keep things stable while
2102 * moving.
2104 * Counts are printed before the name using at most 2 digits.
2105 * Numbers greater than 99 are XX
2106 * Ch Wi Co File
2107 * 27 5 1 drivers/md/md.c
2109 * A directory show the sum in all children.
2111 * Commands:
2112 * select: enter, space, mouseclick
2113 * on file, go to file window
2114 * on directory, toggle open
2115 * up: k, p, control-p uparrow
2116 * Move to previous open object
2117 * down: j, n, control-n, downarrow
2118 * Move to next open object
2120 * A W C: select All Wiggles or Conflicts
2121 * mode
2124 int pos = 0; /* position in file */
2125 int row = 1; /* position on screen */
2126 int rows = 0; /* size of screen in rows */
2127 int cols = 0;
2128 int tpos, i;
2129 int refresh = 2;
2130 int c = 0;
2131 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2133 freopen("/dev/null","w",stderr);
2134 term_init();
2135 pl = sort_patches(pl, &n);
2137 while (1) {
2138 if (refresh == 2) {
2139 clear(); (void)attrset(0);
2140 attron(A_BOLD);
2141 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2142 move(2, 0);
2143 attroff(A_BOLD);
2144 refresh = 1;
2146 if (row < 1 || row >= rows)
2147 refresh = 1;
2148 if (refresh) {
2149 refresh = 0;
2150 getmaxyx(stdscr, rows, cols);
2151 cols = cols; /* Silence warning that 'cols' isn't used */
2152 if (row >= rows + 3)
2153 row = (rows+1)/2;
2154 if (row >= rows)
2155 row = rows-1;
2156 tpos = pos;
2157 for (i = row; i > 1; i--) {
2158 tpos = get_prev(tpos, pl, n, mode);
2159 if (tpos == -1) {
2160 row = row - i + 1;
2161 break;
2164 /* Ok, row and pos could be trustworthy now */
2165 tpos = pos;
2166 for (i = row; i >= 1; i--) {
2167 draw_one(i, &pl[tpos], f, reverse);
2168 tpos = get_prev(tpos, pl, n, mode);
2170 tpos = pos;
2171 for (i = row+1; i < rows; i++) {
2172 tpos = get_next(tpos, pl, n, mode, f, reverse);
2173 if (tpos >= 0)
2174 draw_one(i, &pl[tpos], f, reverse);
2175 else
2176 draw_one(i, NULL, f, reverse);
2179 {char bb[20];
2180 sprintf(bb, "%d", c);
2181 mvaddstr(0, 70, bb);
2182 clrtoeol();
2184 move(row, 9);
2185 c = getch();
2186 switch (c) {
2187 case 'j':
2188 case 'n':
2189 case 'N':
2190 case 'N'-64:
2191 case KEY_DOWN:
2192 tpos = get_next(pos, pl, n, mode, f, reverse);
2193 if (tpos >= 0) {
2194 pos = tpos;
2195 row++;
2197 break;
2198 case 'k':
2199 case 'p':
2200 case 'P':
2201 case 'P'-64:
2202 case KEY_UP:
2203 tpos = get_prev(pos, pl, n, mode);
2204 if (tpos >= 0) {
2205 pos = tpos;
2206 row--;
2208 break;
2210 case ' ':
2211 case 13:
2212 if (pl[pos].end == 0) {
2213 pl[pos].open = !pl[pos].open;
2214 refresh = 1;
2215 } else {
2216 /* diff_window(&pl[pos], f); */
2217 merge_window(&pl[pos], f, reverse);
2218 refresh = 2;
2220 break;
2221 case 27: /* escape */
2222 mvaddstr(0, 70, "ESC..."); clrtoeol();
2223 c = getch();
2224 switch (c) {
2226 break;
2227 case 'q':
2228 return;
2230 case 'A':
2231 mode = 0; refresh = 1;
2232 break;
2233 case 'W':
2234 mode = 1; refresh = 1;
2235 break;
2236 case 'C':
2237 mode = 2; refresh = 1;
2238 break;
2240 case '?':
2241 help_window(main_help, NULL);
2242 refresh = 2;
2243 break;
2245 case KEY_RESIZE:
2246 refresh = 2;
2247 break;
2252 static void catch(int sig)
2254 if (sig == SIGINT) {
2255 signal(sig, catch);
2256 return;
2258 nocbreak();
2259 nl();
2260 endwin();
2261 printf("Died on signal %d\n", sig);
2262 exit(2);
2265 static void term_init(void)
2268 static int init_done = 0;
2270 if (init_done)
2271 return;
2272 init_done = 1;
2274 signal(SIGINT, catch);
2275 signal(SIGQUIT, catch);
2276 signal(SIGTERM, catch);
2277 signal(SIGBUS, catch);
2278 signal(SIGSEGV, catch);
2280 initscr(); cbreak(); noecho();
2281 start_color();
2282 use_default_colors();
2283 if (!has_colors()) {
2284 a_delete = A_UNDERLINE;
2285 a_added = A_BOLD;
2286 a_common = A_NORMAL;
2287 a_sep = A_STANDOUT;
2288 a_already = A_STANDOUT;
2289 a_has_conflicts = A_UNDERLINE;
2290 a_has_wiggles = A_BOLD;
2291 a_no_wiggles = A_NORMAL;
2292 } else {
2293 init_pair(1, COLOR_RED, -1);
2294 a_delete = COLOR_PAIR(1);
2295 init_pair(2, COLOR_GREEN, -1);
2296 a_added = COLOR_PAIR(2);
2297 a_common = A_NORMAL;
2298 init_pair(3, COLOR_WHITE, COLOR_GREEN);
2299 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
2300 init_pair(4, -1, COLOR_YELLOW);
2301 a_void = COLOR_PAIR(4);
2302 init_pair(5, COLOR_BLUE, -1);
2303 a_unmatched = COLOR_PAIR(5);
2304 init_pair(6, COLOR_CYAN, -1);
2305 a_extra = COLOR_PAIR(6);
2307 init_pair(7, COLOR_BLACK, COLOR_CYAN);
2308 a_already = COLOR_PAIR(7);
2310 a_has_conflicts = a_delete;
2311 a_has_wiggles = a_added;
2312 a_no_wiggles = a_unmatched;
2314 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
2315 mousemask(ALL_MOUSE_EVENTS, NULL);
2318 int vpatch(int argc, char *argv[], int patch, int strip,
2319 int reverse, int replace)
2321 /* NOTE argv[0] is first arg...
2322 * Behaviour depends on number of args:
2323 * 0: A multi-file patch is read from stdin
2324 * 1: if 'patch', parse it as a multi-file patch and allow
2325 * the files to be browsed.
2326 * if filename ends '.rej', then treat it as a patch again
2327 * a file with the same basename
2328 * Else treat the file as a merge (with conflicts) and view it.
2329 * 2: First file is original, second is patch
2330 * 3: Files are: original previous new. The diff between 'previous' and
2331 * 'new' needs to be applied to 'original'.
2333 * If a multi-file patch is being read, 'strip' tells how many
2334 * path components to strip. If it is -1, we guess based on
2335 * existing files.
2336 * If 'reverse' is given, when we invert any patch or diff
2337 * If 'replace' then we save the resulting merge.
2339 FILE *in;
2340 FILE *f;
2341 struct plist *pl;
2342 int num_patches;
2344 switch (argc) {
2345 default:
2346 fprintf(stderr, "%s: too many file names given.\n", Cmd);
2347 exit(1);
2349 case 0: /* stdin is a patch */
2350 if (lseek(fileno(stdin), 0L, 1) == -1) {
2351 /* cannot seek, so need to copy to a temp file */
2352 f = tmpfile();
2353 if (!f) {
2354 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
2355 exit(1);
2357 pl = parse_patch(stdin, f, &num_patches);
2358 in = f;
2359 } else {
2360 pl = parse_patch(stdin, NULL, &num_patches);
2361 in = fdopen(dup(0), "r");
2363 /* use stderr for keyboard input */
2364 dup2(2, 0);
2365 if (set_prefix(pl, num_patches, strip) == 0) {
2366 fprintf(stderr, "%s: aborting\n", Cmd);
2367 exit(2);
2369 main_window(pl, num_patches, in, reverse);
2370 break;
2372 case 1: /* a patch, a .rej, or a merge file */
2373 f = fopen(argv[0], "r");
2374 if (!f) {
2375 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2376 exit(1);
2378 if (patch) {
2379 pl = parse_patch(f, NULL, &num_patches);
2380 if (set_prefix(pl, num_patches, strip) == 0) {
2381 fprintf(stderr, "%s: aborting\n", Cmd);
2382 exit(2);
2384 main_window(pl, num_patches, f, reverse);
2385 } else if (strlen(argv[0]) > 4 &&
2386 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
2387 char *origname = strdup(argv[0]);
2388 origname[strlen(origname) - 4] = '\0';
2389 show_merge(origname, f, reverse, 0, NULL, NULL);
2390 } else
2391 show_merge(argv[0], f, reverse, 1, NULL, NULL);
2393 break;
2394 case 2: /* an orig and a diff/.ref */
2395 f = fopen(argv[1], "r");
2396 if (!f) {
2397 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2398 exit(1);
2400 show_merge(argv[0], f, reverse, 0, NULL, NULL);
2401 break;
2402 case 3: /* orig, before, after */
2403 show_merge(argv[0], NULL, reverse, 1, argv[1], argv[2]);
2404 break;
2407 nocbreak();
2408 nl();
2409 endwin();
2410 exit(0);