]> git.neil.brown.name Git - wiggle.git/commitdiff
browser: rearrange code
authorNeil Brown <neilb@suse.de>
Sun, 4 Apr 2010 06:14:25 +0000 (16:14 +1000)
committerNeil Brown <neilb@suse.de>
Sun, 4 Apr 2010 06:14:25 +0000 (16:14 +1000)
Put all the 'main window' code together, and all the
'merge window' code together.

Signed-off-by: Neil Brown <neilb@suse.de>
vpatch.c

index f7ae935823314e5932fec03b7129ced31ea22ca3..c5f3ce90597f2b7209823cb46ddd7cb380a5bb70 100644 (file)
--- a/vpatch.c
+++ b/vpatch.c
 
 #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; i<n; i++)
-               pl = add_dir(pl, np, pl[i].file, curr);
-
-       qsort(pl, *np, sizeof(struct plist), pl_cmp);
+       if (pos->s == 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; i<n; i++) {
-               int d = common_depth(prev, pl[i].file);
-               if (d == 0)
-                       pl[i].parent = -1;
-               else {
-                       pl[i].parent = parents[d-1];
-                       pl[pl[i].parent].last = i;
+       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;
+                       }
                }
-               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 && i<n  && strip < 0; i++)
-               strip = get_strip(pl[i].file);
+       int rv = 0;
+       struct elmnt e;
+       int unmatched = 0;
 
-       if (strip < 0) {
-               fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
-                       Cmd);
-               return 0;
-       }
-       for (i=0; i<n; i++) {
-               char *p = pl[i].file;
-               int j;
-               for (j=0; j<strip; j++) {
-                       if (p) p = strchr(p,'/');
-                       while (p && *p == '/') p++;
+
+       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;
                }
-               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; i<e.len; i++)
+                               if (strncmp(e.start+i, search, len)==0)
+                                       return 1;
+               }
+       } while (e.start != NULL &&
+                (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
+       return 0;
 }
 
-/* global attributes */
-int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already;
+/* 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).
+ */
 
-void draw_one(int row, struct plist *pl, FILE *f, int reverse)
+
+
+/* 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)
 {
-       char hdr[12];
-       hdr[0] = 0;
+       struct elmnt e;
+       int col = 0;
+       char tag;
 
-       if (pl == NULL) {
-               move(row,0);
-               clrtoeol();
+       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;
        }
-       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 (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<e.len; i++)
-                               if (strncmp(e.start+i, search, len)==0)
-                                       return 1;
+                       }
+                       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);
                }
-       } 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<cols; i++)
+                               mvaddstr(splitrow, i, "-");
 
-       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);
+                       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<rows && ci.merger[tpos.p.m].type != End; i++) {
+                               draw_mline(smode, i, start, cols, fm, fb,fa,ci.merger,
+                                          tpos, 0, NULL);
+                               next_mline(&tpos, fm,fb,fa,ci.merger, smode);
                        }
-                       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;
+#define META(c) ((c)|0x1000)
+#define        SEARCH(c) ((c)|0x2000)
+               move(rows,0);
+               (void)attrset(A_NORMAL);
+               if (num>=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; i<cols; i++)
-                               mvaddstr(splitrow, i, "-");
+int common_depth(char *a, char *b)
+{
+       /* find number of path segments that these two have
+        * in common
+        */
+       int depth = 0;
+       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++;
 
-                       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<rows && ci.merger[tpos.p.m].type != End; i++) {
-                               draw_mline(smode, i, start, cols, fm, fb,fa,ci.merger,
-                                          tpos, 0, NULL);
-                               next_mline(&tpos, fm,fb,fa,ci.merger, smode);
-                       }
-               }
-#define META(c) ((c)|0x1000)
-#define        SEARCH(c) ((c)|0x2000)
-               move(rows,0);
-               (void)attrset(A_NORMAL);
-               if (num>=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<n; i++)
+               pl = add_dir(pl, np, pl[i].file, curr);
 
-                       break;
-               case 'L'-64:
-                       refresh = 2;
-                       row = lastrow / 2;
-                       break;
+       qsort(pl, *np, sizeof(struct plist), pl_cmp);
 
-               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;
+       /* array is now stable, so set up parent pointers */
+       n = *np;
+       curr[0] = 0;
+       prevnode[0] = -1;
+       prev = "";
+       for (i=0; i<n; i++) {
+               int d = common_depth(prev, pl[i].file);
+               if (d == 0)
+                       pl[i].parent = -1;
+               else {
+                       pl[i].parent = parents[d-1];
+                       pl[pl[i].parent].last = i;
+               }
+               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;
+}
 
-               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<n  && strip < 0; i++)
+               strip = get_strip(pl[i].file);
 
-               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;
+       if (strip < 0) {
+               fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
+                       Cmd);
+               return 0;
+       }
+       for (i=0; i<n; i++) {
+               char *p = pl[i].file;
+               int j;
+               for (j=0; j<strip; j++) {
+                       if (p) p = strchr(p,'/');
+                       while (p && *p == '/') p++;
+               }
+               if (p == NULL) {
+                       fprintf(stderr, "%s: cannot strip %d segments from %s\n",
+                               Cmd, strip, pl[i].file);
+                       return 0;
+               }
+               pl[i].file = p;
+       }
+       return 1;
+}
 
-               case '^':
-               case 'A'-64:
-                       /* Start of line */
-                       target = 0;
-                       break;
-               case '$':
-               case 'E'-64:
-                       /* End of line */
-                       target = 1000;
-                       break;
 
-               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;
+int get_prev(int pos, struct plist *pl, int n)
+{
+       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;
+}
 
-               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... */