Browser: improve rendering of conflicts in the the 'merge' view.
[wiggle/upstream.git] / vpatch.c
blobf32389132c964261a19e1983bf55da91ae618da8
1 /*
2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2011 Neil Brown <neilb@suse.de>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * Author: Neil Brown
23 * Email: <neilb@suse.de>
27 * vpatch - visual front end for wiggle - aka Browse mode.
29 * "files" display, lists all files with statistics
30 * - can hide various lines including subdirectories
31 * and files without wiggles or conflicts
32 * "merge" display shows various views of merged file with different
33 * parts in different colours.
35 * The window can be split horizontally to show the original and result
36 * beside the diff, and each different branch can be shown alone.
40 #include "wiggle.h"
41 #include <curses.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <signal.h>
45 #include <fcntl.h>
46 #include <ctype.h>
48 static void term_init(void);
50 #define assert(x) do { if (!(x)) abort(); } while (0)
52 static struct stream load_segment(FILE *f,
53 unsigned int start, unsigned int end);
55 /* global attributes */
56 unsigned int a_delete, a_added, a_common, a_sep, a_void,
57 a_unmatched, a_extra, a_already;
59 /******************************************************************
60 * Help window
61 * We display help in an insert, leaving 5 columns left and right,
62 * and 2 rows top and bottom, but at most 58x15 plus border
63 * In help mode:
64 * SPC or RTN moves down or to next page
65 * BKSPC goes backwards
66 * 'q' returns to origin screen
67 * '?' show help on help
68 * left and right scroll help view
70 * A help text is an array of lines of text
73 char *help_help[] = {
74 " You are viewing the help page for the help viewer.",
75 "You normally get here by typing '?'",
76 "",
77 "The following keystrokes work in the help viewer:",
78 " ? display this help message",
79 " q return to previous view",
80 " SPC move forward through help document",
81 " RTN same as SPC",
82 " BKSP move backward through help document",
83 " RIGHT scroll help window so text on the right appears",
84 " LEFT scroll help window so text on the left appears",
85 NULL
88 char *help_missing[] = {
89 "The file that this patch applies to appears",
90 "to be missing.",
91 "Please type 'q' to continue",
92 NULL
95 char *help_corrupt[] = {
96 "This patch appears to be corrupt",
97 "Please type 'q' to continue",
98 NULL
101 /* We can give one or two pages to display in the help window.
102 * The first is specific to the current context. The second
103 * is optional and may provide help in a more broad context.
105 static void help_window(char *page1[], char *page2[])
107 int rows, cols;
108 int top, left;
109 int r, c;
110 int ch;
111 char **page = page1;
112 int line = 0;
113 int shift = 0;
115 getmaxyx(stdscr, rows, cols);
117 if (cols < 70) {
118 left = 6;
119 cols = cols-12;
120 } else {
121 left = (cols-58)/2 - 1;
122 cols = 58;
125 if (rows < 21) {
126 top = 3;
127 rows = rows - 6;
128 } else {
129 top = (rows-15)/2 - 1;
130 rows = 15;
133 /* Draw a border around the 'help' area */
134 (void)attrset(A_STANDOUT);
135 for (c = left; c < left+cols; c++) {
136 mvaddch(top-1, c, '-');
137 mvaddch(top+rows, c, '-');
139 for (r = top; r < top + rows ; r++) {
140 mvaddch(r, left-1, '|');
141 mvaddch(r, left+cols, '|');
143 mvaddch(top-1, left-1, '/');
144 mvaddch(top-1, left+cols, '\\');
145 mvaddch(top+rows, left-1, '\\');
146 mvaddch(top+rows, left+cols, '/');
147 mvaddstr(top-1, left + cols/2 - 9, "HELP - 'q' to exit");
148 mvaddstr(top+rows, left+cols/2 - 17, "Press SPACE for more, '?' for help");
149 (void)attrset(A_NORMAL);
151 while (1) {
152 char **lnp = page + line;
154 /* Draw as much of the page at the current offset
155 * as fits.
157 for (r = 0; r < rows; r++) {
158 char *ln = *lnp;
159 int sh = shift;
160 if (ln)
161 lnp++;
162 else
163 ln = "";
165 while (*ln && sh > 0) {
166 ln++;
167 sh--;
169 for (c = 0; c < cols; c++) {
170 int chr = *ln;
171 if (chr)
172 ln++;
173 else
174 chr = ' ';
175 mvaddch(top+r, left+c, chr);
178 move(top+rows-1, left);
179 ch = getch();
181 switch (ch) {
182 case 'q':
183 return;
184 case '?':
185 if (page1 != help_help)
186 help_window(help_help, NULL);
187 break;
188 case ' ':
189 case '\r': /* page-down */
190 for (r = 0; r < rows-2; r++)
191 if (page[line])
192 line++;
193 if (!page[line]) {
194 line = 0;
195 if (page == page1)
196 page = page2;
197 else
198 page = NULL;
199 if (page == NULL)
200 return;
202 break;
204 case '\b': /* page up */
205 if (line > 0) {
206 line -= (rows-2);
207 if (line < 0)
208 line = 0;
209 } else {
210 if (page == page2)
211 page = page1;
212 else
213 page = page2;
214 if (page == NULL)
215 page = page1;
216 line = 0;
218 break;
220 case KEY_LEFT:
221 if (shift > 0)
222 shift--;
223 break;
224 case KEY_RIGHT:
225 shift++;
226 break;
228 case KEY_UP:
229 if (line > 0)
230 line--;
231 break;
232 case KEY_DOWN:
233 if (page[line])
234 line++;
235 break;
240 static char *typenames[] = {
241 [End] = "End",
242 [Unmatched] = "Unmatched",
243 [Unchanged] = "Unchanged",
244 [Extraneous] = "Extraneous",
245 [Changed] = "Changed",
246 [Conflict] = "Conflict",
247 [AlreadyApplied] = "AlreadyApplied",
250 /* When we merge the original and the diff together we need
251 * to keep track of where everything came from.
252 * When we display the different views, we need to be able to
253 * select certain portions of the whole document.
254 * These flags are used to identify what is present, and to
255 * request different parts be extracted. They also help
256 * guide choice of colour.
258 #define BEFORE 1
259 #define AFTER 2
260 #define ORIG 4
261 #define RESULT 8
262 #define CHANGED 16 /* The RESULT is different to ORIG */
263 #define CHANGES 32 /* AFTER is different to BEFORE */
264 #define WIGGLED 64 /* a conflict that was successfully resolved */
265 #define CONFLICTED 128 /* a conflict that was not successfully resolved */
267 /* Displaying a Merge.
268 * The first step is to linearise the merge. The merge in inherently
269 * parallel with before/after streams. However much of the whole document
270 * is linear as normally much of the original in unchanged.
271 * All parallelism comes from the patch. This normally produces two
272 * parallel stream, but in the case of a conflict can produce three.
273 * For browsing the merge we only ever show two alternates in-line.
274 * When there are three we use two panes with 1 or 2 alternates in each.
275 * So to linearise the two streams we find lines that are completely
276 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
277 * a region where there are changes. We include everything between
278 * these twice, in two separate passes. The exact interpretation of the
279 * passes is handled at a higher level but will be one of:
280 * original and result
281 * before and after
282 * original and after (for a conflict)
283 * This is all encoded in the 'struct merge'. An array of these describes
284 * the whole document.
286 * At any position in the merge we can be in one of 3 states:
287 * 0: unchanged section
288 * 1: first pass
289 * 2: second pass
291 * So to walk a merge in display order we need a position in the merge,
292 * a current state, and when in a changed section, we need to know the
293 * bounds of that changed section.
294 * This is all encoded in 'struct mpos'.
296 * Each location may or may not be visible depending on certain
297 * display options.
299 * Also, some locations might be 'invalid' in that they don't need to be displayed.
300 * For example when the patch leaves a section of the original unchanged,
301 * we only need to see the original - the before/after sections are treated
302 * as invalid and are not displayed.
303 * The visibility of newlines is crucial and guides the display. One line
304 * of displayed text is all the visible sections between two visible newlines.
306 * Counting lines is a bit tricky. We only worry about line numbers in the
307 * original (stream 0) as these could compare with line numbers mentioned in
308 * patch chunks.
309 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
310 * That way we don't get a full counted line until we see the first char after the
311 * newline, so '+' lines are counted with the previous line.
314 struct mp {
315 int m; /* merger index */
316 int s; /* stream 0,1,2 for a,b,c */
317 int o; /* offset in that stream */
318 int lineno; /* Counts newlines in stream 0
319 * set lsb when see newline.
320 * add one when not newline and lsb set
323 struct mpos {
324 struct mp p, /* the current point (end of a line) */
325 lo, /* eol for start of the current group */
326 hi; /* eol for end of the current group */
327 int state; /*
328 * 0 if on an unchanged (lo/hi not meaningful)
329 * 1 if on the '-' of a diff,
330 * 2 if on the '+' of a diff
334 struct cursor {
335 struct mp pos; /* where in the document we are (an element) */
336 int offset; /* which char in that element */
337 int target; /* display column - or -1 if we are looking for 'pos' */
338 int col; /* where we found pos or target */
339 int width; /* Size of char, for moving to the right */
340 int alt; /* Cursor is in alternate window */
343 /* used for checking location during search */
344 static int same_mp(struct mp a, struct mp b)
346 return a.m == b.m &&
347 a.s == b.s &&
348 a.o == b.o;
350 static int same_mpos(struct mpos a, struct mpos b)
352 return same_mp(a.p, b.p) &&
353 (a.state == b.state || a.state == 0 || b.state == 0);
356 /* Check if a particular stream is meaningful in a particular merge
357 * section. e.g. in an Unchanged section, only stream 0, the
358 * original, is meaningful. This is used to avoid walking down
359 * pointless paths.
361 static int stream_valid(int s, enum mergetype type)
363 switch (type) {
364 case End:
365 return 1;
366 case Unmatched:
367 return s == 0;
368 case Unchanged:
369 return s == 0;
370 case Extraneous:
371 return s == 2;
372 case Changed:
373 return s != 1;
374 case Conflict:
375 return 1;
376 case AlreadyApplied:
377 return 1;
379 return 0;
383 * Advance the 'pos' in the current mergepos returning the next
384 * element (word).
385 * This walks the merges in sequence, and the streams within
386 * each merge.
388 static struct elmnt next_melmnt(struct mp *pos,
389 struct file fm, struct file fb, struct file fa,
390 struct merge *m)
392 pos->o++;
393 while (1) {
394 int l = 0; /* Length remaining in current merge section */
395 if (pos->m >= 0)
396 switch (pos->s) {
397 case 0:
398 l = m[pos->m].al;
399 break;
400 case 1:
401 l = m[pos->m].bl;
402 break;
403 case 2:
404 l = m[pos->m].cl;
405 break;
407 if (pos->o >= l) {
408 /* Offset has reached length, choose new stream or
409 * new merge */
410 pos->o = 0;
411 do {
412 pos->s++;
413 if (pos->s > 2) {
414 pos->s = 0;
415 pos->m++;
417 } while (!stream_valid(pos->s, m[pos->m].type));
418 } else
419 break;
421 if (pos->m == -1 || m[pos->m].type == End) {
422 struct elmnt e;
423 e.start = NULL; e.len = 0;
424 return e;
426 switch (pos->s) {
427 default: /* keep compiler happy */
428 case 0:
429 if (pos->lineno & 1)
430 pos->lineno++;
431 if (ends_mline(fm.list[m[pos->m].a + pos->o]))
432 pos->lineno++;
433 return fm.list[m[pos->m].a + pos->o];
434 case 1: return fb.list[m[pos->m].b + pos->o];
435 case 2: return fa.list[m[pos->m].c + pos->o];
439 /* step current position.p backwards */
440 static struct elmnt prev_melmnt(struct mp *pos,
441 struct file fm, struct file fb, struct file fa,
442 struct merge *m)
444 if (pos->s == 0) {
445 if (ends_mline(fm.list[m[pos->m].a + pos->o]))
446 pos->lineno--;
447 if (pos->lineno & 1)
448 pos->lineno--;
451 pos->o--;
452 while (pos->m >= 0 && pos->o < 0) {
453 do {
454 pos->s--;
455 if (pos->s < 0) {
456 pos->s = 2;
457 pos->m--;
459 } while (pos->m >= 0 &&
460 !stream_valid(pos->s, m[pos->m].type));
461 if (pos->m >= 0) {
462 switch (pos->s) {
463 case 0:
464 pos->o = m[pos->m].al-1;
465 break;
466 case 1:
467 pos->o = m[pos->m].bl-1;
468 break;
469 case 2:
470 pos->o = m[pos->m].cl-1;
471 break;
475 if (pos->m < 0) {
476 struct elmnt e;
477 e.start = NULL; e.len = 0;
478 return e;
480 switch (pos->s) {
481 default: /* keep compiler happy */
482 case 0: return fm.list[m[pos->m].a + pos->o];
483 case 1: return fb.list[m[pos->m].b + pos->o];
484 case 2: return fa.list[m[pos->m].c + pos->o];
488 /* 'visible' not only checks if this stream in this merge should be
489 * visible in this mode, but also chooses which colour/highlight to use
490 * to display it.
492 static int visible(int mode, enum mergetype type, int stream)
494 if (mode == 0)
495 return -1;
496 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
497 switch (type) {
498 case End: /* The END is always visible */
499 return A_NORMAL;
500 case Unmatched: /* Visible in ORIG and RESULT */
501 if (mode & (ORIG|RESULT))
502 return a_unmatched;
503 break;
504 case Unchanged: /* visible everywhere, but only show stream 0 */
505 if (stream == 0)
506 return a_common;
507 break;
508 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
509 if ((mode & (BEFORE|AFTER))
510 && stream == 2)
511 return a_extra;
512 break;
513 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
514 if (stream == 0 &&
515 (mode & (ORIG|BEFORE)))
516 return a_delete;
517 if (stream == 2 &&
518 (mode & (RESULT|AFTER)))
519 return a_added;
520 break;
521 case Conflict:
522 switch (stream) {
523 case 0:
524 if (mode & ORIG)
525 return a_unmatched | A_REVERSE;
526 break;
527 case 1:
528 if (mode & BEFORE)
529 return a_extra | A_UNDERLINE;
530 break;
531 case 2:
532 if (mode & (AFTER|RESULT))
533 return a_added | A_UNDERLINE;
534 break;
536 break;
537 case AlreadyApplied:
538 switch (stream) {
539 case 0:
540 if (mode & (ORIG|RESULT))
541 return a_already;
542 break;
543 case 1:
544 if (mode & BEFORE)
545 return a_delete | A_UNDERLINE;
546 break;
547 case 2:
548 if (mode & AFTER)
549 return a_added | A_UNDERLINE;
550 break;
552 break;
554 return -1;
557 /* checkline creates a summary of the sort of changes that
558 * are in a line, returning an "or" of
559 * CHANGED
560 * CHANGES
561 * WIGGLED
562 * CONFLICTED
564 static int check_line(struct mpos pos, struct file fm, struct file fb,
565 struct file fa,
566 struct merge *m, int mode)
568 int rv = 0;
569 struct elmnt e;
570 int unmatched = 0;
572 do {
573 if (m[pos.p.m].type == Changed)
574 rv |= CHANGED | CHANGES;
575 else if ((m[pos.p.m].type == AlreadyApplied ||
576 m[pos.p.m].type == Conflict))
577 rv |= CONFLICTED | CHANGES;
578 else if (m[pos.p.m].type == Extraneous)
579 rv |= WIGGLED;
580 else if (m[pos.p.m].type == Unmatched)
581 unmatched = 1;
582 if (m[pos.p.m].in_conflict)
583 rv |= CONFLICTED | CHANGES;
584 e = prev_melmnt(&pos.p, fm, fb, fa, m);
585 } while (e.start != NULL &&
586 (!ends_mline(e)
587 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
589 if (unmatched && (rv & CHANGES))
590 rv |= WIGGLED;
591 return rv;
594 /* Find the next line in the merge which is visible.
595 * If we hit the end of a conflicted set during pass-1
596 * we rewind for pass-2.
597 * 'mode' tells which bits we want to see, possible one of
598 * the 4 parts (before/after/orig/result) or one of the pairs
599 * before+after or orig+result.
601 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
602 struct file fa,
603 struct merge *m, int mode)
605 int mask;
606 do {
607 struct mp prv;
608 int mode2;
610 prv = pos->p;
611 while (1) {
612 struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
613 if (e.start == NULL)
614 break;
615 if (ends_mline(e) &&
616 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
617 break;
619 mode2 = check_line(*pos, fm, fb, fa, m, mode);
621 if ((mode2 & CHANGES) && pos->state == 0) {
622 /* Just entered a diff-set */
623 pos->lo = pos->p;
624 pos->state = 1;
625 } else if (!(mode2 & CHANGES) && pos->state) {
626 /* Come to the end of a diff-set */
627 switch (pos->state) {
628 case 1:
629 /* Need to record the end */
630 pos->hi = prv;
631 /* time for another pass */
632 pos->p = pos->lo;
633 pos->state++;
634 break;
635 case 2:
636 /* finished final pass */
637 pos->state = 0;
638 break;
641 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
642 switch (pos->state) {
643 case 1:
644 mask &= ~(RESULT|AFTER);
645 break;
646 case 2:
647 mask &= ~(ORIG|BEFORE);
648 break;
650 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
654 /* Move to previous line - simply the reverse of next_mline */
655 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
656 struct file fa,
657 struct merge *m, int mode)
659 int mask;
660 do {
661 struct mp prv;
662 int mode2;
664 prv = pos->p;
665 if (pos->p.m < 0)
666 return;
667 while (1) {
668 struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
669 if (e.start == NULL)
670 break;
671 if (ends_mline(e) &&
672 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
673 break;
675 mode2 = check_line(*pos, fm, fb, fa, m, mode);
677 if ((mode2 & CHANGES) && pos->state == 0) {
678 /* Just entered a diff-set */
679 pos->hi = pos->p;
680 pos->state = 2;
681 } else if (!(mode2 & CHANGES) && pos->state) {
682 /* Come to the end (start) of a diff-set */
683 switch (pos->state) {
684 case 1:
685 /* finished final pass */
686 pos->state = 0;
687 break;
688 case 2:
689 /* Need to record the start */
690 pos->lo = prv;
691 /* time for another pass */
692 pos->p = pos->hi;
693 pos->state--;
694 break;
697 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
698 switch (pos->state) {
699 case 1:
700 mask &= ~(RESULT|AFTER);
701 break;
702 case 2:
703 mask &= ~(ORIG|BEFORE);
704 break;
706 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
709 /* blank a whole row of display */
710 static void blank(int row, int start, int cols, unsigned int attr)
712 (void)attrset(attr);
713 move(row, start);
714 while (cols-- > 0)
715 addch(' ');
718 /* search of a string on one display line. If found, update the
719 * cursor.
722 static int mcontains(struct mpos pos,
723 struct file fm, struct file fb, struct file fa,
724 struct merge *m,
725 int mode, char *search, struct cursor *curs,
726 int dir, int ignore_case)
728 /* See if any of the files, between start of this line and here,
729 * contain the search string.
730 * However this is modified by dir:
731 * -2: find last match *before* curs
732 * -1: find last match at-or-before curs
733 * 1: find first match at-or-after curs
734 * 2: find first match *after* curs
736 * We only test for equality with curs, so if it is on a different
737 * line it will not be found and everything is before/after.
738 * As we search from end-of-line to start we find the last
739 * match first.
740 * For a forward search, we stop when we find curs.
741 * For a backward search, we forget anything found when we find curs.
743 struct elmnt e;
744 int found = 0;
745 struct mp mp;
746 int o;
747 int len = strlen(search);
749 do {
750 e = prev_melmnt(&pos.p, fm, fb, fa, m);
751 if (e.start && e.start[0]) {
752 int i;
753 int curs_i;
754 if (same_mp(pos.p, curs->pos))
755 curs_i = curs->offset;
756 else
757 curs_i = -1;
758 for (i = e.len-1; i >= 0; i--) {
759 if (i == curs_i && dir == -1)
760 /* next match is the one we want */
761 found = 0;
762 if (i == curs_i && dir == 2)
763 /* future matches not accepted */
764 goto break_while;
765 if ((!found || dir > 0) &&
766 (ignore_case ? strncasecmp : strncmp)
767 (e.start+i, search, len) == 0) {
768 mp = pos.p;
769 o = i;
770 found = 1;
772 if (i == curs_i && dir == -2)
773 /* next match is the one we want */
774 found = 0;
775 if (i == curs_i && dir == 1)
776 /* future matches not accepted */
777 goto break_while;
780 } while (e.start != NULL &&
781 (!ends_mline(e)
782 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
783 break_while:
784 if (found) {
785 curs->pos = mp;
786 curs->offset = o;
788 return found;
791 /* Drawing the display window.
792 * There are 7 different ways we can display the data, each
793 * of which can be configured by a keystroke:
794 * o original - just show the original file with no changes, but still
795 * with highlights of what is changed or unmatched
796 * r result - show just the result of the merge. Conflicts just show
797 * the original, not the before/after options
798 * b before - show the 'before' stream of the patch
799 * a after - show the 'after' stream of the patch
800 * d diff - show just the patch, both before and after
801 * m merge - show the full merge with -+ sections for changes.
802 * If point is in a wiggled or conflicted section the
803 * window is split horizontally and the diff is shown
804 * in the bottom window
805 * | sidebyside - two panes, left and right. Left holds the merge,
806 * right holds the diff. In the case of a conflict,
807 * left holds orig/after, right holds before/after
809 * The horizontal split for 'merge' mode is managed as follows.
810 * - The window is split when we first visit a line that contains
811 * a wiggle or a conflict, and the second pane is removed when
812 * we next visit a line that contains no changes (is fully Unchanged).
813 * - to display the second pane, we find a visible end-of-line in the
814 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
815 * the we centre that line.
816 * - We need to rewind to an unchanged section, and wind forward again
817 * to make sure that 'lo' and 'hi' are set properly.
818 * - every time we move, we redraw the second pane (see how that goes).
821 /* draw_mside draws one text line or, in the case of sidebyside, one side
822 * of a textline.
823 * The 'mode' tells us what to draw via the 'visible()' function.
824 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
825 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
826 * The desired cursor position is given in 'target' the actual end
827 * cursor position (allowing e.g. for tabs) is returned in *colp.
829 static void draw_mside(int mode, int row, int offset, int start, int cols,
830 struct file fm, struct file fb, struct file fa,
831 struct merge *m,
832 struct mpos pos,
833 struct cursor *curs)
835 struct elmnt e;
836 int col = 0;
837 char tag;
838 unsigned int tag_attr;
840 switch (pos.state) {
841 case 0: /* unchanged line */
842 tag = ' ';
843 tag_attr = A_NORMAL;
844 break;
845 case 1: /* 'before' text */
846 tag = '-';
847 tag_attr = a_delete;
848 if ((mode & ORIG) && (mode & CONFLICTED)) {
849 tag = '|';
850 tag_attr = a_delete;
852 mode &= (ORIG|BEFORE);
853 break;
854 case 2: /* the 'after' part */
855 tag = '+';
856 tag_attr = a_added;
857 mode &= (AFTER|RESULT);
858 break;
861 if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) {
862 /* Not visible, just draw a blank */
863 blank(row, offset, cols, a_void);
864 if (curs) {
865 curs->width = -1;
866 curs->col = 0;
867 curs->pos = pos.p;
868 curs->offset = 0;
870 return;
873 (void)attrset(tag_attr);
874 mvaddch(row, offset, tag);
875 offset++;
876 cols--;
877 (void)attrset(A_NORMAL);
879 /* find previous visible newline, or start of file */
881 e = prev_melmnt(&pos.p, fm, fb, fa, m);
882 while (e.start != NULL &&
883 (!ends_mline(e) ||
884 visible(mode, m[pos.p.m].type, pos.p.s) == -1));
886 while (1) {
887 unsigned char *c;
888 int l;
889 e = next_melmnt(&pos.p, fm, fb, fa, m);
890 if (e.start == NULL ||
891 (ends_mline(e)
892 && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
893 /* We have reached the end of visible line, or end of file */
894 if (curs) {
895 curs->col = col;
896 if (col >= start + cols)
897 curs->width = 0;
898 else
899 curs->width = -1; /* end of line */
900 if (curs->target >= 0) {
901 curs->pos = pos.p;
902 curs->offset = 0;
903 } else if (same_mp(pos.p, curs->pos))
904 curs->target = col;
906 if (col < start)
907 col = start;
908 if (e.start && e.start[0] == 0) {
909 char b[40];
910 struct elmnt e1;
911 if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) {
912 int A, B, C, D, E, F;
913 e1 = fb.list[m[pos.p.m].b + pos.p.o];
914 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
915 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
916 sprintf(b, "@@ -%d,%d +%d,%d @@\n", B, C, E, F);
917 (void)attrset(a_sep);
918 } else {
919 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
920 sprintf(b, "<%.17s>", e.start+1);
922 mvaddstr(row, col-start+offset, b);
923 col += strlen(b);
925 blank(row, col-start+offset, start+cols-col,
926 e.start
927 ? (unsigned)visible(mode, m[pos.p.m].type, pos.p.s)
928 : A_NORMAL);
929 return;
931 if (visible(mode, m[pos.p.m].type, pos.p.s) == -1)
932 continue;
933 if (e.start[0] == 0)
934 continue;
935 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
936 c = (unsigned char *)e.start;
937 for (l = 0; l < e.len; l++) {
938 int scol = col;
939 if (*c >= ' ' && *c != 0x7f) {
940 if (col >= start && col < start+cols)
941 mvaddch(row, col-start+offset, *c);
942 col++;
943 } else if (*c == '\t') {
944 do {
945 if (col >= start && col < start+cols) {
946 mvaddch(row, col-start+offset, ' ');
947 } col++;
948 } while ((col&7) != 0);
949 } else {
950 if (col >= start && col < start+cols)
951 mvaddch(row, col-start+offset, '?');
952 col++;
954 if (curs) {
955 if (curs->target >= 0) {
956 if (curs->target < col) {
957 /* Found target column */
958 curs->pos = pos.p;
959 curs->offset = l;
960 curs->col = scol;
961 if (scol >= start + cols)
962 /* Didn't appear on screen */
963 curs->width = 0;
964 else
965 curs->width = col - scol;
966 curs = NULL;
968 } else if (l == curs->offset &&
969 same_mp(pos.p, curs->pos)) {
970 /* Found the pos */
971 curs->target = scol;
972 curs->col = scol;
973 if (scol >= start + cols)
974 /* Didn't appear on screen */
975 curs->width = 0;
976 else
977 curs->width = col - scol;
978 curs = NULL;
981 c++;
986 /* Draw either 1 or 2 sides depending on the mode. */
988 static void draw_mline(int mode, int row, int start, int cols,
989 struct file fm, struct file fb, struct file fa,
990 struct merge *m,
991 struct mpos pos,
992 struct cursor *curs)
995 * Draw the left and right images of this line
996 * One side might be a_blank depending on the
997 * visibility of this newline
999 int lcols, rcols;
1001 mode |= check_line(pos, fm, fb, fa, m, mode);
1003 if ((mode & (BEFORE|AFTER)) &&
1004 (mode & (ORIG|RESULT))) {
1006 lcols = (cols-1)/2;
1007 rcols = cols - lcols - 1;
1009 (void)attrset(A_STANDOUT);
1010 mvaddch(row, lcols, '|');
1012 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1013 fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1015 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1016 fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1017 } else
1018 draw_mside(mode, row, 0, start, cols,
1019 fm, fb, fa, m, pos, curs);
1022 static char *merge_help[] = {
1023 "This view shows the merge of the patch with the",
1024 "original file. It is like a full-context diff showing",
1025 "removed lines with a '-' prefix and added lines with a",
1026 "'+' prefix.",
1027 "In cases where a patch chunk could not be successfully",
1028 "applied, the original text is prefixed with a '|', and",
1029 "the text that the patch wanted to add is prefixed with",
1030 "a '+'.",
1031 "When the cursor is over such a conflict, or over a chunk",
1032 "which required wiggling to apply (i.e. there was unmatched",
1033 "text in the original, or extraneous unchanged text in",
1034 "the patch), the terminal is split and the bottom pane is",
1035 "use to display the part of the patch that applied to",
1036 "this section of the original. This allows you to confirm",
1037 "that a wiggled patch applied correctly, and to see",
1038 "why there was a conflict",
1039 NULL
1041 static char *diff_help[] = {
1042 "This is the 'diff' or 'patch' view. It shows",
1043 "only the patch that is being applied without the",
1044 "original to which it is being applied.",
1045 "Underlined text indicates parts of the patch which",
1046 "resulted in a conflict when applied to the",
1047 "original.",
1048 NULL
1050 static char *orig_help[] = {
1051 "This is the 'original' view which simply shows",
1052 "the original file before applying the patch.",
1053 "Sections of code that would be changed by the patch",
1054 "are highlighted in red.",
1055 NULL
1057 static char *result_help[] = {
1058 "This is the 'result' view which shows just the",
1059 "result of applying the patch. When a conflict",
1060 "occurred this view does not show the full conflict",
1061 "but only the 'after' part of the patch. To see",
1062 "the full conflict, use the 'merge' or 'sidebyside'",
1063 "views.",
1064 NULL
1066 static char *before_help[] = {
1067 "This view shows the 'before' section of a patch.",
1068 "It allows the expected match text to be seen uncluttered",
1069 "by text that is meant to replaced it."
1070 "Red text is text that will be removed by the patch",
1071 NULL
1073 static char *after_help[] = {
1074 "This view shows the 'after' section of a patch.",
1075 "It allows the intended result to be seen uncluttered",
1076 "by text that was meant to be matched and replaced."
1077 "Green text is text that was added by the patch - it",
1078 "was not present in the 'before' part of the patch",
1079 NULL
1081 static char *sidebyside_help[] = {
1082 "This is the Side By Side view of a patched file.",
1083 "The left side shows the original and the result.",
1084 "The right side shows the patch which was applied",
1085 "and lines up with the original/result as much as",
1086 "possible.",
1088 "Where one side has no line which matches the",
1089 "other side it is displayed as a solid colour in the",
1090 "yellow family (depending on your terminal window).",
1091 NULL
1093 static char *merge_window_help[] = {
1094 " Highlight Colours and Keystroke commands",
1096 "In all different views of a merge, highlight colours",
1097 "are used to show which parts of lines were added,",
1098 "removed, already changed, unchanged or in conflict.",
1099 "Colours and their use are:",
1100 " normal unchanged text",
1101 " red text that was removed or changed",
1102 " green text that was added or the result",
1103 " of a change",
1104 " yellow background used in side-by-side for a line",
1105 " which has no match on the other",
1106 " side",
1107 " blue text in the original which did not",
1108 " match anything in the patch",
1109 " cyan text in the patch which did not",
1110 " match anything in the original",
1111 " cyan background already changed text: the result",
1112 " of the patch matches the original",
1113 " underline remove or added text can also be",
1114 " underlined indicating that it",
1115 " was involved in a conflict",
1117 "While viewing a merge various keystroke commands can",
1118 "be used to move around and change the view. Basic",
1119 "movement commands from both 'vi' and 'emacs' are",
1120 "available:",
1122 " p control-p k UP Move to previous line",
1123 " n control-n j DOWN Move to next line",
1124 " l LEFT Move one char to right",
1125 " h RIGHT Move one char to left",
1126 " / control-s Enter incremental search mode",
1127 " control-r Enter reverse-search mode",
1128 " control-g Search again",
1129 " ? Display help message",
1130 " ESC-< 0-G Go to start of file",
1131 " ESC-> G Go to end of file",
1132 " q Return to list of files or exit",
1133 " control-L recenter current line",
1134 " control-V page down",
1135 " ESC-v page up",
1136 " N go to next patch chunk",
1137 " P go to previous patch chunk",
1138 " O move cursor to alternate pane",
1139 " ^ control-A go to start of line",
1140 " $ control-E go to end of line",
1142 " a display 'after' view",
1143 " b display 'before' view",
1144 " o display 'original' view",
1145 " r display 'result' view",
1146 " d display 'diff' or 'patch' view",
1147 " m display 'merge' view",
1148 " | display side-by-side view",
1149 NULL
1152 /* plist stores a list of patched files in an array
1153 * Each entry identifies a file, the range of the
1154 * original patch which applies to this file, some
1155 * statistics concerning how many conflicts etc, and
1156 * some linkage information so the list can be viewed
1157 * as a directory-tree.
1159 struct plist {
1160 char *file;
1161 unsigned int start, end;
1162 int parent;
1163 int next, prev, last;
1164 int open;
1165 int chunks, wiggles, conflicts;
1166 int calced;
1167 int is_merge;
1168 char *before, *after;
1171 static void merge_window(struct plist *p, FILE *f, int reverse)
1173 /* Display the merge window in one of the selectable modes,
1174 * starting with the 'merge' mode.
1176 * Newlines are the key to display.
1177 * 'pos' is always a visible newline (or eof).
1178 * In sidebyside mode it might only be visible on one side,
1179 * in which case the other side will be blank.
1180 * Where the newline is visible, we rewind the previous visible
1181 * newline visible and display the stuff in between
1183 * A 'position' is a struct mpos
1186 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1187 struct file fm, fb, fa;
1188 struct csl *csl1, *csl2;
1189 struct ci ci;
1190 int ch; /* count of chunks */
1191 /* Always refresh the current line.
1192 * If refresh == 1, refresh all lines. If == 2, clear first
1194 int refresh = 2;
1195 int rows = 0, cols = 0;
1196 int splitrow = -1; /* screen row for split - diff appears below */
1197 int lastrow = 0; /* end of screen, or just above 'splitrow' */
1198 int i, c, cswitch;
1199 int mode = ORIG|RESULT;
1200 int mmode = mode; /* Mode for moving - used when in 'other' pane */
1201 char *modename = "merge";
1202 char **modehelp = merge_help;
1204 int row, start = 0;
1205 int trow; /* screen-row while searching. If we cannot find,
1206 * we forget this number */
1207 struct cursor curs;
1208 struct mpos pos; /* current point */
1209 struct mpos tpos, /* temp point while drawing lines above and below pos */
1210 toppos, /* pos at top of screen - for page-up */
1211 botpos; /* pos at bottom of screen - for page-down */
1212 struct mpos vpos, tvpos;
1213 int botrow = 0;
1214 int meta = 0, /* mode for multi-key commands- SEARCH or META */
1215 tmeta;
1216 int num = -1, /* numeric arg being typed. */
1217 tnum;
1218 char search[80]; /* string we are searching for */
1219 unsigned int searchlen = 0;
1220 int search_notfound = 0;
1221 int searchdir = 0;
1222 /* ignore_case:
1223 * 0 == no
1224 * 1 == no because there are upper-case chars
1225 * 2 == yes as there are no upper-case chars
1226 * 3 == yes
1228 int ignore_case = 2;
1229 /* We record all the places we find so 'backspace'
1230 * can easily return to the previous one
1232 struct search_anchor {
1233 struct search_anchor *next;
1234 struct mpos pos;
1235 struct cursor curs;
1236 int notfound;
1237 int row, start;
1238 unsigned int searchlen;
1239 } *anchor = NULL;
1241 if (f == NULL) {
1242 /* three separate files */
1243 sm = load_file(p->file);
1244 sb = load_file(p->before);
1245 sa = load_file(p->after);
1246 ch = 0;
1247 } else {
1248 sp = load_segment(f, p->start, p->end);
1249 if (p->is_merge) {
1250 if (reverse)
1251 split_merge(sp, &sm, &sa, &sb);
1252 else
1253 split_merge(sp, &sm, &sb, &sa);
1254 ch = 0;
1255 } else {
1256 if (reverse)
1257 ch = split_patch(sp, &sa, &sb);
1258 else
1259 ch = split_patch(sp, &sb, &sa);
1261 sm = load_file(p->file);
1264 if (!sm.body || !sb.body || !sa.body) {
1265 term_init();
1266 if (!sm.body)
1267 help_window(help_missing, NULL);
1268 else
1269 help_window(help_corrupt, NULL);
1270 return;
1272 /* FIXME check for errors in the stream */
1273 fm = split_stream(sm, ByWord);
1274 fb = split_stream(sb, ByWord);
1275 fa = split_stream(sa, ByWord);
1277 if (ch)
1278 csl1 = pdiff(fm, fb, ch);
1279 else
1280 csl1 = diff(fm, fb);
1281 csl2 = diff(fb, fa);
1283 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
1285 term_init();
1287 row = 1;
1288 pos.p.m = 0; /* merge node */
1289 pos.p.s = 0; /* stream number */
1290 pos.p.o = -1; /* offset */
1291 pos.p.lineno = 1;
1292 pos.state = 0;
1293 next_mline(&pos, fm, fb, fa, ci.merger, mode);
1294 memset(&curs, 0, sizeof(curs));
1295 vpos = pos;
1296 while (1) {
1297 if (refresh >= 2) {
1298 char buf[100];
1299 clear();
1300 snprintf(buf, 100, "File: %s%s Mode: %s\n",
1301 p->file, reverse ? " - reversed" : "", modename);
1302 (void)attrset(A_BOLD);
1303 mvaddstr(0, 0, buf);
1304 clrtoeol();
1305 (void)attrset(A_NORMAL);
1306 refresh = 1;
1308 if (row < 1 || row >= lastrow)
1309 refresh = 1;
1310 if (curs.alt)
1311 refresh = 1;
1313 if (mode == (ORIG|RESULT)) {
1314 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1315 if (splitrow < 0 && (cmode & (WIGGLED|CONFLICTED))) {
1316 splitrow = (rows+1)/2;
1317 lastrow = splitrow - 1;
1318 refresh = 1;
1320 if (!curs.alt && splitrow >= 0 && !(cmode & CHANGES)) {
1321 splitrow = -1;
1322 lastrow = rows-1;
1323 refresh = 1;
1325 } else if (splitrow >= 0) {
1326 splitrow = -1;
1327 lastrow = rows-1;
1328 refresh = 1;
1331 if (refresh) {
1332 getmaxyx(stdscr, rows, cols);
1333 rows--; /* keep last row clear */
1334 if (splitrow >= 0) {
1335 splitrow = (rows+1)/2;
1336 lastrow = splitrow - 1;
1337 } else
1338 lastrow = rows - 1;
1340 if (row < -3)
1341 row = lastrow/2+1;
1342 if (row < 1)
1343 row = 1;
1344 if (row > lastrow+3)
1345 row = lastrow/2+1;
1346 if (row >= lastrow)
1347 row = lastrow-1;
1349 if (getenv("WIGGLE_VTRACE")) {
1350 char b[100];
1351 char *e, e2[7];
1352 int i;
1353 switch (vpos.p.s) {
1354 case 0:
1355 e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start;
1356 break;
1357 case 1:
1358 e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start;
1359 break;
1360 case 2:
1361 e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start;
1362 break;
1364 for (i = 0; i < 6; i++) {
1365 e2[i] = e[i];
1366 if (e2[i] < 32 || e2[i] >= 127)
1367 e2[i] = '?';
1369 sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
1370 vpos.p.s, vpos.p.o,
1371 vpos.p.m, typenames[ci.merger[vpos.p.m].type],
1372 ci.merger[vpos.p.m].al,
1373 ci.merger[vpos.p.m].bl,
1374 ci.merger[vpos.p.m].cl,
1375 ci.merger[vpos.p.m].in_conflict,
1378 (void)attrset(A_NORMAL);
1379 mvaddstr(0, 50, b);
1380 clrtoeol();
1383 /* Always refresh the line */
1384 while (start > curs.target) {
1385 start -= 8;
1386 refresh = 1;
1388 if (start < 0)
1389 start = 0;
1390 retry:
1391 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1392 pos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1393 if (curs.width == 0 && start < curs.col) {
1394 /* width == 0 implies it appear after end-of-screen */
1395 start += 8;
1396 refresh = 1;
1397 goto retry;
1399 if (curs.col < start) {
1400 start -= 8;
1401 refresh = 1;
1402 if (start < 0)
1403 start = 0;
1404 goto retry;
1406 if (refresh) {
1407 refresh = 0;
1408 tpos = pos;
1410 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1411 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1412 draw_mline(mode, i--, start, cols,
1413 fm, fb, fa, ci.merger,
1414 tpos, NULL);
1417 if (i) {
1418 row -= (i+1);
1419 refresh = 1;
1420 goto retry;
1422 toppos = tpos;
1423 while (i >= 1)
1424 blank(i--, 0, cols, a_void);
1425 tpos = pos;
1426 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1427 draw_mline(mode, i++, start, cols,
1428 fm, fb, fa, ci.merger,
1429 tpos, NULL);
1430 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1432 botpos = tpos; botrow = i;
1433 while (i <= lastrow)
1434 blank(i++, 0, cols, a_void);
1437 if (splitrow >= 0) {
1438 struct mpos spos = pos;
1439 int smode = BEFORE|AFTER;
1440 int srow = (rows + splitrow)/2;
1441 if (visible(smode, ci.merger[spos.p.m].type,
1442 spos.p.s) < 0)
1443 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1444 /* Now hi/lo might be wrong, so lets fix it. */
1445 tpos = spos;
1446 while (spos.p.m >= 0 && spos.state != 0)
1447 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1448 while (!same_mpos(spos, tpos))
1449 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1451 (void)attrset(a_sep);
1452 for (i = 0; i < cols; i++)
1453 mvaddstr(splitrow, i, "-");
1455 tpos = spos;
1456 for (i = srow-1; i > splitrow; i--) {
1457 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1458 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1459 tpos, NULL);
1461 while (i > splitrow)
1462 blank(i--, 0, cols, a_void);
1463 tpos = spos;
1464 for (i = srow;
1465 i < rows && ci.merger[tpos.p.m].type != End;
1466 i++) {
1467 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1468 tpos,
1469 (i == srow && curs.alt) ? &curs : NULL);
1470 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1472 while (i < rows)
1473 blank(i++, 0, cols, a_void);
1475 /* Now that curs is accurate, report the type */
1477 char lbuf[30];
1478 (void)attrset(A_BOLD);
1479 snprintf(lbuf, 29, "%s ln:%d",
1480 typenames[ci.merger[curs.pos.m].type],
1481 (pos.p.lineno-1)/2);
1482 mvaddstr(0, cols - strlen(lbuf) - 4, " ");
1483 mvaddstr(0, cols - strlen(lbuf) - 1, lbuf);
1485 #define META(c) ((c)|0x1000)
1486 #define SEARCH(c) ((c)|0x2000)
1487 move(rows, 0);
1488 (void)attrset(A_NORMAL);
1489 if (num >= 0) {
1490 char buf[10];
1491 snprintf(buf, 10, "%d ", num);
1492 addstr(buf);
1494 if (meta & META(0))
1495 addstr("ESC...");
1496 if (meta & SEARCH(0)) {
1497 if (searchdir < 0)
1498 addstr("Backwards ");
1499 addstr("Search: ");
1500 addstr(search);
1501 if (search_notfound)
1502 addstr(" - Not Found.");
1503 search_notfound = 0;
1505 clrtoeol();
1506 /* '+1' to skip over the leading +/-/| char */
1507 if (curs.alt && splitrow > 0)
1508 move((rows + splitrow)/2, curs.col - start + 1);
1509 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1510 (mode & (ORIG|RESULT))))
1511 move(row, curs.col-start + (cols-1)/2+2);
1512 else
1513 move(row, curs.col-start+1);
1514 c = getch();
1515 tmeta = meta; meta = 0;
1516 tnum = num; num = -1;
1517 tvpos = vpos; vpos = pos;
1518 cswitch = c | tmeta;
1519 /* Handle some ranges */
1520 /* case '0' ... '9': */
1521 if (cswitch >= '0' && cswitch <= '9')
1522 cswitch = '0';
1523 /* case SEARCH(' ') ... SEARCH('~'): */
1524 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1525 cswitch = SEARCH(' ');
1527 switch (cswitch) {
1528 case 27: /* escape */
1529 case META(27):
1530 meta = META(0);
1531 break;
1532 case META('<'): /* start of file */
1533 start:
1534 tpos = pos; row++;
1535 do {
1536 pos = tpos; row--;
1537 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1538 } while (tpos.p.m >= 0);
1539 if (row <= 0)
1540 row = 0;
1541 break;
1542 case META('>'): /* end of file */
1543 case 'G':
1544 if (tnum >= 0)
1545 goto start;
1546 tpos = pos; row--;
1547 do {
1548 pos = tpos; row++;
1549 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1550 } while (ci.merger[tpos.p.m].type != End);
1551 if (row >= lastrow)
1552 row = lastrow;
1553 break;
1554 case '0': /* actually '0'...'9' */
1555 if (tnum < 0)
1556 tnum = 0;
1557 num = tnum*10 + (c-'0');
1558 break;
1559 case 'q':
1560 return;
1562 case '/':
1563 case 'S'-64:
1564 /* incr search forward */
1565 meta = SEARCH(0);
1566 searchlen = 0;
1567 search[searchlen] = 0;
1568 searchdir = 1;
1569 break;
1570 case '\\':
1571 case 'R'-64:
1572 /* incr search backwards */
1573 meta = SEARCH(0);
1574 searchlen = 0;
1575 search[searchlen] = 0;
1576 searchdir = -1;
1577 break;
1578 case SEARCH('G'-64):
1579 case SEARCH('S'-64):
1580 case SEARCH('R'-64):
1581 /* search again */
1582 if ((c|tmeta) == SEARCH('R'-64))
1583 searchdir = -2;
1584 else if ((c|tmeta) == SEARCH('S'-64))
1585 searchdir = 2;
1586 else
1587 searchdir *= 2;
1588 meta = SEARCH(0);
1589 tpos = pos; trow = row;
1590 goto search_again;
1592 case SEARCH('H'-64):
1593 case SEARCH(KEY_BACKSPACE):
1594 meta = SEARCH(0);
1595 if (anchor) {
1596 struct search_anchor *a;
1597 a = anchor;
1598 anchor = a->next;
1599 free(a);
1601 if (anchor) {
1602 struct search_anchor *a;
1603 a = anchor;
1604 anchor = a->next;
1605 pos = a->pos;
1606 row = a->row;
1607 start = a->start;
1608 curs = a->curs;
1609 curs.target = -1;
1610 search_notfound = a->notfound;
1611 searchlen = a->searchlen;
1612 search[searchlen] = 0;
1613 free(a);
1614 refresh = 1;
1616 break;
1617 case SEARCH(' '): /* actually ' '...'~' */
1618 case SEARCH('\t'):
1619 meta = SEARCH(0);
1620 if (searchlen < sizeof(search)-1)
1621 search[searchlen++] = c & (0x7f);
1622 search[searchlen] = 0;
1623 tpos = pos; trow = row;
1624 search_again:
1625 search_notfound = 1;
1626 if (ignore_case == 1 || ignore_case == 2) {
1627 unsigned int i;
1628 ignore_case = 2;
1629 for (i=0; i < searchlen; i++)
1630 if (isupper(search[i])) {
1631 ignore_case = 1;
1632 break;
1635 do {
1636 if (mcontains(tpos, fm, fb, fa, ci.merger,
1637 mmode, search, &curs, searchdir,
1638 ignore_case >= 2)) {
1639 curs.target = -1;
1640 pos = tpos;
1641 row = trow;
1642 search_notfound = 0;
1643 break;
1645 if (searchdir < 0) {
1646 trow--;
1647 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1648 } else {
1649 trow++;
1650 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1652 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1653 searchdir /= abs(searchdir);
1655 break;
1656 case 'L'-64:
1657 refresh = 2;
1658 row = lastrow / 2;
1659 break;
1661 case 'V'-64: /* page down */
1662 pos = botpos;
1663 if (botrow <= lastrow)
1664 row = botrow;
1665 else
1666 row = 2;
1667 refresh = 1;
1668 break;
1669 case META('v'): /* page up */
1670 pos = toppos;
1671 row = lastrow-1;
1672 refresh = 1;
1673 break;
1675 case 'j':
1676 case 'n':
1677 case 'N'-64:
1678 case KEY_DOWN:
1679 if (tnum < 0)
1680 tnum = 1;
1681 for (; tnum > 0 ; tnum--) {
1682 tpos = pos;
1683 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1684 if (ci.merger[tpos.p.m].type != End) {
1685 pos = tpos;
1686 row++;
1689 break;
1690 case 'N':
1691 /* Next diff */
1692 tpos = pos; row--;
1693 do {
1694 pos = tpos; row++;
1695 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1696 } while (pos.state != 0 && ci.merger[tpos.p.m].type != End);
1697 tpos = pos; row--;
1698 do {
1699 pos = tpos; row++;
1700 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1701 } while (pos.state == 0 && ci.merger[tpos.p.m].type != End);
1703 break;
1704 case 'P':
1705 /* Previous diff */
1706 tpos = pos; row++;
1707 do {
1708 pos = tpos; row--;
1709 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1710 } while (tpos.state == 0 && tpos.p.m >= 0);
1711 tpos = pos; row++;
1712 do {
1713 pos = tpos; row--;
1714 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1715 } while (tpos.state != 0 && tpos.p.m >= 0);
1716 break;
1718 case 'k':
1719 case 'p':
1720 case 'P'-64:
1721 case KEY_UP:
1722 if (tnum < 0)
1723 tnum = 1;
1724 for (; tnum > 0 ; tnum--) {
1725 tpos = pos;
1726 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1727 if (tpos.p.m >= 0) {
1728 pos = tpos;
1729 row--;
1732 break;
1734 case KEY_LEFT:
1735 case 'h':
1736 /* left */
1737 curs.target = curs.col - 1;
1738 if (curs.target < 0) {
1739 /* Try to go to end of previous line */
1740 tpos = pos;
1741 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1742 if (tpos.p.m >= 0) {
1743 pos = tpos;
1744 row--;
1745 curs.pos = pos.p;
1746 curs.target = -1;
1747 } else
1748 curs.target = 0;
1750 break;
1751 case KEY_RIGHT:
1752 case 'l':
1753 /* right */
1754 if (curs.width >= 0)
1755 curs.target = curs.col + curs.width;
1756 else {
1757 /* end of line, go to next */
1758 tpos = pos;
1759 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1760 if (ci.merger[tpos.p.m].type != End) {
1761 pos = tpos;
1762 curs.pos = pos.p;
1763 row++;
1764 curs.target = 0;
1767 break;
1769 case '^':
1770 case 'A'-64:
1771 /* Start of line */
1772 curs.target = 0;
1773 break;
1774 case '$':
1775 case 'E'-64:
1776 /* End of line */
1777 curs.target = 1000;
1778 break;
1780 case 'O':
1781 curs.alt = !curs.alt;
1782 if (curs.alt && mode == (ORIG|RESULT))
1783 mmode = (BEFORE|AFTER);
1784 else
1785 mmode = mode;
1786 break;
1788 case 'a': /* 'after' view in patch window */
1789 mode = AFTER; modename = "after"; modehelp = after_help;
1790 mmode = mode; curs.alt = 0;
1791 refresh = 3;
1792 break;
1793 case 'b': /* 'before' view in patch window */
1794 mode = BEFORE; modename = "before"; modehelp = before_help;
1795 mmode = mode; curs.alt = 0;
1796 refresh = 3;
1797 break;
1798 case 'o': /* 'original' view in the merge window */
1799 mode = ORIG; modename = "original"; modehelp = orig_help;
1800 mmode = mode; curs.alt = 0;
1801 refresh = 3;
1802 break;
1803 case 'r': /* the 'result' view in the merge window */
1804 mode = RESULT; modename = "result"; modehelp = result_help;
1805 mmode = mode; curs.alt = 0;
1806 refresh = 3;
1807 break;
1808 case 'd':
1809 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
1810 mmode = mode; curs.alt = 0;
1811 refresh = 3;
1812 break;
1813 case 'm':
1814 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
1815 mmode = mode; curs.alt = 0;
1816 refresh = 3;
1817 break;
1819 case '|':
1820 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
1821 mmode = mode; curs.alt = 0;
1822 refresh = 3;
1823 break;
1825 case 'H': /* scroll window to the right */
1826 if (start > 0)
1827 start--;
1828 curs.target = start + 1;
1829 refresh = 1;
1830 break;
1831 case 'L': /* scroll window to the left */
1832 if (start < cols)
1833 start++;
1834 curs.target = start + 1;
1835 refresh = 1;
1836 break;
1838 case '<':
1839 prev_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1840 if (tvpos.p.m >= 0)
1841 vpos = tvpos;
1842 break;
1843 case '>':
1844 next_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1845 if (ci.merger[tvpos.p.m].type != End)
1846 vpos = tvpos;
1847 break;
1849 case '?':
1850 help_window(modehelp, merge_window_help);
1851 refresh = 2;
1852 break;
1854 case KEY_RESIZE:
1855 refresh = 2;
1856 break;
1859 if (meta == SEARCH(0)) {
1860 if (anchor == NULL ||
1861 !same_mpos(anchor->pos, pos) ||
1862 anchor->searchlen != searchlen ||
1863 !same_mp(anchor->curs.pos, curs.pos)) {
1864 struct search_anchor *a = xmalloc(sizeof(*a));
1865 a->pos = pos;
1866 a->row = row;
1867 a->start = start;
1868 a->curs = curs;
1869 a->searchlen = searchlen;
1870 a->notfound = search_notfound;
1871 a->next = anchor;
1872 anchor = a;
1874 } else {
1875 while (anchor) {
1876 struct search_anchor *a = anchor;
1877 anchor = a->next;
1878 free(a);
1881 if (refresh == 3) {
1882 /* move backward and forward to make sure we
1883 * are on a visible line
1885 tpos = pos;
1886 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1887 if (tpos.p.m >= 0)
1888 pos = tpos;
1889 tpos = pos;
1890 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1891 if (ci.merger[tpos.p.m].type != End)
1892 pos = tpos;
1897 static void show_merge(char *origname, FILE *patch, int reverse,
1898 int is_merge, char *before, char *after)
1900 struct plist p;
1902 p.file = origname;
1903 if (patch) {
1904 p.start = 0;
1905 fseek(patch, 0, SEEK_END);
1906 p.end = ftell(patch);
1907 fseek(patch, 0, SEEK_SET);
1909 p.calced = 0;
1910 p.is_merge = is_merge;
1911 p.before = before;
1912 p.after = after;
1914 freopen("/dev/null","w",stderr);
1915 merge_window(&p, patch, reverse);
1918 static struct plist *patch_add_file(struct plist *pl, int *np, char *file,
1919 unsigned int start, unsigned int end)
1921 /* size of pl is 0, 16, n^2 */
1922 int n = *np;
1923 int asize;
1925 while (*file == '/')
1926 /* leading '/' are bad... */
1927 file++;
1929 if (n == 0)
1930 asize = 0;
1931 else if (n <= 16)
1932 asize = 16;
1933 else if ((n&(n-1)) == 0)
1934 asize = n;
1935 else
1936 asize = n+1; /* not accurate, but not too large */
1937 if (asize <= n) {
1938 /* need to extend array */
1939 struct plist *npl;
1940 if (asize < 16)
1941 asize = 16;
1942 else
1943 asize += asize;
1944 npl = realloc(pl, asize * sizeof(struct plist));
1945 if (!npl) {
1946 fprintf(stderr, "realloc failed - skipping %s\n", file);
1947 return pl;
1949 pl = npl;
1951 pl[n].file = file;
1952 pl[n].start = start;
1953 pl[n].end = end;
1954 pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
1955 pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100;
1956 pl[n].open = 1;
1957 pl[n].calced = 0;
1958 pl[n].is_merge = 0;
1959 *np = n+1;
1960 return pl;
1963 static struct plist *parse_patch(FILE *f, FILE *of, int *np)
1965 /* read a multi-file patch from 'f' and record relevant
1966 * details in a plist.
1967 * if 'of' >= 0, fd might not be seekable so we write
1968 * to 'of' and use lseek on 'of' to determine position
1970 struct plist *plist = NULL;
1972 *np = 0;
1973 while (!feof(f)) {
1974 /* first, find the start of a patch: "\n+++ "
1975 * grab the file name and scan to the end of a line
1977 char *target = "\n+++ ";
1978 char *target2 = "\n--- ";
1979 char *pos = target;
1980 int c;
1981 char name[1024];
1982 unsigned start, end;
1984 while (*pos && (c = fgetc(f)) != EOF) {
1985 if (of)
1986 fputc(c, of);
1987 if (c == *pos)
1988 pos++;
1989 else
1990 pos = target;
1992 if (c == EOF)
1993 break;
1994 assert(c == ' ');
1995 /* now read a file name */
1996 pos = name;
1997 while ((c = fgetc(f)) != EOF
1998 && c != '\t' && c != '\n' && c != ' ' &&
1999 pos - name < 1023) {
2000 *pos++ = c;
2001 if (of)
2002 fputc(c, of);
2004 *pos = 0;
2005 if (c == EOF)
2006 break;
2007 if (of)
2008 fputc(c, of);
2009 while (c != '\n' && (c = fgetc(f)) != EOF)
2010 if (of)
2011 fputc(c, of);
2013 start = ftell(of ?: f);
2015 if (c == EOF)
2016 break;
2018 /* now skip to end - "\n--- " */
2019 pos = target2+1;
2021 while (*pos && (c = fgetc(f)) != EOF) {
2022 if (of)
2023 fputc(c, of);
2024 if (c == *pos)
2025 pos++;
2026 else
2027 pos = target2;
2029 end = ftell(of ?: f);
2030 if (pos > target2)
2031 end -= (pos - target2) - 1;
2032 plist = patch_add_file(plist, np,
2033 strdup(name), start, end);
2035 return plist;
2038 static struct stream load_segment(FILE *f,
2039 unsigned int start, unsigned int end)
2041 struct stream s;
2042 s.len = end - start;
2043 s.body = xmalloc(s.len);
2044 fseek(f, start, 0);
2045 if (fread(s.body, 1, s.len, f) != (size_t)s.len)
2046 die();
2047 return s;
2050 static int pl_cmp(const void *av, const void *bv)
2052 const struct plist *a = av;
2053 const struct plist *b = bv;
2054 return strcmp(a->file, b->file);
2057 static int common_depth(char *a, char *b)
2059 /* find number of path segments that these two have
2060 * in common
2062 int depth = 0;
2063 while (1) {
2064 char *c;
2065 int al, bl;
2066 c = strchr(a, '/');
2067 if (c)
2068 al = c-a;
2069 else
2070 al = strlen(a);
2071 c = strchr(b, '/');
2072 if (c)
2073 bl = c-b;
2074 else
2075 bl = strlen(b);
2076 if (al == 0 || al != bl || strncmp(a, b, al) != 0)
2077 return depth;
2078 a += al;
2079 while (*a == '/')
2080 a++;
2081 b += bl;
2082 while (*b == '/')
2083 b++;
2085 depth++;
2089 static struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
2091 /* any parent of file that is not a parent of curr
2092 * needs to be added to pl
2094 int d = common_depth(file, curr);
2095 char *buf = curr;
2096 while (d) {
2097 char *c = strchr(file, '/');
2098 int l;
2099 if (c)
2100 l = c-file;
2101 else
2102 l = strlen(file);
2103 file += l;
2104 curr += l;
2105 while (*file == '/')
2106 file++;
2107 while (*curr == '/')
2108 curr++;
2109 d--;
2111 while (*file) {
2112 if (curr > buf && curr[-1] != '/')
2113 *curr++ = '/';
2114 while (*file && *file != '/')
2115 *curr++ = *file++;
2116 while (*file == '/')
2117 file++;
2118 *curr = '\0';
2119 if (*file)
2120 pl = patch_add_file(pl, np, strdup(buf),
2121 0, 0);
2123 return pl;
2126 static struct plist *sort_patches(struct plist *pl, int *np)
2128 /* sort the patches, add directory names, and re-sort */
2129 char curr[1024];
2130 char *prev;
2131 int parents[100];
2132 int prevnode[100];
2133 int i, n;
2134 qsort(pl, *np, sizeof(struct plist), pl_cmp);
2135 curr[0] = 0;
2136 n = *np;
2137 for (i = 0; i < n; i++)
2138 pl = add_dir(pl, np, pl[i].file, curr);
2140 qsort(pl, *np, sizeof(struct plist), pl_cmp);
2142 /* array is now stable, so set up parent pointers */
2143 n = *np;
2144 curr[0] = 0;
2145 prevnode[0] = -1;
2146 prev = "";
2147 for (i = 0; i < n; i++) {
2148 int d = common_depth(prev, pl[i].file);
2149 if (d == 0)
2150 pl[i].parent = -1;
2151 else {
2152 pl[i].parent = parents[d-1];
2153 pl[pl[i].parent].last = i;
2155 pl[i].prev = prevnode[d];
2156 if (pl[i].prev > -1)
2157 pl[pl[i].prev].next = i;
2158 prev = pl[i].file;
2159 parents[d] = i;
2160 prevnode[d] = i;
2161 prevnode[d+1] = -1;
2163 return pl;
2166 /* determine how much we need to stripe of the front of
2167 * paths to find them from current directory. This is
2168 * used to guess correct '-p' value.
2170 static int get_strip(char *file)
2172 int fd;
2173 int strip = 0;
2175 while (file && *file) {
2176 fd = open(file, O_RDONLY);
2177 if (fd >= 0) {
2178 close(fd);
2179 return strip;
2181 strip++;
2182 file = strchr(file, '/');
2183 if (file)
2184 while (*file == '/')
2185 file++;
2187 return -1;
2191 static int set_prefix(struct plist *pl, int n, int strip)
2193 int i;
2194 for (i = 0; i < 4 && i < n && strip < 0; i++)
2195 strip = get_strip(pl[i].file);
2197 if (strip < 0) {
2198 fprintf(stderr, "%s: Cannot find files to patch: please specify --strip\n",
2199 Cmd);
2200 return 0;
2202 for (i = 0; i < n; i++) {
2203 char *p = pl[i].file;
2204 int j;
2205 for (j = 0; j < strip; j++) {
2206 if (p)
2207 p = strchr(p, '/');
2208 while (p && *p == '/')
2209 p++;
2211 if (p == NULL) {
2212 fprintf(stderr, "%s: cannot strip %d segments from %s\n",
2213 Cmd, strip, pl[i].file);
2214 return 0;
2216 pl[i].file = p;
2218 return 1;
2221 static void calc_one(struct plist *pl, FILE *f, int reverse)
2223 struct stream s1, s2;
2224 struct stream s = load_segment(f, pl->start, pl->end);
2225 struct stream sf;
2226 if (pl->is_merge) {
2227 if (reverse)
2228 split_merge(s, &sf, &s2, &s1);
2229 else
2230 split_merge(s, &sf, &s1, &s2);
2231 pl->chunks = 0;
2232 } else {
2233 sf = load_file(pl->file);
2234 if (reverse)
2235 pl->chunks = split_patch(s, &s2, &s1);
2236 else
2237 pl->chunks = split_patch(s, &s1, &s2);
2239 if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
2240 pl->wiggles = pl->conflicts = -1;
2241 } else {
2242 struct file ff, fp1, fp2;
2243 struct csl *csl1, *csl2;
2244 struct ci ci;
2245 ff = split_stream(sf, ByWord);
2246 fp1 = split_stream(s1, ByWord);
2247 fp2 = split_stream(s2, ByWord);
2248 if (pl->chunks)
2249 csl1 = pdiff(ff, fp1, pl->chunks);
2250 else
2251 csl1 = diff(ff, fp1);
2252 csl2 = diff(fp1, fp2);
2253 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
2254 pl->wiggles = ci.wiggles;
2255 pl->conflicts = ci.conflicts;
2256 free(csl1);
2257 free(csl2);
2258 free(ff.list);
2259 free(fp1.list);
2260 free(fp2.list);
2263 free(s1.body);
2264 free(s2.body);
2265 free(s.body);
2266 free(sf.body);
2267 pl->calced = 1;
2270 static int get_prev(int pos, struct plist *pl, int n, int mode)
2272 int found = 0;
2273 if (pos == -1)
2274 return pos;
2275 do {
2276 if (pl[pos].prev == -1)
2277 return pl[pos].parent;
2278 pos = pl[pos].prev;
2279 while (pl[pos].open &&
2280 pl[pos].last >= 0)
2281 pos = pl[pos].last;
2282 if (pl[pos].last >= 0)
2283 /* always see directories */
2284 found = 1;
2285 else if (mode == 0)
2286 found = 1;
2287 else if (mode <= 1 && pl[pos].wiggles > 0)
2288 found = 1;
2289 else if (mode <= 2 && pl[pos].conflicts > 0)
2290 found = 1;
2291 } while (pos >= 0 && !found);
2292 return pos;
2295 static int get_next(int pos, struct plist *pl, int n, int mode,
2296 FILE *f, int reverse)
2298 int found = 0;
2299 if (pos == -1)
2300 return pos;
2301 do {
2302 if (pl[pos].open) {
2303 if (pos + 1 < n)
2304 pos = pos+1;
2305 else
2306 return -1;
2307 } else {
2308 while (pos >= 0 && pl[pos].next == -1)
2309 pos = pl[pos].parent;
2310 if (pos >= 0)
2311 pos = pl[pos].next;
2313 if (pos < 0)
2314 return -1;
2315 if (pl[pos].calced == 0 && pl[pos].end)
2316 calc_one(pl+pos, f, reverse);
2317 if (pl[pos].last >= 0)
2318 /* always see directories */
2319 found = 1;
2320 else if (mode == 0)
2321 found = 1;
2322 else if (mode <= 1 && pl[pos].wiggles > 0)
2323 found = 1;
2324 else if (mode <= 2 && pl[pos].conflicts > 0)
2325 found = 1;
2326 } while (pos >= 0 && !found);
2327 return pos;
2330 static void draw_one(int row, struct plist *pl, FILE *f, int reverse)
2332 char hdr[12];
2333 hdr[0] = 0;
2335 if (pl == NULL) {
2336 move(row, 0);
2337 clrtoeol();
2338 return;
2340 if (pl->calced == 0 && pl->end)
2341 /* better load the patch and count the chunks */
2342 calc_one(pl, f, reverse);
2343 if (pl->end == 0) {
2344 strcpy(hdr, " ");
2345 } else {
2346 if (pl->chunks > 99)
2347 strcpy(hdr, "XX");
2348 else
2349 sprintf(hdr, "%2d", pl->chunks);
2350 if (pl->wiggles > 99)
2351 strcpy(hdr+2, " XX");
2352 else
2353 sprintf(hdr+2, " %2d", pl->wiggles);
2354 if (pl->conflicts > 99)
2355 strcpy(hdr+5, " XX ");
2356 else
2357 sprintf(hdr+5, " %2d ", pl->conflicts);
2359 if (pl->end)
2360 strcpy(hdr+9, "= ");
2361 else if (pl->open)
2362 strcpy(hdr+9, "+ ");
2363 else
2364 strcpy(hdr+9, "- ");
2366 mvaddstr(row, 0, hdr);
2367 mvaddstr(row, 11, pl->file);
2368 clrtoeol();
2371 static char *main_help[] = {
2372 " You are using the \"browse\" mode of wiggle.",
2373 "This page shows a list of files in a patch together with",
2374 "the directories that contain them.",
2375 "A directory is indicated by a '+' if the contents are",
2376 "listed or a '-' if the contents are hidden. A file is",
2377 "indicated by an '='. Typing <space> or <return> will",
2378 "expose or hide a directory, and will visit a file.",
2380 "The three columns of numbers are:",
2381 " Ch The number of patch chunks which applied to",
2382 " this file",
2383 " Wi The number of chunks that needed to be wiggled",
2384 " in to place",
2385 " Co The number of chunks that created an unresolvable",
2386 " conflict",
2388 "Keystrokes recognised in this page are:",
2389 " ? Display this help",
2390 " SPC On a directory, toggle hiding of contents",
2391 " On file, visit the file",
2392 " RTN Same as SPC",
2393 " q Quit program",
2394 " n,j,DOWN Go to next line",
2395 " p,k,UP Go to previous line",
2397 " A list All files",
2398 " W only list files with a wiggle or a conflict",
2399 " C only list files with a conflict",
2400 NULL
2403 static void main_window(struct plist *pl, int n, FILE *f, int reverse)
2405 /* The main window lists all files together with summary information:
2406 * number of chunks, number of wiggles, number of conflicts.
2407 * The list is scrollable
2408 * When a entry is 'selected', we switch to the 'file' window
2409 * The list can be condensed by removing files with no conflict
2410 * or no wiggles, or removing subdirectories
2412 * We record which file in the list is 'current', and which
2413 * screen line it is on. We try to keep things stable while
2414 * moving.
2416 * Counts are printed before the name using at most 2 digits.
2417 * Numbers greater than 99 are XX
2418 * Ch Wi Co File
2419 * 27 5 1 drivers/md/md.c
2421 * A directory show the sum in all children.
2423 * Commands:
2424 * select: enter, space, mouseclick
2425 * on file, go to file window
2426 * on directory, toggle open
2427 * up: k, p, control-p uparrow
2428 * Move to previous open object
2429 * down: j, n, control-n, downarrow
2430 * Move to next open object
2432 * A W C: select All Wiggles or Conflicts
2433 * mode
2436 int pos = 0; /* position in file */
2437 int row = 1; /* position on screen */
2438 int rows = 0; /* size of screen in rows */
2439 int cols = 0;
2440 int tpos, i;
2441 int refresh = 2;
2442 int c = 0;
2443 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2445 freopen("/dev/null","w",stderr);
2446 term_init();
2447 pl = sort_patches(pl, &n);
2449 while (1) {
2450 if (refresh == 2) {
2451 clear(); (void)attrset(0);
2452 attron(A_BOLD);
2453 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2454 move(2, 0);
2455 attroff(A_BOLD);
2456 refresh = 1;
2458 if (row < 1 || row >= rows)
2459 refresh = 1;
2460 if (refresh) {
2461 refresh = 0;
2462 getmaxyx(stdscr, rows, cols);
2463 cols = cols; /* Silence warning that 'cols' isn't used */
2464 if (row >= rows + 3)
2465 row = (rows+1)/2;
2466 if (row >= rows)
2467 row = rows-1;
2468 tpos = pos;
2469 for (i = row; i > 1; i--) {
2470 tpos = get_prev(tpos, pl, n, mode);
2471 if (tpos == -1) {
2472 row = row - i + 1;
2473 break;
2476 /* Ok, row and pos could be trustworthy now */
2477 tpos = pos;
2478 for (i = row; i >= 1; i--) {
2479 draw_one(i, &pl[tpos], f, reverse);
2480 tpos = get_prev(tpos, pl, n, mode);
2482 tpos = pos;
2483 for (i = row+1; i < rows; i++) {
2484 tpos = get_next(tpos, pl, n, mode, f, reverse);
2485 if (tpos >= 0)
2486 draw_one(i, &pl[tpos], f, reverse);
2487 else
2488 draw_one(i, NULL, f, reverse);
2491 {char bb[20];
2492 sprintf(bb, "%d", c);
2493 mvaddstr(0, 70, bb);
2494 clrtoeol();
2496 move(row, 9);
2497 c = getch();
2498 switch (c) {
2499 case 'j':
2500 case 'n':
2501 case 'N':
2502 case 'N'-64:
2503 case KEY_DOWN:
2504 tpos = get_next(pos, pl, n, mode, f, reverse);
2505 if (tpos >= 0) {
2506 pos = tpos;
2507 row++;
2509 break;
2510 case 'k':
2511 case 'p':
2512 case 'P':
2513 case 'P'-64:
2514 case KEY_UP:
2515 tpos = get_prev(pos, pl, n, mode);
2516 if (tpos >= 0) {
2517 pos = tpos;
2518 row--;
2520 break;
2522 case ' ':
2523 case 13:
2524 if (pl[pos].end == 0) {
2525 pl[pos].open = !pl[pos].open;
2526 refresh = 1;
2527 } else {
2528 /* diff_window(&pl[pos], f); */
2529 merge_window(&pl[pos], f, reverse);
2530 refresh = 2;
2532 break;
2533 case 27: /* escape */
2534 mvaddstr(0, 70, "ESC..."); clrtoeol();
2535 c = getch();
2536 switch (c) {
2538 break;
2539 case 'q':
2540 return;
2542 case 'A':
2543 mode = 0; refresh = 1;
2544 break;
2545 case 'W':
2546 mode = 1; refresh = 1;
2547 break;
2548 case 'C':
2549 mode = 2; refresh = 1;
2550 break;
2552 case '?':
2553 help_window(main_help, NULL);
2554 refresh = 2;
2555 break;
2557 case KEY_RESIZE:
2558 refresh = 2;
2559 break;
2564 static void catch(int sig)
2566 if (sig == SIGINT) {
2567 signal(sig, catch);
2568 return;
2570 nocbreak();
2571 nl();
2572 endwin();
2573 printf("Died on signal %d\n", sig);
2574 exit(2);
2577 static void term_init(void)
2580 static int init_done = 0;
2582 if (init_done)
2583 return;
2584 init_done = 1;
2586 signal(SIGINT, catch);
2587 signal(SIGQUIT, catch);
2588 signal(SIGTERM, catch);
2589 signal(SIGBUS, catch);
2590 signal(SIGSEGV, catch);
2592 initscr(); cbreak(); noecho();
2593 start_color();
2594 use_default_colors();
2595 if (!has_colors()) {
2596 a_delete = A_UNDERLINE;
2597 a_added = A_BOLD;
2598 a_common = A_NORMAL;
2599 a_sep = A_STANDOUT;
2600 a_already = A_STANDOUT;
2601 } else {
2602 init_pair(1, COLOR_RED, -1);
2603 a_delete = COLOR_PAIR(1);
2604 init_pair(2, COLOR_GREEN, -1);
2605 a_added = COLOR_PAIR(2);
2606 a_common = A_NORMAL;
2607 init_pair(3, COLOR_WHITE, COLOR_GREEN);
2608 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
2609 init_pair(4, -1, COLOR_YELLOW);
2610 a_void = COLOR_PAIR(4);
2611 init_pair(5, COLOR_BLUE, -1);
2612 a_unmatched = COLOR_PAIR(5);
2613 init_pair(6, COLOR_CYAN, -1);
2614 a_extra = COLOR_PAIR(6);
2616 init_pair(7, COLOR_BLACK, COLOR_CYAN);
2617 a_already = COLOR_PAIR(7);
2619 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
2620 mousemask(ALL_MOUSE_EVENTS, NULL);
2623 int vpatch(int argc, char *argv[], int patch, int strip,
2624 int reverse, int replace)
2626 /* NOTE argv[0] is first arg...
2627 * Behaviour depends on number of args:
2628 * 0: A multi-file patch is read from stdin
2629 * 1: if 'patch', parse it as a multi-file patch and allow
2630 * the files to be browsed.
2631 * if filename ends '.rej', then treat it as a patch again
2632 * a file with the same basename
2633 * Else treat the file as a merge (with conflicts) and view it.
2634 * 2: First file is original, second is patch
2635 * 3: Files are: original previous new. The diff between 'previous' and
2636 * 'new' needs to be applied to 'original'.
2638 * If a multi-file patch is being read, 'strip' tells how many
2639 * path components to strip. If it is -1, we guess based on
2640 * existing files.
2641 * If 'reverse' is given, when we invert any patch or diff
2642 * If 'replace' then we save the resulting merge.
2644 FILE *in;
2645 FILE *f;
2646 struct plist *pl;
2647 int num_patches;
2649 switch (argc) {
2650 default:
2651 fprintf(stderr, "%s: too many file names given.\n", Cmd);
2652 exit(1);
2654 case 0: /* stdin is a patch */
2655 if (lseek(fileno(stdin), 0L, 1) == -1) {
2656 /* cannot seek, so need to copy to a temp file */
2657 f = tmpfile();
2658 if (!f) {
2659 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
2660 exit(1);
2662 pl = parse_patch(stdin, f, &num_patches);
2663 in = f;
2664 } else {
2665 pl = parse_patch(stdin, NULL, &num_patches);
2666 in = fdopen(dup(0), "r");
2668 /* use stderr for keyboard input */
2669 dup2(2, 0);
2670 if (set_prefix(pl, num_patches, strip) == 0) {
2671 fprintf(stderr, "%s: aborting\n", Cmd);
2672 exit(2);
2674 main_window(pl, num_patches, in, reverse);
2675 break;
2677 case 1: /* a patch, a .rej, or a merge file */
2678 f = fopen(argv[0], "r");
2679 if (!f) {
2680 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2681 exit(1);
2683 if (patch) {
2684 pl = parse_patch(f, NULL, &num_patches);
2685 if (set_prefix(pl, num_patches, strip) == 0) {
2686 fprintf(stderr, "%s: aborting\n", Cmd);
2687 exit(2);
2689 main_window(pl, num_patches, f, reverse);
2690 } else if (strlen(argv[0]) > 4 &&
2691 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
2692 char *origname = strdup(argv[0]);
2693 origname[strlen(origname) - 4] = '\0';
2694 show_merge(origname, f, reverse, 0, NULL, NULL);
2695 } else
2696 show_merge(argv[0], f, reverse, 1, NULL, NULL);
2698 break;
2699 case 2: /* an orig and a diff/.ref */
2700 f = fopen(argv[1], "r");
2701 if (!f) {
2702 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2703 exit(1);
2705 show_merge(argv[0], f, reverse, 0, NULL, NULL);
2706 break;
2707 case 3: /* orig, before, after */
2708 show_merge(argv[0], NULL, reverse, 1, argv[1], argv[2]);
2709 break;
2712 nocbreak();
2713 nl();
2714 endwin();
2715 exit(0);