Support searching backwards too.
[wiggle/upstream.git] / vpatch.c
blob9f33b85f8acb9db3015c9d8d5b329d234d245437
1 /*
2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Author: Neil Brown
22 * Email: <neilb@cse.unsw.edu.au>
23 * Paper: Neil Brown
24 * School of Computer Science and Engineering
25 * The University of New South Wales
26 * Sydney, 2052
27 * Australia
31 * vpatch - visual front end for wiggle
33 * "files" display, lists all files with statistics
34 * - can hide various lines including subdirectories
35 * and files without wiggles or conflicts
36 * "diff" display shows merged file with different parts
37 * in different colours
38 * - untouched are pale A_DIM
39 * - matched/remaining are regular A_NORMAL
40 * - matched/removed are red/underlined A_UNDERLINE
41 * - unmatched in file are A_STANDOUT
42 * - unmatched in patch are A_STANDOUT|A_UNDERLINE ???
43 * - inserted are inverse/green ?? A_REVERSE
45 * The window can be split horiz or vert and two different
46 * views displayed. They will have different parts missing
48 * So a display of NORMAL, underline, standout|underline reverse
49 * should show a normal patch.
53 #include "wiggle.h"
54 #include <malloc.h>
55 #include <string.h>
56 #include <curses.h>
57 #include <unistd.h>
58 #include <stdlib.h>
59 #include <signal.h>
60 #include <fcntl.h>
62 #define assert(x) do { if (!(x)) abort(); } while (0)
64 struct plist {
65 char *file;
66 unsigned int start, end;
67 int parent;
68 int next, prev, last;
69 int open;
70 int chunks, wiggles, conflicts;
71 int calced;
74 struct plist *patch_add_file(struct plist *pl, int *np, char *file,
75 unsigned int start, unsigned int end)
77 /* size of pl is 0, 16, n^2 */
78 int n = *np;
79 int asize;
81 while (*file == '/') file++; /* leading '/' are bad... */
83 /* printf("adding %s at %d: %u %u\n", file, n, start, end); */
84 if (n==0) asize = 0;
85 else if (n<=16) asize = 16;
86 else if ((n&(n-1))==0) asize = n;
87 else asize = n+1; /* not accurate, but not too large */
88 if (asize <= n) {
89 /* need to extend array */
90 struct plist *npl;
91 if (asize < 16) asize = 16;
92 else asize += asize;
93 npl = realloc(pl, asize * sizeof(struct plist));
94 if (!npl) {
95 fprintf(stderr, "malloc failed - skipping %s\n", file);
96 return pl;
98 pl = npl;
100 pl[n].file = file;
101 pl[n].start = start;
102 pl[n].end = end;
103 pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
104 pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100;
105 pl[n].open = 1;
106 pl[n].calced = 0;
107 *np = n+1;
108 return pl;
113 struct plist *parse_patch(FILE *f, FILE *of, int *np)
115 /* read a multi-file patch from 'f' and record relevant
116 * details in a plist.
117 * if 'of' >= 0, fd might not be seekable so we write
118 * to 'of' and use lseek on 'of' to determine position
120 struct plist *plist = NULL;
122 while (!feof(f)) {
123 /* first, find the start of a patch: "\n+++ "
124 * grab the file name and scan to the end of a line
126 char *target="\n+++ ";
127 char *target2="\n--- ";
128 char *pos = target;
129 int c;
130 char name[1024];
131 unsigned start, end;
133 while (*pos && (c=fgetc(f)) != EOF ) {
134 if (of) fputc(c, of);
135 if (c == *pos)
136 pos++;
137 else pos = target;
139 if (c == EOF)
140 break;
141 assert(c == ' ');
142 /* now read a file name */
143 pos = name;
144 while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' &&
145 pos - name < 1023) {
146 *pos++ = c;
147 if (of) fputc(c, of);
149 *pos = 0;
150 if (c == EOF)
151 break;
152 if (of) fputc(c, of);
153 while (c != '\n' && (c=fgetc(f)) != EOF) {
154 if (of) fputc(c, of);
156 start = of ? ftell(of) : ftell(f);
158 if (c == EOF) break;
160 /* now skip to end - "\n--- " */
161 pos = target2+1;
163 while (*pos && (c=fgetc(f)) != EOF) {
164 if (of) fputc(c, of);
165 if (c == *pos)
166 pos++;
167 else pos = target2;
169 end = of ? ftell(of) : ftell(f);
170 if (pos > target2)
171 end -= (pos - target2) - 1;
172 plist = patch_add_file(plist, np,
173 strdup(name), start, end);
175 return plist;
177 #if 0
178 void die()
180 fprintf(stderr,"vpatch: fatal error\n");
181 abort();
182 exit(3);
184 #endif
186 static struct stream load_segment(FILE *f,
187 unsigned int start, unsigned int end)
189 struct stream s;
190 s.len = end - start;
191 s.body = malloc(s.len);
192 if (s.body) {
193 fseek(f, start, 0);
194 if (fread(s.body, 1, s.len, f) != s.len) {
195 free(s.body);
196 s.body = NULL;
198 } else
199 die();
200 return s;
204 void catch(int sig)
206 if (sig == SIGINT) {
207 signal(sig, catch);
208 return;
210 nocbreak();nl();endwin();
211 printf("Died on signal %d\n", sig);
212 exit(2);
215 int pl_cmp(const void *av, const void *bv)
217 const struct plist *a = av;
218 const struct plist *b = bv;
219 return strcmp(a->file, b->file);
222 int common_depth(char *a, char *b)
224 /* find number of patch segments that these two have
225 * in common
227 int depth = 0;
228 while(1) {
229 char *c;
230 int al, bl;
231 c = strchr(a, '/');
232 if (c) al = c-a; else al = strlen(a);
233 c = strchr(b, '/');
234 if (c) bl = c-b; else bl = strlen(b);
235 if (al == 0 || al != bl || strncmp(a,b,al) != 0)
236 return depth;
237 a+= al;
238 while (*a=='/') a++;
239 b+= bl;
240 while(*b=='/') b++;
242 depth++;
246 struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
248 /* any parent of file that is not a parent of curr
249 * needs to be added to pl
251 int d = common_depth(file, curr);
252 char *buf = curr;
253 while (d) {
254 char *c = strchr(file, '/');
255 int l;
256 if (c) l = c-file; else l = strlen(file);
257 file += l;
258 curr += l;
259 while (*file == '/') file++;
260 while (*curr == '/') curr++;
261 d--;
263 while (*file) {
264 if (curr > buf && curr[-1] != '/')
265 *curr++ = '/';
266 while (*file && *file != '/')
267 *curr++ = *file++;
268 while (*file == '/') *file++;
269 *curr = '\0';
270 if (*file)
271 pl = patch_add_file(pl, np, strdup(buf),
272 0, 0);
274 return pl;
277 struct plist *sort_patches(struct plist *pl, int *np)
279 /* sort the patches, add directory names, and re-sort */
280 char curr[1024];
281 char *prev;
282 int parents[100];
283 int prevnode[100];
284 int i, n;
285 qsort(pl, *np, sizeof(struct plist), pl_cmp);
286 curr[0] = 0;
287 n = *np;
288 for (i=0; i<n; i++)
289 pl = add_dir(pl, np, pl[i].file, curr);
291 qsort(pl, *np, sizeof(struct plist), pl_cmp);
293 /* array is now stable, so set up parent pointers */
294 n = *np;
295 curr[0] = 0;
296 prevnode[0] = -1;
297 prev = "";
298 for (i=0; i<n; i++) {
299 int d = common_depth(prev, pl[i].file);
300 if (d == 0)
301 pl[i].parent = -1;
302 else {
303 pl[i].parent = parents[d-1];
304 pl[pl[i].parent].last = i;
306 pl[i].prev = prevnode[d];
307 if (pl[i].prev > -1)
308 pl[pl[i].prev].next = i;
309 prev = pl[i].file;
310 parents[d] = i;
311 prevnode[d] = i;
312 prevnode[d+1] = -1;
314 return pl;
317 int get_strip(char *file)
319 int fd;
320 int strip = 0;
322 while (file && *file) {
323 fd = open(file, O_RDONLY);
324 if (fd >= 0) {
325 close(fd);
326 return strip;
328 strip++;
329 file = strchr(file, '/');
330 if (file)
331 while(*file == '/')
332 file++;
334 return -1;
337 int set_prefix(struct plist *pl, int n, int strip)
339 int i;
340 for(i=0; i<4 && i<n && strip < 0; i++)
341 strip = get_strip(pl[i].file);
343 if (strip < 0) {
344 fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
345 Cmd);
346 return 0;
348 for (i=0; i<n; i++) {
349 char *p = pl[i].file;
350 int j;
351 for (j=0; j<strip; j++) {
352 if (p) p = strchr(p,'/');
353 while (p && *p == '/') p++;
355 if (p == NULL) {
356 fprintf(stderr, "%s: cannot strip %d segments from %s\n",
357 Cmd, strip, pl[i].file);
358 return 0;
360 pl[i].file = p;
362 return 1;
366 int get_prev(int pos, struct plist *pl, int n)
368 if (pos == -1) return pos;
369 if (pl[pos].prev == -1)
370 return pl[pos].parent;
371 pos = pl[pos].prev;
372 while (pl[pos].open &&
373 pl[pos].last >= 0)
374 pos = pl[pos].last;
375 return pos;
378 int get_next(int pos, struct plist *pl, int n)
380 if (pos == -1) return pos;
381 if (pl[pos].open) {
382 if (pos +1 < n)
383 return pos+1;
384 else
385 return -1;
387 while (pos >= 0 && pl[pos].next == -1)
388 pos = pl[pos].parent;
389 if (pos >= 0)
390 pos = pl[pos].next;
391 return pos;
394 /* global attributes */
395 int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already;
397 void draw_one(int row, struct plist *pl, FILE *f, int reverse)
399 char hdr[10];
400 hdr[0] = 0;
402 if (pl == NULL) {
403 move(row,0);
404 clrtoeol();
405 return;
407 if (pl->calced == 0 && pl->end) {
408 /* better load the patch and count the chunks */
409 struct stream s1, s2;
410 struct stream s = load_segment(f, pl->start, pl->end);
411 struct stream sf = load_file(pl->file);
412 if (reverse)
413 pl->chunks = split_patch(s, &s2, &s1);
414 else
415 pl->chunks = split_patch(s, &s1, &s2);
417 if (sf.body == NULL) {
418 pl->wiggles = pl->conflicts = -1;
419 } else {
420 struct file ff, fp1, fp2;
421 struct csl *csl1, *csl2;
422 struct ci ci;
423 ff = split_stream(sf, ByWord, 0);
424 fp1 = split_stream(s1, ByWord, 0);
425 fp2 = split_stream(s2, ByWord, 0);
426 csl1 = pdiff(ff, fp1, pl->chunks);
427 csl2 = diff(fp1,fp2);
428 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0);
429 pl->wiggles = ci.wiggles;
430 pl->conflicts = ci.conflicts;
431 free(csl1);
432 free(csl2);
433 free(ff.list);
434 free(fp1.list);
435 free(fp2.list);
438 free(s1.body);
439 free(s2.body);
440 free(s.body);
441 pl->calced = 1;
443 if (pl->end == 0) {
444 strcpy(hdr, " ");
445 } else {
446 if (pl->chunks > 99)
447 strcpy(hdr, "XX");
448 else sprintf(hdr, "%2d", pl->chunks);
449 if (pl->wiggles > 99)
450 strcpy(hdr+2, " XX");
451 else sprintf(hdr+2, " %2d", pl->wiggles);
452 if (pl->conflicts > 99)
453 strcpy(hdr+5, " XX ");
454 else sprintf(hdr+5, " %2d ", pl->conflicts);
456 if (pl->end)
457 strcpy(hdr+9, "= ");
458 else if (pl->open)
459 strcpy(hdr+9, "+ ");
460 else strcpy(hdr+9, "- ");
462 mvaddstr(row, 0, hdr);
463 mvaddstr(row, 11, pl->file);
464 clrtoeol();
467 void addword(struct elmnt e)
469 addnstr(e.start, e.len);
471 void addsep(struct elmnt e1, struct elmnt e2)
473 int a,b,c,d,e,f;
474 char buf[50];
475 attron(a_sep);
476 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
477 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
478 sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c,e,f);
479 addstr(buf);
480 attroff(a_sep);
482 #define BEFORE 1
483 #define AFTER 2
484 #define ORIG 4
485 #define RESULT 8
486 #define CHANGED 32 /* The RESULT is different to ORIG */
487 #define CHANGES 64 /* AFTER is different to BEFORE */
489 struct pos {
490 int a,b,c;
492 struct elmnt prev_elmnt(struct pos *pos, int mode,
493 struct file f1, struct file f2, struct csl *csl)
495 while(1) {
496 int a1, b1;
497 if (pos->a > csl[pos->c].a) {
498 assert(pos->b > csl[pos->c].b);
499 pos->a--;
500 pos->b--;
501 return f1.list[pos->a];
503 b1=0;
504 if (pos->c) b1 = csl[pos->c-1].b+csl[pos->c-1].len;
505 if (pos->b > b1) {
506 pos->b--;
507 if (!(mode&AFTER))
508 continue;
509 return f2.list[pos->b];
511 a1=0;
512 if (pos->c) a1 = csl[pos->c-1].a+csl[pos->c-1].len;
513 if (pos->a > a1) {
514 pos->a--;
515 if (!(mode&BEFORE))
516 continue;
517 return f1.list[pos->a];
519 /* must be time to go to previous common segment */
520 pos->c--;
521 if (pos->c < 0) {
522 struct elmnt e;
523 e.start = NULL; e.len = 0;
524 return e;
529 struct elmnt next_elmnt(struct pos *pos, int mode, int *type,
530 struct file f1, struct file f2, struct csl *csl)
532 if (pos->c < 0) {
533 struct elmnt e;
534 e.start = NULL; e.len = 0;
535 return e;
537 while(1) {
538 int a1;
539 *type = a_delete;
540 if (pos->a < csl[pos->c].a) {
541 if (mode & BEFORE)
542 return f1.list[pos->a++];
543 pos->a++;
544 continue;
546 *type = a_added;
547 if (pos->b < csl[pos->c].b) {
548 if (mode & AFTER)
549 return f2.list[pos->b++];
550 pos->b++;
551 continue;
553 *type = a_common;
554 a1 = csl[pos->c].a + csl[pos->c].len;
555 if (pos->a < a1) {
556 pos->b++;
557 return f1.list[pos->a++];
559 if (csl[pos->c].len == 0) {
560 struct elmnt e;
561 e.start = NULL; e.len = 0;
562 pos->c = -1;
563 return e;
565 pos->c++;
569 void pos_sol(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
571 /* find the start-of-line before 'pos' considering 'mode' only.
573 if (pos->c < 0) return;
574 while(1) {
575 struct pos tpos = *pos;
576 struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
577 if (e.start == NULL) {
578 return;
580 if (e.start[0] == '\n' || e.start[0] == 0)
581 return;
582 *pos = tpos;
586 void prev_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
588 /* find the start-of-line before 'pos' considering 'mode' only.
590 int eol=0;
591 if (pos->c < 0) return;
592 while(1) {
593 struct pos tpos = *pos;
594 struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
595 if (e.start == NULL) {
596 pos->c = -1;
597 return;
599 if (e.start[0] == '\n' || e.start[0] == 0) {
600 if (eol == 1)
601 return;
602 eol=1;
604 *pos = tpos;
605 if (e.start[0] == 0) return;
610 void next_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
612 /* find the start-of-line after 'pos' considering 'mode' only.
614 int type;
615 if (pos->c < 0) return;
616 while(1) {
617 struct pos tpos = *pos;
618 struct elmnt e = next_elmnt(&tpos, mode, &type, f1, f2, csl);
619 if (e.start == NULL) {
620 pos->c = -1;
621 return;
623 *pos = tpos;
624 if (e.start[0] == '\n') {
625 return;
627 if (e.start[0] == 0)
628 return;
632 void draw_line(int i, struct pos pos, int mode,
633 struct file f1, struct file f2, struct csl *csl, int start, int len)
635 int col = 0;
636 int attr;
637 struct elmnt e1;
638 move(i, col);
639 while (1) {
640 e1 = next_elmnt(&pos, mode, &attr, f1, f2, csl);
641 if (e1.start == NULL) {
642 attr = a_void;
643 break;
645 if (e1.start[0] == '\0') {
646 int a,b,c;
647 int d,e,f;
648 struct elmnt e2 = f2.list[pos.b-1];
649 char buf[50];
650 attrset(a_sep);
651 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
652 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
653 sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c, e,f);
654 addstr(buf);
655 break;
656 } else {
657 unsigned char *c;
658 int l;
659 attrset(attr);
660 if (e1.start[0] == '\n') {
661 break;
663 c = (unsigned char *)e1.start;
664 l = e1.len;
665 while (l) {
666 if (*c >= ' ' && *c != 0x7f) {
667 /* normal width char */
668 if (col >= start && col < len+start)
669 mvaddch(i,col-start, *c);
670 col++;
671 } else if (*c == '\t') {
672 do {
673 if (col >= start && col < len+start)
674 mvaddch(i, col-start, ' ');
675 col++;
676 } while ((col&7)!= 0);
677 } else {
678 if (col>=start && col < len+start)
679 mvaddch(i, col-start, *c=='\n'?'@':'?');
680 col++;
682 c++;
683 l--;
687 bkgdset(attr);
688 clrtoeol();
689 bkgdset(A_NORMAL);
692 void diff_window(struct plist *p, FILE *f)
694 /* display just the diff, either 'before' or 'after' or all.
696 * In any case highlighting is the same
698 * Current pos is given as a,b,csl where a and b are
699 * before or in the common segment, and one is immediately
700 * after a newline.
701 * The current row is given by 'row'.
702 * Lines do not wrap, be horizontal scrolling is supported.
704 * We don't insist that the top line lies at or above the top
705 * of the screen, as that allows alignment of different views
706 * more easily.
708 struct stream s;
709 struct stream s1, s2;
710 struct file f1, f2;
711 struct csl *csl;
712 char buf[100];
713 int ch;
714 int row, rows, cols;
715 int start = 0;
716 int i;
717 int c;
718 int refresh = 2;
719 int mode = BEFORE|AFTER;
721 struct pos pos, tpos;
723 s = load_segment(f, p->start, p->end);
724 ch = split_patch(s, &s1, &s2);
727 f1 = split_stream(s1, ByWord, 0);
728 f2 = split_stream(s2, ByWord, 0);
730 csl = diff(f1, f2);
732 pos.a=0; pos.b=0; pos.c=0;
733 row = 1;
735 while(1) {
736 if (refresh == 2) {
737 clear();
738 sprintf(buf, "File: %s\n", p->file);
739 attrset(A_BOLD); mvaddstr(0,0,buf); clrtoeol(); attrset(A_NORMAL);
740 refresh = 1;
742 if (row < 1 || row >= rows)
743 refresh = 1;
744 if (refresh) {
745 refresh = 0;
746 getmaxyx(stdscr,rows,cols);
747 if (row < -3)
748 row = (rows+1)/2;
749 if (row < 1)
750 row = 1;
751 if (row >= rows+3)
752 row = (rows+1)/2;
753 if (row >= rows)
754 row = rows-1;
755 tpos = pos;
756 pos_sol(&tpos, mode, f1, f2, csl);
757 draw_line(row, tpos, mode, f1, f2, csl, start, cols);
758 for (i=row-1; i>=1; i--) {
759 prev_pos(&tpos, mode, f1,f2,csl);
760 draw_line(i, tpos, mode, f1, f2,csl, start,cols);
762 tpos = pos;
763 for (i=row+1; i<rows; i++) {
764 next_pos(&tpos, mode, f1, f2, csl);
765 draw_line(i, tpos, mode, f1, f2, csl, start,cols);
768 move(row,0);
769 c = getch();
770 switch(c) {
771 case 27: /* escape */
772 break;
773 case 'q':
774 return;
775 case 'L'-64:
776 refresh = 2;
777 break;
779 case 'j':
780 case 'n':
781 case 'N':
782 case 'N'-64:
783 case KEY_DOWN:
784 tpos = pos;
785 next_pos(&tpos, mode, f1, f2, csl);
786 if (tpos.c >= 0) {
787 pos = tpos;
788 row++;
790 break;
791 case 'k':
792 case 'p':
793 case 'P':
794 case 'P'-64:
795 case KEY_UP:
796 tpos = pos;
797 prev_pos(&tpos, mode, f1, f2, csl);
798 if (tpos.c >= 0) {
799 pos = tpos;
800 row--;
802 break;
804 case 'a':
805 mode = AFTER;
806 refresh = 1;
807 break;
808 case 'b':
809 mode = BEFORE;
810 refresh = 1;
811 break;
812 case 'x':
813 mode = AFTER|BEFORE;
814 refresh = 1;
815 break;
817 case 'h':
818 case KEY_LEFT:
819 if (start > 0) start--;
820 refresh = 1;
821 break;
822 case 'l':
823 case KEY_RIGHT:
824 if (start < cols) start++;
825 refresh = 1;
826 break;
831 struct mpos {
832 struct mp {
833 int m; /* merger index */
834 int s; /* stream 0,1,2 for a,b,c */
835 int o; /* offset in that stream */
836 } p, /* the current point */
837 lo, /* eol for start of the current group */
838 hi; /* eol for end of the current group */
839 int side; /* -1 if on the '-' of a diff,
840 * +1 if on the '+',
841 * 0 if on an unchanged (lo/hi not meaningful)
844 int same_mpos(struct mpos a, struct mpos b)
846 return a.p.m == b.p.m &&
847 a.p.s == b.p.s &&
848 a.p.o == b.p.o &&
849 a.side == b.side;
852 int invalid(int s, enum mergetype type)
854 switch(type) {
855 case End: return 0;
856 case Unmatched: return s>0;
857 case Unchanged: return s>0;
858 case Extraneous: return s<2;
859 case Changed: return s==1;
860 case Conflict: return 0;
861 case AlreadyApplied: return 0;
863 return 0;
866 struct elmnt next_melmnt(struct mpos *pos,
867 struct file fm, struct file fb, struct file fa,
868 struct merge *m)
870 int l;
871 pos->p.o++;
872 while(1) {
873 if (pos->p.m < 0)
874 l = 0;
875 else switch(pos->p.s) {
876 case 0: l = m[pos->p.m].al; break;
877 case 1: l = m[pos->p.m].bl; break;
878 case 2: l = m[pos->p.m].cl; break;
880 if (pos->p.o >= l) {
881 pos->p.o = 0;
882 do {
883 pos->p.s++;
884 if (pos->p.s > 2) {
885 pos->p.s = 0;
886 pos->p.m++;
888 } while (invalid(pos->p.s, m[pos->p.m].type));
889 } else
890 break;
892 if (pos->p.m == -1 || m[pos->p.m].type == End) {
893 struct elmnt e;
894 e.start = NULL; e.len = 0;
895 return e;
897 switch(pos->p.s) {
898 default: /* keep compiler happy */
899 case 0: return fm.list[m[pos->p.m].a + pos->p.o];
900 case 1: return fb.list[m[pos->p.m].b + pos->p.o];
901 case 2: return fa.list[m[pos->p.m].c + pos->p.o];
905 struct elmnt prev_melmnt(struct mpos *pos,
906 struct file fm, struct file fb, struct file fa,
907 struct merge *m)
909 pos->p.o--;
910 while (pos->p.m >=0 && pos->p.o < 0) {
911 do {
912 pos->p.s--;
913 if (pos->p.s < 0) {
914 pos->p.s = 2;
915 pos->p.m--;
917 } while (pos->p.m >= 0 && invalid(pos->p.s, m[pos->p.m].type));
918 if (pos->p.m>=0) {
919 switch(pos->p.s) {
920 case 0: pos->p.o = m[pos->p.m].al-1; break;
921 case 1: pos->p.o = m[pos->p.m].bl-1; break;
922 case 2: pos->p.o = m[pos->p.m].cl-1; break;
926 if (pos->p.m < 0) {
927 struct elmnt e;
928 e.start = NULL; e.len = 0;
929 return e;
931 switch(pos->p.s) {
932 default: /* keep compiler happy */
933 case 0: return fm.list[m[pos->p.m].a + pos->p.o];
934 case 1: return fb.list[m[pos->p.m].b + pos->p.o];
935 case 2: return fa.list[m[pos->p.m].c + pos->p.o];
939 int visible(int mode, enum mergetype type, int stream)
941 #if 0
942 switch(side){
943 case 0: /* left - orig plus merge */
944 switch(type) {
945 case End: return A_NORMAL;
946 case Unmatched: return stream == 0 ? a_unmatched : -1;
947 case Unchanged: return stream == 0 ? a_common : -1;
948 case Extraneous: return -1;
949 case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
950 case Conflict: return stream == 0 ? a_unmatched : -1;
951 case AlreadyApplied: return stream == 0 ? a_unmatched : -1;
953 break;
954 case 1: /* right - old plus new */
955 switch(type) {
956 case End: return A_NORMAL;
957 case Unmatched: return -1;
958 case Unchanged: return stream == 0 ? a_common : -1;
959 case Extraneous: return stream==2 ? a_extra : -1;
960 case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
961 case Conflict: return stream==0?-1:stream==1?(a_delete|A_UNDERLINE):a_added;
962 case AlreadyApplied: return stream==0?-1:stream==1?a_delete:a_added;
965 return 1;
966 #endif
967 if (mode == 0) return -1;
968 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
969 switch(type) {
970 case End: /* The END is always visible */
971 return A_NORMAL;
972 case Unmatched: /* Visible in ORIG and RESULT */
973 if (mode & (ORIG|RESULT))
974 return a_unmatched;
975 break;
976 case Unchanged: /* visible everywhere, but only show stream 0 */
977 if (stream == 0) return a_common;
978 break;
979 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
980 if ((mode & (BEFORE|AFTER))
981 && stream == 2)
982 return a_extra;
983 break;
984 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
985 if (stream == 0 &&
986 (mode & (ORIG|BEFORE)))
987 return a_delete;
988 if (stream == 2 &&
989 (mode & (RESULT|AFTER)))
990 return a_added;
991 break;
992 case Conflict:
993 switch(stream) {
994 case 0:
995 if (mode & (ORIG|RESULT))
996 return a_unmatched | A_REVERSE;
997 break;
998 case 1:
999 if (mode & BEFORE)
1000 return a_extra | A_UNDERLINE;
1001 break;
1002 case 2:
1003 if (mode & (AFTER|RESULT))
1004 return a_added | A_UNDERLINE;
1005 break;
1007 break;
1008 case AlreadyApplied:
1009 switch(stream) {
1010 case 0:
1011 if (mode & (ORIG|RESULT))
1012 return a_already;
1013 break;
1014 case 1:
1015 if (mode & BEFORE)
1016 return a_delete | A_UNDERLINE;
1017 break;
1018 case 2:
1019 if (mode & AFTER)
1020 return a_added | A_UNDERLINE;
1021 break;
1023 break;
1025 return -1;
1029 int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa,
1030 struct merge *m, int mode);
1032 void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
1033 struct merge *m, int mode)
1035 int mask;
1036 do {
1037 struct mp prv;
1038 int mode2;
1040 prv = pos->p;
1041 #if 0
1042 if (pos->p.m < 0 || m[pos->p.m].type == End) {
1043 if (pos->side == -1) {
1044 pos->side = 1;
1045 pos->hi = prv;
1046 pos->p = pos->lo;
1047 continue;
1048 } else
1049 return;
1051 #endif
1052 while (1) {
1053 struct elmnt e = next_melmnt(pos, fm,fb,fa,m);
1054 if (e.start == NULL)
1055 break;
1056 if (ends_mline(e) &&
1057 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
1058 break;
1060 mode2 = check_line(*pos, fm,fb,fa,m,mode);
1062 if (pos->side == 1 && !(mode2 & CHANGES))
1063 /* left a diff-set */
1064 pos->side = 0;
1065 else if (pos->side == -1 && !(mode2 & CHANGES)) {
1066 /*flip from '-' to '+' - need to ensure still visible */
1067 pos->side = 1;
1068 pos->hi = prv;
1069 pos->p = pos->lo;
1070 } else if (pos->side == 0 && (mode2 & CHANGES)) {
1071 /* entered a diff-set */
1072 pos->side = -1;
1073 pos->lo = pos->p;
1075 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
1076 if (pos->side == 1)
1077 mask &= ~(ORIG|BEFORE);
1078 if (pos->side == -1)
1079 mask &= ~(RESULT|AFTER);
1080 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
1084 void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
1085 struct merge *m, int mode)
1087 int mask;
1088 do {
1089 struct mp prv;
1090 int mode2;
1092 prv = pos->p;
1093 if (pos->p.m < 0)
1094 return;
1095 while(1) {
1096 struct elmnt e = prev_melmnt(pos, fm,fb,fa,m);
1097 if (e.start == NULL)
1098 break;
1099 if (ends_mline(e) &&
1100 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
1101 break;
1103 mode2 = check_line(*pos, fm,fb,fa,m,mode);
1105 if (pos->side == -1 && !(mode2 & CHANGES))
1106 /* we have stepped out of a diff-set */
1107 pos->side = 0;
1108 else if (pos->side == 1 && !(mode2 & CHANGES)) {
1109 /* flipped from '+' to '-' */
1110 pos->side = -1;
1111 pos->lo = prv;
1112 pos->p = pos->hi;
1113 } else if (pos->side == 0 && (mode2 & CHANGES)) {
1114 /* entered a diffset */
1115 pos->side = 1;
1116 pos->hi = pos->p;
1118 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
1119 if (pos->side == 1)
1120 mask &= ~(ORIG|BEFORE);
1121 if (pos->side == -1)
1122 mask &= ~(RESULT|AFTER);
1123 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
1127 void blank(int row, int start, int cols, int attr)
1129 attrset(attr);
1130 move(row,start);
1131 while (cols-- > 0)
1132 addch(' ');
1135 /* Checkline determines how many screen lines are needed to display
1136 * a file line.
1137 * Options are:
1138 * - one line that is before/original
1139 * - one line that is after/result
1140 * - one line that is unchanged/unmatched/extraneous
1141 * - two lines, but only one on the left.
1142 * - two lines, one before and one after.
1143 * CLARIFY/CORRECT this.
1145 int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa,
1146 struct merge *m, int mode)
1148 int rv = 0;
1149 struct elmnt e;
1151 if (visible(ORIG, m[pos.p.m].type, pos.p.s) >= 0)
1152 rv |= ORIG;
1153 if (visible(RESULT, m[pos.p.m].type, pos.p.s) >= 0)
1154 rv |= RESULT;
1155 if (visible(BEFORE, m[pos.p.m].type, pos.p.s) >= 0)
1156 rv |= BEFORE;
1157 if (visible(AFTER, m[pos.p.m].type, pos.p.s) >= 0)
1158 rv |= AFTER;
1160 do {
1161 if (m[pos.p.m].type == Changed)
1162 rv |= CHANGED | CHANGES;
1163 else if ((m[pos.p.m].type == AlreadyApplied ||
1164 m[pos.p.m].type == Conflict))
1165 rv |= CHANGES;
1166 e = prev_melmnt(&pos, fm,fb,fa,m);
1167 } while (e.start != NULL &&
1168 (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
1170 return rv & (mode | CHANGED | CHANGES);
1173 int mcontains(struct mpos pos,
1174 struct file fm, struct file fb, struct file fa, struct merge *m,
1175 int mode, char *search)
1177 /* See if and of the files, between start of this line and here,
1178 * contain the search string
1180 struct elmnt e;
1181 int len = strlen(search);
1182 do {
1183 e = prev_melmnt(&pos, fm,fb,fa,m);
1184 if (e.start) {
1185 int i;
1186 for (i=0; i<e.len; i++)
1187 if (strncmp(e.start+i, search, len)==0)
1188 return 1;
1190 } while (e.start != NULL &&
1191 (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
1192 return 0;
1195 void draw_mside(int mode, int row, int offset, int start, int cols,
1196 struct file fm, struct file fb, struct file fa, struct merge *m,
1197 struct mpos pos,
1198 int target, int *colp)
1200 /* find previous visible newline, or start of file */
1201 struct elmnt e;
1202 int col = 0;
1203 do {
1204 e = prev_melmnt(&pos, fm,fb,fa,m);
1205 } while (e.start != NULL &&
1206 (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
1208 while (1) {
1209 unsigned char *c;
1210 int l;
1211 e = next_melmnt(&pos, fm,fb,fa,m);
1212 if (e.start == NULL ||
1213 (ends_mline(e) && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
1214 if (colp) *colp = col;
1215 if (col < start) col = start;
1216 if (e.start && e.start[0] == 0) {
1217 attrset(visible(mode, m[pos.p.m].type, pos.p.s));
1218 mvaddstr(row, col-start+offset, "SEP");
1219 col += 3;
1221 blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.p.m].type, pos.p.s):A_NORMAL );
1222 return;
1224 if (visible(mode, m[pos.p.m].type, pos.p.s) == -1) {
1225 continue;
1227 if (e.start[0] == 0)
1228 continue;
1229 attrset(visible(mode, m[pos.p.m].type, pos.p.s));
1230 c = (unsigned char *)e.start;
1231 l = e.len;
1232 while(l) {
1233 if (*c >= ' ' && *c != 0x7f) {
1234 if (col >= start && col < start+cols)
1235 mvaddch(row, col-start+offset, *c);
1236 col++;
1237 } else if (*c == '\t') {
1238 do {
1239 if (col >= start && col < start+cols)
1240 mvaddch(row, col-start+offset, ' ');
1241 col++;
1242 } while ((col&7)!= 0);
1243 } else {
1244 if (col >= start && col < start+cols)
1245 mvaddch(row, col-start+offset, '?');
1246 col++;
1248 c++;
1249 if (colp && target <= col) {
1250 *colp = col;
1251 colp = NULL;
1253 l--;
1258 void draw_mline(int row, struct mpos pos,
1259 struct file fm, struct file fb, struct file fa,
1260 struct merge *m,
1261 int start, int cols, int mode,
1262 int target, int *colp)
1265 * Draw the left and right images of this line
1266 * One side might be a_blank depending on the
1267 * visibility of this newline
1268 * If Changed is found, return ORIG|RESULT | BEFORE|AFTER,
1269 * If AlreadyApplied or Conflict, return BEFORE|AFTER
1271 int lcols, rcols;
1272 lcols = (cols-1)/2-1;
1273 rcols = cols - lcols - 3;
1275 attrset(A_STANDOUT);
1276 mvaddch(row, lcols+1, '|');
1278 attrset(A_NORMAL);
1279 if (!(mode & CHANGES)) {
1280 mvaddch(row, 0, ' ');
1281 mvaddch(row, lcols+2, ' ');
1282 } else if (mode & (ORIG|BEFORE)) {
1283 mvaddch(row, 0, '-');
1284 mvaddch(row, lcols+2, '-');
1285 } else {
1286 mvaddch(row, 0, '+');
1287 mvaddch(row, lcols+2, '+');
1290 if (visible(mode&(ORIG|RESULT), m[pos.p.m].type, pos.p.s) >= 0)
1291 /* visible on left */
1292 draw_mside(mode&(ORIG|RESULT), row, 1, start, lcols,
1293 fm,fb,fa,m, pos, target, colp);
1294 else {
1295 blank(row, 0, lcols+1, a_void);
1296 if (colp) *colp = 0;
1299 if (visible(mode&(BEFORE|AFTER), m[pos.p.m].type, pos.p.s) >= 0)
1300 /* visible on right */
1301 draw_mside(mode&(BEFORE|AFTER), row, lcols+3, start, rcols,
1302 fm,fb,fa,m, pos, 0, NULL);
1303 else
1304 blank(row, lcols+2, rcols+1, a_void);
1307 extern void cleanlist(struct file a, struct file b, struct csl *list);
1309 void merge_window(struct plist *p, FILE *f, int reverse)
1311 /* display the merge in two side-by-side
1312 * panes.
1313 * left side shows diff between original and new
1314 * right side shows the requested patch
1316 * Unmatched: c_unmatched - left only
1317 * Unchanged: c_normal - left and right
1318 * Extraneous: c_extra - right only
1319 * Changed-a: c_changed - left and right
1320 * Changed-c: c_new - left and right
1321 * AlreadyApplied-b: c_old - right only
1322 * AlreadyApplied-c: c_applied - left and right
1323 * Conflict-a: ?? left only
1324 * Conflict-b: ?? left and right
1325 * Conflict-c: ??
1327 * A Conflict is displayed as the original in the
1328 * left side, and the highlighted diff in the right.
1330 * Newlines are the key to display.
1331 * 'pos' is always a newline (or eof).
1332 * For each side that this newline is visible on, we
1333 * rewind the previous newline visible on this side, and
1334 * the display the stuff in between
1336 * A 'position' is an offset in the merger, a stream
1337 * choice (a,b,c - some aren't relevant) and an offset in
1338 * that stream
1341 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1342 struct file fm, fb, fa;
1343 struct csl *csl1, *csl2;
1344 struct ci ci;
1345 char buf[100];
1346 int ch;
1347 int refresh = 2;
1348 int rows,cols;
1349 int i, c;
1350 int mode = ORIG|RESULT | BEFORE|AFTER;
1352 int row,start = 0;
1353 int trow;
1354 int col=0, target=0;
1355 struct mpos pos;
1356 struct mpos tpos, toppos, botpos;
1357 int toprow,botrow;
1358 int mode2;
1359 int meta = 0, tmeta;
1360 int num= -1, tnum;
1361 char search[80];
1362 int searchlen = 0;
1363 int search_notfound = 0;
1364 int searchdir;
1365 struct search_anchor {
1366 struct search_anchor *next;
1367 struct mpos pos;
1368 int notfound;
1369 int row, col, searchlen;
1370 } *anchor = NULL;
1372 sp = load_segment(f, p->start, p->end);
1373 if (reverse)
1374 ch = split_patch(sp, &sa, &sb);
1375 else
1376 ch = split_patch(sp, &sb, &sa);
1378 sm = load_file(p->file);
1379 fm = split_stream(sm, ByWord, 0);
1380 fb = split_stream(sb, ByWord, 0);
1381 fa = split_stream(sa, ByWord, 0);
1383 csl1 = pdiff(fm, fb, ch);
1384 csl2 = diff(fb, fa);
1385 #if 0
1386 cleanlist(fm, fb, csl1);
1387 cleanlist(fb, fa, csl2);
1388 #endif
1390 ci = make_merger(fm, fb, fa, csl1, csl2, 0);
1392 row = 1;
1393 pos.p.m = 0; /* merge node */
1394 pos.p.s = 0; /* stream number */
1395 pos.p.o = -1; /* offset */
1396 next_mline(&pos, fm,fb,fa,ci.merger, mode);
1397 mode2 = check_line(pos, fm,fb,fa, ci.merger, mode);
1398 if (!(mode2 & CHANGES))
1399 pos.side = 0;
1400 else {
1401 if (mode2 & (ORIG|BEFORE))
1402 pos.side = -1;
1403 else
1404 pos.side = 1;
1405 pos.lo = pos.p;
1406 tpos = pos;
1407 do {
1408 pos.hi = tpos.p;
1409 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1410 mode2 = check_line(tpos, fm,fb,fa, ci.merger, mode);
1411 } while ((mode2 & CHANGES) && ci.merger[tpos.p.m].type != End);
1414 while(1) {
1415 if (refresh == 2) {
1416 clear();
1417 sprintf(buf, "File: %s%s\n", p->file,reverse?" - reversed":"");
1418 attrset(A_BOLD); mvaddstr(0,0,buf);
1419 clrtoeol();
1420 attrset(A_NORMAL);
1421 refresh = 1;
1423 if (row < 1 || row >= rows)
1424 refresh = 1;
1427 if (refresh) {
1428 getmaxyx(stdscr, rows, cols);
1429 rows--; /* keep last row clear */
1430 if (row < -3)
1431 row = (rows-1)/2+1;
1432 if (row < 1)
1433 row = 1;
1434 if (row > rows+3)
1435 row = (rows-1)/2+1;
1436 if (row >= rows)
1437 row = rows-1;
1440 /* Always refresh the line */
1441 while (start > target) { start -= 8; refresh = 1;}
1442 if (start < 0) start = 0;
1443 retry:
1444 mode2 = check_line(pos, fm,fb,fa,ci.merger,mode);
1445 if ((pos.side <= 0) && (mode2 & (ORIG|BEFORE)))
1446 draw_mline(row,pos,fm,fb,fa,ci.merger,start,cols, mode2&(ORIG|BEFORE|CHANGED|CHANGES), target, &col);
1447 else if (pos.side > 0 && (mode2 & (RESULT|AFTER))) {
1448 if (mode2 & CHANGED)
1449 draw_mline(row,pos,fm,fb,fa,ci.merger,start,cols, mode2&(RESULT|AFTER|CHANGED|CHANGES), target, &col);
1450 else if (mode2 & CHANGES)
1451 draw_mline(row,pos,fm,fb,fa,ci.merger,start,cols, mode2&(AFTER|CHANGED|CHANGES), target, &col);
1453 if (col > (cols-3)/2+start) {
1454 start += 8;
1455 refresh = 1;
1456 goto retry;
1458 if (col < start) {
1459 start -= 8;
1460 refresh = 1;
1461 if (start < 0) start = 0;
1462 goto retry;
1464 if (refresh) {
1465 refresh = 0;
1466 tpos = pos;
1468 for (i=row-1; i>=1 && tpos.p.m >= 0; ) {
1469 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1470 mode2 = check_line(tpos, fm,fb,fa, ci.merger, mode);
1472 if (tpos.side == 1 &&
1473 (mode2 & (RESULT|AFTER)) &&
1474 (mode2 & (CHANGED)))
1475 draw_mline(i--,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(RESULT|AFTER|CHANGED|CHANGES), 0, NULL);
1476 else if (tpos.side == 1 &&
1477 (mode2 & (RESULT|AFTER)) &&
1478 (mode2 & (CHANGES)))
1479 draw_mline(i--,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(AFTER|CHANGED|CHANGES), 0, NULL);
1480 else if ((tpos.side == 0 || tpos.side == -1) && (mode2 & (ORIG|BEFORE)))
1481 draw_mline(i--,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(ORIG|BEFORE|CHANGED|CHANGES), 0, NULL);
1483 toppos = tpos; toprow = i;
1484 while (i >= 1)
1485 blank(i--, 0, cols, a_void);
1486 tpos = pos;
1487 for (i=row; i<rows && ci.merger[tpos.p.m].type != End; ) {
1488 mode2 = check_line(tpos, fm,fb,fa,ci.merger,mode);
1489 if ((tpos.side <= 0) && (mode2 & (ORIG|BEFORE)))
1490 draw_mline(i++,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(ORIG|BEFORE|CHANGED|CHANGES), 0, NULL);
1491 else if (tpos.side > 0 && (mode2 & (RESULT|AFTER))) {
1492 if (mode2 & CHANGED)
1493 draw_mline(i++,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(RESULT|AFTER|CHANGED|CHANGES), 0, NULL);
1494 else if (mode2 & CHANGES)
1495 draw_mline(i++,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(AFTER|CHANGED|CHANGES), 0, NULL);
1497 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1499 botpos = tpos; botrow = i;
1500 while (i<rows)
1501 blank(i++, 0, cols, a_void);
1502 } else {
1504 #define META(c) ((c)|0x1000)
1505 #define SEARCH(c) ((c)|0x2000)
1506 move(rows,0);
1507 attrset(A_NORMAL);
1508 if (num>=0) { char buf[10]; sprintf(buf, "%d ", num); addstr(buf);}
1509 if (meta & META(0)) addstr("ESC...");
1510 if (meta & SEARCH(0)) {
1511 if (searchdir) addstr("Backwards ");
1512 addstr("Search: ");
1513 addstr(search);
1514 if (search_notfound)
1515 addstr(" - Not Found.");
1516 search_notfound = 0;
1518 clrtoeol();
1519 move(row,col-start);
1520 c = getch();
1521 tmeta = meta; meta = 0;
1522 tnum = num; num = -1;
1523 switch(c | tmeta) {
1524 case 27: /* escape */
1525 case META(27):
1526 meta = META(0);
1527 break;
1528 case META('<'): /* start of file */
1529 start:
1530 tpos = pos; row++;
1531 do {
1532 pos = tpos; row--;
1533 prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
1534 } while (tpos.p.m >= 0);
1535 if (row <= 0)
1536 row = 0;
1537 break;
1538 case META('>'): /* end of file */
1539 case 'G':
1540 if (tnum >=0) goto start;
1541 tpos = pos; row--;
1542 do {
1543 pos = tpos; row++;
1544 next_mline(&tpos, fm,fb,fa,ci.merger,mode);
1545 } while (ci.merger[tpos.p.m].type != End);
1546 if (row >= rows)
1547 row = rows;
1548 break;
1549 case '0' ... '9':
1550 if (tnum < 0) tnum = 0;
1551 num = tnum*10 + (c-'0');
1552 break;
1553 case 'q':
1554 return;
1556 case '/':
1557 case 'S'-64:
1558 /* incr search forward */
1559 meta = SEARCH(0);
1560 searchlen = 0;
1561 search[searchlen] = 0;
1562 searchdir = 0;
1563 break;
1564 case '\\':
1565 case 'R'-64:
1566 /* incr search backwards */
1567 meta = SEARCH(0);
1568 searchlen = 0;
1569 search[searchlen] = 0;
1570 searchdir = 1;
1571 break;
1572 case SEARCH('G'-64):
1573 case SEARCH('S'-64):
1574 /* search again */
1575 meta = SEARCH(0);
1576 tpos = pos; trow = row;
1577 if (searchdir) {
1578 trow--;
1579 prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
1580 } else {
1581 trow++;
1582 next_mline(&tpos, fm,fb,fa,ci.merger,mode);
1584 goto search_again;
1586 case SEARCH('H'-64):
1587 meta = SEARCH(0);
1588 if (anchor) {
1589 struct search_anchor *a;
1590 a = anchor;
1591 anchor = a->next;
1592 free(a);
1594 if (anchor) {
1595 struct search_anchor *a;
1596 a = anchor;
1597 anchor = a->next;
1598 pos = a->pos;
1599 row = a->row;
1600 col = a->col;
1601 search_notfound = a->notfound;
1602 searchlen = a->searchlen;
1603 search[searchlen] = 0;
1604 free(a);
1605 refresh = 1;
1607 break;
1608 case SEARCH(' ') ... SEARCH('~'):
1609 case SEARCH('\t'):
1610 meta = SEARCH(0);
1611 if (searchlen < sizeof(search)-1)
1612 search[searchlen++] = c & (0x7f);
1613 search[searchlen] = 0;
1614 tpos = pos; trow = row;
1615 search_again:
1616 search_notfound = 1;
1617 do {
1618 if (mcontains(tpos, fm,fb,fa,ci.merger,mode,search)) {
1619 pos = tpos;
1620 row = trow;
1621 search_notfound = 0;
1622 break;
1624 if (searchdir) {
1625 trow--;
1626 prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
1627 } else {
1628 trow++;
1629 next_mline(&tpos, fm,fb,fa,ci.merger,mode);
1631 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1633 break;
1634 case 'L'-64:
1635 refresh = 2;
1636 if (toprow >= 1) row -= (toprow+1);
1637 break;
1639 case 'V'-64: /* page down */
1640 pos = botpos;
1641 if (botrow < rows)
1642 row = botrow;
1643 else
1644 row = 2;
1645 refresh = 1;
1646 break;
1647 case META('v'): /* page up */
1648 pos = toppos;
1649 if (toprow >= 1)
1650 row = toprow+1;
1651 else
1652 row = rows-2;
1653 refresh = 1;
1654 break;
1656 case 'j':
1657 case 'n':
1658 case 'N'-64:
1659 case KEY_DOWN:
1660 if (tnum < 0) tnum = 1;
1661 for (; tnum > 0 ; tnum--) {
1662 tpos = pos;
1663 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1664 if (ci.merger[tpos.p.m].type != End) {
1665 pos = tpos;
1666 row++;
1669 break;
1670 case 'N':
1671 /* Next diff */
1672 tpos = pos; row--;
1673 do {
1674 pos = tpos; row++;
1675 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1676 } while (pos.side != 0 && ci.merger[tpos.p.m].type != End);
1677 tpos = pos; row--;
1678 do {
1679 pos = tpos; row++;
1680 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1681 } while (pos.side == 0 && ci.merger[tpos.p.m].type != End);
1683 break;
1684 case 'P':
1685 /* Previous diff */
1686 tpos = pos; row++;
1687 do {
1688 pos = tpos; row--;
1689 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1690 } while (tpos.side == 0 && tpos.p.m >= 0);
1691 tpos = pos; row++;
1692 do {
1693 pos = tpos; row--;
1694 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1695 } while (tpos.side != 0 && tpos.p.m >= 0);
1697 break;
1699 case 'k':
1700 case 'p':
1701 case 'P'-64:
1702 case KEY_UP:
1703 if (tnum < 0) tnum = 1;
1704 for (; tnum > 0 ; tnum--) {
1705 tpos = pos;
1706 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1707 if (tpos.p.m >= 0) {
1708 pos = tpos;
1709 row--;
1712 break;
1714 case KEY_LEFT:
1715 case 'h':
1716 /* left */
1717 target = col - 1;
1718 if (target < 0) target = 0;
1719 break;
1720 case KEY_RIGHT:
1721 case 'l':
1722 /* right */
1723 target = col + 1;
1724 break;
1726 case '^':
1727 case 'A'-64:
1728 /* Start of line */
1729 target = 0;
1730 break;
1731 case '$':
1732 case 'E'-64:
1733 /* End of line */
1734 target = 1000;
1735 break;
1737 case 'a': /* 'after' view in patch window */
1738 if (mode & AFTER)
1739 mode &= ~BEFORE;
1740 else
1741 mode |= AFTER;
1742 refresh = 1;
1743 break;
1744 case 'b': /* 'before' view in patch window */
1745 if (mode & BEFORE)
1746 mode &= ~AFTER;
1747 else
1748 mode |= BEFORE;
1749 refresh = 1;
1750 break;
1751 case 'o': /* 'original' view in the merge window */
1752 if (mode & ORIG)
1753 mode &= ~RESULT;
1754 else
1755 mode |= ORIG;
1756 refresh = 1;
1757 break;
1758 case 'r': /* the 'result' view in the merge window */
1759 if (mode & RESULT)
1760 mode &= ~ORIG;
1761 else
1762 mode |= RESULT;
1763 refresh = 1;
1764 break;
1765 #if 0
1766 case 'h':
1767 case KEY_LEFT:
1768 if (start > 0) start--;
1769 refresh = 1;
1770 break;
1771 case 'l':
1772 case KEY_RIGHT:
1773 if (start < cols) start++;
1774 refresh = 1;
1775 break;
1776 #endif
1779 if (meta == SEARCH(0)) {
1780 if (anchor == NULL ||
1781 !same_mpos(anchor->pos, pos) ||
1782 anchor->searchlen != searchlen ||
1783 anchor->col != col) {
1784 struct search_anchor *a = malloc(sizeof(*a));
1785 a->pos = pos;
1786 a->row = row;
1787 a->col = col;
1788 a->searchlen = searchlen;
1789 a->notfound = search_notfound;
1790 a->next = anchor;
1791 anchor = a;
1793 } else {
1794 while (anchor) {
1795 struct search_anchor *a = anchor;
1796 anchor = a->next;
1797 free(a);
1803 void main_window(struct plist *pl, int n, FILE *f, int reverse)
1805 /* The main window lists all files together with summary information:
1806 * number of chunks, number of wiggles, number of conflicts.
1807 * The list is scrollable
1808 * When a entry is 'selected', we switch to the 'file' window
1809 * The list can be condensed by removing files with no conflict
1810 * or no wiggles, or removing subdirectories
1812 * We record which file in the list is 'current', and which
1813 * screen line it is on. We try to keep things stable while
1814 * moving.
1816 * Counts are printed before the name using at most 2 digits.
1817 * Numbers greater than 99 are XX
1818 * Ch Wi Co File
1819 * 27 5 1 drivers/md/md.c
1821 * A directory show the sum in all children.
1823 * Commands:
1824 * select: enter, space, mouseclick
1825 * on file, go to file window
1826 * on directory, toggle open
1827 * up: k, p, control-p uparrow
1828 * Move to previous open object
1829 * down: j, n, control-n, downarrow
1830 * Move to next open object
1833 int pos=0; /* position in file */
1834 int row=1; /* position on screen */
1835 int rows; /* size of screen in rows */
1836 int cols;
1837 int tpos, i;
1838 int refresh = 2;
1839 int c=0;
1841 while(1) {
1842 if (refresh == 2) {
1843 clear(); attrset(0);
1844 attron(A_BOLD);
1845 mvaddstr(0,0,"Ch Wi Co Patched Files");
1846 move(2,0);
1847 attroff(A_BOLD);
1848 refresh = 1;
1850 if (row <1 || row >= rows)
1851 refresh = 1;
1852 if (refresh) {
1853 refresh = 0;
1854 getmaxyx(stdscr, rows, cols);
1855 if (row >= rows +3)
1856 row = (rows+1)/2;
1857 if (row >= rows)
1858 row = rows-1;
1859 tpos = pos;
1860 for (i=row; i>1; i--) {
1861 tpos = get_prev(tpos, pl, n);
1862 if (tpos == -1) {
1863 row = row - i + 1;
1864 break;
1867 /* Ok, row and pos could be trustworthy now */
1868 tpos = pos;
1869 for (i=row; i>=1; i--) {
1870 draw_one(i, &pl[tpos], f, reverse);
1871 tpos = get_prev(tpos, pl, n);
1873 tpos = pos;
1874 for (i=row+1; i<rows; i++) {
1875 tpos = get_next(tpos, pl, n);
1876 if (tpos >= 0)
1877 draw_one(i, &pl[tpos], f, reverse);
1878 else
1879 draw_one(i, NULL, f, reverse);
1882 {char bb[20]; sprintf(bb,"%d", c); mvaddstr(0, 70, bb); clrtoeol();}
1883 move(row, 9);
1884 c = getch();
1885 switch(c) {
1886 case 'j':
1887 case 'n':
1888 case 'N':
1889 case 'N'-64:
1890 case KEY_DOWN:
1891 tpos = get_next(pos, pl, n);
1892 if (tpos >= 0) {
1893 pos = tpos;
1894 row++;
1896 break;
1897 case 'k':
1898 case 'p':
1899 case 'P':
1900 case 'P'-64:
1901 case KEY_UP:
1902 tpos = get_prev(pos, pl, n);
1903 if (tpos >= 0) {
1904 pos = tpos;
1905 row--;
1907 break;
1909 case ' ':
1910 case 13:
1911 if (pl[pos].end == 0) {
1912 pl[pos].open = ! pl[pos].open;
1913 refresh = 1;
1914 } else {
1915 /* diff_window(&pl[pos], f); */
1916 merge_window(&pl[pos],f,reverse);
1917 refresh = 2;
1919 break;
1920 case 27: /* escape */
1921 mvaddstr(0,70,"ESC..."); clrtoeol();
1922 c = getch();
1923 switch(c) {
1925 break;
1926 case 'q':
1927 return;
1929 case KEY_RESIZE:
1930 refresh = 2;
1931 break;
1937 int vpatch(int argc, char *argv[], int strip, int reverse, int replace)
1939 /* NOT argv[0] is first arg... */
1940 int n = 0;
1941 FILE *f = NULL;
1942 FILE *in = stdin;
1943 struct plist *pl;
1945 if (argc >=1) {
1946 in = fopen(argv[0], "r");
1947 if (!in) {
1948 printf("No %s\n", argv[0]);
1949 exit(1);
1951 } else {
1952 in = stdin;
1954 if (lseek(fileno(in),0L, 1) == -1) {
1955 f = tmpfile();
1956 if (!f) {
1957 fprintf(stderr, "Cannot create a temp file\n");
1958 exit(1);
1962 pl = parse_patch(in, f, &n);
1964 if (set_prefix(pl, n, strip) == 0) {
1965 fprintf(stderr, "%s: aborting\n", Cmd);
1966 exit(2);
1968 pl = sort_patches(pl, &n);
1970 if (f) {
1971 if (fileno(in) == 0) {
1972 close(0);
1973 dup(2);
1974 } else
1975 fclose(in);
1976 in = f;
1978 #if 0
1979 int i;
1980 for (i=0; i<n ; i++) {
1981 printf("%3d: %3d %2d/%2d %s\n", i, pl[i].parent, pl[i].prev, pl[i].next, pl[i].file);
1983 exit(0);
1984 #endif
1985 signal(SIGINT, catch);
1986 signal(SIGQUIT, catch);
1987 signal(SIGTERM, catch);
1988 signal(SIGBUS, catch);
1989 signal(SIGSEGV, catch);
1991 initscr(); cbreak(); noecho();
1992 start_color();
1993 use_default_colors();
1994 if (!has_colors()) {
1995 a_delete = A_UNDERLINE;
1996 a_added = A_BOLD;
1997 a_common = A_NORMAL;
1998 a_sep = A_STANDOUT;
1999 a_already = A_STANDOUT;
2000 } else {
2001 init_pair(1, COLOR_WHITE, COLOR_RED);
2002 a_delete = COLOR_PAIR(1);
2003 init_pair(2, COLOR_WHITE, COLOR_BLUE);
2004 a_added = COLOR_PAIR(2);
2005 a_common = A_NORMAL;
2006 init_pair(3, COLOR_WHITE, COLOR_GREEN);
2007 a_sep = COLOR_PAIR(3);
2008 init_pair(4, COLOR_WHITE, COLOR_YELLOW);
2009 a_void = COLOR_PAIR(4);
2010 init_pair(5, COLOR_BLUE, -1);
2011 a_unmatched = COLOR_PAIR(5);
2012 init_pair(6, COLOR_CYAN, -1);
2013 a_extra = COLOR_PAIR(6);
2015 init_pair(7, COLOR_BLACK, COLOR_CYAN);
2016 a_already = COLOR_PAIR(7);
2018 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
2019 mousemask(ALL_MOUSE_EVENTS, NULL);
2021 main_window(pl, n, in, reverse);
2023 nocbreak();nl();endwin();
2024 return 0;
2025 exit(0);
2027 #if 0
2028 WiggleVerbose=1 ~/work/wiggle/wiggle -mR fs/nfsd/nfs4callback.c .patches/removed/144NfsdV4-033 |less
2029 neilb@notabene:/home/src/mm$
2031 ~/work/wiggle/wiggle -BR .patches/removed/144NfsdV4-033
2034 #endif