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.
48 static void term_init(void);
50 /* global attributes */
51 unsigned int a_delete
, a_added
, a_common
, a_sep
, a_void
,
52 a_unmatched
, a_extra
, a_already
;
53 unsigned int a_has_conflicts
, a_has_wiggles
, a_no_wiggles
;
55 /******************************************************************
57 * We display help in an insert, leaving 5 columns left and right,
58 * and 2 rows top and bottom, but at most 58x15 plus border
60 * SPC or RTN moves down or to next page
61 * BKSPC goes backwards
62 * 'q' returns to origin screen
63 * '?' show help on help
64 * left and right scroll help view
66 * A help text is an array of lines of text
70 " You are viewing the help page for the help viewer.",
71 "You normally get here by typing '?'",
73 "The following keystrokes work in the help viewer:",
74 " ? display this help message",
75 " q return to previous view",
76 " SPC move forward through help document",
78 " BKSP move backward through help document",
79 " RIGHT scroll help window so text on the right appears",
80 " LEFT scroll help window so text on the left appears",
84 char *help_missing
[] = {
85 "The file that this patch applies to appears",
87 "Please type 'q' to continue",
91 char *help_corrupt
[] = {
92 "This patch appears to be corrupt",
93 "Please type 'q' to continue",
97 /* We can give one or two pages to display in the help window.
98 * The first is specific to the current context. The second
99 * is optional and may provide help in a more broad context.
101 static void help_window(char *page1
[], char *page2
[])
111 getmaxyx(stdscr
, rows
, cols
);
117 left
= (cols
-58)/2 - 1;
125 top
= (rows
-15)/2 - 1;
129 /* Draw a border around the 'help' area */
130 (void)attrset(A_STANDOUT
);
131 for (c
= left
; c
< left
+cols
; c
++) {
132 mvaddch(top
-1, c
, '-');
133 mvaddch(top
+rows
, c
, '-');
135 for (r
= top
; r
< top
+ rows
; r
++) {
136 mvaddch(r
, left
-1, '|');
137 mvaddch(r
, left
+cols
, '|');
139 mvaddch(top
-1, left
-1, '/');
140 mvaddch(top
-1, left
+cols
, '\\');
141 mvaddch(top
+rows
, left
-1, '\\');
142 mvaddch(top
+rows
, left
+cols
, '/');
143 mvaddstr(top
-1, left
+ cols
/2 - 9, "HELP - 'q' to exit");
144 mvaddstr(top
+rows
, left
+cols
/2 - 17, "Press SPACE for more, '?' for help");
145 (void)attrset(A_NORMAL
);
148 char **lnp
= page
+ line
;
150 /* Draw as much of the page at the current offset
153 for (r
= 0; r
< rows
; r
++) {
161 while (*ln
&& sh
> 0) {
165 for (c
= 0; c
< cols
; c
++) {
171 mvaddch(top
+r
, left
+c
, chr
);
174 move(top
+rows
-1, left
);
181 if (page1
!= help_help
)
182 help_window(help_help
, NULL
);
185 case '\r': /* page-down */
186 for (r
= 0; r
< rows
-2; r
++)
200 case '\b': /* page up */
236 static char *typenames
[] = {
238 [Unmatched
] = "Unmatched",
239 [Unchanged
] = "Unchanged",
240 [Extraneous
] = "Extraneous",
241 [Changed
] = "Changed",
242 [Conflict
] = "Conflict",
243 [AlreadyApplied
] = "AlreadyApplied",
246 /* When we merge the original and the diff together we need
247 * to keep track of where everything came from.
248 * When we display the different views, we need to be able to
249 * select certain portions of the whole document.
250 * These flags are used to identify what is present, and to
251 * request different parts be extracted. They also help
252 * guide choice of colour.
258 #define CHANGED 16 /* The RESULT is different to ORIG */
259 #define CHANGES 32 /* AFTER is different to BEFORE */
260 #define WIGGLED 64 /* a conflict that was successfully resolved */
261 #define CONFLICTED 128 /* a conflict that was not successfully resolved */
263 /* Displaying a Merge.
264 * The first step is to linearise the merge. The merge in inherently
265 * parallel with before/after streams. However much of the whole document
266 * is linear as normally much of the original in unchanged.
267 * All parallelism comes from the patch. This normally produces two
268 * parallel stream, but in the case of a conflict can produce three.
269 * For browsing the merge we only ever show two alternates in-line.
270 * When there are three we use two panes with 1 or 2 alternates in each.
271 * So to linearise the two streams we find lines that are completely
272 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
273 * a region where there are changes. We include everything between
274 * these twice, in two separate passes. The exact interpretation of the
275 * passes is handled at a higher level but will be one of:
276 * original and result
278 * original and after (for a conflict)
279 * This is all encoded in the 'struct merge'. An array of these describes
280 * the whole document.
282 * At any position in the merge we can be in one of 3 states:
283 * 0: unchanged section
287 * So to walk a merge in display order we need a position in the merge,
288 * a current state, and when in a changed section, we need to know the
289 * bounds of that changed section.
290 * This is all encoded in 'struct mpos'.
292 * Each location may or may not be visible depending on certain
295 * Also, some locations might be 'invalid' in that they don't need to be displayed.
296 * For example when the patch leaves a section of the original unchanged,
297 * we only need to see the original - the before/after sections are treated
298 * as invalid and are not displayed.
299 * The visibility of newlines is crucial and guides the display. One line
300 * of displayed text is all the visible sections between two visible newlines.
302 * Counting lines is a bit tricky. We only worry about line numbers in the
303 * original (stream 0) as these could compare with line numbers mentioned in
305 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
306 * That way we don't get a full counted line until we see the first char after the
307 * newline, so '+' lines are counted with the previous line.
311 int m
; /* merger index */
312 int s
; /* stream 0,1,2 for a,b,c */
313 int o
; /* offset in that stream */
314 int lineno
; /* Counts newlines in stream 0
315 * set lsb when see newline.
316 * add one when not newline and lsb set
320 struct mp p
, /* the current point (end of a line) */
321 lo
, /* eol for start of the current group */
322 hi
; /* eol for end of the current group */
324 * 0 if on an unchanged (lo/hi not meaningful)
325 * 1 if on the '-' of a diff,
326 * 2 if on the '+' of a diff
331 struct mp pos
; /* where in the document we are (an element) */
332 int offset
; /* which char in that element */
333 int target
; /* display column - or -1 if we are looking for 'pos' */
334 int col
; /* where we found pos or target */
335 int width
; /* Size of char, for moving to the right */
336 int alt
; /* Cursor is in alternate window */
339 /* used for checking location during search */
340 static int same_mp(struct mp a
, struct mp b
)
346 static int same_mpos(struct mpos a
, struct mpos b
)
348 return same_mp(a
.p
, b
.p
) &&
349 (a
.state
== b
.state
|| a
.state
== 0 || b
.state
== 0);
352 /* Check if a particular stream is meaningful in a particular merge
353 * section. e.g. in an Unchanged section, only stream 0, the
354 * original, is meaningful. This is used to avoid walking down
357 static int stream_valid(int s
, enum mergetype type
)
379 * Advance the 'pos' in the current mergepos returning the next
381 * This walks the merges in sequence, and the streams within
384 static struct elmnt
next_melmnt(struct mp
*pos
,
385 struct file fm
, struct file fb
, struct file fa
,
390 int l
= 0; /* Length remaining in current merge section */
404 /* Offset has reached length, choose new stream or
413 } while (!stream_valid(pos
->s
, m
[pos
->m
].type
));
417 if (pos
->m
== -1 || m
[pos
->m
].type
== End
) {
419 e
.start
= NULL
; e
.len
= 0;
423 default: /* keep compiler happy */
427 if (ends_mline(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
429 return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
430 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
431 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
435 /* step current position.p backwards */
436 static struct elmnt
prev_melmnt(struct mp
*pos
,
437 struct file fm
, struct file fb
, struct file fa
,
441 if (ends_mline(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
448 while (pos
->m
>= 0 && pos
->o
< 0) {
455 } while (pos
->m
>= 0 &&
456 !stream_valid(pos
->s
, m
[pos
->m
].type
));
460 pos
->o
= m
[pos
->m
].al
-1;
463 pos
->o
= m
[pos
->m
].bl
-1;
466 pos
->o
= m
[pos
->m
].cl
-1;
473 e
.start
= NULL
; e
.len
= 0;
477 default: /* keep compiler happy */
478 case 0: return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
479 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
480 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
484 /* 'visible' not only checks if this stream in this merge should be
485 * visible in this mode, but also chooses which colour/highlight to use
488 static int visible(int mode
, enum mergetype type
, int stream
)
492 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
494 case End
: /* The END is always visible */
496 case Unmatched
: /* Visible in ORIG and RESULT */
497 if (mode
& (ORIG
|RESULT
))
500 case Unchanged
: /* visible everywhere, but only show stream 0 */
504 case Extraneous
: /* stream 2 is visible in BEFORE and AFTER */
505 if ((mode
& (BEFORE
|AFTER
))
509 case Changed
: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
511 (mode
& (ORIG
|BEFORE
)))
514 (mode
& (RESULT
|AFTER
)))
521 return a_unmatched
| A_REVERSE
;
525 return a_extra
| A_UNDERLINE
;
528 if (mode
& (AFTER
|RESULT
))
529 return a_added
| A_UNDERLINE
;
536 if (mode
& (ORIG
|RESULT
))
541 return a_delete
| A_UNDERLINE
;
545 return a_added
| A_UNDERLINE
;
553 /* checkline creates a summary of the sort of changes that
554 * are in a line, returning an "or" of
560 static int check_line(struct mpos pos
, struct file fm
, struct file fb
,
562 struct merge
*m
, int mode
)
569 if (m
[pos
.p
.m
].type
== Changed
)
570 rv
|= CHANGED
| CHANGES
;
571 else if ((m
[pos
.p
.m
].type
== AlreadyApplied
||
572 m
[pos
.p
.m
].type
== Conflict
))
573 rv
|= CONFLICTED
| CHANGES
;
574 else if (m
[pos
.p
.m
].type
== Extraneous
&&
575 /* hunk headers don't count as wiggles */
576 fb
.list
[m
[pos
.p
.m
].b
].start
[0] != '\0')
578 else if (m
[pos
.p
.m
].type
== Unmatched
)
580 if (m
[pos
.p
.m
].in_conflict
&&
581 (pos
.p
.o
< m
[pos
.p
.m
].lo
||
582 pos
.p
.o
> m
[pos
.p
.m
].hi
))
583 rv
|= CONFLICTED
| CHANGES
;
584 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
585 } while (e
.start
!= NULL
&&
587 || visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
589 if (unmatched
&& (rv
& CHANGES
))
594 /* Find the next line in the merge which is visible.
595 * If we hit the end of a conflicted set during pass-1
596 * we rewind for pass-2.
597 * 'mode' tells which bits we want to see, possible one of
598 * the 4 parts (before/after/orig/result) or one of the pairs
599 * before+after or orig+result.
601 static void next_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
603 struct merge
*m
, int mode
)
612 struct elmnt e
= next_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
616 visible(mode
, m
[pos
->p
.m
].type
, pos
->p
.s
) >= 0)
619 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
621 if ((mode2
& CHANGES
) && pos
->state
== 0) {
622 /* Just entered a diff-set */
625 } else if (!(mode2
& CHANGES
) && pos
->state
) {
626 /* Come to the end of a diff-set */
627 switch (pos
->state
) {
629 /* Need to record the end */
631 /* time for another pass */
636 /* finished final pass */
641 mask
= ORIG
|RESULT
|BEFORE
|AFTER
|CHANGES
|CHANGED
;
642 switch (pos
->state
) {
644 mask
&= ~(RESULT
|AFTER
);
647 mask
&= ~(ORIG
|BEFORE
);
650 } while (visible(mode
&mask
, m
[pos
->p
.m
].type
, pos
->p
.s
) < 0);
654 /* Move to previous line - simply the reverse of next_mline */
655 static void prev_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
657 struct merge
*m
, int mode
)
668 struct elmnt e
= prev_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
672 visible(mode
, m
[pos
->p
.m
].type
, pos
->p
.s
) >= 0)
675 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
677 if ((mode2
& CHANGES
) && pos
->state
== 0) {
678 /* Just entered a diff-set */
681 } else if (!(mode2
& CHANGES
) && pos
->state
) {
682 /* Come to the end (start) of a diff-set */
683 switch (pos
->state
) {
685 /* finished final pass */
689 /* Need to record the start */
691 /* time for another pass */
697 mask
= ORIG
|RESULT
|BEFORE
|AFTER
|CHANGES
|CHANGED
;
698 switch (pos
->state
) {
700 mask
&= ~(RESULT
|AFTER
);
703 mask
&= ~(ORIG
|BEFORE
);
706 } while (visible(mode
&mask
, m
[pos
->p
.m
].type
, pos
->p
.s
) < 0);
709 /* blank a whole row of display */
710 static void blank(int row
, int start
, int cols
, unsigned int attr
)
718 /* search of a string on one display line. If found, update the
722 static int mcontains(struct mpos pos
,
723 struct file fm
, struct file fb
, struct file fa
,
725 int mode
, char *search
, struct cursor
*curs
,
726 int dir
, int ignore_case
)
728 /* See if any of the files, between start of this line and here,
729 * contain the search string.
730 * However this is modified by dir:
731 * -2: find last match *before* curs
732 * -1: find last match at-or-before curs
733 * 1: find first match at-or-after curs
734 * 2: find first match *after* curs
736 * We only test for equality with curs, so if it is on a different
737 * line it will not be found and everything is before/after.
738 * As we search from end-of-line to start we find the last
740 * For a forward search, we stop when we find curs.
741 * For a backward search, we forget anything found when we find curs.
747 int len
= strlen(search
);
750 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
751 if (e
.start
&& e
.start
[0]) {
754 if (same_mp(pos
.p
, curs
->pos
))
755 curs_i
= curs
->offset
;
758 for (i
= e
.len
-1; i
>= 0; i
--) {
759 if (i
== curs_i
&& dir
== -1)
760 /* next match is the one we want */
762 if (i
== curs_i
&& dir
== 2)
763 /* future matches not accepted */
765 if ((!found
|| dir
> 0) &&
766 (ignore_case
? strncasecmp
: strncmp
)
767 (e
.start
+i
, search
, len
) == 0) {
772 if (i
== curs_i
&& dir
== -2)
773 /* next match is the one we want */
775 if (i
== curs_i
&& dir
== 1)
776 /* future matches not accepted */
780 } while (e
.start
!= NULL
&&
782 || visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
791 /* Drawing the display window.
792 * There are 7 different ways we can display the data, each
793 * of which can be configured by a keystroke:
794 * o original - just show the original file with no changes, but still
795 * with highlights of what is changed or unmatched
796 * r result - show just the result of the merge. Conflicts just show
797 * the original, not the before/after options
798 * b before - show the 'before' stream of the patch
799 * a after - show the 'after' stream of the patch
800 * d diff - show just the patch, both before and after
801 * m merge - show the full merge with -+ sections for changes.
802 * If point is in a wiggled or conflicted section the
803 * window is split horizontally and the diff is shown
804 * in the bottom window
805 * | sidebyside - two panes, left and right. Left holds the merge,
806 * right holds the diff. In the case of a conflict,
807 * left holds orig/after, right holds before/after
809 * The horizontal split for 'merge' mode is managed as follows.
810 * - The window is split when we first visit a line that contains
811 * a wiggle or a conflict, and the second pane is removed when
812 * we next visit a line that contains no changes (is fully Unchanged).
813 * - to display the second pane, we find a visible end-of-line in the
814 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
815 * the we centre that line.
816 * - We need to rewind to an unchanged section, and wind forward again
817 * to make sure that 'lo' and 'hi' are set properly.
818 * - every time we move, we redraw the second pane (see how that goes).
821 /* draw_mside draws one text line or, in the case of sidebyside, one side
823 * The 'mode' tells us what to draw via the 'visible()' function.
824 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
825 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
826 * The desired cursor position is given in 'target' the actual end
827 * cursor position (allowing e.g. for tabs) is returned in *colp.
829 static void draw_mside(int mode
, int row
, int offset
, int start
, int cols
,
830 struct file fm
, struct file fb
, struct file fa
,
838 unsigned int tag_attr
;
841 case 0: /* unchanged line */
845 case 1: /* 'before' text */
848 if ((mode
& ORIG
) && (mode
& CONFLICTED
)) {
852 mode
&= (ORIG
|BEFORE
);
854 case 2: /* the 'after' part */
857 mode
&= (AFTER
|RESULT
);
861 if (visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) < 0) {
862 /* Not visible, just draw a blank */
863 blank(row
, offset
, cols
, a_void
);
873 (void)attrset(tag_attr
);
874 mvaddch(row
, offset
, tag
);
877 (void)attrset(A_NORMAL
);
879 /* find previous visible newline, or start of file */
881 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
882 while (e
.start
!= NULL
&&
884 visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
889 e
= next_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
890 if (e
.start
== NULL
||
892 && visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) != -1)) {
893 /* We have reached the end of visible line, or end of file */
896 if (col
>= start
+ cols
)
899 curs
->width
= -1; /* end of line */
900 if (curs
->target
>= 0) {
903 } else if (same_mp(pos
.p
, curs
->pos
))
908 if (e
.start
&& e
.start
[0] == 0) {
911 if (pos
.p
.s
== 2 && m
[pos
.p
.m
].type
== Extraneous
) {
912 int A
, B
, C
, D
, E
, F
;
913 e1
= fb
.list
[m
[pos
.p
.m
].b
+ pos
.p
.o
];
914 sscanf(e1
.start
+1, "%d %d %d", &A
, &B
, &C
);
915 sscanf(e
.start
+1, "%d %d %d", &D
, &E
, &F
);
916 sprintf(b
, "@@ -%d,%d +%d,%d @@\n", B
, C
, E
, F
);
917 (void)attrset(a_sep
);
919 (void)attrset(visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
));
920 sprintf(b
, "<%.17s>", e
.start
+1);
922 mvaddstr(row
, col
-start
+offset
, b
);
925 blank(row
, col
-start
+offset
, start
+cols
-col
,
927 ? (unsigned)visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
)
931 if (visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1)
935 (void)attrset(visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
));
936 c
= (unsigned char *)e
.start
;
937 for (l
= 0; l
< e
.len
; l
++) {
939 if (*c
>= ' ' && *c
!= 0x7f) {
940 if (col
>= start
&& col
< start
+cols
)
941 mvaddch(row
, col
-start
+offset
, *c
);
943 } else if (*c
== '\t') {
945 if (col
>= start
&& col
< start
+cols
) {
946 mvaddch(row
, col
-start
+offset
, ' ');
948 } while ((col
&7) != 0);
950 if (col
>= start
&& col
< start
+cols
)
951 mvaddch(row
, col
-start
+offset
, '?');
955 if (curs
->target
>= 0) {
956 if (curs
->target
< col
) {
957 /* Found target column */
961 if (scol
>= start
+ cols
)
962 /* Didn't appear on screen */
965 curs
->width
= col
- scol
;
968 } else if (l
== curs
->offset
&&
969 same_mp(pos
.p
, curs
->pos
)) {
973 if (scol
>= start
+ cols
)
974 /* Didn't appear on screen */
977 curs
->width
= col
- scol
;
986 /* Draw either 1 or 2 sides depending on the mode. */
988 static void draw_mline(int mode
, int row
, int start
, int cols
,
989 struct file fm
, struct file fb
, struct file fa
,
995 * Draw the left and right images of this line
996 * One side might be a_blank depending on the
997 * visibility of this newline
1001 mode
|= check_line(pos
, fm
, fb
, fa
, m
, mode
);
1003 if ((mode
& (BEFORE
|AFTER
)) &&
1004 (mode
& (ORIG
|RESULT
))) {
1007 rcols
= cols
- lcols
- 1;
1009 (void)attrset(A_STANDOUT
);
1010 mvaddch(row
, lcols
, '|');
1012 draw_mside(mode
&~(BEFORE
|AFTER
), row
, 0, start
, lcols
,
1013 fm
, fb
, fa
, m
, pos
, curs
&& !curs
->alt
? curs
: NULL
);
1015 draw_mside(mode
&~(ORIG
|RESULT
), row
, lcols
+1, start
, rcols
,
1016 fm
, fb
, fa
, m
, pos
, curs
&& curs
->alt
? curs
: NULL
);
1018 draw_mside(mode
, row
, 0, start
, cols
,
1019 fm
, fb
, fa
, m
, pos
, curs
);
1022 static char *merge_help
[] = {
1023 "This view shows the merge of the patch with the",
1024 "original file. It is like a full-context diff showing",
1025 "removed lines with a '-' prefix and added lines with a",
1027 "In cases where a patch chunk could not be successfully",
1028 "applied, the original text is prefixed with a '|', and",
1029 "the text that the patch wanted to add is prefixed with",
1031 "When the cursor is over such a conflict, or over a chunk",
1032 "which required wiggling to apply (i.e. there was unmatched",
1033 "text in the original, or extraneous unchanged text in",
1034 "the patch), the terminal is split and the bottom pane is",
1035 "use to display the part of the patch that applied to",
1036 "this section of the original. This allows you to confirm",
1037 "that a wiggled patch applied correctly, and to see",
1038 "why there was a conflict",
1041 static char *diff_help
[] = {
1042 "This is the 'diff' or 'patch' view. It shows",
1043 "only the patch that is being applied without the",
1044 "original to which it is being applied.",
1045 "Underlined text indicates parts of the patch which",
1046 "resulted in a conflict when applied to the",
1050 static char *orig_help
[] = {
1051 "This is the 'original' view which simply shows",
1052 "the original file before applying the patch.",
1053 "Sections of code that would be changed by the patch",
1054 "are highlighted in red.",
1057 static char *result_help
[] = {
1058 "This is the 'result' view which shows just the",
1059 "result of applying the patch. When a conflict",
1060 "occurred this view does not show the full conflict",
1061 "but only the 'after' part of the patch. To see",
1062 "the full conflict, use the 'merge' or 'sidebyside'",
1066 static char *before_help
[] = {
1067 "This view shows the 'before' section of a patch.",
1068 "It allows the expected match text to be seen uncluttered",
1069 "by text that is meant to replaced it."
1070 "Red text is text that will be removed by the patch",
1073 static char *after_help
[] = {
1074 "This view shows the 'after' section of a patch.",
1075 "It allows the intended result to be seen uncluttered",
1076 "by text that was meant to be matched and replaced."
1077 "Green text is text that was added by the patch - it",
1078 "was not present in the 'before' part of the patch",
1081 static char *sidebyside_help
[] = {
1082 "This is the Side By Side view of a patched file.",
1083 "The left side shows the original and the result.",
1084 "The right side shows the patch which was applied",
1085 "and lines up with the original/result as much as",
1088 "Where one side has no line which matches the",
1089 "other side it is displayed as a solid colour in the",
1090 "yellow family (depending on your terminal window).",
1093 static char *merge_window_help
[] = {
1094 " Highlight Colours and Keystroke commands",
1096 "In all different views of a merge, highlight colours",
1097 "are used to show which parts of lines were added,",
1098 "removed, already changed, unchanged or in conflict.",
1099 "Colours and their use are:",
1100 " normal unchanged text",
1101 " red text that was removed or changed",
1102 " green text that was added or the result",
1104 " yellow background used in side-by-side for a line",
1105 " which has no match on the other",
1107 " blue text in the original which did not",
1108 " match anything in the patch",
1109 " cyan text in the patch which did not",
1110 " match anything in the original",
1111 " cyan background already changed text: the result",
1112 " of the patch matches the original",
1113 " underline remove or added text can also be",
1114 " underlined indicating that it",
1115 " was involved in a conflict",
1117 "While viewing a merge various keystroke commands can",
1118 "be used to move around and change the view. Basic",
1119 "movement commands from both 'vi' and 'emacs' are",
1122 " p control-p k UP Move to previous line",
1123 " n control-n j DOWN Move to next line",
1124 " l LEFT Move one char to right",
1125 " h RIGHT Move one char to left",
1126 " / control-s Enter incremental search mode",
1127 " control-r Enter reverse-search mode",
1128 " control-g Search again",
1129 " ? Display help message",
1130 " ESC-< 0-G Go to start of file",
1131 " ESC-> G Go to end of file",
1132 " q Return to list of files or exit",
1133 " control-L recenter current line",
1134 " control-V page down",
1136 " N go to next patch chunk",
1137 " P go to previous patch chunk",
1138 " O move cursor to alternate pane",
1139 " ^ control-A go to start of line",
1140 " $ control-E go to end of line",
1142 " a display 'after' view",
1143 " b display 'before' view",
1144 " o display 'original' view",
1145 " r display 'result' view",
1146 " d display 'diff' or 'patch' view",
1147 " m display 'merge' view",
1148 " | display side-by-side view",
1152 static void merge_window(struct plist
*p
, FILE *f
, int reverse
)
1154 /* Display the merge window in one of the selectable modes,
1155 * starting with the 'merge' mode.
1157 * Newlines are the key to display.
1158 * 'pos' is always a visible newline (or eof).
1159 * In sidebyside mode it might only be visible on one side,
1160 * in which case the other side will be blank.
1161 * Where the newline is visible, we rewind the previous visible
1162 * newline visible and display the stuff in between
1164 * A 'position' is a struct mpos
1167 struct stream sm
, sb
, sa
, sp
; /* main, before, after, patch */
1168 struct file fm
, fb
, fa
;
1169 struct csl
*csl1
, *csl2
;
1171 int ch
; /* count of chunks */
1172 /* Always refresh the current line.
1173 * If refresh == 1, refresh all lines. If == 2, clear first
1176 int rows
= 0, cols
= 0;
1177 int splitrow
= -1; /* screen row for split - diff appears below */
1178 int lastrow
= 0; /* end of screen, or just above 'splitrow' */
1180 int mode
= ORIG
|RESULT
;
1181 int mmode
= mode
; /* Mode for moving - used when in 'other' pane */
1182 char *modename
= "merge";
1183 char **modehelp
= merge_help
;
1186 int trow
; /* screen-row while searching. If we cannot find,
1187 * we forget this number */
1189 struct mpos pos
; /* current point */
1190 struct mpos tpos
, /* temp point while drawing lines above and below pos */
1191 toppos
, /* pos at top of screen - for page-up */
1192 botpos
; /* pos at bottom of screen - for page-down */
1193 struct mpos vpos
, tvpos
;
1195 int meta
= 0, /* mode for multi-key commands- SEARCH or META */
1197 int num
= -1, /* numeric arg being typed. */
1199 char search
[80]; /* string we are searching for */
1200 unsigned int searchlen
= 0;
1201 int search_notfound
= 0;
1205 * 1 == no because there are upper-case chars
1206 * 2 == yes as there are no upper-case chars
1209 int ignore_case
= 2;
1210 /* We record all the places we find so 'backspace'
1211 * can easily return to the previous one
1213 struct search_anchor
{
1214 struct search_anchor
*next
;
1219 unsigned int searchlen
;
1223 /* three separate files */
1224 sm
= load_file(p
->file
);
1225 sb
= load_file(p
->before
);
1226 sa
= load_file(p
->after
);
1229 sp
= load_segment(f
, p
->start
, p
->end
);
1232 split_merge(sp
, &sm
, &sa
, &sb
);
1234 split_merge(sp
, &sm
, &sb
, &sa
);
1238 ch
= split_patch(sp
, &sa
, &sb
);
1240 ch
= split_patch(sp
, &sb
, &sa
);
1242 sm
= load_file(p
->file
);
1245 if (!sm
.body
|| !sb
.body
|| !sa
.body
) {
1248 help_window(help_missing
, NULL
);
1250 help_window(help_corrupt
, NULL
);
1253 /* FIXME check for errors in the stream */
1254 fm
= split_stream(sm
, ByWord
);
1255 fb
= split_stream(sb
, ByWord
);
1256 fa
= split_stream(sa
, ByWord
);
1259 csl1
= pdiff(fm
, fb
, ch
);
1261 csl1
= diff(fm
, fb
);
1262 csl2
= diff(fb
, fa
);
1264 ci
= make_merger(fm
, fb
, fa
, csl1
, csl2
, 0, 1, 0);
1269 pos
.p
.m
= 0; /* merge node */
1270 pos
.p
.s
= 0; /* stream number */
1271 pos
.p
.o
= -1; /* offset */
1274 next_mline(&pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1275 memset(&curs
, 0, sizeof(curs
));
1281 snprintf(buf
, 100, "File: %s%s Mode: %s\n",
1282 p
->file
, reverse
? " - reversed" : "", modename
);
1283 (void)attrset(A_BOLD
);
1284 mvaddstr(0, 0, buf
);
1286 (void)attrset(A_NORMAL
);
1289 if (row
< 1 || row
>= lastrow
)
1294 if (mode
== (ORIG
|RESULT
)) {
1295 int cmode
= check_line(pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1296 if (cmode
& (WIGGLED
| CONFLICTED
)) {
1298 splitrow
= (rows
+1)/2;
1299 lastrow
= splitrow
- 1;
1302 } else if (!curs
.alt
&& splitrow
>= 0) {
1307 } else if (splitrow
>= 0) {
1314 getmaxyx(stdscr
, rows
, cols
);
1315 rows
--; /* keep last row clear */
1316 if (splitrow
>= 0) {
1317 splitrow
= (rows
+1)/2;
1318 lastrow
= splitrow
- 1;
1326 if (row
> lastrow
+3)
1331 if (getenv("WIGGLE_VTRACE")) {
1337 e
= fm
.list
[ci
.merger
[vpos
.p
.m
].a
+ vpos
.p
.o
].start
;
1340 e
= fb
.list
[ci
.merger
[vpos
.p
.m
].b
+ vpos
.p
.o
].start
;
1343 e
= fa
.list
[ci
.merger
[vpos
.p
.m
].c
+ vpos
.p
.o
].start
;
1346 for (i
= 0; i
< 6; i
++) {
1348 if (e2
[i
] < 32 || e2
[i
] >= 127)
1351 sprintf(b
, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos
.state
,
1353 vpos
.p
.m
, typenames
[ci
.merger
[vpos
.p
.m
].type
],
1354 ci
.merger
[vpos
.p
.m
].al
,
1355 ci
.merger
[vpos
.p
.m
].bl
,
1356 ci
.merger
[vpos
.p
.m
].cl
,
1357 ci
.merger
[vpos
.p
.m
].in_conflict
,
1360 (void)attrset(A_NORMAL
);
1365 /* Always refresh the line */
1366 while (start
> curs
.target
) {
1373 draw_mline(mode
, row
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1374 pos
, (splitrow
>= 0 && curs
.alt
) ? NULL
: &curs
);
1375 if (curs
.width
== 0 && start
< curs
.col
) {
1376 /* width == 0 implies it appear after end-of-screen */
1381 if (curs
.col
< start
) {
1392 for (i
= row
-1; i
>= 1 && tpos
.p
.m
>= 0; ) {
1393 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1394 draw_mline(mode
, i
--, start
, cols
,
1395 fm
, fb
, fa
, ci
.merger
,
1406 blank(i
--, 0, cols
, a_void
);
1408 for (i
= row
; i
<= lastrow
&& ci
.merger
[tpos
.p
.m
].type
!= End
; ) {
1409 draw_mline(mode
, i
++, start
, cols
,
1410 fm
, fb
, fa
, ci
.merger
,
1412 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1414 botpos
= tpos
; botrow
= i
;
1415 while (i
<= lastrow
)
1416 blank(i
++, 0, cols
, a_void
);
1419 if (splitrow
>= 0) {
1420 struct mpos spos
= pos
;
1421 int smode
= BEFORE
|AFTER
;
1422 int srow
= (rows
+ splitrow
)/2;
1423 if (visible(smode
, ci
.merger
[spos
.p
.m
].type
,
1425 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1426 /* Now hi/lo might be wrong, so lets fix it. */
1428 while (spos
.p
.m
>= 0 && spos
.state
!= 0)
1429 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1430 while (!same_mpos(spos
, tpos
))
1431 next_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1433 (void)attrset(a_sep
);
1434 for (i
= 0; i
< cols
; i
++)
1435 mvaddstr(splitrow
, i
, "-");
1438 for (i
= srow
-1; i
> splitrow
; i
--) {
1439 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1440 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1443 while (i
> splitrow
)
1444 blank(i
--, 0, cols
, a_void
);
1447 i
< rows
&& ci
.merger
[tpos
.p
.m
].type
!= End
;
1449 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1451 (i
== srow
&& curs
.alt
) ? &curs
: NULL
);
1452 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1455 blank(i
++, 0, cols
, a_void
);
1457 /* Now that curs is accurate, report the type */
1460 (void)attrset(A_BOLD
);
1461 snprintf(lbuf
, 29, "%s ln:%d",
1462 typenames
[ci
.merger
[curs
.pos
.m
].type
],
1463 (pos
.p
.lineno
-1)/2);
1464 mvaddstr(0, cols
- strlen(lbuf
) - 4, " ");
1465 mvaddstr(0, cols
- strlen(lbuf
) - 1, lbuf
);
1467 #define META(c) ((c)|0x1000)
1468 #define SEARCH(c) ((c)|0x2000)
1470 (void)attrset(A_NORMAL
);
1473 snprintf(buf
, 10, "%d ", num
);
1478 if (meta
& SEARCH(0)) {
1480 addstr("Backwards ");
1483 if (search_notfound
)
1484 addstr(" - Not Found.");
1485 search_notfound
= 0;
1488 /* '+1' to skip over the leading +/-/| char */
1489 if (curs
.alt
&& splitrow
> 0)
1490 move((rows
+ splitrow
)/2, curs
.col
- start
+ 1);
1491 else if (curs
.alt
&& ((mode
& (BEFORE
|AFTER
)) &&
1492 (mode
& (ORIG
|RESULT
))))
1493 move(row
, curs
.col
-start
+ (cols
-1)/2+2);
1495 move(row
, curs
.col
-start
+1);
1497 tmeta
= meta
; meta
= 0;
1498 tnum
= num
; num
= -1;
1499 tvpos
= vpos
; vpos
= pos
;
1500 cswitch
= c
| tmeta
;
1501 /* Handle some ranges */
1502 /* case '0' ... '9': */
1503 if (cswitch
>= '0' && cswitch
<= '9')
1505 /* case SEARCH(' ') ... SEARCH('~'): */
1506 if (cswitch
>= SEARCH(' ') && cswitch
<= SEARCH('~'))
1507 cswitch
= SEARCH(' ');
1510 case 27: /* escape */
1514 case META('<'): /* start of file */
1519 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1520 } while (tpos
.p
.m
>= 0);
1524 case META('>'): /* end of file */
1531 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1532 } while (ci
.merger
[tpos
.p
.m
].type
!= End
);
1536 case '0': /* actually '0'...'9' */
1539 num
= tnum
*10 + (c
-'0');
1546 /* incr search forward */
1549 search
[searchlen
] = 0;
1554 /* incr search backwards */
1557 search
[searchlen
] = 0;
1560 case SEARCH('G'-64):
1561 case SEARCH('S'-64):
1562 case SEARCH('R'-64):
1564 if ((c
|tmeta
) == SEARCH('R'-64))
1566 else if ((c
|tmeta
) == SEARCH('S'-64))
1571 tpos
= pos
; trow
= row
;
1574 case SEARCH('H'-64):
1575 case SEARCH(KEY_BACKSPACE
):
1578 struct search_anchor
*a
;
1584 struct search_anchor
*a
;
1592 search_notfound
= a
->notfound
;
1593 searchlen
= a
->searchlen
;
1594 search
[searchlen
] = 0;
1599 case SEARCH(' '): /* actually ' '...'~' */
1602 if (searchlen
< sizeof(search
)-1)
1603 search
[searchlen
++] = c
& (0x7f);
1604 search
[searchlen
] = 0;
1605 tpos
= pos
; trow
= row
;
1607 search_notfound
= 1;
1608 if (ignore_case
== 1 || ignore_case
== 2) {
1611 for (i
=0; i
< searchlen
; i
++)
1612 if (isupper(search
[i
])) {
1618 if (mcontains(tpos
, fm
, fb
, fa
, ci
.merger
,
1619 mmode
, search
, &curs
, searchdir
,
1620 ignore_case
>= 2)) {
1624 search_notfound
= 0;
1627 if (searchdir
< 0) {
1629 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1632 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1634 } while (tpos
.p
.m
>= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1635 searchdir
/= abs(searchdir
);
1643 case 'V'-64: /* page down */
1645 if (botrow
<= lastrow
)
1651 case META('v'): /* page up */
1663 for (; tnum
> 0 ; tnum
--) {
1665 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1666 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
1677 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1678 } while (pos
.state
!= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1682 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1683 } while (pos
.state
== 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1691 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1692 } while (tpos
.state
== 0 && tpos
.p
.m
>= 0);
1696 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1697 } while (tpos
.state
!= 0 && tpos
.p
.m
>= 0);
1706 for (; tnum
> 0 ; tnum
--) {
1708 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1709 if (tpos
.p
.m
>= 0) {
1719 curs
.target
= curs
.col
- 1;
1720 if (curs
.target
< 0) {
1721 /* Try to go to end of previous line */
1723 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1724 if (tpos
.p
.m
>= 0) {
1736 if (curs
.width
>= 0)
1737 curs
.target
= curs
.col
+ curs
.width
;
1739 /* end of line, go to next */
1741 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1742 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
1763 curs
.alt
= !curs
.alt
;
1764 if (curs
.alt
&& mode
== (ORIG
|RESULT
))
1765 mmode
= (BEFORE
|AFTER
);
1770 case 'a': /* 'after' view in patch window */
1771 mode
= AFTER
; modename
= "after"; modehelp
= after_help
;
1772 mmode
= mode
; curs
.alt
= 0;
1775 case 'b': /* 'before' view in patch window */
1776 mode
= BEFORE
; modename
= "before"; modehelp
= before_help
;
1777 mmode
= mode
; curs
.alt
= 0;
1780 case 'o': /* 'original' view in the merge window */
1781 mode
= ORIG
; modename
= "original"; modehelp
= orig_help
;
1782 mmode
= mode
; curs
.alt
= 0;
1785 case 'r': /* the 'result' view in the merge window */
1786 mode
= RESULT
; modename
= "result"; modehelp
= result_help
;
1787 mmode
= mode
; curs
.alt
= 0;
1791 mode
= BEFORE
|AFTER
; modename
= "diff"; modehelp
= diff_help
;
1792 mmode
= mode
; curs
.alt
= 0;
1796 mode
= ORIG
|RESULT
; modename
= "merge"; modehelp
= merge_help
;
1797 mmode
= mode
; curs
.alt
= 0;
1802 mode
= ORIG
|RESULT
|BEFORE
|AFTER
; modename
= "sidebyside"; modehelp
= sidebyside_help
;
1803 mmode
= mode
; curs
.alt
= 0;
1807 case 'H': /* scroll window to the right */
1810 curs
.target
= start
+ 1;
1813 case 'L': /* scroll window to the left */
1816 curs
.target
= start
+ 1;
1821 prev_melmnt(&tvpos
.p
, fm
, fb
, fa
, ci
.merger
);
1826 next_melmnt(&tvpos
.p
, fm
, fb
, fa
, ci
.merger
);
1827 if (ci
.merger
[tvpos
.p
.m
].type
!= End
)
1832 help_window(modehelp
, merge_window_help
);
1841 if (meta
== SEARCH(0)) {
1842 if (anchor
== NULL
||
1843 !same_mpos(anchor
->pos
, pos
) ||
1844 anchor
->searchlen
!= searchlen
||
1845 !same_mp(anchor
->curs
.pos
, curs
.pos
)) {
1846 struct search_anchor
*a
= xmalloc(sizeof(*a
));
1851 a
->searchlen
= searchlen
;
1852 a
->notfound
= search_notfound
;
1858 struct search_anchor
*a
= anchor
;
1864 /* move backward and forward to make sure we
1865 * are on a visible line
1868 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1872 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1873 if (ci
.merger
[tpos
.p
.m
].type
!= End
)
1879 static void show_merge(char *origname
, FILE *patch
, int reverse
,
1880 int is_merge
, char *before
, char *after
)
1887 fseek(patch
, 0, SEEK_END
);
1888 p
.end
= ftell(patch
);
1889 fseek(patch
, 0, SEEK_SET
);
1892 p
.is_merge
= is_merge
;
1896 freopen("/dev/null","w",stderr
);
1897 merge_window(&p
, patch
, reverse
);
1900 static void calc_one(struct plist
*pl
, FILE *f
, int reverse
)
1902 struct stream s1
, s2
;
1903 struct stream s
= load_segment(f
, pl
->start
, pl
->end
);
1907 split_merge(s
, &sf
, &s2
, &s1
);
1909 split_merge(s
, &sf
, &s1
, &s2
);
1912 sf
= load_file(pl
->file
);
1914 pl
->chunks
= split_patch(s
, &s2
, &s1
);
1916 pl
->chunks
= split_patch(s
, &s1
, &s2
);
1918 if (sf
.body
== NULL
|| s1
.body
== NULL
|| s1
.body
== NULL
) {
1919 pl
->wiggles
= pl
->conflicts
= -1;
1921 struct file ff
, fp1
, fp2
;
1922 struct csl
*csl1
, *csl2
;
1924 ff
= split_stream(sf
, ByWord
);
1925 fp1
= split_stream(s1
, ByWord
);
1926 fp2
= split_stream(s2
, ByWord
);
1928 csl1
= pdiff(ff
, fp1
, pl
->chunks
);
1930 csl1
= diff(ff
, fp1
);
1931 csl2
= diff(fp1
, fp2
);
1932 ci
= make_merger(ff
, fp1
, fp2
, csl1
, csl2
, 0, 1, 0);
1933 pl
->wiggles
= ci
.wiggles
;
1934 pl
->conflicts
= ci
.conflicts
;
1949 static int get_prev(int pos
, struct plist
*pl
, int n
, int mode
)
1955 if (pl
[pos
].prev
== -1)
1956 return pl
[pos
].parent
;
1958 while (pl
[pos
].open
&&
1961 if (pl
[pos
].last
>= 0)
1962 /* always see directories */
1966 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
1968 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
1970 } while (pos
>= 0 && !found
);
1974 static int get_next(int pos
, struct plist
*pl
, int n
, int mode
,
1975 FILE *f
, int reverse
)
1987 while (pos
>= 0 && pl
[pos
].next
== -1)
1988 pos
= pl
[pos
].parent
;
1994 if (pl
[pos
].calced
== 0 && pl
[pos
].end
)
1995 calc_one(pl
+pos
, f
, reverse
);
1996 if (pl
[pos
].last
>= 0)
1997 /* always see directories */
2001 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2003 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2005 } while (pos
>= 0 && !found
);
2009 static void draw_one(int row
, struct plist
*pl
, FILE *f
, int reverse
)
2019 if (pl
->calced
== 0 && pl
->end
)
2020 /* better load the patch and count the chunks */
2021 calc_one(pl
, f
, reverse
);
2025 if (pl
->chunks
> 99)
2028 sprintf(hdr
, "%2d", pl
->chunks
);
2029 if (pl
->wiggles
> 99)
2030 strcpy(hdr
+2, " XX");
2032 sprintf(hdr
+2, " %2d", pl
->wiggles
);
2033 if (pl
->conflicts
> 99)
2034 strcpy(hdr
+5, " XX ");
2036 sprintf(hdr
+5, " %2d ", pl
->conflicts
);
2039 strcpy(hdr
+9, "= ");
2041 strcpy(hdr
+9, "+ ");
2043 strcpy(hdr
+9, "- ");
2047 else if (pl
->conflicts
)
2048 attrset(a_has_conflicts
);
2049 else if (pl
->wiggles
)
2050 attrset(a_has_wiggles
);
2052 attrset(a_no_wiggles
);
2054 mvaddstr(row
, 0, hdr
);
2055 mvaddstr(row
, 11, pl
->file
);
2059 static char *main_help
[] = {
2060 " You are using the \"browse\" mode of wiggle.",
2061 "This page shows a list of files in a patch together with",
2062 "the directories that contain them.",
2063 "A directory is indicated by a '+' if the contents are",
2064 "listed or a '-' if the contents are hidden. A file is",
2065 "indicated by an '='. Typing <space> or <return> will",
2066 "expose or hide a directory, and will visit a file.",
2068 "The three columns of numbers are:",
2069 " Ch The number of patch chunks which applied to",
2071 " Wi The number of chunks that needed to be wiggled",
2073 " Co The number of chunks that created an unresolvable",
2076 "Keystrokes recognised in this page are:",
2077 " ? Display this help",
2078 " SPC On a directory, toggle hiding of contents",
2079 " On file, visit the file",
2082 " n,j,DOWN Go to next line",
2083 " p,k,UP Go to previous line",
2085 " A list All files",
2086 " W only list files with a wiggle or a conflict",
2087 " C only list files with a conflict",
2091 static void main_window(struct plist
*pl
, int n
, FILE *f
, int reverse
)
2093 /* The main window lists all files together with summary information:
2094 * number of chunks, number of wiggles, number of conflicts.
2095 * The list is scrollable
2096 * When a entry is 'selected', we switch to the 'file' window
2097 * The list can be condensed by removing files with no conflict
2098 * or no wiggles, or removing subdirectories
2100 * We record which file in the list is 'current', and which
2101 * screen line it is on. We try to keep things stable while
2104 * Counts are printed before the name using at most 2 digits.
2105 * Numbers greater than 99 are XX
2107 * 27 5 1 drivers/md/md.c
2109 * A directory show the sum in all children.
2112 * select: enter, space, mouseclick
2113 * on file, go to file window
2114 * on directory, toggle open
2115 * up: k, p, control-p uparrow
2116 * Move to previous open object
2117 * down: j, n, control-n, downarrow
2118 * Move to next open object
2120 * A W C: select All Wiggles or Conflicts
2124 int pos
= 0; /* position in file */
2125 int row
= 1; /* position on screen */
2126 int rows
= 0; /* size of screen in rows */
2131 int mode
= 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2133 freopen("/dev/null","w",stderr
);
2135 pl
= sort_patches(pl
, &n
);
2139 clear(); (void)attrset(0);
2141 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2146 if (row
< 1 || row
>= rows
)
2150 getmaxyx(stdscr
, rows
, cols
);
2151 cols
= cols
; /* Silence warning that 'cols' isn't used */
2152 if (row
>= rows
+ 3)
2157 for (i
= row
; i
> 1; i
--) {
2158 tpos
= get_prev(tpos
, pl
, n
, mode
);
2164 /* Ok, row and pos could be trustworthy now */
2166 for (i
= row
; i
>= 1; i
--) {
2167 draw_one(i
, &pl
[tpos
], f
, reverse
);
2168 tpos
= get_prev(tpos
, pl
, n
, mode
);
2171 for (i
= row
+1; i
< rows
; i
++) {
2172 tpos
= get_next(tpos
, pl
, n
, mode
, f
, reverse
);
2174 draw_one(i
, &pl
[tpos
], f
, reverse
);
2176 draw_one(i
, NULL
, f
, reverse
);
2180 sprintf(bb
, "%d", c
);
2181 mvaddstr(0, 70, bb
);
2192 tpos
= get_next(pos
, pl
, n
, mode
, f
, reverse
);
2203 tpos
= get_prev(pos
, pl
, n
, mode
);
2212 if (pl
[pos
].end
== 0) {
2213 pl
[pos
].open
= !pl
[pos
].open
;
2216 /* diff_window(&pl[pos], f); */
2217 merge_window(&pl
[pos
], f
, reverse
);
2221 case 27: /* escape */
2222 mvaddstr(0, 70, "ESC..."); clrtoeol();
2231 mode
= 0; refresh
= 1;
2234 mode
= 1; refresh
= 1;
2237 mode
= 2; refresh
= 1;
2241 help_window(main_help
, NULL
);
2252 static void catch(int sig
)
2254 if (sig
== SIGINT
) {
2261 printf("Died on signal %d\n", sig
);
2265 static void term_init(void)
2268 static int init_done
= 0;
2274 signal(SIGINT
, catch);
2275 signal(SIGQUIT
, catch);
2276 signal(SIGTERM
, catch);
2277 signal(SIGBUS
, catch);
2278 signal(SIGSEGV
, catch);
2280 initscr(); cbreak(); noecho();
2282 use_default_colors();
2283 if (!has_colors()) {
2284 a_delete
= A_UNDERLINE
;
2286 a_common
= A_NORMAL
;
2288 a_already
= A_STANDOUT
;
2289 a_has_conflicts
= A_UNDERLINE
;
2290 a_has_wiggles
= A_BOLD
;
2291 a_no_wiggles
= A_NORMAL
;
2293 init_pair(1, COLOR_RED
, -1);
2294 a_delete
= COLOR_PAIR(1);
2295 init_pair(2, COLOR_GREEN
, -1);
2296 a_added
= COLOR_PAIR(2);
2297 a_common
= A_NORMAL
;
2298 init_pair(3, COLOR_WHITE
, COLOR_GREEN
);
2299 a_sep
= COLOR_PAIR(3); a_sep
= A_STANDOUT
;
2300 init_pair(4, -1, COLOR_YELLOW
);
2301 a_void
= COLOR_PAIR(4);
2302 init_pair(5, COLOR_BLUE
, -1);
2303 a_unmatched
= COLOR_PAIR(5);
2304 init_pair(6, COLOR_CYAN
, -1);
2305 a_extra
= COLOR_PAIR(6);
2307 init_pair(7, COLOR_BLACK
, COLOR_CYAN
);
2308 a_already
= COLOR_PAIR(7);
2310 a_has_conflicts
= a_delete
;
2311 a_has_wiggles
= a_added
;
2312 a_no_wiggles
= a_unmatched
;
2314 nonl(); intrflush(stdscr
, FALSE
); keypad(stdscr
, TRUE
);
2315 mousemask(ALL_MOUSE_EVENTS
, NULL
);
2318 int vpatch(int argc
, char *argv
[], int patch
, int strip
,
2319 int reverse
, int replace
)
2321 /* NOTE argv[0] is first arg...
2322 * Behaviour depends on number of args:
2323 * 0: A multi-file patch is read from stdin
2324 * 1: if 'patch', parse it as a multi-file patch and allow
2325 * the files to be browsed.
2326 * if filename ends '.rej', then treat it as a patch again
2327 * a file with the same basename
2328 * Else treat the file as a merge (with conflicts) and view it.
2329 * 2: First file is original, second is patch
2330 * 3: Files are: original previous new. The diff between 'previous' and
2331 * 'new' needs to be applied to 'original'.
2333 * If a multi-file patch is being read, 'strip' tells how many
2334 * path components to strip. If it is -1, we guess based on
2336 * If 'reverse' is given, when we invert any patch or diff
2337 * If 'replace' then we save the resulting merge.
2346 fprintf(stderr
, "%s: too many file names given.\n", Cmd
);
2349 case 0: /* stdin is a patch */
2350 if (lseek(fileno(stdin
), 0L, 1) == -1) {
2351 /* cannot seek, so need to copy to a temp file */
2354 fprintf(stderr
, "%s: Cannot create temp file\n", Cmd
);
2357 pl
= parse_patch(stdin
, f
, &num_patches
);
2360 pl
= parse_patch(stdin
, NULL
, &num_patches
);
2361 in
= fdopen(dup(0), "r");
2363 /* use stderr for keyboard input */
2365 if (set_prefix(pl
, num_patches
, strip
) == 0) {
2366 fprintf(stderr
, "%s: aborting\n", Cmd
);
2369 main_window(pl
, num_patches
, in
, reverse
);
2372 case 1: /* a patch, a .rej, or a merge file */
2373 f
= fopen(argv
[0], "r");
2375 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
2379 pl
= parse_patch(f
, NULL
, &num_patches
);
2380 if (set_prefix(pl
, num_patches
, strip
) == 0) {
2381 fprintf(stderr
, "%s: aborting\n", Cmd
);
2384 main_window(pl
, num_patches
, f
, reverse
);
2385 } else if (strlen(argv
[0]) > 4 &&
2386 strcmp(argv
[0]+strlen(argv
[0])-4, ".rej") == 0) {
2387 char *origname
= strdup(argv
[0]);
2388 origname
[strlen(origname
) - 4] = '\0';
2389 show_merge(origname
, f
, reverse
, 0, NULL
, NULL
);
2391 show_merge(argv
[0], f
, reverse
, 1, NULL
, NULL
);
2394 case 2: /* an orig and a diff/.ref */
2395 f
= fopen(argv
[1], "r");
2397 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
2400 show_merge(argv
[0], f
, reverse
, 0, NULL
, NULL
);
2402 case 3: /* orig, before, after */
2403 show_merge(argv
[0], NULL
, reverse
, 1, argv
[1], argv
[2]);