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