From: Neil Brown Date: Sun, 4 Apr 2010 06:14:25 +0000 (+1000) Subject: browser: rearrange code X-Git-Tag: v0.9~81 X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=73b4985f04cc4b42140d758c271e048ec61f7462;p=wiggle.git browser: rearrange code Put all the 'main window' code together, and all the 'merge window' code together. Signed-off-by: Neil Brown --- diff --git a/vpatch.c b/vpatch.c index f7ae935..c5f3ce9 100644 --- a/vpatch.c +++ b/vpatch.c @@ -49,15 +49,6 @@ #define assert(x) do { if (!(x)) abort(); } while (0) -char *typenames[] = { - [End] = "End", - [Unmatched] = "Unmatched", - [Unchanged] = "Unchanged", - [Extraneous] = "Extraneous", - [Changed] = "Changed", - [Conflict] = "Conflict", - [AlreadyApplied] = "AlreadyApplied", -}; /* plist stores a list of patched files in an array * Each entry identifies a file, the range of the @@ -75,1581 +66,1582 @@ struct plist { int chunks, wiggles, conflicts; int calced; }; +static struct stream load_segment(FILE *f, + unsigned int start, unsigned int end); -struct plist *patch_add_file(struct plist *pl, int *np, char *file, - unsigned int start, unsigned int end) -{ - /* size of pl is 0, 16, n^2 */ - int n = *np; - int asize; - - while (*file == '/') - /* leading '/' are bad... */ - file++; - - if (n==0) - asize = 0; - else if (n<=16) - asize = 16; - else if ((n&(n-1))==0) - asize = n; - else - asize = n+1; /* not accurate, but not too large */ - if (asize <= n) { - /* need to extend array */ - struct plist *npl; - if (asize < 16) asize = 16; - else asize += asize; - npl = realloc(pl, asize * sizeof(struct plist)); - if (!npl) { - fprintf(stderr, "malloc failed - skipping %s\n", file); - return pl; - } - pl = npl; - } - pl[n].file = file; - pl[n].start = start; - pl[n].end = end; - pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1; - pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100; - pl[n].open = 1; - pl[n].calced = 0; - *np = n+1; - return pl; -} - -struct plist *parse_patch(FILE *f, FILE *of, int *np) -{ - /* read a multi-file patch from 'f' and record relevant - * details in a plist. - * if 'of' >= 0, fd might not be seekable so we write - * to 'of' and use lseek on 'of' to determine position - */ - struct plist *plist = NULL; - - while (!feof(f)) { - /* first, find the start of a patch: "\n+++ " - * grab the file name and scan to the end of a line - */ - char *target="\n+++ "; - char *target2="\n--- "; - char *pos = target; - int c; - char name[1024]; - unsigned start, end; - - while (*pos && (c=fgetc(f)) != EOF ) { - if (of) fputc(c, of); - if (c == *pos) - pos++; - else pos = target; - } - if (c == EOF) - break; - assert(c == ' '); - /* now read a file name */ - pos = name; - while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' && - pos - name < 1023) { - *pos++ = c; - if (of) - fputc(c, of); - } - *pos = 0; - if (c == EOF) - break; - if (of) fputc(c, of); - while (c != '\n' && (c=fgetc(f)) != EOF) - if (of) - fputc(c, of); +/* global attributes */ +int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already; - start = ftell(of ?: f); +char *typenames[] = { + [End] = "End", + [Unmatched] = "Unmatched", + [Unchanged] = "Unchanged", + [Extraneous] = "Extraneous", + [Changed] = "Changed", + [Conflict] = "Conflict", + [AlreadyApplied] = "AlreadyApplied", +}; - if (c == EOF) - break; +#define BEFORE 1 +#define AFTER 2 +#define ORIG 4 +#define RESULT 8 +#define CHANGED 16 /* The RESULT is different to ORIG */ +#define CHANGES 32 /* AFTER is different to BEFORE */ +#define WIGGLED 64 /* a conflict that was successfully resolved */ +#define CONFLICTED 128 /* a conflict that was not successfully resolved */ - /* now skip to end - "\n--- " */ - pos = target2+1; - while (*pos && (c=fgetc(f)) != EOF) { - if (of) - fputc(c, of); - if (c == *pos) - pos++; - else - pos = target2; - } - end = ftell(of ?: f); - if (pos > target2) - end -= (pos - target2) - 1; - plist = patch_add_file(plist, np, - strdup(name), start, end); - } - return plist; -} +/* Displaying a Merge. + * The first step is to linearise the merge. The merge in inherently + * parallel with before/after streams. However much of the whole document + * is linear as normally much of the original in unchanged. + * All parallelism comes from the patch. This normally produces two + * parallel stream, but in the case of a conflict can produce three. + * For browsing the merge we only ever show two alternates in-line. + * When there are three we use two panes with 1 or 2 alternates in each. + * So to linearise the two streams we find lines that are completely + * unchanged (same or all 3 streams, or missing in 2nd and 3rd) which bound + * a region where there are changes. We include everything between + * these twice, in two separate passes. The exact interpretation of the + * passes is handled at a higher level but will be one of: + * original and result + * before and after + * original and after (for a conflict) + * + * At any position in the merge we can be in one of 3 states: + * 0: unchanged section + * 1: first pass + * 2: second pass + * + * So to walk a merge in display order we need a position in the merge, + * a current state, and when in a changed section, we need to know the + * bounds of that changed section. + * This is all encoded in 'struct mpos'. + * + * Each location may or may not be visible depending on certain + * display options. + * + * Also, some locations might be 'invalid' in that they don't need to be displayed. + * For example when the patch leaves a section of the original unchanged, + * we only need to see the original - the before/after sections are treated + * as invalid and are not displayed. + * The visibility of newlines is crucial and guides the display. One line + * of displayed text is all the visible sections between two visible newlines. + * + * Counting lines is a bit tricky. We only worry about line numbers in the + * original (stream 0) as these could compare with line numbers mentioned in + * patch chunks. + * We count 2 for every line: 1 for everything before the newline and 1 for the newline. + * That way we don't get a full counted line until we see the first char after the + * newline, so '+' lines are counted with the previous line. + * + */ +struct mpos { + struct mp { + int m; /* merger index */ + int s; /* stream 0,1,2 for a,b,c */ + int o; /* offset in that stream */ + int lineno; /* Counts newlines in stream 0 + * set lsb when see newline. + * add one when not newline and lsb set + */ + } p, /* the current point */ + lo, /* eol for start of the current group */ + hi; /* eol for end of the current group */ + int state; /* + * 0 if on an unchanged (lo/hi not meaningful) + * 1 if on the '-' of a diff, + * 2 if on the '+' of a diff + */ +}; -static struct stream load_segment(FILE *f, - unsigned int start, unsigned int end) +/* used for checking location during search */ +int same_mpos(struct mpos a, struct mpos b) { - struct stream s; - s.len = end - start; - s.body = malloc(s.len); - if (s.body) { - fseek(f, start, 0); - if (fread(s.body, 1, s.len, f) != s.len) { - free(s.body); - s.body = NULL; - } - } else - die(); - return s; + return a.p.m == b.p.m && + a.p.s == b.p.s && + a.p.o == b.p.o && + a.state == b.state; } - -void catch(int sig) +/* Check if a particular stream is meaningful in a particular merge + * section. e.g. in an Unchanged section, only stream 0, the + * original, is meaningful. This is used to avoid walking down + * pointless paths. + */ +int stream_valid(int s, enum mergetype type) { - if (sig == SIGINT) { - signal(sig, catch); - return; + switch(type) { + case End: return 1; + case Unmatched: return s == 0; + case Unchanged: return s == 0; + case Extraneous: return s == 2; + case Changed: return s != 1; + case Conflict: return 1; + case AlreadyApplied: return 1; } - nocbreak();nl();endwin(); - printf("Died on signal %d\n", sig); - exit(2); + return 0; } -int pl_cmp(const void *av, const void *bv) +/* + * Advance the 'pos' in the current mergepos returning the next + * element (word). + * This walks the merges in sequence, and the streams within + * each merge. + */ +struct elmnt next_melmnt(struct mp *pos, + struct file fm, struct file fb, struct file fa, + struct merge *m) { - const struct plist *a = av; - const struct plist *b = bv; - return strcmp(a->file, b->file); -} - -int common_depth(char *a, char *b) -{ - /* find number of path segments that these two have - * in common - */ - int depth = 0; + pos->o++; while(1) { - char *c; - int al, bl; - c = strchr(a, '/'); - if (c) al = c-a; else al = strlen(a); - c = strchr(b, '/'); - if (c) bl = c-b; else bl = strlen(b); - if (al == 0 || al != bl || strncmp(a,b,al) != 0) - return depth; - a+= al; - while (*a=='/') a++; - b+= bl; - while(*b=='/') b++; - - depth++; + int l=0; /* Length remaining in current merge section */ + if (pos->m >= 0) + switch(pos->s) { + case 0: l = m[pos->m].al; break; + case 1: l = m[pos->m].bl; break; + case 2: l = m[pos->m].cl; break; + } + if (pos->o >= l) { + /* Offset has reached length, choose new stream or + * new merge */ + pos->o = 0; + do { + pos->s++; + if (pos->s > 2) { + pos->s = 0; + pos->m++; + } + } while (!stream_valid(pos->s, m[pos->m].type)); + } else + break; } -} - -struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr) -{ - /* any parent of file that is not a parent of curr - * needs to be added to pl - */ - int d = common_depth(file, curr); - char *buf = curr; - while (d) { - char *c = strchr(file, '/'); - int l; - if (c) l = c-file; else l = strlen(file); - file += l; - curr += l; - while (*file == '/') file++; - while (*curr == '/') curr++; - d--; + if (pos->m == -1 || m[pos->m].type == End) { + struct elmnt e; + e.start = NULL; e.len = 0; + return e; } - while (*file) { - if (curr > buf && curr[-1] != '/') - *curr++ = '/'; - while (*file && *file != '/') - *curr++ = *file++; - while (*file == '/') file++; - *curr = '\0'; - if (*file) - pl = patch_add_file(pl, np, strdup(buf), - 0, 0); + switch(pos->s) { + default: /* keep compiler happy */ + case 0: + if (pos->lineno & 1) + pos->lineno ++; + if (ends_mline(fm.list[m[pos->m].a + pos->o])) + pos->lineno ++; + + return fm.list[m[pos->m].a + pos->o]; + case 1: return fb.list[m[pos->m].b + pos->o]; + case 2: return fa.list[m[pos->m].c + pos->o]; } - return pl; } -struct plist *sort_patches(struct plist *pl, int *np) +/* step current position.p backwards */ +struct elmnt prev_melmnt(struct mp *pos, + struct file fm, struct file fb, struct file fa, + struct merge *m) { - /* sort the patches, add directory names, and re-sort */ - char curr[1024]; - char *prev; - int parents[100]; - int prevnode[100]; - int i, n; - qsort(pl, *np, sizeof(struct plist), pl_cmp); - curr[0] = 0; - n = *np; - for (i=0; is == 0) { + if (ends_mline(fm.list[m[pos->m].a + pos->o])) + pos->lineno--; + if (pos->lineno & 1) + pos->lineno--; + } - /* array is now stable, so set up parent pointers */ - n = *np; - curr[0] = 0; - prevnode[0] = -1; - prev = ""; - for (i=0; io--; + while (pos->m >=0 && pos->o < 0) { + do { + pos->s--; + if (pos->s < 0) { + pos->s = 2; + pos->m--; + } + } while (pos->m >= 0 && + !stream_valid(pos->s, m[pos->m].type)); + if (pos->m>=0) { + switch(pos->s) { + case 0: pos->o = m[pos->m].al-1; break; + case 1: pos->o = m[pos->m].bl-1; break; + case 2: pos->o = m[pos->m].cl-1; break; + } } - pl[i].prev = prevnode[d]; - if (pl[i].prev > -1) - pl[pl[i].prev].next = i; - prev = pl[i].file; - parents[d] = i; - prevnode[d] = i; - prevnode[d+1] = -1; } - return pl; + if (pos->m < 0) { + struct elmnt e; + e.start = NULL; e.len = 0; + return e; + } + switch(pos->s) { + default: /* keep compiler happy */ + case 0: return fm.list[m[pos->m].a + pos->o]; + case 1: return fb.list[m[pos->m].b + pos->o]; + case 2: return fa.list[m[pos->m].c + pos->o]; + } } -/* determine how much we need to stripe of the front of - * paths to find them from current directory. This is - * used to guess correct '-p' value. +/* 'visible' not only checks if this stream in this merge should be + * visible in this mode, but also chooses which colour/highlight to use + * to display it. */ -int get_strip(char *file) +int visible(int mode, enum mergetype type, int stream) { - int fd; - int strip = 0; - - while (file && *file) { - fd = open(file, O_RDONLY); - if (fd >= 0) { - close(fd); - return strip; + if (mode == 0) return -1; + /* mode can be any combination of ORIG RESULT BEFORE AFTER */ + switch(type) { + case End: /* The END is always visible */ + return A_NORMAL; + case Unmatched: /* Visible in ORIG and RESULT */ + if (mode & (ORIG|RESULT)) + return a_unmatched; + break; + case Unchanged: /* visible everywhere, but only show stream 0 */ + if (stream == 0) return a_common; + break; + case Extraneous: /* stream 2 is visible in BEFORE and AFTER */ + if ((mode & (BEFORE|AFTER)) + && stream == 2) + return a_extra; + break; + case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */ + if (stream == 0 && + (mode & (ORIG|BEFORE))) + return a_delete; + if (stream == 2 && + (mode & (RESULT|AFTER))) + return a_added; + break; + case Conflict: + switch(stream) { + case 0: + if (mode & ORIG) + return a_unmatched | A_REVERSE; + break; + case 1: + if (mode & BEFORE) + return a_extra | A_UNDERLINE; + break; + case 2: + if (mode & (AFTER|RESULT)) + return a_added | A_UNDERLINE; + break; } - strip++; - file = strchr(file, '/'); - if (file) - while(*file == '/') - file++; + break; + case AlreadyApplied: + switch(stream) { + case 0: + if (mode & (ORIG|RESULT)) + return a_already; + break; + case 1: + if (mode & BEFORE) + return a_delete | A_UNDERLINE; + break; + case 2: + if (mode & AFTER) + return a_added | A_UNDERLINE; + break; + } + break; } return -1; - } -int set_prefix(struct plist *pl, int n, int strip) + + +/* checkline creates a summary of the sort of changes that + * are in a line, returning an or of + * CHANGED + * CHANGES + * WIGGLED + * CONFLICTED + */ +int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa, + struct merge *m, int mode) { - int i; - for(i=0; i<4 && ip; + while (1) { + struct elmnt e = next_melmnt(&pos->p, fm,fb,fa,m); + if (e.start == NULL) + break; + if (ends_mline(e) && + visible(mode, m[pos->p.m].type, pos->p.s) >= 0) + break; } - if (p == NULL) { - fprintf(stderr, "%s: cannot strip %d segments from %s\n", - Cmd, strip, pl[i].file); - return 0; + mode2 = check_line(*pos, fm,fb,fa,m,mode); + + if ((mode2 & CHANGES) && pos->state == 0) { + /* Just entered a diff-set */ + pos->lo = pos->p; + pos->state = 1; + } else if (!(mode2 & CHANGES) && pos->state) { + /* Come to the end of a diff-set */ + if (pos->state == 1) + /* Need to record the end */ + pos->hi = prv; + if (pos->state == 2) { + /* finished final pass */ + pos->state = 0; + } else { + /* time for another pass */ + pos->p = pos->lo; + pos->state ++; + } } - pl[i].file = p; - } - return 1; + mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED; + switch(pos->state) { + case 1: mask &= ~(RESULT|AFTER); break; + case 2: mask &= ~(ORIG|BEFORE); break; + } + } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0); + } +void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa, + struct merge *m, int mode) +{ + int mask; + do { + struct mp prv; + int mode2; -int get_prev(int pos, struct plist *pl, int n) + prv = pos->p; + if (pos->p.m < 0) + return; + while(1) { + struct elmnt e = prev_melmnt(&pos->p, fm,fb,fa,m); + if (e.start == NULL) + break; + if (ends_mline(e) && + visible(mode, m[pos->p.m].type, pos->p.s) >= 0) + break; + } + mode2 = check_line(*pos, fm,fb,fa,m,mode); + + if ((mode2 & CHANGES) && pos->state == 0) { + /* Just entered a diff-set */ + pos->hi = pos->p; + pos->state = 2; + } else if (!(mode2 & CHANGES) && pos->state) { + /* Come to the end (start) of a diff-set */ + if (pos->state == 2) + /* Need to record the start */ + pos->lo = prv; + if (pos->state == 1) { + /* finished final pass */ + pos->state = 0; + } else { + /* time for another pass */ + pos->p = pos->hi; + pos->state --; + } + } + mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED; + switch(pos->state) { + case 1: mask &= ~(RESULT|AFTER); break; + case 2: mask &= ~(ORIG|BEFORE); break; + } + } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0); +} + +/* blank a whole row of display */ +void blank(int row, int start, int cols, int attr) { - if (pos == -1) return pos; - if (pl[pos].prev == -1) - return pl[pos].parent; - pos = pl[pos].prev; - while (pl[pos].open && - pl[pos].last >= 0) - pos = pl[pos].last; - return pos; + (void)attrset(attr); + move(row,start); + while (cols-- > 0) + addch(' '); } -int get_next(int pos, struct plist *pl, int n) +/* search of a string on one display line - just report if found, not where */ + +int mcontains(struct mpos pos, + struct file fm, struct file fb, struct file fa, struct merge *m, + int mode, char *search) { - if (pos == -1) return pos; - if (pl[pos].open) { - if (pos +1 < n) - return pos+1; - else - return -1; - } - while (pos >= 0 && pl[pos].next == -1) - pos = pl[pos].parent; - if (pos >= 0) - pos = pl[pos].next; - return pos; + /* See if any of the files, between start of this line and here, + * contain the search string + */ + struct elmnt e; + int len = strlen(search); + do { + e = prev_melmnt(&pos.p, fm,fb,fa,m); + if (e.start) { + int i; + for (i=0; icalced == 0 && pl->end) { - /* better load the patch and count the chunks */ - struct stream s1, s2; - struct stream s = load_segment(f, pl->start, pl->end); - struct stream sf = load_file(pl->file); - if (reverse) - pl->chunks = split_patch(s, &s2, &s1); - else - pl->chunks = split_patch(s, &s1, &s2); - if (sf.body == NULL) { - pl->wiggles = pl->conflicts = -1; - } else { - struct file ff, fp1, fp2; - struct csl *csl1, *csl2; - struct ci ci; - ff = split_stream(sf, ByWord, 0); - fp1 = split_stream(s1, ByWord, 0); - fp2 = split_stream(s2, ByWord, 0); - csl1 = pdiff(ff, fp1, pl->chunks); - csl2 = diff(fp1,fp2); - ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1); - pl->wiggles = ci.wiggles; - pl->conflicts = ci.conflicts; - free(csl1); - free(csl2); - free(ff.list); - free(fp1.list); - free(fp2.list); - } + (void)attrset(A_NORMAL); + mvaddch(row, offset, tag); + offset++; + cols--; - free(s1.body); - free(s2.body); - free(s.body); - pl->calced = 1; - } - if (pl->end == 0) { - strcpy(hdr, " "); - } else { - if (pl->chunks > 99) - strcpy(hdr, "XX"); - else sprintf(hdr, "%2d", pl->chunks); - if (pl->wiggles > 99) - strcpy(hdr+2, " XX"); - else sprintf(hdr+2, " %2d", pl->wiggles); - if (pl->conflicts > 99) - strcpy(hdr+5, " XX "); - else sprintf(hdr+5, " %2d ", pl->conflicts); - } - if (pl->end) - strcpy(hdr+9, "= "); - else if (pl->open) - strcpy(hdr+9, "+ "); - else strcpy(hdr+9, "- "); + /* find previous visible newline, or start of file */ + do + e = prev_melmnt(&pos.p, fm,fb,fa,m); + while (e.start != NULL && + (!ends_mline(e) || + visible(mode, m[pos.p.m].type, pos.p.s)==-1)); - mvaddstr(row, 0, hdr); - mvaddstr(row, 11, pl->file); - clrtoeol(); + while (1) { + unsigned char *c; + int l; + e = next_melmnt(&pos.p, fm,fb,fa,m); + if (e.start == NULL || + (ends_mline(e) && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) { + if (colp) *colp = col; + if (col < start) col = start; + if (e.start && e.start[0] == 0) { + char b[40]; + struct elmnt e1; + if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) { + int A,B,C,D,E,F; + e1 = fb.list[m[pos.p.m].b + pos.p.o]; + sscanf(e1.start+1, "%d %d %d", &A, &B, &C); + sscanf(e.start+1, "%d %d %d", &D, &E, &F); + sprintf(b, "@@ -%d,%d +%d,%d @@\n", B,C,E,F); + (void)attrset(a_sep); + } else { + (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s)); + sprintf(b, "<%.17s>", e.start+1); + } + mvaddstr(row, col-start+offset, b); + col += strlen(b); + } + blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.p.m].type, pos.p.s):A_NORMAL ); + return; + } + if (visible(mode, m[pos.p.m].type, pos.p.s) == -1) { + continue; + } + if (e.start[0] == 0) + continue; + (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s)); + c = (unsigned char *)e.start; + l = e.len; + while(l) { + if (*c >= ' ' && *c != 0x7f) { + if (col >= start && col < start+cols) + mvaddch(row, col-start+offset, *c); + col++; + } else if (*c == '\t') { + do { + if (col >= start && col < start+cols) + mvaddch(row, col-start+offset, ' '); + col++; + } while ((col&7)!= 0); + } else { + if (col >= start && col < start+cols) + mvaddch(row, col-start+offset, '?'); + col++; + } + c++; + if (colp && target <= col) { + if (col-start >= cols) + *colp = 10*col; + else + *colp = col; + colp = NULL; + } + l--; + } + } } -#define BEFORE 1 -#define AFTER 2 -#define ORIG 4 -#define RESULT 8 -#define CHANGED 16 /* The RESULT is different to ORIG */ -#define CHANGES 32 /* AFTER is different to BEFORE */ -#define WIGGLED 64 /* a conflict that was successfully resolved */ -#define CONFLICTED 128 /* a conflict that was not successfully resolved */ +void draw_mline(int mode, int row, int start, int cols, + struct file fm, struct file fb, struct file fa, + struct merge *m, + struct mpos pos, + int target, int *colp) +{ + /* + * Draw the left and right images of this line + * One side might be a_blank depending on the + * visibility of this newline + */ + int lcols, rcols; + mode |= check_line(pos, fm,fb,fa,m,mode); -/* Displaying a Merge. - * The first step is to linearise the merge. The merge in inherently - * parallel with before/after streams. However much of the whole document - * is linear as normally much of the original in unchanged. - * All parallelism comes from the patch. This normally produces two - * parallel stream, but in the case of a conflict can produce three. - * For browsing the merge we only ever show two alternates in-line. - * When there are three we use two panes with 1 or 2 alternates in each. - * So to linearise the two streams we find lines that are completely - * unchanged (same or all 3 streams, or missing in 2nd and 3rd) which bound - * a region where there are changes. We include everything between - * these twice, in two separate passes. The exact interpretation of the - * passes is handled at a higher level but will be one of: - * original and result - * before and after - * original and after (for a conflict) - * - * At any position in the merge we can be in one of 3 states: - * 0: unchanged section - * 1: first pass - * 2: second pass - * - * So to walk a merge in display order we need a position in the merge, - * a current state, and when in a changed section, we need to know the - * bounds of that changed section. - * This is all encoded in 'struct mpos'. - * - * Each location may or may not be visible depending on certain - * display options. - * - * Also, some locations might be 'invalid' in that they don't need to be displayed. - * For example when the patch leaves a section of the original unchanged, - * we only need to see the original - the before/after sections are treated - * as invalid and are not displayed. - * The visibility of newlines is crucial and guides the display. One line - * of displayed text is all the visible sections between two visible newlines. - * - * Counting lines is a bit tricky. We only worry about line numbers in the - * original (stream 0) as these could compare with line numbers mentioned in - * patch chunks. - * We count 2 for every line: 1 for everything before the newline and 1 for the newline. - * That way we don't get a full counted line until we see the first char after the - * newline, so '+' lines are counted with the previous line. - * - */ -struct mpos { - struct mp { - int m; /* merger index */ - int s; /* stream 0,1,2 for a,b,c */ - int o; /* offset in that stream */ - int lineno; /* Counts newlines in stream 0 - * set lsb when see newline. - * add one when not newline and lsb set - */ - } p, /* the current point */ - lo, /* eol for start of the current group */ - hi; /* eol for end of the current group */ - int state; /* - * 0 if on an unchanged (lo/hi not meaningful) - * 1 if on the '-' of a diff, - * 2 if on the '+' of a diff - */ -}; + if ( (mode & (BEFORE|AFTER)) && + (mode & (ORIG|RESULT))) { -/* used for checking location during search */ -int same_mpos(struct mpos a, struct mpos b) -{ - return a.p.m == b.p.m && - a.p.s == b.p.s && - a.p.o == b.p.o && - a.state == b.state; -} + lcols = (cols-1)/2; + rcols = cols - lcols - 1; -/* Check if a particular stream is meaningful in a particular merge - * section. e.g. in an Unchanged section, only stream 0, the - * original, is meaningful. This is used to avoid walking down - * pointless paths. - */ -int stream_valid(int s, enum mergetype type) -{ - switch(type) { - case End: return 1; - case Unmatched: return s == 0; - case Unchanged: return s == 0; - case Extraneous: return s == 2; - case Changed: return s != 1; - case Conflict: return 1; - case AlreadyApplied: return 1; - } - return 0; + (void)attrset(A_STANDOUT); + mvaddch(row, lcols, '|'); + + draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols, + fm,fb,fa,m, pos, target, colp); + + draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols, + fm,fb,fa,m, pos, 0, NULL); + } else + draw_mside(mode, row, 0, start, cols, + fm,fb,fa,m, pos, target, colp); } -/* - * Advance the 'pos' in the current mergepos returning the next - * element (word). - * This walks the merges in sequence, and the streams within - * each merge. - */ -struct elmnt next_melmnt(struct mp *pos, - struct file fm, struct file fb, struct file fa, - struct merge *m) +extern void cleanlist(struct file a, struct file b, struct csl *list); + +void merge_window(struct plist *p, FILE *f, int reverse) { - pos->o++; - while(1) { - int l=0; /* Length remaining in current merge section */ - if (pos->m >= 0) - switch(pos->s) { - case 0: l = m[pos->m].al; break; - case 1: l = m[pos->m].bl; break; - case 2: l = m[pos->m].cl; break; - } - if (pos->o >= l) { - /* Offset has reached length, choose new stream or - * new merge */ - pos->o = 0; - do { - pos->s++; - if (pos->s > 2) { - pos->s = 0; - pos->m++; - } - } while (!stream_valid(pos->s, m[pos->m].type)); - } else - break; - } - if (pos->m == -1 || m[pos->m].type == End) { - struct elmnt e; - e.start = NULL; e.len = 0; - return e; - } - switch(pos->s) { - default: /* keep compiler happy */ - case 0: - if (pos->lineno & 1) - pos->lineno ++; - if (ends_mline(fm.list[m[pos->m].a + pos->o])) - pos->lineno ++; - - return fm.list[m[pos->m].a + pos->o]; - case 1: return fb.list[m[pos->m].b + pos->o]; - case 2: return fa.list[m[pos->m].c + pos->o]; - } -} + /* display the merge in two side-by-side + * panes. + * left side shows diff between original and new + * right side shows the requested patch + * + * Unmatched: c_unmatched - left only + * Unchanged: c_normal - left and right + * Extraneous: c_extra - right only + * Changed-a: c_changed - left and right + * Changed-c: c_new - left and right + * AlreadyApplied-b: c_old - right only + * AlreadyApplied-c: c_applied - left and right + * Conflict-a: ?? left only + * Conflict-b: ?? left and right + * Conflict-c: ?? + * + * A Conflict is displayed as the original in the + * left side, and the highlighted diff in the right. + * + * Newlines are the key to display. + * 'pos' is always a newline (or eof). + * For each side that this newline is visible on, we + * rewind the previous newline visible on this side, and + * the display the stuff in between + * + * A 'position' is an offset in the merger, a stream + * choice (a,b,c - some aren't relevant) and an offset in + * that stream + */ -/* step current position.p backwards */ -struct elmnt prev_melmnt(struct mp *pos, - struct file fm, struct file fb, struct file fa, - struct merge *m) -{ - if (pos->s == 0) { - if (ends_mline(fm.list[m[pos->m].a + pos->o])) - pos->lineno--; - if (pos->lineno & 1) - pos->lineno--; - } + struct stream sm, sb, sa, sp; /* main, before, after, patch */ + struct file fm, fb, fa; + struct csl *csl1, *csl2; + struct ci ci; + char buf[100]; + int ch; + int refresh = 2; + int rows = 0, cols = 0; + int splitrow = -1; + int lastrow = 0; + int i, c; + int mode = ORIG|RESULT | BEFORE|AFTER; + char *modename = "sidebyside"; - pos->o--; - while (pos->m >=0 && pos->o < 0) { - do { - pos->s--; - if (pos->s < 0) { - pos->s = 2; - pos->m--; - } - } while (pos->m >= 0 && - !stream_valid(pos->s, m[pos->m].type)); - if (pos->m>=0) { - switch(pos->s) { - case 0: pos->o = m[pos->m].al-1; break; - case 1: pos->o = m[pos->m].bl-1; break; - case 2: pos->o = m[pos->m].cl-1; break; - } - } - } - if (pos->m < 0) { - struct elmnt e; - e.start = NULL; e.len = 0; - return e; - } - switch(pos->s) { - default: /* keep compiler happy */ - case 0: return fm.list[m[pos->m].a + pos->o]; - case 1: return fb.list[m[pos->m].b + pos->o]; - case 2: return fa.list[m[pos->m].c + pos->o]; - } -} + int row,start = 0; + int trow; + int col=0, target=0; + struct mpos pos; + struct mpos tpos, toppos, botpos; + struct mpos vpos, tvpos; + int botrow = 0; + int meta = 0, tmeta; + int num= -1, tnum; + char search[80]; + int searchlen = 0; + int search_notfound = 0; + int searchdir = 0; + struct search_anchor { + struct search_anchor *next; + struct mpos pos; + int notfound; + int row, col, searchlen; + } *anchor = NULL; -/* 'visible' not only checks if this stream in this merge should be - * visible in this mode, but also chooses which colour/highlight to use - * to display it. - */ -int visible(int mode, enum mergetype type, int stream) -{ - if (mode == 0) return -1; - /* mode can be any combination of ORIG RESULT BEFORE AFTER */ - switch(type) { - case End: /* The END is always visible */ - return A_NORMAL; - case Unmatched: /* Visible in ORIG and RESULT */ - if (mode & (ORIG|RESULT)) - return a_unmatched; - break; - case Unchanged: /* visible everywhere, but only show stream 0 */ - if (stream == 0) return a_common; - break; - case Extraneous: /* stream 2 is visible in BEFORE and AFTER */ - if ((mode & (BEFORE|AFTER)) - && stream == 2) - return a_extra; - break; - case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */ - if (stream == 0 && - (mode & (ORIG|BEFORE))) - return a_delete; - if (stream == 2 && - (mode & (RESULT|AFTER))) - return a_added; - break; - case Conflict: - switch(stream) { - case 0: - if (mode & ORIG) - return a_unmatched | A_REVERSE; - break; - case 1: - if (mode & BEFORE) - return a_extra | A_UNDERLINE; - break; - case 2: - if (mode & (AFTER|RESULT)) - return a_added | A_UNDERLINE; - break; - } - break; - case AlreadyApplied: - switch(stream) { - case 0: - if (mode & (ORIG|RESULT)) - return a_already; - break; - case 1: - if (mode & BEFORE) - return a_delete | A_UNDERLINE; - break; - case 2: - if (mode & AFTER) - return a_added | A_UNDERLINE; - break; - } - break; - } - return -1; -} + sp = load_segment(f, p->start, p->end); + if (reverse) + ch = split_patch(sp, &sa, &sb); + else + ch = split_patch(sp, &sb, &sa); + sm = load_file(p->file); + fm = split_stream(sm, ByWord, 0); + fb = split_stream(sb, ByWord, 0); + fa = split_stream(sa, ByWord, 0); + csl1 = pdiff(fm, fb, ch); + csl2 = diff(fb, fa); -/* checkline creates a summary of the sort of changes that - * are in a line, returning an or of - * CHANGED - * CHANGES - * WIGGLED - * CONFLICTED - */ -int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa, - struct merge *m, int mode) -{ - int rv = 0; - struct elmnt e; - int unmatched = 0; + ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1); + row = 1; + pos.p.m = 0; /* merge node */ + pos.p.s = 0; /* stream number */ + pos.p.o = -1; /* offset */ + pos.p.lineno = 1; + pos.state = 0; + next_mline(&pos, fm,fb,fa,ci.merger, mode); + vpos = pos; + while(1) { + if (refresh == 2) { + clear(); + sprintf(buf, "File: %s%s Mode: %s\n", + p->file,reverse?" - reversed":"", modename); + (void)attrset(A_BOLD); mvaddstr(0,0,buf); + clrtoeol(); + (void)attrset(A_NORMAL); + refresh = 1; + } + if (row < 1 || row >= lastrow) + refresh = 1; - do { - if (m[pos.p.m].type == Changed) - rv |= CHANGED | CHANGES; - else if ((m[pos.p.m].type == AlreadyApplied || - m[pos.p.m].type == Conflict)) - rv |= CONFLICTED | CHANGES; - else if (m[pos.p.m].type == Extraneous) - rv |= WIGGLED; - else if (m[pos.p.m].type == Unmatched) - unmatched = 1; - e = prev_melmnt(&pos.p, fm,fb,fa,m); - } while (e.start != NULL && - (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1)); - - if (unmatched && (rv & CHANGES)) - rv |= WIGGLED; - return rv; -} - -void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa, - struct merge *m, int mode) -{ - int mask; - do { - struct mp prv; - int mode2; - prv = pos->p; - while (1) { - struct elmnt e = next_melmnt(&pos->p, fm,fb,fa,m); - if (e.start == NULL) - break; - if (ends_mline(e) && - visible(mode, m[pos->p.m].type, pos->p.s) >= 0) - break; - } - mode2 = check_line(*pos, fm,fb,fa,m,mode); - if ((mode2 & CHANGES) && pos->state == 0) { - /* Just entered a diff-set */ - pos->lo = pos->p; - pos->state = 1; - } else if (!(mode2 & CHANGES) && pos->state) { - /* Come to the end of a diff-set */ - if (pos->state == 1) - /* Need to record the end */ - pos->hi = prv; - if (pos->state == 2) { - /* finished final pass */ - pos->state = 0; - } else { - /* time for another pass */ - pos->p = pos->lo; - pos->state ++; + if (mode == (ORIG|RESULT)) { + int cmode = check_line(pos, fm,fb,fa,ci.merger, mode); + if (splitrow < 0 && (cmode & (WIGGLED|CONFLICTED))) { + splitrow = (rows+1)/2; + lastrow = splitrow - 1; + refresh = 1; } + if (splitrow >= 0 && !(cmode & CHANGES)) { + splitrow = -1; + lastrow = rows-1; + refresh = 1; + } + } else if (splitrow >= 0) { + splitrow = -1; + lastrow = rows-1; + refresh = 1; } - mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED; - switch(pos->state) { - case 1: mask &= ~(RESULT|AFTER); break; - case 2: mask &= ~(ORIG|BEFORE); break; - } - } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0); - -} -void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa, - struct merge *m, int mode) -{ - int mask; - do { - struct mp prv; - int mode2; + if (refresh) { + getmaxyx(stdscr, rows, cols); + rows--; /* keep last row clear */ + if (splitrow >= 0) { + splitrow = (rows+1)/2; + lastrow = splitrow - 1; + } else + lastrow = rows - 1; - prv = pos->p; - if (pos->p.m < 0) - return; - while(1) { - struct elmnt e = prev_melmnt(&pos->p, fm,fb,fa,m); - if (e.start == NULL) - break; - if (ends_mline(e) && - visible(mode, m[pos->p.m].type, pos->p.s) >= 0) - break; + if (row < -3) + row = lastrow/2+1; + if (row < 1) + row = 1; + if (row > lastrow+3) + row = lastrow/2+1; + if (row >= lastrow) + row = lastrow-1; } - mode2 = check_line(*pos, fm,fb,fa,m,mode); - - if ((mode2 & CHANGES) && pos->state == 0) { - /* Just entered a diff-set */ - pos->hi = pos->p; - pos->state = 2; - } else if (!(mode2 & CHANGES) && pos->state) { - /* Come to the end (start) of a diff-set */ - if (pos->state == 2) - /* Need to record the start */ - pos->lo = prv; - if (pos->state == 1) { - /* finished final pass */ - pos->state = 0; - } else { - /* time for another pass */ - pos->p = pos->hi; - pos->state --; + if (getenv("WIGGLE_VTRACE")) { + char b[100]; + char *e, e2[7]; + int i; + switch (vpos.p.s) { + case 0: e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start; break; + case 1: e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start; break; + case 2: e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start; break; + } + for (i=0; i<6; i++) { + e2[i] = e[i]; + if (e2[i] < 32 || e2[i] >= 127) e2[i] = '?'; } + sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state, + vpos.p.s, vpos.p.o, + vpos.p.m, typenames[ci.merger[vpos.p.m].type], + ci.merger[vpos.p.m].al, + ci.merger[vpos.p.m].bl, + ci.merger[vpos.p.m].cl, + ci.merger[vpos.p.m].in_conflict, + e2 + ); + (void)attrset(A_NORMAL); + mvaddstr(0, 50, b); + clrtoeol(); } - mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED; - switch(pos->state) { - case 1: mask &= ~(RESULT|AFTER); break; - case 2: mask &= ~(ORIG|BEFORE); break; + { + char lbuf[20]; + (void)attrset(A_BOLD); + sprintf(lbuf, "ln:%d", (pos.p.lineno-1)/2); + mvaddstr(0, cols - strlen(lbuf) - 4, " "); + mvaddstr(0, cols - strlen(lbuf) - 1, lbuf); } - } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0); -} + /* Always refresh the line */ + while (start > target) { start -= 8; refresh = 1;} + if (start < 0) start = 0; + retry: + draw_mline(mode, row, start, cols, fm,fb,fa,ci.merger, + pos, target, &col); -/* blank a whole row of display */ -void blank(int row, int start, int cols, int attr) -{ - (void)attrset(attr); - move(row,start); - while (cols-- > 0) - addch(' '); -} + if (col > cols+start) { + start += 8; + refresh = 1; + goto retry; + } + if (col < start) { + start -= 8; + refresh = 1; + if (start < 0) start = 0; + goto retry; + } + if (refresh) { + refresh = 0; + tpos = pos; -/* search of a string on one display line - just report if found, not where */ + for (i=row-1; i>=1 && tpos.p.m >= 0; ) { + prev_mline(&tpos, fm,fb,fa,ci.merger, mode); + draw_mline(mode, i--, start, cols, fm,fb,fa,ci.merger, + tpos, 0, NULL); -int mcontains(struct mpos pos, - struct file fm, struct file fb, struct file fa, struct merge *m, - int mode, char *search) -{ - /* See if any of the files, between start of this line and here, - * contain the search string - */ - struct elmnt e; - int len = strlen(search); - do { - e = prev_melmnt(&pos.p, fm,fb,fa,m); - if (e.start) { - int i; - for (i=0; i= 1) + blank(i--, 0, cols, a_void); + tpos = pos; + for (i=row; i<= lastrow && ci.merger[tpos.p.m].type != End; ) { + draw_mline(mode, i++, start, cols, fm,fb,fa,ci.merger, + tpos, 0, NULL); + next_mline(&tpos, fm,fb,fa,ci.merger, mode); + } + botpos = tpos; botrow = i; + while (i<=lastrow) + blank(i++, 0, cols, a_void); } - } while (e.start != NULL && - (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1)); - return 0; -} + + if (splitrow >= 0) { + struct mpos spos = pos; + int smode = BEFORE|AFTER; + int srow = (rows + splitrow)/2; + if (visible(smode, ci.merger[spos.p.m].type, + spos.p.s) < 0) + prev_mline(&spos, fm,fb,fa,ci.merger, smode); + /* Now hi/lo might be wrong, so lets fix it. */ + tpos = spos; + while (spos.p.m >= 0 && spos.state != 0) + prev_mline(&spos, fm,fb,fa,ci.merger, smode); + while (!same_mpos(spos, tpos)) + next_mline(&spos, fm,fb,fa,ci.merger, smode); + -/* Drawing the display window. - * There are 7 different ways we can display the data, each - * of which can be configured by a keystroke: - * o original - just show the original file with no changes, but still - * which highlights of what is changed or unmatched - * r result - show just the result of the merge. Conflicts just show - * the original, not the before/after options - * b before - show the 'before' stream of the patch - * a after - show the 'after' stream of the patch - * d diff - show just the patch, both before and after - * m merge - show the full merge with -+ sections for changes. - * If point is in a wiggled or conflicted section the - * window is split horizontally and the diff is shown - * in the bottom window - * | sidebyside - two panes, left and right. Left holds the merge, - * right holds the diff. In the case of a conflict, - * left holds orig/after, right holds before/after - * - * The horizontal split for 'merge' mode is managed as follows. - * - The window is split when we first visit a line that contains - * a wiggle or a conflict, and the second pane is removed when - * we next visit a line that contains no changes (is fully Unchanged). - * - to display the second pane, we go do the previous mline for that - * alternate mode (BEFORE|AFTER) same point is not visible. Then - * we centre that line - * - We need to rewind to an unchanged section, and wind forward again - * to make sure that 'lo' and 'hi' are set properly. - * - every time we move, we redraw the second pane (see how that goes). - */ - - - -/* draw_mside draws one text line or, in the case of sidebyside, one side - * of a textline. - * The 'mode' tells us what to draw via the 'visible()' function. - * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER - * It may also have WIGGLED or CONFLICTED ored in - */ -void draw_mside(int mode, int row, int offset, int start, int cols, - struct file fm, struct file fb, struct file fa, struct merge *m, - struct mpos pos, - int target, int *colp) -{ - struct elmnt e; - int col = 0; - char tag; - - switch(pos.state) { - case 0: /* unchanged line */ - tag = ' '; - break; - case 1: /* 'before' text */ - tag = '-'; - if ((mode & ORIG) && (mode & CONFLICTED)) { - tag = '|'; - } - mode &= (ORIG|BEFORE); - break; - case 2: /* the 'after' part */ - tag = '+'; - mode &= (AFTER|RESULT); - break; - } - - if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) { - /* Not visible, just draw a blank */ - blank(row, offset, cols, a_void); - if (colp) - *colp = 0; - return; - } - - (void)attrset(A_NORMAL); - mvaddch(row, offset, tag); - offset++; - cols--; - - /* find previous visible newline, or start of file */ - do - e = prev_melmnt(&pos.p, fm,fb,fa,m); - while (e.start != NULL && - (!ends_mline(e) || - visible(mode, m[pos.p.m].type, pos.p.s)==-1)); + (void)attrset(a_sep); + for (i=0; i", e.start+1); - } - mvaddstr(row, col-start+offset, b); - col += strlen(b); + tpos = spos; + for (i=srow-1; i>splitrow; i-- ) { + prev_mline(&tpos, fm,fb,fa,ci.merger, smode); + draw_mline(smode, i, start, cols, fm,fb,fa,ci.merger, + tpos, 0, NULL); + } + while (i > splitrow) + blank(i--, 0, cols, a_void); + tpos = spos; + for (i=srow; i=0) { char buf[10]; sprintf(buf, "%d ", num); addstr(buf);} + if (meta & META(0)) addstr("ESC..."); + if (meta & SEARCH(0)) { + if (searchdir) addstr("Backwards "); + addstr("Search: "); + addstr(search); + if (search_notfound) + addstr(" - Not Found."); + search_notfound = 0; } - if (e.start[0] == 0) - continue; - (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s)); - c = (unsigned char *)e.start; - l = e.len; - while(l) { - if (*c >= ' ' && *c != 0x7f) { - if (col >= start && col < start+cols) - mvaddch(row, col-start+offset, *c); - col++; - } else if (*c == '\t') { - do { - if (col >= start && col < start+cols) - mvaddch(row, col-start+offset, ' '); - col++; - } while ((col&7)!= 0); + clrtoeol(); + move(row,col-start); + c = getch(); + tmeta = meta; meta = 0; + tnum = num; num = -1; + tvpos = vpos; vpos = pos; + switch(c | tmeta) { + case 27: /* escape */ + case META(27): + meta = META(0); + break; + case META('<'): /* start of file */ + start: + tpos = pos; row++; + do { + pos = tpos; row--; + prev_mline(&tpos, fm,fb,fa,ci.merger,mode); + } while (tpos.p.m >= 0); + if (row <= 0) + row = 0; + break; + case META('>'): /* end of file */ + case 'G': + if (tnum >=0) goto start; + tpos = pos; row--; + do { + pos = tpos; row++; + next_mline(&tpos, fm,fb,fa,ci.merger,mode); + } while (ci.merger[tpos.p.m].type != End); + if (row >= lastrow) + row = lastrow; + break; + case '0' ... '9': + if (tnum < 0) tnum = 0; + num = tnum*10 + (c-'0'); + break; + case 'q': + return; + + case '/': + case 'S'-64: + /* incr search forward */ + meta = SEARCH(0); + searchlen = 0; + search[searchlen] = 0; + searchdir = 0; + break; + case '\\': + case 'R'-64: + /* incr search backwards */ + meta = SEARCH(0); + searchlen = 0; + search[searchlen] = 0; + searchdir = 1; + break; + case SEARCH('G'-64): + case SEARCH('S'-64): + case SEARCH('R'-64): + /* search again */ + if ((c|tmeta) == SEARCH('R'-64)) + searchdir = 1; + if ((c|tmeta) == SEARCH('S'-64)) + searchdir = 0; + meta = SEARCH(0); + tpos = pos; trow = row; + if (searchdir) { + trow--; + prev_mline(&tpos, fm,fb,fa,ci.merger,mode); } else { - if (col >= start && col < start+cols) - mvaddch(row, col-start+offset, '?'); - col++; - } - c++; - if (colp && target <= col) { - if (col-start >= cols) - *colp = 10*col; - else - *colp = col; - colp = NULL; + trow++; + next_mline(&tpos, fm,fb,fa,ci.merger,mode); } - l--; - } - } -} - -void draw_mline(int mode, int row, int start, int cols, - struct file fm, struct file fb, struct file fa, - struct merge *m, - struct mpos pos, - int target, int *colp) -{ - /* - * Draw the left and right images of this line - * One side might be a_blank depending on the - * visibility of this newline - */ - int lcols, rcols; - - mode |= check_line(pos, fm,fb,fa,m,mode); - - if ( (mode & (BEFORE|AFTER)) && - (mode & (ORIG|RESULT))) { - - lcols = (cols-1)/2; - rcols = cols - lcols - 1; - - (void)attrset(A_STANDOUT); - mvaddch(row, lcols, '|'); + goto search_again; - draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols, - fm,fb,fa,m, pos, target, colp); + case SEARCH('H'-64): + meta = SEARCH(0); + if (anchor) { + struct search_anchor *a; + a = anchor; + anchor = a->next; + free(a); + } + if (anchor) { + struct search_anchor *a; + a = anchor; + anchor = a->next; + pos = a->pos; + row = a->row; + col = a->col; + search_notfound = a->notfound; + searchlen = a->searchlen; + search[searchlen] = 0; + free(a); + refresh = 1; + } + break; + case SEARCH(' ') ... SEARCH('~'): + case SEARCH('\t'): + meta = SEARCH(0); + if (searchlen < sizeof(search)-1) + search[searchlen++] = c & (0x7f); + search[searchlen] = 0; + tpos = pos; trow = row; + search_again: + search_notfound = 1; + do { + if (mcontains(tpos, fm,fb,fa,ci.merger,mode,search)) { + pos = tpos; + row = trow; + search_notfound = 0; + break; + } + if (searchdir) { + trow--; + prev_mline(&tpos, fm,fb,fa,ci.merger,mode); + } else { + trow++; + next_mline(&tpos, fm,fb,fa,ci.merger,mode); + } + } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End); - draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols, - fm,fb,fa,m, pos, 0, NULL); - } else - draw_mside(mode, row, 0, start, cols, - fm,fb,fa,m, pos, target, colp); -} + break; + case 'L'-64: + refresh = 2; + row = lastrow / 2; + break; -extern void cleanlist(struct file a, struct file b, struct csl *list); + case 'V'-64: /* page down */ + pos = botpos; + if (botrow <= lastrow) + row = botrow; + else + row = 2; + refresh = 1; + break; + case META('v'): /* page up */ + pos = toppos; + row = lastrow-1; + refresh = 1; + break; -void merge_window(struct plist *p, FILE *f, int reverse) -{ - /* display the merge in two side-by-side - * panes. - * left side shows diff between original and new - * right side shows the requested patch - * - * Unmatched: c_unmatched - left only - * Unchanged: c_normal - left and right - * Extraneous: c_extra - right only - * Changed-a: c_changed - left and right - * Changed-c: c_new - left and right - * AlreadyApplied-b: c_old - right only - * AlreadyApplied-c: c_applied - left and right - * Conflict-a: ?? left only - * Conflict-b: ?? left and right - * Conflict-c: ?? - * - * A Conflict is displayed as the original in the - * left side, and the highlighted diff in the right. - * - * Newlines are the key to display. - * 'pos' is always a newline (or eof). - * For each side that this newline is visible on, we - * rewind the previous newline visible on this side, and - * the display the stuff in between - * - * A 'position' is an offset in the merger, a stream - * choice (a,b,c - some aren't relevant) and an offset in - * that stream - */ + case 'j': + case 'n': + case 'N'-64: + case KEY_DOWN: + if (tnum < 0) tnum = 1; + for (; tnum > 0 ; tnum--) { + tpos = pos; + next_mline(&tpos, fm,fb,fa,ci.merger, mode); + if (ci.merger[tpos.p.m].type != End) { + pos = tpos; + row++; + } + } + break; + case 'N': + /* Next diff */ + tpos = pos; row--; + do { + pos = tpos; row++; + next_mline(&tpos, fm,fb,fa,ci.merger, mode); + } while (pos.state != 0 && ci.merger[tpos.p.m].type != End); + tpos = pos; row--; + do { + pos = tpos; row++; + next_mline(&tpos, fm,fb,fa,ci.merger, mode); + } while (pos.state == 0 && ci.merger[tpos.p.m].type != End); - struct stream sm, sb, sa, sp; /* main, before, after, patch */ - struct file fm, fb, fa; - struct csl *csl1, *csl2; - struct ci ci; - char buf[100]; - int ch; - int refresh = 2; - int rows = 0, cols = 0; - int splitrow = -1; - int lastrow = 0; - int i, c; - int mode = ORIG|RESULT | BEFORE|AFTER; - char *modename = "sidebyside"; + break; + case 'P': + /* Previous diff */ + tpos = pos; row++; + do { + pos = tpos; row--; + prev_mline(&tpos, fm,fb,fa,ci.merger, mode); + } while (tpos.state == 0 && tpos.p.m >= 0); + tpos = pos; row++; + do { + pos = tpos; row--; + prev_mline(&tpos, fm,fb,fa,ci.merger, mode); + } while (tpos.state != 0 && tpos.p.m >= 0); + break; - int row,start = 0; - int trow; - int col=0, target=0; - struct mpos pos; - struct mpos tpos, toppos, botpos; - struct mpos vpos, tvpos; - int botrow = 0; - int meta = 0, tmeta; - int num= -1, tnum; - char search[80]; - int searchlen = 0; - int search_notfound = 0; - int searchdir = 0; - struct search_anchor { - struct search_anchor *next; - struct mpos pos; - int notfound; - int row, col, searchlen; - } *anchor = NULL; + case 'k': + case 'p': + case 'P'-64: + case KEY_UP: + if (tnum < 0) tnum = 1; + for (; tnum > 0 ; tnum--) { + tpos = pos; + prev_mline(&tpos, fm,fb,fa,ci.merger, mode); + if (tpos.p.m >= 0) { + pos = tpos; + row--; + } + } + break; - sp = load_segment(f, p->start, p->end); - if (reverse) - ch = split_patch(sp, &sa, &sb); - else - ch = split_patch(sp, &sb, &sa); + case KEY_LEFT: + case 'h': + /* left */ + target = col - 1; + if (target < 0) target = 0; + break; + case KEY_RIGHT: + case 'l': + /* right */ + target = col + 1; + break; - sm = load_file(p->file); - fm = split_stream(sm, ByWord, 0); - fb = split_stream(sb, ByWord, 0); - fa = split_stream(sa, ByWord, 0); + case '^': + case 'A'-64: + /* Start of line */ + target = 0; + break; + case '$': + case 'E'-64: + /* End of line */ + target = 1000; + break; - csl1 = pdiff(fm, fb, ch); - csl2 = diff(fb, fa); + case 'a': /* 'after' view in patch window */ + mode = AFTER; modename = "after"; + refresh = 2; + break; + case 'b': /* 'before' view in patch window */ + mode = BEFORE; modename = "before"; + refresh = 2; + break; + case 'o': /* 'original' view in the merge window */ + mode = ORIG; modename = "original"; + refresh = 2; + break; + case 'r': /* the 'result' view in the merge window */ + mode = RESULT; modename = "result"; + refresh = 2; + break; + case 'd': + mode = BEFORE|AFTER; modename = "diff"; + refresh = 2; + break; + case 'm': + mode = ORIG|RESULT; modename = "merge"; + refresh = 2; + break; - ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1); + case '|': + mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; + refresh = 1; + break; - row = 1; - pos.p.m = 0; /* merge node */ - pos.p.s = 0; /* stream number */ - pos.p.o = -1; /* offset */ - pos.p.lineno = 1; - pos.state = 0; - next_mline(&pos, fm,fb,fa,ci.merger, mode); - vpos = pos; - while(1) { - if (refresh == 2) { - clear(); - sprintf(buf, "File: %s%s Mode: %s\n", - p->file,reverse?" - reversed":"", modename); - (void)attrset(A_BOLD); mvaddstr(0,0,buf); - clrtoeol(); - (void)attrset(A_NORMAL); + case 'H': + if (start > 0) start--; + target = start + 1; refresh = 1; - } - if (row < 1 || row >= lastrow) + break; + case 'L': + if (start < cols) start++; + target = start + 1; refresh = 1; + break; + case '<': + prev_melmnt(&tvpos.p, fm,fb,fa,ci.merger); + if (tvpos.p.m >= 0) + vpos = tvpos; + break; + case '>': + next_melmnt(&tvpos.p, fm,fb,fa,ci.merger); + if (ci.merger[tvpos.p.m].type != End) + vpos = tvpos; + break; + } - - if (mode == (ORIG|RESULT)) { - int cmode = check_line(pos, fm,fb,fa,ci.merger, mode); - if (splitrow < 0 && (cmode & (WIGGLED|CONFLICTED))) { - splitrow = (rows+1)/2; - lastrow = splitrow - 1; - refresh = 1; + if (meta == SEARCH(0)) { + if (anchor == NULL || + !same_mpos(anchor->pos, pos) || + anchor->searchlen != searchlen || + anchor->col != col) { + struct search_anchor *a = malloc(sizeof(*a)); + a->pos = pos; + a->row = row; + a->col = col; + a->searchlen = searchlen; + a->notfound = search_notfound; + a->next = anchor; + anchor = a; } - if (splitrow >= 0 && !(cmode & CHANGES)) { - splitrow = -1; - lastrow = rows-1; - refresh = 1; + } else { + while (anchor) { + struct search_anchor *a = anchor; + anchor = a->next; + free(a); } - } else if (splitrow >= 0) { - splitrow = -1; - lastrow = rows-1; - refresh = 1; } + } +} - if (refresh) { - getmaxyx(stdscr, rows, cols); - rows--; /* keep last row clear */ - if (splitrow >= 0) { - splitrow = (rows+1)/2; - lastrow = splitrow - 1; - } else - lastrow = rows - 1; +struct plist *patch_add_file(struct plist *pl, int *np, char *file, + unsigned int start, unsigned int end) +{ + /* size of pl is 0, 16, n^2 */ + int n = *np; + int asize; - if (row < -3) - row = lastrow/2+1; - if (row < 1) - row = 1; - if (row > lastrow+3) - row = lastrow/2+1; - if (row >= lastrow) - row = lastrow-1; + while (*file == '/') + /* leading '/' are bad... */ + file++; + + if (n==0) + asize = 0; + else if (n<=16) + asize = 16; + else if ((n&(n-1))==0) + asize = n; + else + asize = n+1; /* not accurate, but not too large */ + if (asize <= n) { + /* need to extend array */ + struct plist *npl; + if (asize < 16) asize = 16; + else asize += asize; + npl = realloc(pl, asize * sizeof(struct plist)); + if (!npl) { + fprintf(stderr, "malloc failed - skipping %s\n", file); + return pl; } - if (getenv("WIGGLE_VTRACE")) { - char b[100]; - char *e, e2[7]; - int i; - switch (vpos.p.s) { - case 0: e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start; break; - case 1: e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start; break; - case 2: e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start; break; - } - for (i=0; i<6; i++) { - e2[i] = e[i]; - if (e2[i] < 32 || e2[i] >= 127) e2[i] = '?'; - } - sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state, - vpos.p.s, vpos.p.o, - vpos.p.m, typenames[ci.merger[vpos.p.m].type], - ci.merger[vpos.p.m].al, - ci.merger[vpos.p.m].bl, - ci.merger[vpos.p.m].cl, - ci.merger[vpos.p.m].in_conflict, - e2 - ); - (void)attrset(A_NORMAL); - mvaddstr(0, 50, b); - clrtoeol(); + pl = npl; + } + pl[n].file = file; + pl[n].start = start; + pl[n].end = end; + pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1; + pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100; + pl[n].open = 1; + pl[n].calced = 0; + *np = n+1; + return pl; +} + +struct plist *parse_patch(FILE *f, FILE *of, int *np) +{ + /* read a multi-file patch from 'f' and record relevant + * details in a plist. + * if 'of' >= 0, fd might not be seekable so we write + * to 'of' and use lseek on 'of' to determine position + */ + struct plist *plist = NULL; + + while (!feof(f)) { + /* first, find the start of a patch: "\n+++ " + * grab the file name and scan to the end of a line + */ + char *target="\n+++ "; + char *target2="\n--- "; + char *pos = target; + int c; + char name[1024]; + unsigned start, end; + + while (*pos && (c=fgetc(f)) != EOF ) { + if (of) fputc(c, of); + if (c == *pos) + pos++; + else pos = target; } - { - char lbuf[20]; - (void)attrset(A_BOLD); - sprintf(lbuf, "ln:%d", (pos.p.lineno-1)/2); - mvaddstr(0, cols - strlen(lbuf) - 4, " "); - mvaddstr(0, cols - strlen(lbuf) - 1, lbuf); + if (c == EOF) + break; + assert(c == ' '); + /* now read a file name */ + pos = name; + while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' && + pos - name < 1023) { + *pos++ = c; + if (of) + fputc(c, of); } - /* Always refresh the line */ - while (start > target) { start -= 8; refresh = 1;} - if (start < 0) start = 0; - retry: - draw_mline(mode, row, start, cols, fm,fb,fa,ci.merger, - pos, target, &col); + *pos = 0; + if (c == EOF) + break; + if (of) fputc(c, of); + while (c != '\n' && (c=fgetc(f)) != EOF) + if (of) + fputc(c, of); - if (col > cols+start) { - start += 8; - refresh = 1; - goto retry; + start = ftell(of ?: f); + + if (c == EOF) + break; + + /* now skip to end - "\n--- " */ + pos = target2+1; + + while (*pos && (c=fgetc(f)) != EOF) { + if (of) + fputc(c, of); + if (c == *pos) + pos++; + else + pos = target2; } - if (col < start) { - start -= 8; - refresh = 1; - if (start < 0) start = 0; - goto retry; + end = ftell(of ?: f); + if (pos > target2) + end -= (pos - target2) - 1; + plist = patch_add_file(plist, np, + strdup(name), start, end); + } + return plist; +} + +static struct stream load_segment(FILE *f, + unsigned int start, unsigned int end) +{ + struct stream s; + s.len = end - start; + s.body = malloc(s.len); + if (s.body) { + fseek(f, start, 0); + if (fread(s.body, 1, s.len, f) != s.len) { + free(s.body); + s.body = NULL; } - if (refresh) { - refresh = 0; - tpos = pos; + } else + die(); + return s; +} - for (i=row-1; i>=1 && tpos.p.m >= 0; ) { - prev_mline(&tpos, fm,fb,fa,ci.merger, mode); - draw_mline(mode, i--, start, cols, fm,fb,fa,ci.merger, - tpos, 0, NULL); - } - if (i) { - row -= (i+1); - refresh = 1; - goto retry; - } - toppos = tpos; - while (i >= 1) - blank(i--, 0, cols, a_void); - tpos = pos; - for (i=row; i<= lastrow && ci.merger[tpos.p.m].type != End; ) { - draw_mline(mode, i++, start, cols, fm,fb,fa,ci.merger, - tpos, 0, NULL); - next_mline(&tpos, fm,fb,fa,ci.merger, mode); - } - botpos = tpos; botrow = i; - while (i<=lastrow) - blank(i++, 0, cols, a_void); - } - - if (splitrow >= 0) { - struct mpos spos = pos; - int smode = BEFORE|AFTER; - int srow = (rows + splitrow)/2; - if (visible(smode, ci.merger[spos.p.m].type, - spos.p.s) < 0) - prev_mline(&spos, fm,fb,fa,ci.merger, smode); - /* Now hi/lo might be wrong, so lets fix it. */ - tpos = spos; - while (spos.p.m >= 0 && spos.state != 0) - prev_mline(&spos, fm,fb,fa,ci.merger, smode); - while (!same_mpos(spos, tpos)) - next_mline(&spos, fm,fb,fa,ci.merger, smode); - +int pl_cmp(const void *av, const void *bv) +{ + const struct plist *a = av; + const struct plist *b = bv; + return strcmp(a->file, b->file); +} - (void)attrset(a_sep); - for (i=0; isplitrow; i-- ) { - prev_mline(&tpos, fm,fb,fa,ci.merger, smode); - draw_mline(smode, i, start, cols, fm,fb,fa,ci.merger, - tpos, 0, NULL); - } - while (i > splitrow) - blank(i--, 0, cols, a_void); - tpos = spos; - for (i=srow; i=0) { char buf[10]; sprintf(buf, "%d ", num); addstr(buf);} - if (meta & META(0)) addstr("ESC..."); - if (meta & SEARCH(0)) { - if (searchdir) addstr("Backwards "); - addstr("Search: "); - addstr(search); - if (search_notfound) - addstr(" - Not Found."); - search_notfound = 0; - } - clrtoeol(); - move(row,col-start); - c = getch(); - tmeta = meta; meta = 0; - tnum = num; num = -1; - tvpos = vpos; vpos = pos; - switch(c | tmeta) { - case 27: /* escape */ - case META(27): - meta = META(0); - break; - case META('<'): /* start of file */ - start: - tpos = pos; row++; - do { - pos = tpos; row--; - prev_mline(&tpos, fm,fb,fa,ci.merger,mode); - } while (tpos.p.m >= 0); - if (row <= 0) - row = 0; - break; - case META('>'): /* end of file */ - case 'G': - if (tnum >=0) goto start; - tpos = pos; row--; - do { - pos = tpos; row++; - next_mline(&tpos, fm,fb,fa,ci.merger,mode); - } while (ci.merger[tpos.p.m].type != End); - if (row >= lastrow) - row = lastrow; - break; - case '0' ... '9': - if (tnum < 0) tnum = 0; - num = tnum*10 + (c-'0'); - break; - case 'q': - return; + depth++; + } +} - case '/': - case 'S'-64: - /* incr search forward */ - meta = SEARCH(0); - searchlen = 0; - search[searchlen] = 0; - searchdir = 0; - break; - case '\\': - case 'R'-64: - /* incr search backwards */ - meta = SEARCH(0); - searchlen = 0; - search[searchlen] = 0; - searchdir = 1; - break; - case SEARCH('G'-64): - case SEARCH('S'-64): - case SEARCH('R'-64): - /* search again */ - if ((c|tmeta) == SEARCH('R'-64)) - searchdir = 1; - if ((c|tmeta) == SEARCH('S'-64)) - searchdir = 0; - meta = SEARCH(0); - tpos = pos; trow = row; - if (searchdir) { - trow--; - prev_mline(&tpos, fm,fb,fa,ci.merger,mode); - } else { - trow++; - next_mline(&tpos, fm,fb,fa,ci.merger,mode); - } - goto search_again; +struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr) +{ + /* any parent of file that is not a parent of curr + * needs to be added to pl + */ + int d = common_depth(file, curr); + char *buf = curr; + while (d) { + char *c = strchr(file, '/'); + int l; + if (c) l = c-file; else l = strlen(file); + file += l; + curr += l; + while (*file == '/') file++; + while (*curr == '/') curr++; + d--; + } + while (*file) { + if (curr > buf && curr[-1] != '/') + *curr++ = '/'; + while (*file && *file != '/') + *curr++ = *file++; + while (*file == '/') file++; + *curr = '\0'; + if (*file) + pl = patch_add_file(pl, np, strdup(buf), + 0, 0); + } + return pl; +} - case SEARCH('H'-64): - meta = SEARCH(0); - if (anchor) { - struct search_anchor *a; - a = anchor; - anchor = a->next; - free(a); - } - if (anchor) { - struct search_anchor *a; - a = anchor; - anchor = a->next; - pos = a->pos; - row = a->row; - col = a->col; - search_notfound = a->notfound; - searchlen = a->searchlen; - search[searchlen] = 0; - free(a); - refresh = 1; - } - break; - case SEARCH(' ') ... SEARCH('~'): - case SEARCH('\t'): - meta = SEARCH(0); - if (searchlen < sizeof(search)-1) - search[searchlen++] = c & (0x7f); - search[searchlen] = 0; - tpos = pos; trow = row; - search_again: - search_notfound = 1; - do { - if (mcontains(tpos, fm,fb,fa,ci.merger,mode,search)) { - pos = tpos; - row = trow; - search_notfound = 0; - break; - } - if (searchdir) { - trow--; - prev_mline(&tpos, fm,fb,fa,ci.merger,mode); - } else { - trow++; - next_mline(&tpos, fm,fb,fa,ci.merger,mode); - } - } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End); +struct plist *sort_patches(struct plist *pl, int *np) +{ + /* sort the patches, add directory names, and re-sort */ + char curr[1024]; + char *prev; + int parents[100]; + int prevnode[100]; + int i, n; + qsort(pl, *np, sizeof(struct plist), pl_cmp); + curr[0] = 0; + n = *np; + for (i=0; i -1) + pl[pl[i].prev].next = i; + prev = pl[i].file; + parents[d] = i; + prevnode[d] = i; + prevnode[d+1] = -1; + } + return pl; +} - case 'j': - case 'n': - case 'N'-64: - case KEY_DOWN: - if (tnum < 0) tnum = 1; - for (; tnum > 0 ; tnum--) { - tpos = pos; - next_mline(&tpos, fm,fb,fa,ci.merger, mode); - if (ci.merger[tpos.p.m].type != End) { - pos = tpos; - row++; - } - } - break; - case 'N': - /* Next diff */ - tpos = pos; row--; - do { - pos = tpos; row++; - next_mline(&tpos, fm,fb,fa,ci.merger, mode); - } while (pos.state != 0 && ci.merger[tpos.p.m].type != End); - tpos = pos; row--; - do { - pos = tpos; row++; - next_mline(&tpos, fm,fb,fa,ci.merger, mode); - } while (pos.state == 0 && ci.merger[tpos.p.m].type != End); +/* determine how much we need to stripe of the front of + * paths to find them from current directory. This is + * used to guess correct '-p' value. + */ +int get_strip(char *file) +{ + int fd; + int strip = 0; + + while (file && *file) { + fd = open(file, O_RDONLY); + if (fd >= 0) { + close(fd); + return strip; + } + strip++; + file = strchr(file, '/'); + if (file) + while(*file == '/') + file++; + } + return -1; - break; - case 'P': - /* Previous diff */ - tpos = pos; row++; - do { - pos = tpos; row--; - prev_mline(&tpos, fm,fb,fa,ci.merger, mode); - } while (tpos.state == 0 && tpos.p.m >= 0); - tpos = pos; row++; - do { - pos = tpos; row--; - prev_mline(&tpos, fm,fb,fa,ci.merger, mode); - } while (tpos.state != 0 && tpos.p.m >= 0); - break; +} - case 'k': - case 'p': - case 'P'-64: - case KEY_UP: - if (tnum < 0) tnum = 1; - for (; tnum > 0 ; tnum--) { - tpos = pos; - prev_mline(&tpos, fm,fb,fa,ci.merger, mode); - if (tpos.p.m >= 0) { - pos = tpos; - row--; - } - } - break; +int set_prefix(struct plist *pl, int n, int strip) +{ + int i; + for(i=0; i<4 && i= 0) + pos = pl[pos].last; + return pos; +} - case '|': - mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; - refresh = 1; - break; +int get_next(int pos, struct plist *pl, int n) +{ + if (pos == -1) return pos; + if (pl[pos].open) { + if (pos +1 < n) + return pos+1; + else + return -1; + } + while (pos >= 0 && pl[pos].next == -1) + pos = pl[pos].parent; + if (pos >= 0) + pos = pl[pos].next; + return pos; +} - case 'H': - if (start > 0) start--; - target = start + 1; - refresh = 1; - break; - case 'L': - if (start < cols) start++; - target = start + 1; - refresh = 1; - break; +void draw_one(int row, struct plist *pl, FILE *f, int reverse) +{ + char hdr[12]; + hdr[0] = 0; - case '<': - prev_melmnt(&tvpos.p, fm,fb,fa,ci.merger); - if (tvpos.p.m >= 0) - vpos = tvpos; - break; - case '>': - next_melmnt(&tvpos.p, fm,fb,fa,ci.merger); - if (ci.merger[tvpos.p.m].type != End) - vpos = tvpos; - break; - } + if (pl == NULL) { + move(row,0); + clrtoeol(); + return; + } + if (pl->calced == 0 && pl->end) { + /* better load the patch and count the chunks */ + struct stream s1, s2; + struct stream s = load_segment(f, pl->start, pl->end); + struct stream sf = load_file(pl->file); + if (reverse) + pl->chunks = split_patch(s, &s2, &s1); + else + pl->chunks = split_patch(s, &s1, &s2); - if (meta == SEARCH(0)) { - if (anchor == NULL || - !same_mpos(anchor->pos, pos) || - anchor->searchlen != searchlen || - anchor->col != col) { - struct search_anchor *a = malloc(sizeof(*a)); - a->pos = pos; - a->row = row; - a->col = col; - a->searchlen = searchlen; - a->notfound = search_notfound; - a->next = anchor; - anchor = a; - } + if (sf.body == NULL) { + pl->wiggles = pl->conflicts = -1; } else { - while (anchor) { - struct search_anchor *a = anchor; - anchor = a->next; - free(a); - } + struct file ff, fp1, fp2; + struct csl *csl1, *csl2; + struct ci ci; + ff = split_stream(sf, ByWord, 0); + fp1 = split_stream(s1, ByWord, 0); + fp2 = split_stream(s2, ByWord, 0); + csl1 = pdiff(ff, fp1, pl->chunks); + csl2 = diff(fp1,fp2); + ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1); + pl->wiggles = ci.wiggles; + pl->conflicts = ci.conflicts; + free(csl1); + free(csl2); + free(ff.list); + free(fp1.list); + free(fp2.list); } + + free(s1.body); + free(s2.body); + free(s.body); + pl->calced = 1; + } + if (pl->end == 0) { + strcpy(hdr, " "); + } else { + if (pl->chunks > 99) + strcpy(hdr, "XX"); + else sprintf(hdr, "%2d", pl->chunks); + if (pl->wiggles > 99) + strcpy(hdr+2, " XX"); + else sprintf(hdr+2, " %2d", pl->wiggles); + if (pl->conflicts > 99) + strcpy(hdr+5, " XX "); + else sprintf(hdr+5, " %2d ", pl->conflicts); } + if (pl->end) + strcpy(hdr+9, "= "); + else if (pl->open) + strcpy(hdr+9, "+ "); + else strcpy(hdr+9, "- "); + + mvaddstr(row, 0, hdr); + mvaddstr(row, 11, pl->file); + clrtoeol(); } void main_window(struct plist *pl, int n, FILE *f, int reverse) @@ -1786,6 +1778,18 @@ void main_window(struct plist *pl, int n, FILE *f, int reverse) } + +void catch(int sig) +{ + if (sig == SIGINT) { + signal(sig, catch); + return; + } + nocbreak();nl();endwin(); + printf("Died on signal %d\n", sig); + exit(2); +} + int vpatch(int argc, char *argv[], int strip, int reverse, int replace) { /* NOTE argv[0] is first arg... */