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