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.
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.
49 static void term_init(int raw);
51 static int intr_kills = 0;
53 /* global attributes */
54 unsigned int a_delete, a_added, a_common, a_sep, a_void,
55 a_unmatched, a_extra, a_already;
56 unsigned int a_has_conflicts, a_has_wiggles, a_no_wiggles, a_saved;
58 /******************************************************************
60 * We display help in an insert, leaving 5 columns left and right,
61 * and 2 rows top and bottom, but at most 58x15 plus border
63 * SPC or RTN moves down or to next page
64 * BKSPC goes backwards
65 * 'q' returns to origin screen
66 * '?' show help on help
67 * left and right scroll help view
69 * A help text is an array of lines of text
73 " You are viewing the help page for the help viewer.",
74 "You normally get here by typing '?'",
76 "The following keystrokes work in the help viewer:",
77 " ? display this help message",
78 " q return to previous view",
79 " SPC move forward through help document",
81 " BKSP move backward through help document",
82 " RIGHT scroll help window so text on the right appears",
83 " LEFT scroll help window so text on the left appears",
87 char *help_missing[] = {
88 "The file that this patch applies to appears",
90 "Please type 'q' to continue",
94 char *help_corrupt[] = {
95 "This patch appears to be corrupt",
96 "Please type 'q' to continue",
100 /* We can give one or two pages to display in the help window.
101 * The first is specific to the current context. The second
102 * is optional and may provide help in a more broad context.
104 static int help_window(char *page1[], char *page2[], int query)
114 getmaxyx(stdscr, rows, cols);
120 left = (cols-58)/2 - 1;
128 top = (rows-15)/2 - 1;
132 /* Draw a border around the 'help' area */
133 (void)attrset(A_STANDOUT);
134 for (c = left; c < left+cols; c++) {
135 mvaddch(top-1, c, '-');
136 mvaddch(top+rows, c, '-');
138 for (r = top; r < top + rows ; r++) {
139 mvaddch(r, left-1, '|');
140 mvaddch(r, left+cols, '|');
142 mvaddch(top-1, left-1, '/');
143 mvaddch(top-1, left+cols, '\\');
144 mvaddch(top+rows, left-1, '\\');
145 mvaddch(top+rows, left+cols, '/');
147 mvaddstr(top-1, left + cols/2 - 4, "Question");
148 mvaddstr(top+rows, left + cols/2 - 9,
149 "Answer Y, N, or Q.");
151 mvaddstr(top-1, left + cols/2 - 9,
152 "HELP - 'q' to exit");
153 mvaddstr(top+rows, left+cols/2 - 17,
154 "Press SPACE for more, '?' for help");
156 (void)attrset(A_NORMAL);
159 char **lnp = page + line;
161 /* Draw as much of the page at the current offset
164 for (r = 0; r < rows; r++) {
172 while (*ln && sh > 0) {
176 for (c = 0; c < cols; c++) {
182 mvaddch(top+r, left+c, chr);
185 move(top+rows-1, left);
206 if (page1 != help_help)
207 help_window(help_help, NULL, 0);
210 case '\r': /* page-down */
211 for (r = 0; r < rows-2; r++)
214 if (!page[line] && !query) {
225 case '\b': /* page up */
261 static char *typenames[] = {
263 [Unmatched] = "Unmatched",
264 [Unchanged] = "Unchanged",
265 [Extraneous] = "Extraneous",
266 [Changed] = "Changed",
267 [Conflict] = "Conflict",
268 [AlreadyApplied] = "AlreadyApplied",
271 /* When we merge the original and the diff together we need
272 * to keep track of where everything came from.
273 * When we display the different views, we need to be able to
274 * select certain portions of the whole document.
275 * These flags are used to identify what is present, and to
276 * request different parts be extracted. They also help
277 * guide choice of colour.
283 #define CHANGES 16 /* A change is visible here,
284 * so 2 streams need to be shown */
285 #define WIGGLED 32 /* a conflict that was successfully resolved */
286 #define CONFLICTED 64 /* a conflict that was not successfully resolved */
288 /* Displaying a Merge.
289 * The first step is to linearise the merge. The merge in inherently
290 * parallel with before/after streams. However much of the whole document
291 * is linear as normally much of the original in unchanged.
292 * All parallelism comes from the patch. This normally produces two
293 * parallel stream, but in the case of a conflict can produce three.
294 * For browsing the merge we only ever show two alternates in-line.
295 * When there are three we use two panes with 1 or 2 alternates in each.
296 * So to linearise the two streams we find lines that are completely
297 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
298 * a region where there are changes. We include everything between
299 * these twice, in two separate passes. The exact interpretation of the
300 * passes is handled at a higher level but will be one of:
301 * original and result
303 * original and after (for a conflict)
304 * This is all encoded in the 'struct merge'. An array of these describes
305 * the whole document.
307 * At any position in the merge we can be in one of 3 states:
308 * 0: unchanged section
312 * So to walk a merge in display order we need a position in the merge,
313 * a current state, and when in a changed section, we need to know the
314 * bounds of that changed section.
315 * This is all encoded in 'struct mpos'.
317 * Each location may or may not be visible depending on certain
320 * Also, some locations might be 'invalid' in that they don't need to be displayed.
321 * For example when the patch leaves a section of the original unchanged,
322 * we only need to see the original - the before/after sections are treated
323 * as invalid and are not displayed.
324 * The visibility of newlines is crucial and guides the display. One line
325 * of displayed text is all the visible sections between two visible newlines.
327 * Counting lines is a bit tricky. We only worry about line numbers in the
328 * original (stream 0) as these could compare with line numbers mentioned in
330 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
331 * That way we don't get a full counted line until we see the first char after the
332 * newline, so '+' lines are counted with the previous line.
336 int m; /* merger index */
337 int s; /* stream 0,1,2 for a,b,c */
338 int o; /* offset in that stream */
339 int lineno; /* Counts newlines in stream 0
340 * set lsb when see newline.
341 * add one when not newline and lsb set
345 struct mp p, /* the current point (end of a line) */
346 lo, /* eol for start of the current group */
347 hi; /* eol for end of the current group */
349 * 0 if on an unchanged (lo/hi not meaningful)
350 * 1 if on the '-' of a diff,
351 * 2 if on the '+' of a diff
356 struct mp pos; /* where in the document we are (an element) */
357 int offset; /* which char in that element */
358 int target; /* display column - or -1 if we are looking for 'pos' */
359 int col; /* where we found pos or target */
360 int width; /* Size of char, for moving to the right */
361 int alt; /* Cursor is in alternate window */
364 /* used for checking location during search */
365 static int same_mp(struct mp a, struct mp b)
371 static int same_mpos(struct mpos a, struct mpos b)
373 return same_mp(a.p, b.p) &&
374 (a.state == b.state || a.state == 0 || b.state == 0);
377 /* Check if a particular stream is meaningful in a particular merge
378 * section. e.g. in an Unchanged section, only stream 0, the
379 * original, is meaningful. This is used to avoid walking down
382 static int stream_valid(int s, enum mergetype type)
404 * Advance the 'pos' in the current mergepos returning the next
406 * This walks the merges in sequence, and the streams within
409 static struct elmnt next_melmnt(struct mp *pos,
410 struct file fm, struct file fb, struct file fa,
414 while (pos->m < 0 || m[pos->m].type != End) {
415 int l = 0; /* Length remaining in current merge section */
429 /* Offset has reached length, choose new stream or
438 } while (!stream_valid(pos->s, m[pos->m].oldtype));
442 if (pos->m == -1 || m[pos->m].type == End) {
444 e.start = NULL; e.hash = 0; e.len = 0;
448 default: /* keep compiler happy */
452 if (ends_line(fm.list[m[pos->m].a + pos->o]))
454 return fm.list[m[pos->m].a + pos->o];
455 case 1: return fb.list[m[pos->m].b + pos->o];
456 case 2: return fa.list[m[pos->m].c + pos->o];
460 /* step current position.p backwards */
461 static struct elmnt prev_melmnt(struct mp *pos,
462 struct file fm, struct file fb, struct file fa,
466 if (m[pos->m].a + pos->o < fm.elcnt &&
467 ends_line(fm.list[m[pos->m].a + pos->o]))
474 while (pos->m >= 0 && pos->o < 0) {
481 } while (pos->m >= 0 &&
482 !stream_valid(pos->s, m[pos->m].oldtype));
486 pos->o = m[pos->m].al-1;
489 pos->o = m[pos->m].bl-1;
492 pos->o = m[pos->m].cl-1;
497 if (pos->m < 0 || m[pos->m].type == End) {
499 e.start = NULL; e.hash = 0; e.len = 0;
503 default: /* keep compiler happy */
504 case 0: return fm.list[m[pos->m].a + pos->o];
505 case 1: return fb.list[m[pos->m].b + pos->o];
506 case 2: return fa.list[m[pos->m].c + pos->o];
510 /* 'visible' not only checks if this stream in this merge should be
511 * visible in this mode, but also chooses which colour/highlight to use
514 static int visible(int mode, struct merge *m, struct mpos *pos)
517 int stream = pos->p.s;
523 else if (mode & RESULT)
524 type = m[pos->p.m].type;
526 type = m[pos->p.m].oldtype;
527 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
529 case End: /* The END is always visible */
531 case Unmatched: /* Visible in ORIG and RESULT */
532 if (mode & (ORIG|RESULT))
535 case Unchanged: /* visible everywhere, but only show stream 0 */
539 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
540 if ((mode & (BEFORE|AFTER))
544 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
546 (mode & (ORIG|BEFORE)))
549 (mode & (RESULT|AFTER)))
556 return a_unmatched | A_REVERSE;
560 return a_extra | A_UNDERLINE;
563 if (mode & (AFTER|RESULT))
564 return a_added | A_UNDERLINE;
571 if (mode & (ORIG|RESULT))
576 return a_delete | A_UNDERLINE;
580 return a_added | A_UNDERLINE;
588 /* checkline creates a summary of the sort of changes that
589 * are in a line, returning an "or" of
594 static int check_line(struct mpos pos, struct file fm, struct file fb,
596 struct merge *m, int mode)
605 int type = m[pos.p.m].oldtype;
607 type = m[pos.p.m].type;
610 else if (type == Conflict) {
611 rv |= CONFLICTED | CHANGES;
612 } else if (type == AlreadyApplied) {
614 if (mode & (BEFORE|AFTER))
616 } else if (type == Extraneous) {
617 if (fb.list[m[pos.p.m].b].start[0] == '\0')
618 /* hunk headers don't count as wiggles
619 * and nothing before a hunk header
620 * can possibly be part of this 'line' */
624 } else if (type == Unmatched)
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 &&
636 || visible(mode, m, &pos) == -1));
638 if (unmatched && (rv & CHANGES))
643 /* Find the next line in the merge which is visible.
644 * If we hit the end of a conflicted set during pass-1
645 * we rewind for pass-2.
646 * 'mode' tells which bits we want to see, possible one of
647 * the 4 parts (before/after/orig/result) or one of the pairs
648 * before+after or orig+result.
650 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
652 struct merge *m, int mode)
661 struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
665 visible(mode, m, pos) >= 0)
668 mode2 = check_line(*pos, fm, fb, fa, m, mode);
670 if ((mode2 & CHANGES) && pos->state == 0) {
671 /* Just entered a diff-set */
674 } else if (!(mode2 & CHANGES) && pos->state) {
675 /* Come to the end of a diff-set */
676 switch (pos->state) {
678 /* Need to record the end */
680 /* time for another pass */
685 /* finished final pass */
690 mask = ORIG|RESULT|BEFORE|AFTER;
691 switch (pos->state) {
693 mask &= ~(RESULT|AFTER);
696 mask &= ~(ORIG|BEFORE);
699 } while (visible(mode&mask, m, pos) < 0);
703 /* Move to previous line - simply the reverse of next_mline */
704 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
706 struct merge *m, int mode)
717 struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
721 visible(mode, m, pos) >= 0)
724 mode2 = check_line(*pos, fm, fb, fa, m, mode);
726 if ((mode2 & CHANGES) && pos->state == 0) {
727 /* Just entered a diff-set */
730 } else if (!(mode2 & CHANGES) && pos->state) {
731 /* Come to the end (start) of a diff-set */
732 switch (pos->state) {
734 /* finished final pass */
738 /* Need to record the start */
740 /* time for another pass */
746 mask = ORIG|RESULT|BEFORE|AFTER;
747 switch (pos->state) {
749 mask &= ~(RESULT|AFTER);
752 mask &= ~(ORIG|BEFORE);
755 } while (visible(mode&mask, m, pos) < 0);
758 /* blank a whole row of display */
759 static void blank(int row, int start, int cols, unsigned int attr)
767 /* search of a string on one display line. If found, update the
771 static int mcontains(struct mpos pos,
772 struct file fm, struct file fb, struct file fa,
774 int mode, char *search, struct cursor *curs,
775 int dir, int ignore_case)
777 /* See if any of the files, between start of this line and here,
778 * contain the search string.
779 * However this is modified by dir:
780 * -2: find last match *before* curs
781 * -1: find last match at-or-before curs
782 * 1: find first match at-or-after curs
783 * 2: find first match *after* curs
785 * We only test for equality with curs, so if it is on a different
786 * line it will not be found and everything is before/after.
787 * As we search from end-of-line to start we find the last
789 * For a forward search, we stop when we find curs.
790 * For a backward search, we forget anything found when we find curs.
796 int len = strlen(search);
799 e = prev_melmnt(&pos.p, fm, fb, fa, m);
800 if (e.start && e.start[0]) {
803 if (same_mp(pos.p, curs->pos))
804 curs_i = curs->offset;
807 for (i = e.len-1; i >= 0; i--) {
808 if (i == curs_i && dir == -1)
809 /* next match is the one we want */
811 if (i == curs_i && dir == 2)
812 /* future matches not accepted */
814 if ((!found || dir > 0) &&
815 (ignore_case ? strncasecmp : strncmp)
816 (e.start+i, search, len) == 0) {
821 if (i == curs_i && dir == -2)
822 /* next match is the one we want */
824 if (i == curs_i && dir == 1)
825 /* future matches not accepted */
829 } while (e.start != NULL &&
831 || visible(mode, m, &pos) == -1));
840 /* Drawing the display window.
841 * There are 7 different ways we can display the data, each
842 * of which can be configured by a keystroke:
843 * o original - just show the original file with no changes, but still
844 * with highlights of what is changed or unmatched
845 * r result - show just the result of the merge. Conflicts just show
846 * the original, not the before/after options
847 * b before - show the 'before' stream of the patch
848 * a after - show the 'after' stream of the patch
849 * d diff - show just the patch, both before and after
850 * m merge - show the full merge with -+ sections for changes.
851 * If point is in a wiggled or conflicted section the
852 * window is split horizontally and the diff is shown
853 * in the bottom window
854 * | sidebyside - two panes, left and right. Left holds the merge,
855 * right holds the diff. In the case of a conflict,
856 * left holds orig/after, right holds before/after
858 * The horizontal split for 'merge' mode is managed as follows.
859 * - The window is split when we first visit a line that contains
860 * a wiggle or a conflict, and the second pane is removed when
861 * we next visit a line that contains no changes (is fully Unchanged).
862 * - to display the second pane, we find a visible end-of-line in the
863 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
864 * the we centre that line.
865 * - We need to rewind to an unchanged section, and wind forward again
866 * to make sure that 'lo' and 'hi' are set properly.
867 * - every time we move, we redraw the second pane (see how that goes).
870 /* draw_mside draws one text line or, in the case of sidebyside, one side
872 * The 'mode' tells us what to draw via the 'visible()' function.
873 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
874 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
875 * The desired cursor position is given in 'target' the actual end
876 * cursor position (allowing e.g. for tabs) is returned in *colp.
878 static void draw_mside(int mode, int row, int offset, int start, int cols,
879 struct file fm, struct file fb, struct file fa,
887 unsigned int tag_attr;
891 default: /* keep compiler happy */
892 case 0: /* unchanged line */
896 case 1: /* 'before' text */
899 if ((mode & ORIG) && (mode & CONFLICTED)) {
901 tag_attr = a_delete | A_REVERSE;
903 mode &= (ORIG|BEFORE);
905 case 2: /* the 'after' part */
908 mode &= (AFTER|RESULT);
912 if (visible(mode, m, &pos) < 0) {
913 /* Not visible, just draw a blank */
914 blank(row, offset, cols, a_void);
924 (void)attrset(tag_attr);
925 mvaddch(row, offset, tag);
928 (void)attrset(A_NORMAL);
930 if (check_line(pos, fm, fb, fa, m, mode))
933 /* find previous visible newline, or start of file */
935 e = prev_melmnt(&pos.p, fm, fb, fa, m);
936 while (e.start != NULL &&
938 visible(mode, m, &pos) == -1));
945 e = next_melmnt(&pos.p, fm, fb, fa, m);
949 if (visible(mode, m, &pos) == -1)
953 c = (unsigned char *)e.start - e.prefix;
955 attr = visible(mode, m, &pos);
956 if ((attr == a_unmatched || attr == a_extra) &&
958 /* Only highlight spaces if there is a tab nearby */
959 for (l = 0; l < e.plen + e.prefix; l++)
962 if (!highlight_space && (c[0] == ' ' || c[0] == '\t')) {
963 /* always highlight space/tab at end-of-line */
964 struct mp nxt = pos.p;
965 struct elmnt nxte = next_melmnt(&nxt, fm, fb, fa, m);
966 if (nxte.start[0] == '\n')
969 for (l = 0; l < e.plen + e.prefix; l++) {
974 if (*c >= ' ' && *c != 0x7f) {
976 (void)attrset(attr|A_REVERSE);
977 if (col >= start && col < start+cols)
978 mvaddch(row, col-start+offset, *c);
980 } else if (*c == '\t') {
982 (void)attrset(attr|A_UNDERLINE);
984 if (col >= start && col < start+cols) {
985 mvaddch(row, col-start+offset, ' ');
987 } while ((col&7) != 0);
989 if (col >= start && col < start+cols)
990 mvaddch(row, col-start+offset, '?');
994 if (curs->target >= 0) {
995 if (curs->target < col) {
996 /* Found target column */
1000 if (scol >= start + cols)
1001 /* Didn't appear on screen */
1004 curs->width = col - scol;
1007 } else if (l == curs->offset &&
1008 same_mp(pos.p, curs->pos)) {
1010 curs->target = scol;
1012 if (scol >= start + cols)
1013 /* Didn't appear on screen */
1016 curs->width = col - scol;
1023 && visible(mode, m, &pos) != -1))
1027 /* We have reached the end of visible line, or end of file */
1030 if (col >= start + cols)
1033 curs->width = -1; /* end of line */
1034 if (curs->target >= 0) {
1037 } else if (same_mp(pos.p, curs->pos))
1042 if (e.start && e.start[0] == 0) {
1045 int A, B, C, D, E, F;
1046 e1 = fb.list[m[pos.p.m].b + pos.p.o];
1047 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
1048 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
1049 snprintf(b, sizeof(b), "@@ -%d,%d +%d,%d @@%s", B, C, E, F, e1.start+18);
1050 (void)attrset(a_sep);
1052 mvaddstr(row, col-start+offset, b);
1055 blank(row, col-start+offset, start+cols-col,
1057 ? (unsigned)visible(mode, m, &pos)
1061 /* Draw either 1 or 2 sides depending on the mode. */
1063 static void draw_mline(int mode, int row, int start, int cols,
1064 struct file fm, struct file fb, struct file fa,
1067 struct cursor *curs)
1070 * Draw the left and right images of this line
1071 * One side might be a_blank depending on the
1072 * visibility of this newline
1076 mode |= check_line(pos, fm, fb, fa, m, mode);
1078 if ((mode & (BEFORE|AFTER)) &&
1079 (mode & (ORIG|RESULT))) {
1082 rcols = cols - lcols - 1;
1084 (void)attrset(A_STANDOUT);
1085 mvaddch(row, lcols, '|');
1087 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1088 fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1090 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1091 fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1093 draw_mside(mode, row, 0, start, cols,
1094 fm, fb, fa, m, pos, curs);
1097 static char *merge_help[] = {
1098 "This view shows the merge of the patch with the",
1099 "original file. It is like a full-context diff showing",
1100 "removed lines with a '-' prefix and added lines with a",
1102 "In cases where a patch chunk could not be successfully",
1103 "applied, the original text is prefixed with a '|', and",
1104 "the text that the patch wanted to add is prefixed with",
1106 "When the cursor is over such a conflict, or over a chunk",
1107 "which required wiggling to apply (i.e. there was unmatched",
1108 "text in the original, or extraneous unchanged text in",
1109 "the patch), the terminal is split and the bottom pane is",
1110 "use to display the part of the patch that applied to",
1111 "this section of the original. This allows you to confirm",
1112 "that a wiggled patch applied correctly, and to see",
1113 "why there was a conflict",
1116 static char *diff_help[] = {
1117 "This is the 'diff' or 'patch' view. It shows",
1118 "only the patch that is being applied without the",
1119 "original to which it is being applied.",
1120 "Underlined text indicates parts of the patch which",
1121 "resulted in a conflict when applied to the",
1125 static char *orig_help[] = {
1126 "This is the 'original' view which simply shows",
1127 "the original file before applying the patch.",
1128 "Sections of code that would be changed by the patch",
1129 "are highlighted in red.",
1132 static char *result_help[] = {
1133 "This is the 'result' view which shows just the",
1134 "result of applying the patch. When a conflict",
1135 "occurred this view does not show the full conflict",
1136 "but only the 'after' part of the patch. To see",
1137 "the full conflict, use the 'merge' or 'sidebyside'",
1141 static char *before_help[] = {
1142 "This view shows the 'before' section of a patch.",
1143 "It allows the expected match text to be seen uncluttered",
1144 "by text that is meant to replaced it.",
1145 "Red text is text that will be removed by the patch",
1148 static char *after_help[] = {
1149 "This view shows the 'after' section of a patch.",
1150 "It allows the intended result to be seen uncluttered",
1151 "by text that was meant to be matched and replaced.",
1152 "Green text is text that was added by the patch - it",
1153 "was not present in the 'before' part of the patch",
1156 static char *sidebyside_help[] = {
1157 "This is the Side By Side view of a patched file.",
1158 "The left side shows the original and the result.",
1159 "The right side shows the patch which was applied",
1160 "and lines up with the original/result as much as",
1163 "Where one side has no line which matches the",
1164 "other side it is displayed as a solid colour in the",
1165 "yellow family (depending on your terminal window).",
1168 static char *merge_window_help[] = {
1169 " Highlight Colours and Keystroke commands",
1171 "In all different views of a merge, highlight colours",
1172 "are used to show which parts of lines were added,",
1173 "removed, already changed, unchanged or in conflict.",
1174 "Colours and their use are:",
1175 " normal unchanged text",
1176 " red text that was removed or changed",
1177 " green text that was added or the result",
1179 " yellow background used in side-by-side for a line",
1180 " which has no match on the other",
1182 " blue text in the original which did not",
1183 " match anything in the patch",
1184 " cyan text in the patch which did not",
1185 " match anything in the original",
1186 " cyan background already changed text: the result",
1187 " of the patch matches the original",
1188 " underline remove or added text can also be",
1189 " underlined indicating that it",
1190 " was involved in a conflict",
1192 "While viewing a merge various keystroke commands can",
1193 "be used to move around and change the view. Basic",
1194 "movement commands from both 'vi' and 'emacs' are",
1197 " p control-p k UP Move to previous line",
1198 " n control-n j DOWN Move to next line",
1199 " l LEFT Move one char to right",
1200 " h RIGHT Move one char to left",
1201 " / control-s Enter incremental search mode",
1202 " control-r Enter reverse-search mode",
1203 " control-g Search again",
1204 " ? Display help message",
1205 " ESC-< 0-G Go to start of file",
1206 " ESC-> G Go to end of file",
1207 " q Return to list of files or exit",
1208 " S Arrange for merge to be saved on exit",
1209 " control-C Disable auto-save-on-exit",
1210 " control-L recenter current line",
1211 " control-V SPACE page down",
1212 " ESC-v BACKSPC page up",
1213 " N go to next patch chunk",
1214 " P go to previous patch chunk",
1215 " C go to next conflicted chunk",
1216 " C-X-o O move cursor to alternate pane",
1217 " ^ control-A go to start of line",
1218 " $ control-E go to end of line",
1220 " a display 'after' view",
1221 " b display 'before' view",
1222 " o display 'original' view",
1223 " r display 'result' view",
1224 " d display 'diff' or 'patch' view",
1225 " m display 'merge' view",
1226 " | display side-by-side view",
1228 " I toggle whether spaces are ignored",
1229 " when matching text.",
1230 " x toggle ignoring of current Changed,",
1231 " Conflict, or Unmatched item",
1232 " c toggle accepting of result of conflict",
1233 " X toggle ignored of all Change, Conflict",
1234 " and Unmatched items in current line",
1235 " v Save the current merge and run the",
1236 " default editor on the file.",
1239 static char *save_query[] = {
1241 "You have modified the merge.",
1242 "Would you like to save it?",
1243 " Y = save the modified merge",
1244 " N = discard modifications, don't save",
1245 " Q = return to viewing modified merge",
1249 static char *toggle_ignore[] = {
1251 "You have modified the merge.",
1252 "Toggling ignoring of spaces will discard changes.",
1253 "Do you want to proceed?",
1254 " Y = discard changes and toggle ignoring of spaces",
1255 " N = keep changes, don't toggle",
1259 static void do_edit(char *file, int line)
1261 char *ed = getenv("VISUAL");
1264 ed = getenv("EDITOR");
1266 ed = "/usr/bin/edit";
1267 snprintf(linebuf, sizeof(linebuf), "+%d", line);
1270 execlp(ed, ed, linebuf, file, NULL);
1279 static void *memdup(void *a, int len)
1281 char *r = malloc(len);
1286 static int merge_window(struct plist *p, FILE *f, int reverse, int replace,
1287 int selftest, int ignore_blanks, int just_diff)
1289 /* Display the merge window in one of the selectable modes,
1290 * starting with the 'merge' mode.
1292 * Newlines are the key to display.
1293 * 'pos' is always a visible newline (or eof).
1294 * In sidebyside mode it might only be visible on one side,
1295 * in which case the other side will be blank.
1296 * Where the newline is visible, we rewind the previous visible
1297 * newline visible and display the stuff in between
1299 * A 'position' is a struct mpos
1302 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1303 struct file fm, fb, fa;
1304 struct csl *csl1, *csl2;
1306 int ch; /* count of chunks */
1307 /* Always refresh the current line.
1308 * If refresh == 1, refresh all lines. If == 2, clear first
1311 int rows = 0, cols = 0;
1312 int splitrow = -1; /* screen row for split - diff appears below */
1313 int lastrow = 0; /* end of screen, or just above 'splitrow' */
1316 int mode = just_diff ? (BEFORE|AFTER) : (ORIG|RESULT);
1317 int mmode = mode; /* Mode for moving - used when in 'other' pane */
1318 char *modename = just_diff ? "diff" : "merge";
1319 char **modehelp = just_diff ? diff_help : merge_help;
1323 int trow; /* screen-row while searching. If we cannot find,
1324 * we forget this number */
1326 struct mpos pos; /* current point */
1327 struct mpos tpos, /* temp point while drawing lines above and below pos */
1328 toppos, /* pos at top of screen - for page-up */
1329 botpos; /* pos at bottom of screen - for page-down */
1330 struct mpos vpos, tvpos, vispos;
1332 int meta = 0, /* mode for multi-key commands- SEARCH or META */
1334 int num = -1, /* numeric arg being typed. */
1337 int changes = 0; /* If any edits have been made to the merge */
1338 int answer; /* answer to 'save changes?' question */
1342 char search[80]; /* string we are searching for */
1343 unsigned int searchlen = 0;
1344 int search_notfound = 0;
1348 * 1 == no because there are upper-case chars
1349 * 2 == yes as there are no upper-case chars
1352 int ignore_case = 2;
1353 /* We record all the places we find so 'backspace'
1354 * can easily return to the previous one
1356 struct search_anchor {
1357 struct search_anchor *next;
1362 unsigned int searchlen;
1365 void free_stuff(void)
1374 void find_line(int ln)
1376 pos.p.m = 0; /* merge node */
1377 pos.p.s = 0; /* stream number */
1378 pos.p.o = -1; /* offset */
1381 memset(&curs, 0, sizeof(curs));
1383 next_mline(&pos, fm, fb, fa, ci.merger, mode);
1384 while (pos.p.lineno < ln && ci.merger[pos.p.m].type != End);
1387 void prepare_merge(int ch)
1389 /* FIXME check for errors in the stream */
1390 fm = split_stream(sm, ByWord | ignore_blanks);
1391 fb = split_stream(sb, ByWord | ignore_blanks);
1392 fa = split_stream(sa, ByWord | ignore_blanks);
1394 if (ch && !just_diff)
1395 csl1 = pdiff(fm, fb, ch);
1397 csl1 = diff(fm, fb);
1398 csl2 = diff_patch(fb, fa);
1400 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
1401 for (i = 0; ci.merger[i].type != End; i++)
1402 ci.merger[i].oldtype = ci.merger[i].type;
1412 /* three separate files */
1413 sb = load_file(p->before);
1414 sa = load_file(p->after);
1418 sm = load_file(p->file);
1420 /* One merge file */
1421 sp = load_file(p->file);
1423 split_merge(sp, &sm, &sa, &sb);
1425 split_merge(sp, &sm, &sb, &sa);
1430 sp = load_segment(f, p->start, p->end);
1433 split_merge(sp, &sm, &sa, &sb);
1435 split_merge(sp, &sm, &sb, &sa);
1439 ch = split_patch(sp, &sa, &sb);
1441 ch = split_patch(sp, &sb, &sa);
1445 sm = load_file(p->file);
1449 if (!sm.body || !sb.body || !sa.body) {
1456 help_window(help_missing, NULL, 0);
1458 help_window(help_corrupt, NULL, 0);
1463 term_init(!selftest);
1474 if (row < 1 || row >= lastrow)
1479 if (mode == (ORIG|RESULT)) {
1480 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1481 if (cmode & (WIGGLED | CONFLICTED)) {
1483 splitrow = (rows+1)/2;
1484 lastrow = splitrow - 1;
1487 } else if (!curs.alt && splitrow >= 0) {
1492 } else if (splitrow >= 0) {
1499 getmaxyx(stdscr, rows, cols);
1500 rows--; /* keep last row clear */
1501 if (splitrow >= 0) {
1502 splitrow = (rows+1)/2;
1503 lastrow = splitrow - 1;
1511 if (row > lastrow+3)
1516 if (getenv("WIGGLE_VTRACE")) {
1521 default: /* keep compiler happy */
1523 e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start;
1526 e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start;
1529 e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start;
1532 for (i = 0; i < 6; i++) {
1534 if (e2[i] < 32 || e2[i] >= 127)
1537 sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
1539 vpos.p.m, typenames[ci.merger[vpos.p.m].type],
1540 ci.merger[vpos.p.m].al,
1541 ci.merger[vpos.p.m].bl,
1542 ci.merger[vpos.p.m].cl,
1543 ci.merger[vpos.p.m].in_conflict,
1546 (void)attrset(A_NORMAL);
1551 /* Always refresh the line */
1552 while (start > curs.target) {
1558 vispos = pos; /* visible position - if cursor is in
1559 * alternate pane, pos might not be visible
1561 if (check_line(vispos, fm, fb, fa, ci.merger, mode)
1563 if (vispos.state == 0)
1569 if (visible(mode, ci.merger, &vispos) < 0)
1570 prev_mline(&vispos, fm, fb, fa, ci.merger, mode);
1574 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1575 vispos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1576 if (curs.width == 0 && start < curs.col) {
1577 /* width == 0 implies it appear after end-of-screen */
1582 if (curs.col < start) {
1594 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1595 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1596 draw_mline(mode, i--, start, cols,
1597 fm, fb, fa, ci.merger,
1608 blank(i--, 0, cols, a_void);
1610 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1611 draw_mline(mode, i++, start, cols,
1612 fm, fb, fa, ci.merger,
1614 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1616 botpos = tpos; botrow = i;
1617 while (i <= lastrow)
1618 blank(i++, 0, cols, a_void);
1621 if (splitrow >= 0) {
1622 struct mpos spos = pos;
1623 int smode = BEFORE|AFTER;
1624 int srow = (rows + splitrow)/2;
1625 if (check_line(spos, fm, fb, fa, ci.merger, smode)
1627 if (spos.state == 0)
1632 if (visible(smode, ci.merger, &spos) < 0)
1633 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1634 /* Now hi/lo might be wrong, so lets fix it. */
1637 /* 'hi' might be wrong so we mustn't depend
1638 * on it while walking back. So set state
1639 * to 1 to avoid ever testing it.
1642 while (spos.p.m >= 0 && spos.state != 0)
1643 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1644 while (!same_mpos(spos, tpos))
1645 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1647 (void)attrset(a_sep);
1648 for (i = 0; i < cols; i++)
1649 mvaddstr(splitrow, i, "-");
1652 for (i = srow-1; i > splitrow; i--) {
1653 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1654 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1657 while (i > splitrow)
1658 blank(i--, 0, cols, a_void);
1661 i < rows && ci.merger[tpos.p.m].type != End;
1663 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1665 (i == srow && curs.alt) ? &curs : NULL);
1666 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1669 blank(i++, 0, cols, a_void);
1671 /* Now that curs is accurate, report the type */
1676 l = snprintf(buf, 100, "File: %s%s ",
1677 p->file, reverse ? " - reversed" : "");
1678 snprintf(buf+l, 100-l, "Mode: %s", modename);
1679 (void)attrset(A_BOLD);
1680 mvaddstr(0, 0, buf);
1681 (void)attrset(A_NORMAL);
1683 addstr(" (ignoring blanks)");
1685 (void)attrset(A_BOLD);
1686 if (ci.merger[curs.pos.m].type != ci.merger[curs.pos.m].oldtype)
1687 l = snprintf(buf, sizeof(buf), "%s->", typenames[ci.merger[curs.pos.m].oldtype]);
1688 snprintf(buf+l, sizeof(buf)-l, "%s ln:%d",
1689 typenames[ci.merger[curs.pos.m].type],
1690 (pos.p.lineno-1)/2);
1691 mvaddstr(0, cols - strlen(buf) - 1, buf);
1693 #define META(c) ((c)|0x1000)
1694 #define SEARCH(c) ((c)|0x2000)
1695 #define CTRLX(c) ((c)|0x4000)
1697 (void)attrset(A_NORMAL);
1706 snprintf(buf, 10, "%d ", num);
1711 if (meta & CTRLX(0))
1713 if (meta & SEARCH(0)) {
1715 addstr("Backwards ");
1718 if (search_notfound)
1719 addstr(" - Not Found.");
1720 search_notfound = 0;
1723 /* '+1' to skip over the leading +/-/| char */
1724 if (curs.alt && splitrow > 0)
1725 move((rows + splitrow)/2, curs.col - start + 1);
1726 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1727 (mode & (ORIG|RESULT))))
1728 move(row, curs.col-start + (cols-1)/2+2);
1730 move(row, curs.col-start+1);
1739 tmeta = meta; meta = 0;
1740 tnum = num; num = -1;
1741 tvpos = vpos; vpos = pos;
1742 cswitch = c | tmeta;
1743 /* Handle some ranges */
1744 /* case '0' ... '9': */
1745 if (cswitch >= '0' && cswitch <= '9')
1747 /* case SEARCH(' ') ... SEARCH('~'): */
1748 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1749 cswitch = SEARCH(' ');
1752 case 27: /* escape */
1762 case META('<'): /* start of file */
1767 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1768 } while (tpos.p.m >= 0);
1772 case META('>'): /* end of file */
1779 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1780 } while (ci.merger[tpos.p.m].type != End);
1784 case '0': /* actually '0'...'9' */
1787 num = tnum*10 + (c-'0');
1791 mesg = "Autosave disabled";
1793 mesg = "Use 'q' to quit";
1797 mesg = "Will auto-save on exit, using Ctrl-C to cancel";
1806 answer = help_window(save_query, NULL, 1);
1811 p->conflicts = isolate_conflicts(
1812 fm, fb, fa, csl1, csl2, 0,
1813 ci.merger, 0, &p->wiggles);
1814 p->chunks = p->conflicts;
1815 save_merge(fm, fb, fa, ci.merger,
1816 p->file, !p->is_merge);
1826 case 'I': /* Toggle ignoring of spaces */
1829 answer = help_window(toggle_ignore, NULL, 1);
1835 ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
1837 find_line(pos.p.lineno);
1843 if (!p->file || just_diff) {
1844 mesg = "Cannot run editor when diffing";
1847 lineno = save_tmp_merge(fm, fb, fa, ci.merger,
1849 ci.merger + pos.p.m,
1854 do_edit(tempname, lineno);
1855 sp = load_file(tempname);
1856 split_merge(sp, &sm, &sb, &sa);
1857 if (sp.len == sm.len &&
1858 memcmp(sp.body, sm.body, sm.len) == 0 &&
1860 /* no conflicts left, so display diff */
1862 sm = load_file(p->file);
1865 sb.body = memdup(sm.body, sm.len);
1872 find_line(pos.p.lineno);
1879 /* incr search forward */
1882 search[searchlen] = 0;
1887 /* incr search backwards */
1890 search[searchlen] = 0;
1893 case SEARCH('G'-64):
1894 case SEARCH('S'-64):
1895 case SEARCH('R'-64):
1897 if ((c|tmeta) == SEARCH('R'-64))
1902 tpos = pos; trow = row;
1905 case SEARCH('H'-64):
1906 case SEARCH(KEY_BACKSPACE):
1909 struct search_anchor *a;
1915 struct search_anchor *a;
1923 search_notfound = a->notfound;
1924 searchlen = a->searchlen;
1925 search[searchlen] = 0;
1930 case SEARCH(' '): /* actually ' '...'~' */
1933 if (searchlen < sizeof(search)-1)
1934 search[searchlen++] = c & (0x7f);
1935 search[searchlen] = 0;
1936 tpos = pos; trow = row;
1938 search_notfound = 1;
1939 if (ignore_case == 1 || ignore_case == 2) {
1942 for (i=0; i < searchlen; i++)
1943 if (isupper(search[i])) {
1949 if (mcontains(tpos, fm, fb, fa, ci.merger,
1950 mmode, search, &curs, searchdir,
1951 ignore_case >= 2)) {
1955 search_notfound = 0;
1958 if (searchdir < 0) {
1960 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1963 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1965 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1966 searchdir /= abs(searchdir);
1976 case 'V'-64: /* page down */
1978 if (botrow <= lastrow) {
1988 case META('v'): /* page up */
1995 if (getmouse(&mevent) != OK)
1997 /* First see if this is on the 'other' pane */
1999 /* merge mode, top and bottom */
2000 if ((curs.alt && mevent.y < splitrow) ||
2001 (!curs.alt && mevent.y > splitrow)) {
2004 } else if (mode == (ORIG|RESULT|BEFORE|AFTER)) {
2005 /* side-by-side mode */
2006 if ((curs.alt && mevent.x < cols/2) ||
2007 (!curs.alt && mevent.x > cols/2)) {
2011 /* Now try to find the right line */
2012 if (splitrow < 0 || !curs.alt)
2015 trow = (rows + splitrow)/2;
2016 while (trow > mevent.y) {
2018 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2019 if (tpos.p.m >= 0) {
2025 while (trow < mevent.y) {
2027 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2028 if (ci.merger[tpos.p.m].type != End) {
2034 if (splitrow < 0 || !curs.alt)
2035 /* it is OK to change the row */
2038 /* Now set the target column */
2039 if (mode == (ORIG|RESULT|BEFORE|AFTER) &&
2041 curs.target = start + mevent.x - cols / 2 - 1;
2043 curs.target = start + mevent.x - 1;
2051 for (; tnum > 0 ; tnum--) {
2053 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2054 if (ci.merger[tpos.p.m].type != End) {
2069 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2070 } while (!(pos.state == 0
2071 && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2072 & (CONFLICTED|WIGGLED)) == 0)
2073 && ci.merger[tpos.p.m].type != End);
2077 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2078 } while (pos.state == 0
2079 && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2080 & (CONFLICTED|WIGGLED)) == 0
2081 && ci.merger[tpos.p.m].type != End);
2089 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2090 } while (!(check_line(pos, fm, fb, fa, ci.merger, mmode)
2092 && ci.merger[tpos.p.m].type != End);
2096 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2097 } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2099 && ci.merger[tpos.p.m].type != End);
2108 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2109 } while (tpos.state == 0
2110 && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2111 & (CONFLICTED|WIGGLED)) == 0
2116 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2117 } while (!(tpos.state == 0
2118 && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2119 & (CONFLICTED|WIGGLED)) == 0)
2129 for (; tnum > 0 ; tnum--) {
2131 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2132 if (tpos.p.m >= 0) {
2143 curs.target = curs.col - 1;
2144 if (curs.target < 0) {
2145 /* Try to go to end of previous line */
2147 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2148 if (tpos.p.m >= 0) {
2160 if (curs.width >= 0)
2161 curs.target = curs.col + curs.width;
2163 /* end of line, go to next */
2165 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2166 if (ci.merger[tpos.p.m].type != End) {
2189 curs.alt = !curs.alt;
2190 if (curs.alt && mode == (ORIG|RESULT))
2191 mmode = (BEFORE|AFTER);
2196 case 'a': /* 'after' view in patch window */
2199 mode = AFTER; modename = "after"; modehelp = after_help;
2200 mmode = mode; curs.alt = 0;
2203 case 'b': /* 'before' view in patch window */
2206 mode = BEFORE; modename = "before"; modehelp = before_help;
2207 mmode = mode; curs.alt = 0;
2210 case 'o': /* 'original' view in the merge window */
2213 mode = ORIG; modename = "original"; modehelp = orig_help;
2214 mmode = mode; curs.alt = 0;
2217 case 'r': /* the 'result' view in the merge window */
2220 mode = RESULT; modename = "result"; modehelp = result_help;
2221 mmode = mode; curs.alt = 0;
2225 if (mode == (BEFORE|AFTER))
2227 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
2228 mmode = mode; curs.alt = 0;
2233 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
2234 mmode = mode; curs.alt = 0;
2239 if (mode == (ORIG|RESULT|BEFORE|AFTER))
2241 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
2242 mmode = mode; curs.alt = 0;
2246 case 'H': /* scroll window to the right */
2249 curs.target = start + 1;
2252 case 'L': /* scroll window to the left */
2255 curs.target = start + 1;
2260 prev_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
2265 next_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
2266 if (ci.merger[tvpos.p.m].type != End)
2270 case 'x': /* Toggle rejecting of conflict.
2271 * A 'Conflict' or 'Changed' becomes 'Unchanged'
2272 * 'Unmatched' becomes 'Changed'
2274 if (ci.merger[curs.pos.m].oldtype == Conflict ||
2275 ci.merger[curs.pos.m].oldtype == Changed)
2277 else if (ci.merger[curs.pos.m].oldtype == Unmatched)
2282 if (ci.merger[curs.pos.m].type == next)
2283 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2285 ci.merger[curs.pos.m].type = next;
2286 p->conflicts = isolate_conflicts(
2287 fm, fb, fa, csl1, csl2, 0,
2288 ci.merger, 0, &p->wiggles);
2293 case 'c': /* Toggle accepting of conflict.
2294 * A 'Conflict' becomes 'Changed'
2296 if (ci.merger[curs.pos.m].oldtype != Conflict)
2299 if (ci.merger[curs.pos.m].type == Changed)
2300 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2302 ci.merger[curs.pos.m].type = Changed;
2303 p->conflicts = isolate_conflicts(
2304 fm, fb, fa, csl1, csl2, 0,
2305 ci.merger, 0, &p->wiggles);
2310 case 'X': /* toggle where all Conflicts and Changeds
2311 * in the current line are marked Unchanged.
2312 * If any are not mark, mark them all, else
2318 if ((ci.merger[tpos.p.m].oldtype == Conflict ||
2319 ci.merger[tpos.p.m].oldtype == Changed ||
2320 ci.merger[tpos.p.m].oldtype == Unmatched)
2321 && (ci.merger[tpos.p.m].type ==
2322 ci.merger[tpos.p.m].oldtype))
2324 e = prev_melmnt(&tpos.p, fm, fb, fa, ci.merger);
2327 } while (!ends_line(e) ||
2328 visible(mode & (RESULT|AFTER), ci.merger, &tpos) < 0);
2331 if (ci.merger[tpos.p.m].oldtype == Conflict ||
2332 ci.merger[tpos.p.m].oldtype == Changed ||
2333 ci.merger[tpos.p.m].oldtype == Unmatched) {
2335 if (ci.merger[tpos.p.m].oldtype == Unmatched)
2338 ci.merger[tpos.p.m].type = next;
2340 ci.merger[tpos.p.m].type =
2341 ci.merger[tpos.p.m].oldtype;
2343 e = prev_melmnt(&tpos.p, fm, fb, fa, ci.merger);
2346 } while (!ends_line(e) ||
2347 visible(mode & (RESULT|AFTER), ci.merger, &tpos) < 0);
2348 p->conflicts = isolate_conflicts(
2349 fm, fb, fa, csl1, csl2, 0,
2350 ci.merger, 0, &p->wiggles);
2356 help_window(modehelp, merge_window_help, 0);
2365 if (meta == SEARCH(0)) {
2366 if (anchor == NULL ||
2367 !same_mpos(anchor->pos, pos) ||
2368 anchor->searchlen != searchlen ||
2369 !same_mp(anchor->curs.pos, curs.pos)) {
2370 struct search_anchor *a = xmalloc(sizeof(*a));
2375 a->searchlen = searchlen;
2376 a->notfound = search_notfound;
2382 struct search_anchor *a = anchor;
2388 /* move backward and forward to make sure we
2389 * are on a visible line
2392 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2396 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2397 if (ci.merger[tpos.p.m].type != End)
2403 static int show_merge(char *origname, FILE *patch, int reverse,
2404 int is_merge, char *before, char *after,
2405 int replace, int selftest, int ignore_blanks,
2413 fseek(patch, 0, SEEK_END);
2414 p.end = ftell(patch);
2415 fseek(patch, 0, SEEK_SET);
2418 p.is_merge = is_merge;
2422 freopen("/dev/null","w",stderr);
2423 return merge_window(&p, patch, reverse, replace, selftest,
2424 ignore_blanks, just_diff);
2427 static void calc_one(struct plist *pl, FILE *f, int reverse,
2428 int ignore_blanks, int just_diff)
2430 struct stream s1, s2;
2431 struct stream s = load_segment(f, pl->start, pl->end);
2435 split_merge(s, &sf, &s2, &s1);
2437 split_merge(s, &sf, &s1, &s2);
2441 pl->chunks = split_patch(s, &s2, &s1);
2443 pl->chunks = split_patch(s, &s1, &s2);
2447 sf = load_file(pl->file);
2449 if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
2450 pl->wiggles = pl->conflicts = -1;
2452 struct file ff, fp1, fp2;
2453 struct csl *csl1, *csl2;
2455 ff = split_stream(sf, ByWord | ignore_blanks);
2456 fp1 = split_stream(s1, ByWord | ignore_blanks);
2457 fp2 = split_stream(s2, ByWord | ignore_blanks);
2458 if (pl->chunks && !just_diff)
2459 csl1 = pdiff(ff, fp1, pl->chunks);
2461 csl1 = diff(ff, fp1);
2462 csl2 = diff_patch(fp1, fp2);
2463 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
2464 pl->wiggles = ci.wiggles;
2465 pl->conflicts = ci.conflicts;
2482 static int get_prev(int pos, struct plist *pl, int n, int mode)
2488 if (pl[pos].prev == -1)
2489 return pl[pos].parent;
2491 while (pl[pos].open &&
2494 if (pl[pos].last >= 0)
2495 /* always see directories */
2499 else if (mode <= 1 && pl[pos].wiggles > 0)
2501 else if (mode <= 2 && pl[pos].conflicts > 0)
2503 } while (pos >= 0 && !found);
2507 static int get_next(int pos, struct plist *pl, int n, int mode,
2508 FILE *f, int reverse, int ignore_blanks, int just_diff)
2520 while (pos >= 0 && pl[pos].next == -1)
2521 pos = pl[pos].parent;
2527 if (pl[pos].calced == 0 && pl[pos].end)
2528 calc_one(pl+pos, f, reverse, ignore_blanks, just_diff);
2529 if (pl[pos].last >= 0)
2530 /* always see directories */
2534 else if (mode <= 1 && pl[pos].wiggles > 0)
2536 else if (mode <= 2 && pl[pos].conflicts > 0)
2538 } while (pos >= 0 && !found);
2542 static void draw_one(int row, struct plist *pl, FILE *f, int reverse,
2543 int ignore_blanks, int just_diff)
2553 if (pl->calced == 0 && pl->end)
2554 /* better load the patch and count the chunks */
2555 calc_one(pl, f, reverse, ignore_blanks, just_diff);
2559 if (pl->chunks > 99)
2562 sprintf(hdr, "%2d", pl->chunks);
2563 if (pl->wiggles > 99)
2564 strcpy(hdr+2, " XX");
2566 sprintf(hdr+2, " %2d", pl->wiggles);
2567 if (pl->conflicts > 99)
2568 strcpy(hdr+5, " XX ");
2570 sprintf(hdr+5, " %2d ", pl->conflicts);
2573 strcpy(hdr+9, "= ");
2575 strcpy(hdr+9, "+ ");
2577 strcpy(hdr+9, "- ");
2581 else if (pl->is_merge)
2583 else if (pl->conflicts)
2584 attrset(a_has_conflicts);
2585 else if (pl->wiggles)
2586 attrset(a_has_wiggles);
2588 attrset(a_no_wiggles);
2590 mvaddstr(row, 0, hdr);
2591 mvaddstr(row, 11, pl->file);
2595 static int save_one(FILE *f, struct plist *pl, int reverse,
2598 struct stream sp, sa, sb, sm;
2599 struct file fa, fb, fm;
2600 struct csl *csl1, *csl2;
2603 sp = load_segment(f, pl->start,
2606 chunks = split_patch(sp, &sa, &sb);
2608 chunks = split_patch(sp, &sb, &sa);
2609 fb = split_stream(sb, ByWord | ignore_blanks);
2610 fa = split_stream(sa, ByWord | ignore_blanks);
2611 sm = load_file(pl->file);
2612 fm = split_stream(sm, ByWord | ignore_blanks);
2613 csl1 = pdiff(fm, fb, chunks);
2614 csl2 = diff_patch(fb, fa);
2615 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
2616 return save_merge(fm, fb, fa, ci.merger,
2620 static char *main_help[] = {
2621 " You are using the \"browse\" mode of wiggle.",
2622 "This page shows a list of files in a patch together with",
2623 "the directories that contain them.",
2624 "A directory is indicated by a '+' if the contents are",
2625 "listed or a '-' if the contents are hidden. A file is",
2626 "indicated by an '='. Typing <space> or <return> will",
2627 "expose or hide a directory, and will visit a file.",
2629 "The three columns of numbers are:",
2630 " Ch The number of patch chunks which applied to",
2632 " Wi The number of chunks that needed to be wiggled",
2634 " Co The number of chunks that created an unresolvable",
2637 "Keystrokes recognised in this page are:",
2638 " ? Display this help",
2639 " SPC On a directory, toggle hiding of contents",
2640 " On file, visit the file",
2643 " control-C Disable auto-save-on-exit",
2644 " n,j,DOWN Go to next line",
2645 " p,k,UP Go to previous line",
2647 " A list All files",
2648 " W only list files with a wiggle or a conflict",
2649 " C only list files with a conflict",
2651 " S Save this file with changes applied. If",
2652 " some but not all files are saved, wiggle will",
2653 " prompt on exit to save the rest.",
2654 " R Revert the current saved file to its original",
2656 " I toggle whether spaces are ignored",
2657 " when matching text.",
2660 static char *saveall_msg = " %d file%s (of %d) have not been saved.";
2661 static char saveall_buf[200];
2662 static char *saveall_query[] = {
2665 " Would you like to save them?",
2666 " Y = yes, save them all",
2667 " N = no, exit without saving anything else",
2668 " Q = Don't quit just yet",
2671 static void main_window(struct plist *pl, int *np, FILE *f, int reverse,
2672 int replace, int ignore_blanks, int just_diff)
2674 /* The main window lists all files together with summary information:
2675 * number of chunks, number of wiggles, number of conflicts.
2676 * The list is scrollable
2677 * When a entry is 'selected', we switch to the 'file' window
2678 * The list can be condensed by removing files with no conflict
2679 * or no wiggles, or removing subdirectories
2681 * We record which file in the list is 'current', and which
2682 * screen line it is on. We try to keep things stable while
2685 * Counts are printed before the name using at most 2 digits.
2686 * Numbers greater than 99 are XX
2688 * 27 5 1 drivers/md/md.c
2690 * A directory show the sum in all children.
2693 * select: enter, space, mouseclick
2694 * on file, go to file window
2695 * on directory, toggle open
2696 * up: k, p, control-p uparrow
2697 * Move to previous open object
2698 * down: j, n, control-n, downarrow
2699 * Move to next open object
2701 * A W C: select All Wiggles or Conflicts
2706 char mesg_buf[1024];
2707 int last_mesg_len = 0;
2708 int pos = 0; /* position in file */
2709 int row = 1; /* position on screen */
2710 int rows = 0; /* size of screen in rows */
2715 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2716 int cnt; /* count of files that need saving */
2717 int any; /* count of files that have been save*/
2720 char *debug = getenv("WIGGLE_DEBUG");
2722 if (debug && !*debug)
2725 freopen("/dev/null","w",stderr);
2727 pl = sort_patches(pl, np);
2731 clear(); (void)attrset(0);
2733 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2736 addstr(" (ignoring blanks)");
2740 if (row < 1 || row >= rows)
2744 getmaxyx(stdscr, rows, cols);
2746 if (row >= rows + 3)
2751 for (i = row; i > 1; i--) {
2752 tpos = get_prev(tpos, pl, *np, mode);
2758 /* Ok, row and pos could be trustworthy now */
2760 for (i = row; i >= 1; i--) {
2761 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2762 tpos = get_prev(tpos, pl, *np, mode);
2765 for (i = row+1; i < rows; i++) {
2766 tpos = get_next(tpos, pl, *np, mode, f, reverse,ignore_blanks, just_diff);
2768 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2770 draw_one(i, NULL, f, reverse, ignore_blanks, just_diff);
2774 if (last_mesg_len) {
2775 move(0, cols - last_mesg_len);
2780 last_mesg_len = strlen(mesg);
2781 move(0, cols - last_mesg_len);
2785 /* debugging help: report last keystroke */
2787 sprintf(bb, "last-key = 0%o", c);
2789 last_mesg_len = strlen(bb);
2790 mvaddstr(0, cols - last_mesg_len, bb);
2800 tpos = get_next(pos, pl, *np, mode, f, reverse, ignore_blanks, just_diff);
2811 tpos = get_prev(pos, pl, *np, mode);
2819 if (getmouse(&mevent) != OK)
2821 while (row < mevent.y &&
2822 (tpos = get_next(pos, pl, *np, mode, f, reverse, ignore_blanks, just_diff))
2827 while (row > mevent.y &&
2828 (tpos = get_prev(pos, pl, *np, mode)) >= 0) {
2832 if (row != mevent.y)
2833 /* couldn't find the line */
2838 if (pl[pos].end == 0) {
2839 pl[pos].open = !pl[pos].open;
2842 mesg = "Opened folder";
2844 mesg = "Closed folder";
2847 if (pl[pos].is_merge)
2848 c = merge_window(&pl[pos], NULL, reverse, 0, 0, ignore_blanks, just_diff);
2850 c = merge_window(&pl[pos], f, reverse, 0, 0, ignore_blanks, just_diff);
2853 pl[pos].is_merge = 1;
2854 snprintf(mesg_buf, cols,
2861 case 27: /* escape */
2863 mvaddstr(0, cols-10, "ESC..."); clrtoeol();
2867 move(0, cols-10); clrtoeol();
2871 mesg = "Save-on-exit disabled. Use 'q' to quit.";
2873 mesg = "Use 'q' to quit.";
2880 for (i = 0; i < *np; i++)
2881 if (pl[i].end && !pl[i].is_merge)
2893 sprintf(saveall_buf, saveall_msg,
2894 cnt, cnt == 1 ? "" : "s", cnt+any);
2895 ans = help_window(saveall_query, NULL, 1);
2901 for (i = 0; i < *np; i++) {
2912 printf("%d file%s saved\n", cnt,
2913 cnt == 1 ? "" : "s");
2917 mode = 0; refresh = 1;
2918 mesg = "Showing ALL files";
2921 mode = 1; refresh = 1;
2922 mesg = "Showing Wiggled files";
2925 mode = 2; refresh = 1;
2926 mesg = "Showing Conflicted files";
2929 case 'S': /* Save updated file */
2930 if (pl[pos].end == 0) {
2932 mesg = "Cannot save a folder.";
2933 } else if (pl[pos].is_merge) {
2935 mesg = "File is already saved.";
2937 if (save_one(f, &pl[pos], reverse, ignore_blanks) == 0) {
2938 pl[pos].is_merge = 1;
2939 snprintf(mesg_buf, cols,
2942 pl[pos].chunks = pl[pos].conflicts;
2943 pl[pos].wiggles = 0;
2945 snprintf(mesg_buf, cols,
2946 "Failed to save file %s.",
2953 case 'R': /* Restore updated file */
2954 if (pl[pos].end == 0)
2955 mesg = "Cannot restore a folder.";
2956 else if (!pl[pos].is_merge)
2957 mesg = "File has not been saved, cannot restore.";
2959 /* rename foo.porig to foo, and clear is_merge */
2960 char *file = pl[pos].file;
2961 char *orignew = xmalloc(strlen(file) + 20);
2962 strcpy(orignew, file);
2963 strcat(orignew, ".porig");
2964 if (rename(orignew, file) == 0) {
2965 mesg = "File has been restored.";
2966 pl[pos].is_merge = 0;
2968 calc_one(&pl[pos], f, reverse, ignore_blanks, just_diff);
2970 mesg = "Could not restore file!";
2974 case 'I': /* Toggle ignoring blanks */
2975 ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
2977 for (i = 0; i < *np; i++)
2982 help_window(main_help, NULL, 0);
2993 static void catch(int sig)
2995 if (sig == SIGINT && !intr_kills) {
3002 printf("Died on signal %d\n", sig);
3004 if (sig != SIGBUS && sig != SIGSEGV)
3007 /* Otherwise return and die */
3011 static void term_init(int doraw)
3014 static int init_done = 0;
3020 signal(SIGINT, catch);
3021 signal(SIGQUIT, catch);
3022 signal(SIGTERM, catch);
3023 signal(SIGBUS, catch);
3024 signal(SIGSEGV, catch);
3033 use_default_colors();
3034 if (!has_colors()) {
3035 a_delete = A_UNDERLINE;
3037 a_common = A_NORMAL;
3039 a_already = A_STANDOUT;
3040 a_has_conflicts = A_UNDERLINE;
3041 a_has_wiggles = A_BOLD;
3042 a_no_wiggles = A_NORMAL;
3044 init_pair(1, COLOR_RED, -1);
3045 a_delete = COLOR_PAIR(1);
3046 init_pair(2, COLOR_GREEN, -1);
3047 a_added = COLOR_PAIR(2);
3048 a_common = A_NORMAL;
3049 init_pair(3, COLOR_WHITE, COLOR_GREEN);
3050 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
3051 init_pair(4, -1, COLOR_YELLOW);
3052 a_void = COLOR_PAIR(4);
3053 init_pair(5, COLOR_BLUE, -1);
3054 a_unmatched = COLOR_PAIR(5);
3055 init_pair(6, COLOR_CYAN, -1);
3056 a_extra = COLOR_PAIR(6);
3058 init_pair(7, COLOR_BLACK, COLOR_CYAN);
3059 a_already = COLOR_PAIR(7);
3061 a_has_conflicts = a_delete;
3062 a_has_wiggles = a_added;
3063 a_no_wiggles = a_unmatched;
3066 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
3067 mousemask(ALL_MOUSE_EVENTS, NULL);
3070 int vpatch(int argc, char *argv[], int patch, int strip,
3071 int reverse, int replace, int selftest, int ignore_blanks)
3073 /* NOTE argv[0] is first arg...
3074 * Behaviour depends on number of args and 'patch'.
3075 * If 'patch' is '1', assume a patch. if '2', assume a diff.
3076 * 0: A multi-file patch or diff is read from stdin.
3077 * A 'patch' is applies to relevant files. A 'diff' is just
3079 * 1: if 'patch', parse it as a multi-file patch/diff and allow
3080 * the files to be browsed.
3081 * if filename ends '.rej', then treat it as a patch/diff again
3082 * a file with the same basename
3083 * Else treat the file as a merge (with conflicts) and view it.
3085 * 2: First file is original, second is patch unless patch==2,
3086 * then two files need to be diffed.
3087 * 3: Files are: original previous new. The diff between 'previous' and
3088 * 'new' needs to be applied to 'original'.
3090 * If a multi-file patch is being read, 'strip' tells how many
3091 * path components to strip. If it is -1, we guess based on
3093 * If 'reverse' is given, when we invert any patch or diff
3094 * If 'replace' then we save the resulting merge.
3100 int just_diff = (patch == 2);
3104 fprintf(stderr, "%s: too many file names given.\n", Cmd);
3107 case 0: /* stdin is a patch or diff */
3108 if (lseek(fileno(stdin), 0L, 1) == -1) {
3109 /* cannot seek, so need to copy to a temp file */
3112 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
3115 pl = parse_patch(stdin, f, &num_patches);
3118 pl = parse_patch(stdin, NULL, &num_patches);
3119 in = fdopen(dup(0), "r");
3121 /* use stderr for keyboard input */
3124 set_prefix(pl, num_patches, strip) == 0) {
3125 fprintf(stderr, "%s: aborting\n", Cmd);
3128 main_window(pl, &num_patches, in, reverse, replace, ignore_blanks, just_diff);
3129 plist_free(pl, num_patches);
3133 case 1: /* a patch/diff, a .rej, or a merge file */
3134 f = fopen(argv[0], "r");
3136 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
3139 check_dir(argv[0], fileno(f));
3141 pl = parse_patch(f, NULL, &num_patches);
3142 if (!just_diff && set_prefix(pl, num_patches, strip) == 0) {
3143 fprintf(stderr, "%s: aborting\n", Cmd);
3146 main_window(pl, &num_patches, f, reverse, replace,ignore_blanks, just_diff);
3147 plist_free(pl, num_patches);
3148 } else if (strlen(argv[0]) > 4 &&
3149 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
3150 char *origname = strdup(argv[0]);
3151 origname[strlen(origname) - 4] = '\0';
3152 show_merge(origname, f, reverse, 0, NULL, NULL,
3153 replace, selftest, ignore_blanks, just_diff);
3155 show_merge(argv[0], f, reverse, 1, NULL, NULL,
3156 replace, selftest, ignore_blanks, just_diff);
3159 case 2: /* an orig and a diff/.rej or two files */
3161 show_merge(NULL, NULL, reverse, 0, argv[0], argv[1],
3162 replace, selftest, ignore_blanks, just_diff);
3165 f = fopen(argv[1], "r");
3166 check_dir(argv[1], fileno(f));
3168 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
3171 show_merge(argv[0], f, reverse, 0, NULL, NULL,
3172 replace, selftest, ignore_blanks, just_diff);
3174 case 3: /* orig, before, after */
3175 show_merge(argv[0], NULL, reverse, 0, argv[1], argv[2],
3176 replace, selftest, ignore_blanks, just_diff);