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