Index: trunk/gui/src/RpDiffview.c
===================================================================
--- trunk/gui/src/RpDiffview.c	(revision 2119)
+++ trunk/gui/src/RpDiffview.c	(revision 2125)
@@ -3,5 +3,23 @@
  *  RpDiffview - like a textbox, but for showing diffs
  *
- *  Loads two strings, performs a diff, and shows the results.
+ *  Loads two strings, performs a diff, and shows the results.  Use
+ *  it as follows:
+ *
+ *  Rappture::Diffview .dv \
+ *      -addedbackground blue -addedforeground white \
+ *      -deletedbackground red -deletedforeground white -overstrike true \
+ *      -changedbackground black -changedforeground yellow \
+ *      -xscrollcommand {.x set} -yscrollcommand {.y set}
+ *  
+ *  scrollbar .x -orient horizontal -command {.dv xview}
+ *  scrollbar .y -orient vertical -command {.dv yview}
+ *  
+ *  .dv text 1 [exec cat /tmp/file1]
+ *  .dv text 2 [exec cat /tmp/file2]
+ *  .dv configure -diff 2->1 -layout sidebyside
+ *  
+ *  puts "DIFF OUTPUT:"
+ *  puts [.dv diffs -std]
+ *
  * ======================================================================
  *  AUTHOR:  Michael McLennan, Purdue University
@@ -14,4 +32,5 @@
 #include "tk.h"
 #include <string.h>
+#include <stdlib.h>
 
 /*
@@ -46,5 +65,4 @@
     int numLines;		/* number of lines */
     int maxLines;		/* max size of storage in startPtr/endPtr */
-    int maxWidth;		/* max length in pixels for all lines */
     char **startPtr;		/* array of start pointers for each line */
     int *lenPtr;		/* array of lengths for each line */
@@ -77,4 +95,6 @@
     int fromIndex2;		/* starts at this line in buffer 2 */
     int toIndex2;		/* ends at this line in buffer 2 */
+    int fromWorld;		/* starts at this line in world view */
+    int toWorld;		/* ends at this line in world view */
 } DiffviewDiffOp;
 
@@ -84,4 +104,20 @@
     DiffviewDiffOp *ops;	/* list of diffs */
 } DiffviewDiffs;
+
+/*
+ * Data structure used for each line of the final layout:
+ */
+typedef struct {
+    char style;			/* layout style: 'a'=add, 'd'=del, etc. */
+    char bnum;			/* show line from this buffer */
+    int bline;			/* line number in buffer bnum */
+    int diffNum;		/* line is part of this diff */
+} DiffviewLayoutLine;
+
+typedef struct {
+    int maxLines;		/* maximum number of lines allocated */
+    int numLines;		/* current number of lines in layout */
+    DiffviewLayoutLine *lines;	/* info for all lines in world view */
+} DiffviewLayout;
 
 /*
@@ -109,29 +145,32 @@
     DiffviewBuffer buffer[2];	/* text to diff -- buffers #1 and #2 */
     DiffviewDiffs *diffsPtr;    /* diff: longest common subseq btwn buffers */
-
-    enum diffdir diffdir;       /* diff between these buffers */
-    enum layoutStyle layout;    /* layout style (side-by-side or inline) */
-    int maxWidth;               /* maximum width of world view in pixels */
-    int maxHeight;              /* maximum height of world view in pixels */
-    int lineHeight;             /* height of a line with current font */
-    int lineAscent;             /* ascent of a line with current font */
-    int topLine;                /* index of topmost line in view */
-    int btmLine;                /* index of bottommost line in view */
-    int fullLines;              /* number of full lines that fit in view */
-
-    int xOffset;                /* offset in pixels to left edge of view */
-    int xScrollUnit;            /* num pixels for one unit of horizontal
-                                 * scrolling (one average character) */
-    int yOffset;                /* offset in pixels to top edge of view */
-    int yScrollUnit;            /* num pixels for one unit of vertical
-                                 * scrolling (one average line) */
+    DiffviewLayout worldview;   /* array mapping each real line in world
+				 * coordinates to the corresponding line
+				 * in buffer #1/#2, along with its style */
+
+    enum diffdir diffdir;	/* diff between these buffers */
+    enum layoutStyle layout;	/* layout style (side-by-side or inline) */
+    int maxWidth;		/* maximum width of world view in pixels */
+    int maxHeight;		/* maximum height of world view in pixels */
+    int lineHeight;		/* height of a line with current font */
+    int lineAscent;		/* ascent of a line with current font */
+    int topLine;		/* index of topmost line in view */
+    int btmLine;		/* index of bottommost line in view */
+    int fullLines;		/* number of full lines that fit in view */
+
+    int xOffset;		/* offset in pixels to left edge of view */
+    int xScrollUnit;		/* num pixels for one unit of horizontal
+				 * scrolling (one average character) */
+    int yOffset;		/* offset in pixels to top edge of view */
+    int yScrollUnit;		/* num pixels for one unit of vertical
+				 * scrolling (one average line) */
     char *takeFocus;		/* value of -takefocus option */
     char *yScrollCmd;		/* command prefix for scrolling */
     char *xScrollCmd;		/* command prefix for scrolling */
 
-    int scanMarkX;              /* x-coord for "scan mark" */
-    int scanMarkY;              /* y-coord for "scan mark" */
-    int scanMarkXStart;         /* xOffset at start of scan */
-    int scanMarkYStart;         /* yOffset at start of scan */
+    int scanMarkX;		/* x-coord for "scan mark" */
+    int scanMarkY;		/* y-coord for "scan mark" */
+    int scanMarkXStart;		/* xOffset at start of scan */
+    int scanMarkYStart;		/* yOffset at start of scan */
 
 
@@ -139,5 +178,15 @@
     Tk_Font tklastfont;		/* previous text font (so we detect changes) */
     XColor *fgColorPtr;		/* normal foreground color */
-    GC textGC;			/* GC for drawing text */
+    XColor *addBgColorPtr;	/* background for "added" text in diff */
+    XColor *addFgColorPtr;	/* foreground for "added" text in diff */
+    XColor *delBgColorPtr;	/* background for "deleted" text in diff */
+    XColor *delFgColorPtr;	/* foreground for "deleted" text in diff */
+    XColor *chgBgColorPtr;	/* background for "changed" text in diff */
+    XColor *chgFgColorPtr;	/* foreground for "changed" text in diff */
+    int overStrDel;		/* non-zero => overstrike deleted text */
+    GC normGC;			/* GC for drawing normal text */
+    GC addFgGC;			/* GC for drawing "added" text in diff */
+    GC delFgGC;			/* GC for drawing "deleted" text in diff */
+    GC chgFgGC;			/* GC for drawing "changed" text in diff */
     int width;			/* overall width of widget, in pixels */
     int height;			/* overall height of widget, in pixels */
@@ -172,11 +221,17 @@
 #define NORMAL_BG			"#d9d9d9"
 
+#define DEF_DIFFVIEW_ADDBG		"#ccffcc"
+#define DEF_DIFFVIEW_ADDFG		BLACK
 #define DEF_DIFFVIEW_BG_COLOR		NORMAL_BG
 #define DEF_DIFFVIEW_BG_MONO		WHITE
 #define DEF_DIFFVIEW_BORDERWIDTH	"2"
+#define DEF_DIFFVIEW_CHGBG		"#ffffcc"
+#define DEF_DIFFVIEW_CHGFG		BLACK
 #define DEF_DIFFVIEW_CURSOR		""
+#define DEF_DIFFVIEW_DELBG		"#ffcccc"
+#define DEF_DIFFVIEW_DELFG		"#666666"
 #define DEF_DIFFVIEW_DIFF		"1->2"
 #define DEF_DIFFVIEW_FG			BLACK
-#define DEF_DIFFVIEW_FONT		"*-Helvetica-Medium-R-Normal-*-12-120-*"
+#define DEF_DIFFVIEW_FONT		"Courier -12"
 #define DEF_DIFFVIEW_HEIGHT		"2i"
 #define DEF_DIFFVIEW_HIGHLIGHT_BG	NORMAL_BG
@@ -184,4 +239,5 @@
 #define DEF_DIFFVIEW_HIGHLIGHT_WIDTH	"1"
 #define DEF_DIFFVIEW_LAYOUT		"inline"
+#define DEF_DIFFVIEW_OVERSTRDEL		"true"
 #define DEF_DIFFVIEW_RELIEF		"sunken"
 #define DEF_DIFFVIEW_TAKE_FOCUS		(char*)NULL
@@ -194,4 +250,10 @@
  */
 static Tk_OptionSpec optionSpecs[] = {
+    {TK_OPTION_COLOR, "-addedbackground", "addedBackground", "Background",
+	 DEF_DIFFVIEW_ADDBG, -1, Tk_Offset(Diffview, addBgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-addedforeground", "addedForeground", "Foreground",
+	 DEF_DIFFVIEW_ADDFG, -1, Tk_Offset(Diffview, addFgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
     {TK_OPTION_BORDER, "-background", "background", "Background",
 	 DEF_DIFFVIEW_BG_COLOR, -1, Tk_Offset(Diffview, normalBorder),
@@ -204,6 +266,18 @@
 	 DEF_DIFFVIEW_BORDERWIDTH, -1, Tk_Offset(Diffview, borderWidth),
 	 0, 0, 0},
+    {TK_OPTION_COLOR, "-changedbackground", "changedBackground", "Background",
+	 DEF_DIFFVIEW_CHGBG, -1, Tk_Offset(Diffview, chgBgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-changedforeground", "changedForeground", "Foreground",
+	 DEF_DIFFVIEW_CHGFG, -1, Tk_Offset(Diffview, chgFgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
     {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
 	 DEF_DIFFVIEW_CURSOR, -1, Tk_Offset(Diffview, cursor),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-deletedbackground", "deletedBackground", "Background",
+	 DEF_DIFFVIEW_DELBG, -1, Tk_Offset(Diffview, delBgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-deletedforeground", "deletedForeground", "Foreground",
+	 DEF_DIFFVIEW_DELFG, -1, Tk_Offset(Diffview, delFgColorPtr),
 	 TK_OPTION_NULL_OK, 0, 0},
     {TK_OPTION_STRING_TABLE, "-diff", "diff", "Diff",
@@ -230,4 +304,6 @@
         DEF_DIFFVIEW_LAYOUT, -1, Tk_Offset(Diffview, layout),
         0, (ClientData)layoutStyleStrings, 0},
+    {TK_OPTION_BOOLEAN, "-overstrike", "overstrike", "Overstrike",
+	 DEF_DIFFVIEW_OVERSTRDEL, -1, Tk_Offset(Diffview, overStrDel), 0, 0, 0},
     {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
 	 DEF_DIFFVIEW_RELIEF, -1, Tk_Offset(Diffview, relief), 0, 0, 0},
@@ -273,5 +349,5 @@
 	                    Tcl_Obj *CONST objv[]));
 static int		DiffviewBboxSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
-	                    Diffview *dvPtr, int index));
+			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
 static int		DiffviewDiffsSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
 	                    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
@@ -282,4 +358,7 @@
 static int		DiffviewYviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
 			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
+static int		DiffviewGetIndex _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Diffview *dvPtr, Tcl_Obj *objPtr, int *linePtr,
+			    DiffviewDiffOp **diffOpPtrPtr));
 
 static void		DestroyDiffview _ANSI_ARGS_((char *memPtr));
@@ -319,4 +398,11 @@
 static void		DiffviewDiffsFree _ANSI_ARGS_((
 			    DiffviewDiffs *diffsPtr));
+static void		DiffviewLayoutAdd _ANSI_ARGS_((
+			    DiffviewLayout *layoutPtr,
+			    DiffviewLayoutLine *linePtr));
+static void		DiffviewLayoutClear _ANSI_ARGS_((
+			    DiffviewLayout *layoutPtr));
+static void		DiffviewLayoutFree _ANSI_ARGS_((
+			    DiffviewLayout *layoutPtr));
 
 /*
@@ -410,5 +496,8 @@
     dvPtr->cursor = None;
     dvPtr->relief = TK_RELIEF_SUNKEN;
-    dvPtr->textGC = None;
+    dvPtr->normGC = None;
+    dvPtr->addFgGC = None;
+    dvPtr->delFgGC = None;
+    dvPtr->chgFgGC = None;
     dvPtr->xScrollUnit = 1;
     dvPtr->yScrollUnit = 1;
@@ -460,5 +549,5 @@
     Diffview *dvPtr = (Diffview*)clientData;
     int result = TCL_OK;
-    int cmdToken, index;
+    int cmdToken;
     
     if (objc < 2) {
@@ -482,14 +571,5 @@
     switch (cmdToken) {
         case COMMAND_BBOX: {
-            if (objc != 3) {
-                Tcl_WrongNumArgs(interp, 2, objv, "index");
-                result = TCL_ERROR;
-                break;
-            }
-            result = Tcl_GetIntFromObj(interp, objv[2], &index);
-            if (result != TCL_OK) {
-                break;
-            }
-            result = DiffviewBboxSubCmd(interp, dvPtr, index);
+            result = DiffviewBboxSubCmd(interp, dvPtr, objc, objv);
             break;
         }
@@ -579,5 +659,7 @@
 
         case COMMAND_SEE: {
-            int maxLines;
+            DiffviewDiffOp *diffOpPtr;
+            int line1, line2, topline;
+
             if (objc != 3) {
                 Tcl_WrongNumArgs(interp, 2, objv, "index");
@@ -585,24 +667,33 @@
                 break;
             }
-            result = Tcl_GetIntFromObj(interp, objv[2], &index);
+            result = DiffviewGetIndex(interp, dvPtr, objv[2],
+                &line1, &diffOpPtr);
             if (result != TCL_OK) {
                 break;
             }
 
-            maxLines = dvPtr->maxHeight/dvPtr->lineHeight;
-            if (index >= maxLines) {
-                index = maxLines-1;
-            }
-            if (index < 0) {
-                index = 0;
-            }
-
-            if (index < dvPtr->topLine || index > dvPtr->btmLine) {
+            if (line1 < 0 && diffOpPtr != NULL) {
+                line1 = diffOpPtr->fromWorld;
+                line2 = diffOpPtr->toWorld;
+            } else {
+                line2 = line1;
+            }
+
+            if (line1 >= dvPtr->worldview.numLines) {
+                line1 = line2 = dvPtr->worldview.numLines-1;
+            }
+            if (line1 < 0) {
+                line1 = line2 = 0;
+            }
+
+            if (line1 < dvPtr->topLine || line1 > dvPtr->btmLine) {
                 /* location off screen? then center it in y-view */
-                index -= dvPtr->fullLines/2;
-                if (index < 0) {
-                    index = 0;
+                if (line2-line1 >= dvPtr->fullLines) {
+                    topline = line1;
+                } else {
+                    topline = (line1+line2)/2 - dvPtr->fullLines/2;
+                    if (topline < 0) topline = 0;
                 }
-                ChangeDiffviewView(dvPtr, index);
+                ChangeDiffviewView(dvPtr, topline);
             }
             result = TCL_OK;
@@ -640,42 +731,77 @@
  */
 static int
-DiffviewBboxSubCmd(interp, dvPtr, index)
+DiffviewBboxSubCmd(interp, dvPtr, objc, objv)
     Tcl_Interp *interp;       /* interp handling this command */
     Diffview *dvPtr;          /* widget data */
-    int index;                /* desired element for bounding box */
-{
-#ifdef FOO
-    int lastVisibleIndex;
-    /* Determine the index of the last visible item in the listbox */
-    lastVisibleIndex = dvPtr->topIndex + dvPtr->fullLines
-        + dvPtr->partialLine;
-    if (dvPtr->nElements < lastVisibleIndex) {
-        lastVisibleIndex = dvPtr->nElements;
-    }
-
-    /* Only allow bbox requests for indices that are visible */
-    if ((dvPtr->topIndex <= index) && (index < lastVisibleIndex)) {
-        char buf[TCL_INTEGER_SPACE * 4];
-        Tcl_Obj *el;
-        char *stringRep;
-        int pixelWidth, stringLen, x, y, result;
-        Tk_FontMetrics fm;
-
-        /* Compute the pixel width of the requested element */
-        result = Tcl_ListObjIndex(interp, dvPtr->listObj, index, &el);
-        if (result != TCL_OK) {
-            return result;
-        }
-
-        stringRep = Tcl_GetStringFromObj(el, &stringLen);
-        Tk_GetFontMetrics(dvPtr->tkfont, &fm);
-        pixelWidth = Tk_TextWidth(dvPtr->tkfont, stringRep, stringLen);
-
-        x = dvPtr->inset - dvPtr->xOffset;
-        y = ((index - dvPtr->topIndex)*dvPtr->lineHeight) + dvPtr->inset;
-        sprintf(buf, "%d %d %d %d", x, y, pixelWidth, fm.linespace);
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+    char *opt, buf[256];
+    DiffviewDiffOp *diffOpPtr;
+    int line, bnum, bline, x1, y0, y1, wd;
+    char *textPtr; int textLen;
+
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "index");
+        return TCL_ERROR;
+    }
+
+    /* make sure that our layout info is up to date for queries below */
+    DiffviewUpdateLayout(dvPtr);
+
+    opt = Tcl_GetString(objv[2]);
+    if (*opt == 'a' && strcmp(opt,"all") == 0) {
+        sprintf(buf, "0 0 %d %d", dvPtr->maxWidth, dvPtr->maxHeight);
         Tcl_SetResult(interp, buf, TCL_VOLATILE);
-    }
-#endif
+        return TCL_OK;
+    }
+
+    if (DiffviewGetIndex(interp, dvPtr, objv[2], &line, &diffOpPtr) != TCL_OK) {
+        Tcl_AppendResult(interp, " or all", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    /* return the bounding box for a line in world coords */
+    if (line >= 0) {
+        y0 = line*dvPtr->lineHeight;
+        y1 = (line+1)*dvPtr->lineHeight;
+        x1 = 0;
+        if (line < dvPtr->worldview.numLines) {
+            bnum = dvPtr->worldview.lines[line].bnum;
+            bline = dvPtr->worldview.lines[line].bline;
+            if (bline >= 0) {
+                textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
+                textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
+                x1 = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
+            }
+        }
+        sprintf(buf, "0 %d %d %d", y0, x1, y1);
+        Tcl_SetResult(interp, buf, TCL_VOLATILE);
+        return TCL_OK;
+    }
+
+    /* return the bounding box for a diff in world coords */
+    if (diffOpPtr) {
+        y0 = diffOpPtr->fromWorld*dvPtr->lineHeight;
+        y1 = (diffOpPtr->toWorld+1)*dvPtr->lineHeight;
+        x1 = 0;
+
+        for (line=diffOpPtr->fromWorld; line <= diffOpPtr->toWorld; line++) {
+            if (line < dvPtr->worldview.numLines) {
+                bnum = dvPtr->worldview.lines[line].bnum;
+                bline = dvPtr->worldview.lines[line].bline;
+                if (bline >= 0) {
+                    textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
+                    textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
+                    wd = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
+                    if (wd > x1) {
+                        x1 = wd;
+                    }
+                }
+            }
+        }
+        sprintf(buf, "0 %d %d %d", y0, x1, y1);
+        Tcl_SetResult(interp, buf, TCL_VOLATILE);
+    }
     return TCL_OK;
 }
@@ -1125,4 +1251,59 @@
 /*
  * ----------------------------------------------------------------------
+ * DiffviewGetIndex()
+ *
+ * Used by many of the widget operations to parse an index into the
+ * widget view.  Handles the following syntax:
+ *   #NN ...... particular diff number, starting from 1
+ *   NN ....... particular line number in world view, starting from 0
+ *
+ * Returns TCL_OK if successful, and TCL_ERROR (along with an error
+ * message) if anything goes wrong.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewGetIndex(interp, dvPtr, objPtr, linePtr, diffOpPtrPtr)
+    Tcl_Interp *interp;             /* interp handling this command */
+    Diffview *dvPtr;                /* widget data */
+    Tcl_Obj *objPtr;                /* argument being parsed */
+    int *linePtr;                   /* returns: line number or -1 */
+    DiffviewDiffOp **diffOpPtrPtr;  /* returns: specific diff or NULL */
+{
+    int result = TCL_OK;
+    char *opt = Tcl_GetString(objPtr);
+    char *tail;
+    int dnum;
+
+    /* clear the return values */
+    if (linePtr) *linePtr = -1;
+    if (diffOpPtrPtr) *diffOpPtrPtr = NULL;
+
+    if (*opt == '#') {
+        dnum = strtol(opt+1, &tail, 10);
+        if (*tail != '\0' || dnum <= 0) {
+           result = TCL_ERROR;
+        }
+        else if (dvPtr->diffsPtr == NULL
+                   || dnum > dvPtr->diffsPtr->numDiffs) {
+           Tcl_AppendResult(interp, "diff \"", opt, "\" doesn't exist",
+               (char*)NULL);
+           return TCL_ERROR;
+        }
+        *diffOpPtrPtr = &dvPtr->diffsPtr->ops[dnum-1];
+    } else {
+        result = Tcl_GetIntFromObj(interp, objPtr, linePtr);
+    }
+
+    if (result != TCL_OK) {
+        Tcl_ResetResult(interp);
+        Tcl_AppendResult(interp, "bad index \"", opt, "\": should be "
+            "\"#n\" for diff, or an integer value for line number",
+            (char*)NULL);
+    }
+    return result;
+}
+
+/*
+ * ----------------------------------------------------------------------
  * DestroyDiffview()
  *
@@ -1154,11 +1335,22 @@
         DiffviewDiffsFree(dvPtr->diffsPtr);
     }
+    DiffviewLayoutFree(&dvPtr->worldview);
 
     /*
      * Free up GCs and configuration options.
      */
-    if (dvPtr->textGC != None) {
-        Tk_FreeGC(dvPtr->display, dvPtr->textGC);
-    }
+    if (dvPtr->normGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
+    }
+    if (dvPtr->addFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
+    }
+    if (dvPtr->delFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
+    }
+    if (dvPtr->chgFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
+    }
+
     Tk_FreeConfigOptions((char*)dvPtr, dvPtr->optionTable, dvPtr->tkwin);
     Tcl_Release((ClientData) dvPtr->tkwin);
@@ -1240,4 +1432,5 @@
     unsigned long mask;
 
+    /* GC for normal widget text */
     gcValues.foreground = dvPtr->fgColorPtr->pixel;
     gcValues.graphics_exposures = False;
@@ -1246,8 +1439,47 @@
 
     gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
-    if (dvPtr->textGC != None) {
-        Tk_FreeGC(dvPtr->display, dvPtr->textGC);
-    }
-    dvPtr->textGC = gc;
+    if (dvPtr->normGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
+    }
+    dvPtr->normGC = gc;
+
+    /* GC for added diff text */
+    gcValues.foreground = dvPtr->addFgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->addFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
+    }
+    dvPtr->addFgGC = gc;
+
+    /* GC for deleted diff text */
+    gcValues.foreground = dvPtr->delFgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->delFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
+    }
+    dvPtr->delFgGC = gc;
+
+    /* GC for changed diff text */
+    gcValues.foreground = dvPtr->chgFgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->chgFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
+    }
+    dvPtr->chgFgGC = gc;
+
+    /* this call comes when the font changes -- fix the layout */
+    dvPtr->flags |= FONT_CHANGED;
 
     /* get ready to redraw */
@@ -1291,7 +1523,8 @@
     Tk_Window tkwin = dvPtr->tkwin;
 
-    int i, bnum, x, y;
+    int i, bnum, bline, x, xw, y, ymid, width;
     char *textPtr; int textLen;
     Pixmap pixmap;
+    GC bg, fg;
 
     /* handling redraw now -- no longer pending */
@@ -1350,32 +1583,59 @@
           - dvPtr->yOffset;
     x = dvPtr->inset - dvPtr->xOffset;
-
-    if (dvPtr->diffdir == DIFF_1TO2) {
-        bnum = 1;  /* 1->2 means: show lines from buffer #2 */
-    } else {
-        bnum = 0;  /* 2->1 means: show lines from buffer #1 */
-    }
-
-    if (dvPtr->buffer[bnum].lineLimits) {
-        for (i=dvPtr->topLine; i <= dvPtr->btmLine; i++) {
-            if (i >= dvPtr->buffer[bnum].lineLimits->numLines) {
+    width = Tk_Width(tkwin);
+
+    for (i=dvPtr->topLine;
+         i <= dvPtr->btmLine && i < dvPtr->worldview.numLines;
+         i++) {
+
+        /* draw any diff rectangle for this line */
+        fg = dvPtr->normGC;
+        bg = None;
+
+        switch (dvPtr->worldview.lines[i].style) {
+            case 'a': {
+                fg = dvPtr->addFgGC;
+                bg = Tk_GCForColor(dvPtr->addBgColorPtr, pixmap);
                 break;
             }
-
-            /* draw any diff rectangle for this line */
-            /*
-            Tk_Fill3DRectangle(tkwin, pixmap, selectedBg, x, y,
-                width, dvPtr->lineHeight, 0, TK_RELIEF_FLAT);
-            */
-
-            textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[i];
-            textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[i];
+            case 'd': {
+                fg = dvPtr->delFgGC;
+                bg = Tk_GCForColor(dvPtr->delBgColorPtr, pixmap);
+                break;
+            }
+            case 'c': {
+                fg = dvPtr->chgFgGC;
+                bg = Tk_GCForColor(dvPtr->chgBgColorPtr, pixmap);
+                break;
+            }
+        }
+
+        if (bg != None) {
+            XFillRectangle(Tk_Display(tkwin), pixmap, bg,
+                x, y - dvPtr->lineAscent,
+                (unsigned int)width, (unsigned int)dvPtr->lineHeight);
+        }
+
+        bnum = dvPtr->worldview.lines[i].bnum;
+        bline = dvPtr->worldview.lines[i].bline;
+
+        /* a negative number means "leave the line blank" */
+        if (bline >= 0) {
+            textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
+            textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
 
             /* Draw the actual text of this item */
-            Tk_DrawChars(dvPtr->display, pixmap, dvPtr->textGC, dvPtr->tkfont,
-                    textPtr, textLen, x, y);
-
-            y += dvPtr->lineHeight;
-        }
+            Tk_DrawChars(dvPtr->display, pixmap, fg, dvPtr->tkfont,
+                textPtr, textLen, x, y);
+
+            /* Draw an overstrike on deleted text */
+            if (dvPtr->worldview.lines[i].style == 'd' && dvPtr->overStrDel) {
+                xw = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen) + 5;
+                ymid = y - dvPtr->lineAscent/2;
+                XDrawLine(Tk_Display(tkwin), pixmap, fg, 0, ymid, xw, ymid);
+            }
+        }
+
+        y += dvPtr->lineHeight;
     }
 
@@ -1401,5 +1661,5 @@
 #ifndef TK_NO_DOUBLE_BUFFERING
     XCopyArea(dvPtr->display, pixmap, Tk_WindowId(tkwin),
-            dvPtr->textGC, 0, 0, (unsigned) Tk_Width(tkwin),
+            dvPtr->normGC, 0, 0, (unsigned) Tk_Width(tkwin),
             (unsigned) Tk_Height(tkwin), 0, 0);
     Tk_FreePixmap(dvPtr->display, pixmap);
@@ -1611,5 +1871,8 @@
     int changes = 0;   /* no layout changes yet */
     DiffviewBuffer* bufferPtr;
-    int i, bnum, pixelWidth, maxWidth, numLines, ySize;
+    DiffviewDiffOp *currOp;
+    DiffviewLayoutLine line;
+    int i, bnum, bline, pixelWidth, maxWidth, ySize;
+    int numLines1, numLines2, lnum1, lnum2, dnum, lastdnum, pastDiff;
     char *textPtr, *textPtr2; int textLen;
     Tk_FontMetrics fm;
@@ -1639,4 +1902,5 @@
             textPtr = Tcl_GetStringFromObj(dvPtr->buffer[0].textObj, &textLen);
             textPtr2 = Tcl_GetStringFromObj(dvPtr->buffer[1].textObj, &textLen);
+
             dvPtr->diffsPtr = DiffviewDiffsCreate(
                 textPtr, dvPtr->buffer[0].lineLimits,
@@ -1644,14 +1908,212 @@
         }
 
-        /* compute overall size of each buffer */
-        for (bnum=0; bnum < 2; bnum++) {
-            bufferPtr = &dvPtr->buffer[bnum];
-
-            if (bufferPtr->lineLimits) {
-                maxWidth = 0;
-
-                for (i=0; i < bufferPtr->lineLimits->numLines; i++) {
-                    textPtr = bufferPtr->lineLimits->startPtr[i];
-                    textLen = bufferPtr->lineLimits->lenPtr[i];
+        /*
+         * Compute the layout of all lines according to the current
+         * view mode.  Each line in the world view is stored in the
+         * array dvPtr->worldview.lines.  Each line has a style (color
+         * for normal, add, delete, etc.) and an indication of which
+         * buffer it comes from.
+         */
+        DiffviewLayoutClear(&dvPtr->worldview);
+
+        /*
+         * March through the lines and figure out the source and style
+         * of each line based on the diffs:
+         *   'n' = normal (common) line
+         *   'a' = draw with the "added" style
+         *   'd' = draw with the "deleted" style
+         *   'c' = draw with the "changed" style
+         */
+        numLines1 = (dvPtr->buffer[0].lineLimits)
+                       ? dvPtr->buffer[0].lineLimits->numLines : 0;
+        numLines2 = (dvPtr->buffer[1].lineLimits)
+                       ? dvPtr->buffer[1].lineLimits->numLines : 0;
+
+        dnum = 0;  /* current difference in diffsPtr */
+        lnum1 = lnum2 = 0;
+
+        while (lnum1 < numLines1 || lnum2 < numLines2) {
+            /* assume it's a normal-looking line (buffers are the same) */
+            line.style = 'n';
+            line.bnum = 0;
+            line.bline = lnum1;
+            line.diffNum = -1;
+
+            /* is there a diff that contains this line? */
+            if (dvPtr->diffsPtr && dnum < dvPtr->diffsPtr->numDiffs) {
+              currOp = &dvPtr->diffsPtr->ops[dnum];
+
+              if ( (lnum1 >= currOp->fromIndex1
+                     && lnum1 <= currOp->toIndex1)
+                || (lnum2 >= currOp->fromIndex2
+                     && lnum2 <= currOp->toIndex2) ) {
+
+                line.diffNum = dnum;  /* line is part of this diff */
+
+                switch (currOp->op) {
+                    case 'c': {
+                        if (dvPtr->layout == LAYOUT_INLINE) {
+                            if (dvPtr->diffdir == DIFF_1TO2) {
+                                /* show the buffer #1 lines first, then #2 */
+                                if (lnum1 <= currOp->toIndex1) {
+                                    line.style = 'd';
+                                    line.bnum = 0;
+                                    line.bline = lnum1++;
+                                } else {
+                                    line.style = 'a';
+                                    line.bnum = 1;
+                                    line.bline = lnum2++;
+                                }
+                            } else {
+                                /* reverse -- show #2 lines first, then #1 */
+                                if (lnum2 <= currOp->toIndex2) {
+                                    line.style = 'd';
+                                    line.bnum = 1;
+                                    line.bline = lnum2++;
+                                } else {
+                                    line.style = 'a';
+                                    line.bnum = 0;
+                                    line.bline = lnum1++;
+                                }
+                            }
+                        } else {
+                            if (dvPtr->diffdir == DIFF_1TO2) {
+                                /* show final lines in buf #2 as "changed" */
+                                line.style = 'c';
+                                line.bnum = 1;
+                                line.bline = lnum2++;
+                                lnum1 = currOp->toIndex1+1;
+                            } else {
+                                /* show final lines in buf #1 as "changed" */
+                                line.style = 'c';
+                                line.bnum = 0;
+                                line.bline = lnum1++;
+                                lnum2 = currOp->toIndex2+1;
+                            }
+                        }
+                        break;
+                    }
+                    case 'a': {
+                        if (dvPtr->diffdir == DIFF_1TO2) {
+                            /* show lines in buffer #2 as 'added' */
+                            line.style = 'a';
+                            line.bnum = 1;
+                            line.bline = lnum2++;
+                            lnum1 = currOp->toIndex1;
+                        } else {
+                            /* reverse diff -- like we're deleting lines */
+                            if (dvPtr->layout == LAYOUT_INLINE) {
+                                line.style = 'd';
+                                line.bnum = 1;
+                                line.bline = lnum2++;
+                                lnum1 = currOp->toIndex1;
+                            } else {
+                                /* show lines from buffer #2 as empty */
+                                line.style = 'd';
+                                line.bnum = 0;
+                                line.bline = -1;
+                                lnum2++;
+                                lnum1 = currOp->toIndex1;
+                            }
+                        }
+                        break;
+                    }
+                    case 'd': {
+                        if (dvPtr->diffdir == DIFF_1TO2) {
+                            if (dvPtr->layout == LAYOUT_INLINE) {
+                                line.style = 'd';
+                                line.bnum = 0;
+                                line.bline = lnum1++;
+                                lnum2 = currOp->toIndex2;
+                            } else {
+                                /* show lines from buffer #2 as empty */
+                                line.style = 'd';
+                                line.bnum = 0;
+                                line.bline = -1;
+                                lnum1++;
+                                lnum2 = currOp->toIndex2;
+                            }
+                        } else {
+                            /* reverse diff -- like we're adding lines */
+                            line.style = 'a';
+                            line.bnum = 0;
+                            line.bline = lnum1++;
+                            lnum2 = currOp->toIndex2;
+                        }
+                        break;
+                    }
+                    default: {
+                        Tcl_Panic("bad diff type '%c' found in layout",
+                            currOp->op);
+                        break;
+                    }
+                }
+              } else {
+                /* normal line -- keep moving forward */
+                lnum1++; lnum2++;
+              }
+
+              /* have we reached the end of the diff? then move on */
+              pastDiff = 0;
+              switch (currOp->op) {
+                  case 'c':
+                      pastDiff = (lnum1 > currOp->toIndex1
+                               && lnum2 > currOp->toIndex2);
+                      break;
+                  case 'a':
+                      pastDiff = (lnum2 > currOp->toIndex2);
+                      break;
+                  case 'd':
+                      pastDiff = (lnum1 > currOp->toIndex1);
+                      break;
+              }
+              if (pastDiff) {
+                  dnum++;
+              }
+            } else {
+                /* no more diffs -- keep moving forward */
+                lnum1++; lnum2++;
+            }
+
+            /* add this new line to the layout */
+            DiffviewLayoutAdd(&dvPtr->worldview, &line);
+        }
+
+        /*
+         * Figure out where the diffs are located, and put that info
+         * back into the diffs.  This makes it easy later to refer to
+         * diff "#3" and understand what lines we're talking about.
+         */
+        lastdnum = -1;
+        for (i=0; i < dvPtr->worldview.numLines; i++) {
+            dnum = dvPtr->worldview.lines[i].diffNum;
+            if (dnum != lastdnum) {
+                if (lastdnum < 0) {
+                    /* leading edge -- catch the "from" line */
+                    lnum1 = i;
+                } else {
+                    /* trailing edge -- save diff info */
+                    dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
+                    dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
+                }
+            }
+            lastdnum = dnum;
+        }
+        if (lastdnum >= 0) {
+            dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
+            dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
+        }
+
+        /* compute overall text width for all lines */
+        maxWidth = 0;
+        for (i=0; i < dvPtr->worldview.numLines; i++) {
+            bline = dvPtr->worldview.lines[i].bline;
+            if (bline >= 0) {
+                bnum = dvPtr->worldview.lines[i].bnum;
+                bufferPtr = &dvPtr->buffer[bnum];
+
+                if (bufferPtr->lineLimits) {
+                    textPtr = bufferPtr->lineLimits->startPtr[bline];
+                    textLen = bufferPtr->lineLimits->lenPtr[bline];
                     pixelWidth = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
 
@@ -1660,37 +2122,13 @@
                     }
                 }
-                bufferPtr->lineLimits->maxWidth = maxWidth;
-            }
-        }
-
-        /* compute overall size of the widget depending on the layout */
-        if (dvPtr->diffdir == DIFF_1TO2) {
-            bnum = 0;
-        } else {
-            bnum = 1;
-        }
-
-        maxWidth = 0;
-        numLines = 0;
-        if (dvPtr->buffer[bnum].lineLimits) {
-            maxWidth = dvPtr->buffer[bnum].lineLimits->maxWidth;
-            if (dvPtr->layout == LAYOUT_INLINE) {
-                numLines = dvPtr->buffer[bnum].lineLimits->numLines;
-            } else {
-                /* WARNING - should compute the height by looking at diffs */
-                for (i=0; i < 2; i++) {
-                    if (dvPtr->buffer[i].lineLimits
-                          && dvPtr->buffer[i].lineLimits->numLines > numLines) {
-                        numLines = dvPtr->buffer[i].lineLimits->numLines;
-                    }
-                }
             }
         }
         dvPtr->maxWidth = maxWidth;
 
+        /* compute overall height of the widget */
         Tk_GetFontMetrics(dvPtr->tkfont, &fm);
         dvPtr->lineHeight = fm.linespace;
         dvPtr->lineAscent = fm.ascent;
-        dvPtr->maxHeight = numLines * fm.linespace;
+        dvPtr->maxHeight = dvPtr->worldview.numLines * fm.linespace;
 
         /* figure out the limits of the current view */
@@ -1840,5 +2278,4 @@
     lineLimitsPtr = (DiffviewLines*)ckalloc(sizeof(DiffviewLines));
     lineLimitsPtr->maxLines = 0;
-    lineLimitsPtr->maxWidth = 0;
     lineLimitsPtr->startPtr = NULL;
     lineLimitsPtr->lenPtr = NULL;
@@ -2221,6 +2658,6 @@
  * DiffviewDiffsFree()
  *
- * Appends a "diff" operation onto a list of diffs.  The list is extended
- * automatically, if need be, to store the new element.
+ * Frees up the storage previously created by calling
+ * DiffviewDiffsAppend().
  * ----------------------------------------------------------------------
  */
@@ -2234,2 +2671,88 @@
     ckfree((char*)diffsPtr);
 }
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLayoutAdd()
+ *
+ * Clears out the layout storage in preparation for building another
+ * layout.  If the previous number of lines was much less than the
+ * maximum allocated, then this routine reallocates a smaller storage
+ * space.  This provides a way to give back a large chunk of memory
+ * if the contents of the widget is nulled out, for example.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLayoutAdd(layoutPtr, linePtr)
+    DiffviewLayout *layoutPtr;    /* line layout being updated */
+    DiffviewLayoutLine *linePtr;  /* line being added to layout */
+{
+    DiffviewLayoutLine *newLineArray;
+
+    /* expand the array as needed */
+    if (layoutPtr->numLines >= layoutPtr->maxLines) {
+        if (layoutPtr->maxLines == 0) {
+            layoutPtr->maxLines = 100;
+        } else {
+            layoutPtr->maxLines *= 2;
+        }
+        newLineArray = (DiffviewLayoutLine*)ckalloc(
+            (unsigned)(layoutPtr->maxLines*sizeof(DiffviewLayoutLine)));
+
+        if (layoutPtr->lines) {
+            memcpy((VOID*)newLineArray, (VOID*)layoutPtr->lines,
+                layoutPtr->numLines*sizeof(DiffviewLayoutLine));
+            ckfree((char*)layoutPtr->lines);
+        }
+        layoutPtr->lines = newLineArray;
+    }
+
+    /* copy the latest line in and bump the count */
+    memcpy((VOID*)&layoutPtr->lines[layoutPtr->numLines],
+        (VOID*)linePtr, sizeof(DiffviewLayoutLine));
+
+    layoutPtr->numLines++;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLayoutClear()
+ *
+ * Clears out the layout storage in preparation for building another
+ * layout.  If the previous number of lines was much less than the
+ * maximum allocated, then this routine reallocates a smaller storage
+ * space.  This provides a way to give back a large chunk of memory
+ * if the contents of the widget is nulled out, for example.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLayoutClear(layoutPtr)
+    DiffviewLayout *layoutPtr;  /* line layout being freed */
+{
+    if (layoutPtr->numLines > 0
+          && layoutPtr->numLines < layoutPtr->maxLines/3
+          && layoutPtr->maxLines > 100) {
+        ckfree((char*)layoutPtr->lines);
+        layoutPtr->lines = (DiffviewLayoutLine*)ckalloc(
+            (unsigned)(layoutPtr->numLines * sizeof(DiffviewLayoutLine)));
+    }
+    layoutPtr->numLines = 0;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLayoutFree()
+ *
+ * Frees the storage associated with the layout of all lines within
+ * the widget.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLayoutFree(layoutPtr)
+    DiffviewLayout *layoutPtr;  /* line layout being freed */
+{
+    if (layoutPtr->lines) {
+        ckfree((char*)layoutPtr->lines);
+        layoutPtr->lines = NULL;
+    }
+}
