Index: src/diff.c
==================================================================
--- src/diff.c
+++ src/diff.c
@@ -21,10 +21,22 @@
 #include "config.h"
 #include "diff.h"
 #include <assert.h>
 
 
+#if INTERFACE
+/*
+** Allowed flag parameters to the text_diff() and html_sbsdiff() funtions:
+*/
+#define DIFF_CONTEXT_MASK  0x0000fff  /* Lines of context.  Default if 0 */
+#define DIFF_WIDTH_MASK    0x00ff000  /* side-by-side column width */
+#define DIFF_IGNORE_EOLWS  0x0100000  /* Ignore end-of-line whitespace */
+#define DIFF_SIDEBYSIDE    0x0200000  /* Generate a side-by-side diff */
+#define DIFF_NEWFILE       0x0400000  /* Missing files are as empty files */
+
+#endif /* INTERFACE */
+
 /*
 ** Maximum length of a line in a text file.  (8192)
 */
 #define LENGTH_MASK_SZ  13
 #define LENGTH_MASK     ((1<<LENGTH_MASK_SZ)-1)
@@ -285,10 +297,176 @@
     for(j=0; j<m; j++){
       appendDiffLine(pOut, " ", &B[b+j]);
     }
   }
 }
+
+/*
+** Append spaces to a blob
+*/
+static void appendSpace(Blob *pOut, int n){
+  const char z100[101] = 
+     "                                                  "
+     "                                                  ";
+  while( n>100 ){
+    blob_append(pOut, z100, 100); n -= 100;
+  }
+  if( n>0 ){
+    blob_append(pOut, z100, n);
+  }
+}
+
+/*
+** Append text to a sbs diff output
+*/
+static void appendSbsLine(Blob *pOut, DLine *pLine, int width, int pad){
+  int sz = pLine->h & LENGTH_MASK;
+  if( sz<width ){
+    blob_append(pOut, pLine->z, sz);
+    if( pad ) appendSpace(pOut, width-sz);
+  }else{
+    blob_append(pOut, pLine->z, width);
+  }
+}
+
+
+/*
+** Given a diff context in which the aEdit[] array has been filled
+** in, compute a side-by-side diff into pOut.
+*/
+static void sbsDiff(DContext *p, Blob *pOut, int nContext, int width){
+  DLine *A;     /* Left side of the diff */
+  DLine *B;     /* Right side of the diff */  
+  int a = 0;    /* Index of next line in A[] */
+  int b = 0;    /* Index of next line in B[] */
+  int *R;       /* Array of COPY/DELETE/INSERT triples */
+  int r;        /* Index into R[] */
+  int nr;       /* Number of COPY/DELETE/INSERT triples to process */
+  int mxr;      /* Maximum value for r */
+  int na, nb;   /* Number of lines shown from A and B */
+  int i, j;     /* Loop counters */
+  int m, ma, mb;/* Number of lines to output */
+  int skip;     /* Number of lines to skip */
+  char zFormat[50];  /* Output format */
+
+  sqlite3_snprintf(sizeof(zFormat), zFormat,
+                   "%%4d %%%d.%ds %%c %%4d %%.%ds\n",
+                   width, width, width);
+  A = p->aFrom;
+  B = p->aTo;
+  R = p->aEdit;
+  mxr = p->nEdit;
+  while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
+  for(r=0; r<mxr; r += 3*nr){
+    /* Figure out how many triples to show in a single block */
+    for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
+    /* printf("r=%d nr=%d\n", r, nr); */
+
+    /* For the current block comprising nr triples, figure out
+    ** how many lines of A and B are to be displayed
+    */
+    if( R[r]>nContext ){
+      na = nb = nContext;
+      skip = R[r] - nContext;
+    }else{
+      na = nb = R[r];
+      skip = 0;
+    }
+    for(i=0; i<nr; i++){
+      na += R[r+i*3+1];
+      nb += R[r+i*3+2];
+    }
+    if( R[r+nr*3]>nContext ){
+      na += nContext;
+      nb += nContext;
+    }else{
+      na += R[r+nr*3];
+      nb += R[r+nr*3];
+    }
+    for(i=1; i<nr; i++){
+      na += R[r+i*3];
+      nb += R[r+i*3];
+    }
+    /*
+     * If the patch changes an empty file or results in an empty file,
+     * the block header must use 0,0 as position indicator and not 1,0.
+     * Otherwise, patch would be confused and may reject the diff.
+     */
+    blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n",
+      na ? a+skip+1 : 0, na,
+      nb ? b+skip+1 : 0, nb);
+
+    /* Show the initial common area */
+    a += skip;
+    b += skip;
+    m = R[r] - skip;
+    for(j=0; j<m; j++){
+      blob_appendf(pOut, "%6d ", a+j);
+      appendSbsLine(pOut, &A[a+j], width, 1);
+      blob_appendf(pOut, "   %6d ", b+j);
+      appendSbsLine(pOut, &B[b+j], width, 0);
+      blob_append(pOut, "\n", 1);
+    }
+    a += m;
+    b += m;
+
+    /* Show the differences */
+    for(i=0; i<nr; i++){
+      ma = R[r+i*3+1];
+      mb = R[r+i*3+2];
+      m = ma<mb ? ma : mb;
+      for(j=0; j<m; j++){
+        blob_appendf(pOut, "%6d ", a+j);
+        appendSbsLine(pOut, &A[a+j], width, 1);
+        blob_appendf(pOut, " | %6d ", b+j);
+        appendSbsLine(pOut, &B[b+j], width, 0);
+        blob_append(pOut, "\n", 1);
+      }
+      a += m;
+      b += m;
+      ma -= m;
+      mb -= m;
+      for(j=0; j<ma; j++){
+        blob_appendf(pOut, "%6d ", a+j);
+        appendSbsLine(pOut, &A[a+j], width, 1);
+        blob_append(pOut, " <\n", 3);
+      }
+      a += ma;
+      for(j=0; j<mb; j++){
+        appendSpace(pOut, width+7);
+        blob_appendf(pOut, " > %6d ", b+j);
+        appendSbsLine(pOut, &B[b+j], width, 0);
+        blob_append(pOut, "\n", 1);
+      }
+      b += mb;
+      if( i<nr-1 ){
+        m = R[r+i*3+3];
+        for(j=0; j<m; j++){
+          blob_appendf(pOut, "%6d ", a+j);
+          appendSbsLine(pOut, &A[a+j], width, 1);
+          blob_appendf(pOut, "   %6d ", b+j);
+          appendSbsLine(pOut, &B[b+j], width, 0);
+          blob_append(pOut, "\n", 1);
+        }
+        b += m;
+        a += m;
+      }
+    }
+
+    /* Show the final common area */
+    assert( nr==i );
+    m = R[r+nr*3];
+    if( m>nContext ) m = nContext;
+    for(j=0; j<m; j++){
+      blob_appendf(pOut, "%6d ", a+j);
+      appendSbsLine(pOut, &A[a+j], width, 1);
+      blob_appendf(pOut, "   %6d ", b+j);
+      appendSbsLine(pOut, &B[b+j], width, 0);
+      blob_append(pOut, "\n", 1);
+    }
+  }
+}
 
 /*
 ** Compute the optimal longest common subsequence (LCS) using an
 ** exhaustive search.  This version of the LCS is only used for
 ** shorter input strings since runtime is O(N*N) where N is the
@@ -538,16 +716,21 @@
 ** text "cannot compute difference between binary files".
 */
 int *text_diff(
   Blob *pA_Blob,   /* FROM file */
   Blob *pB_Blob,   /* TO file */
-  Blob *pOut,      /* Write unified diff here if not NULL */
-  int nContext,    /* Amount of context to unified diff */
-  int ignoreEolWs  /* Ignore whitespace at the end of lines */
+  Blob *pOut,      /* Write diff here if not NULL */
+  int diffFlags    /* DIFF_* flags defined above */
 ){
+  int ignoreEolWs; /* Ignore whitespace at the end of lines */
+  int nContext;    /* Amount of context to display */	
   DContext c;
- 
+
+  nContext = diffFlags & DIFF_CONTEXT_MASK;
+  if( nContext==0 ) nContext = 5;
+  ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0;
+
   /* Prepare the input files */
   memset(&c, 0, sizeof(c));
   c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
                              &c.nFrom, ignoreEolWs);
   c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
@@ -563,12 +746,18 @@
 
   /* Compute the difference */
   diff_all(&c);
 
   if( pOut ){
-    /* Compute a context diff if requested */
-    contextDiff(&c, pOut, nContext);
+    /* Compute a context or side-by-side diff into pOut */
+    if( diffFlags & DIFF_SIDEBYSIDE ){
+      int width = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
+      if( width==0 ) width = 80;
+      sbsDiff(&c, pOut, nContext, width);
+    }else{
+      contextDiff(&c, pOut, nContext);
+    }
     free(c.aFrom);
     free(c.aTo);
     free(c.aEdit);
     return 0;
   }else{
@@ -660,11 +849,11 @@
   while( i<c.nEdit ){
     int j;
     /* Copied lines */
     for( j=0; j<c.aEdit[i]; j++){
       /* Hide lines which are copied and are further away from block boundaries
-      ** than nConext lines. For each block with hidden lines, show a row
+      ** than nContext lines. For each block with hidden lines, show a row
       ** notifying the user about the hidden rows.
       */
       if( j<nContext || j>c.aEdit[i]-nContext-1 ){
         @ <tr>
       }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){
@@ -776,11 +965,11 @@
   if( g.argc<4 ) usage("FILE1 FILE2 ...");
   blob_read_from_file(&a, g.argv[2]);
   for(i=3; i<g.argc; i++){
     if( i>3 ) fossil_print("-------------------------------\n");
     blob_read_from_file(&b, g.argv[i]);
-    R = text_diff(&a, &b, 0, 0, 0);
+    R = text_diff(&a, &b, 0, 0);
     for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
       fossil_print(" copy %4d  delete %4d  insert %4d\n", R[r], R[r+1], R[r+2]);
     }
     /* free(R); */
     blob_reset(&b);
@@ -790,15 +979,23 @@
 /*
 ** COMMAND: test-udiff
 */
 void test_udiff_cmd(void){
   Blob a, b, out;
-  if( g.argc!=4 ) usage("FILE1 FILE2");
+  int diffFlag = find_option("sbs",0,0)!=0 ? DIFF_SIDEBYSIDE : 0;
+  int nContext = 5;
+  const char *z;
+  if( (z = find_option("context","c",1))!=0 && atoi(z)>0 ){
+    nContext = atoi(z);
+  }
+  if( nContext<=0 ) nContext = 5;
+  if( (nContext&DIFF_CONTEXT_MASK)!=nContext ) nContext = DIFF_CONTEXT_MASK;
+  if( g.argc!=4 ) usage("[--sbs] [--context N] FILE1 FILE2");
   blob_read_from_file(&a, g.argv[2]);
   blob_read_from_file(&b, g.argv[3]);
   blob_zero(&out);
-  text_diff(&a, &b, &out, 3, 0);
+  text_diff(&a, &b, &out, nContext | diffFlag);
   blob_write_to_file(&out, "-");
 }
 
 /**************************************************************************
 ** The basic difference engine is above.  What follows is the annotation

Index: src/diffcmd.c
==================================================================
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -19,16 +19,10 @@
 */
 #include "config.h"
 #include "diffcmd.h"
 #include <assert.h>
 
-/*
-** Diff option flags
-*/
-#define DIFF_NEWFILE  0x01    /* Treat non-existing fails as empty files */
-#define DIFF_NOEOLWS  0x02    /* Ignore whitespace at the end of lines */
-
 /*
 ** Output the results of a diff.  Output goes to stdout for command-line
 ** or to the CGI/HTTP result buffer for web pages.
 */
 static void diff_printf(const char *zFormat, ...){
@@ -62,11 +56,11 @@
 void diff_file(
   Blob *pFile1,             /* In memory content to compare from */
   const char *zFile2,       /* On disk content to compare to */
   const char *zName,        /* Display name of the file */
   const char *zDiffCmd,     /* Command for comparison */
-  int ignoreEolWs           /* Ignore whitespace at end of line */
+  int diffFlags             /* Flags to control the diff */
 ){
   if( zDiffCmd==0 ){
     Blob out;                 /* Diff output text */
     Blob file2;               /* Content of zFile2 */
     const char *zName2;       /* Name of zFile2 for display */
@@ -84,11 +78,11 @@
       zName2 = zName;
     }
 
     /* Compute and output the differences */
     blob_zero(&out);
-    text_diff(pFile1, &file2, &out, 5, ignoreEolWs);
+    text_diff(pFile1, &file2, &out, diffFlags);
     if( blob_size(&out) ){
       diff_printf("--- %s\n+++ %s\n", zName, zName2);
       diff_printf("%s\n", blob_str(&out));
     }
 
@@ -138,17 +132,17 @@
 void diff_file_mem(
   Blob *pFile1,             /* In memory content to compare from */
   Blob *pFile2,             /* In memory content to compare to */
   const char *zName,        /* Display name of the file */
   const char *zDiffCmd,     /* Command for comparison */
-  int ignoreEolWs           /* Ignore whitespace at end of lines */
+  int diffFlags             /* Diff flags */
 ){
   if( zDiffCmd==0 ){
     Blob out;      /* Diff output text */
 
     blob_zero(&out);
-    text_diff(pFile1, pFile2, &out, 5, ignoreEolWs);
+    text_diff(pFile1, pFile2, &out, diffFlags);
     diff_printf("--- %s\n+++ %s\n", zName, zName);
     diff_printf("%s\n", blob_str(&out));
 
     /* Release memory resources */
     blob_reset(&out);
@@ -185,11 +179,11 @@
 ** against the same file on disk.
 */
 static void diff_one_against_disk(
   const char *zFrom,        /* Name of file */
   const char *zDiffCmd,     /* Use this "diff" command */
-  int ignoreEolWs,          /* Ignore whitespace changes at end of lines */
+  int diffFlags,            /* Diff control flags */
   const char *zFileTreeName
 ){
   Blob fname;
   Blob content;
   int isLink;
@@ -196,11 +190,11 @@
   file_tree_name(zFileTreeName, &fname, 1);
   historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
   if( !isLink != !file_wd_islink(zFrom) ){
     diff_printf("cannot compute difference between symlink and regular file\n");
   }else{
-    diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, ignoreEolWs);
+    diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
   }
   blob_reset(&content);
   blob_reset(&fname);
 }
 
@@ -215,14 +209,12 @@
   int diffFlags             /* Flags controlling diff output */
 ){
   int vid;
   Blob sql;
   Stmt q;
-  int ignoreEolWs;          /* Ignore end-of-line whitespace */
   int asNewFile;            /* Treat non-existant files as empty files */
 
-  ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0;
   asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
   vid = db_lget_int("checkout", 0);
   vfile_check_signature(vid, 1, 0);
   blob_zero(&sql);
   db_begin_transaction();
@@ -300,11 +292,11 @@
         content_get(srcid, &content);
       }else{
         blob_zero(&content);
       }
       diff_print_index(zPathname);
-      diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs);
+      diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
       blob_reset(&content);
     }
     free(zToFree);
   }
   db_finalize(&q);
@@ -317,11 +309,11 @@
 */
 static void diff_one_two_versions(
   const char *zFrom,
   const char *zTo,
   const char *zDiffCmd,
-  int ignoreEolWs,
+  int diffFlags,
   const char *zFileTreeName
 ){
   char *zName;
   Blob fname;
   Blob v1, v2;
@@ -332,11 +324,11 @@
   historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
   if( isLink1 != isLink2 ){
     diff_printf("--- %s\n+++ %s\n", zName, zName);
     diff_printf("cannot compute difference between symlink and regular file\n");
   }else{
-    diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs);
+    diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
   }
   blob_reset(&v1);
   blob_reset(&v2);
   blob_reset(&fname);
 }
@@ -347,11 +339,11 @@
 */
 static void diff_manifest_entry(
   struct ManifestFile *pFrom,
   struct ManifestFile *pTo,
   const char *zDiffCmd,
-  int ignoreEolWs
+  int diffFlags
 ){
   Blob f1, f2;
   int rid;
   const char *zName =  pFrom ? pFrom->zName : pTo->zName;
   diff_print_index(zName);
@@ -365,11 +357,11 @@
     rid = uuid_to_rid(pTo->zUuid, 0);
     content_get(rid, &f2);
   }else{
     blob_zero(&f2);
   }
-  diff_file_mem(&f1, &f2, zName, zDiffCmd, ignoreEolWs);
+  diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
   blob_reset(&f1);
   blob_reset(&f2);
 }
 
 /*
@@ -381,11 +373,10 @@
   const char *zDiffCmd,
   int diffFlags
 ){
   Manifest *pFrom, *pTo;
   ManifestFile *pFromFile, *pToFile;
-  int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
   int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
 
   pFrom = manifest_get_by_name(zFrom, 0);
   manifest_file_rewind(pFrom);
   pFromFile = manifest_file_next(pFrom,0);
@@ -403,26 +394,26 @@
       cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
     }
     if( cmp<0 ){
       diff_printf("DELETED %s\n", pFromFile->zName);
       if( asNewFlag ){
-        diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs);
+        diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
       }
       pFromFile = manifest_file_next(pFrom,0);
     }else if( cmp>0 ){
       diff_printf("ADDED   %s\n", pToFile->zName);
       if( asNewFlag ){
-        diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs);
+        diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
       }
       pToFile = manifest_file_next(pTo,0);
     }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
       /* No changes */
       pFromFile = manifest_file_next(pFrom,0);
       pToFile = manifest_file_next(pTo,0);
     }else{
       /* diff_printf("CHANGED %s\n", pFromFile->zName); */
-      diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs);
+      diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
       pFromFile = manifest_file_next(pFrom,0);
       pToFile = manifest_file_next(pTo,0);
     }
   }
   manifest_destroy(pFrom);
@@ -456,30 +447,44 @@
 **
 ** The "-N" or "--new-file" option causes the complete text of added or
 ** deleted files to be displayed.
 **
 ** Options:
+**   --context|-c N      Use N lines of context 
 **   --from|-r VERSION   select VERSION as source for the diff
 **   --new-file|-N       output complete text of added or deleted files
 **   -i                  use internal diff logic
 **   --to VERSION        select VERSION as target for the diff
+**   --side-by-side|-y   side-by-side diff
+**   --width|-W N        Width of lines in side-by-side diff 
 */
 void diff_cmd(void){
   int isGDiff;               /* True for gdiff.  False for normal diff */
   int isInternDiff;          /* True for internal diff */
   int hasNFlag;              /* True if -N or --new-file flag is used */
   const char *zFrom;         /* Source version number */
   const char *zTo;           /* Target version number */
   const char *zDiffCmd = 0;  /* External diff command. NULL for internal diff */
   int diffFlags = 0;         /* Flags to control the DIFF */
+  const char *z;
   int f;
 
   isGDiff = g.argv[1][0]=='g';
   isInternDiff = find_option("internal","i",0)!=0;
   zFrom = find_option("from", "r", 1);
   zTo = find_option("to", 0, 1);
   hasNFlag = find_option("new-file","N",0)!=0;
+  if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
+  if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>0 ){
+    if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
+    diffFlags |= f;
+  }
+  if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
+    f *= DIFF_CONTEXT_MASK+1;
+    if( f > DIFF_WIDTH_MASK ) f = DIFF_CONTEXT_MASK;
+    diffFlags |= f;
+  }
 
 
   if( hasNFlag ) diffFlags |= DIFF_NEWFILE;
   if( zTo==0 ){
     db_must_be_within_tree();

Index: src/info.c
==================================================================
--- src/info.c
+++ src/info.c
@@ -270,11 +270,11 @@
     content_get(toid, &to);
   }else{
     blob_zero(&to);
   }
   blob_zero(&out);
-  text_diff(&from, &to, &out, 5, 1);
+  text_diff(&from, &to, &out, DIFF_IGNORE_EOLWS | 5);
   @ %h(blob_str(&out))
   blob_reset(&from);
   blob_reset(&to);
   blob_reset(&out);  
 }
@@ -1091,11 +1091,11 @@
     pOut = &diff;
   }
   if( !sideBySide || isPatch ){
     content_get(v1, &c1);
     content_get(v2, &c2);
-    text_diff(&c1, &c2, pOut, 4, 1);
+    text_diff(&c1, &c2, pOut, 4 | 0);
     blob_reset(&c1);
     blob_reset(&c2);
   }
   if( !isPatch ){
     style_header("Diff");

Index: src/merge3.c
==================================================================
--- src/merge3.c
+++ src/merge3.c
@@ -171,12 +171,12 @@
   ** is the number of lines of text to copy directly from the pivot,
   ** the second integer is the number of lines of text to omit from the
   ** pivot, and the third integer is the number of lines of text that are
   ** inserted.  The edit array ends with a triple of 0,0,0.
   */
-  aC1 = text_diff(pPivot, pV1, 0, 0, 0);
-  aC2 = text_diff(pPivot, pV2, 0, 0, 0);
+  aC1 = text_diff(pPivot, pV1, 0, 0);
+  aC2 = text_diff(pPivot, pV2, 0, 0);
   if( aC1==0 || aC2==0 ){
     free(aC1);
     free(aC2);
     return -1;
   }

Index: src/wiki.c
==================================================================
--- src/wiki.c
+++ src/wiki.c
@@ -621,11 +621,11 @@
   blob_zero(&w2);
   if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
     blob_init(&w2, pW2->zWiki, -1);
   }
   blob_zero(&d);
-  text_diff(&w2, &w1, &d, 5, 1);
+  text_diff(&w2, &w1, &d, 5 | DIFF_IGNORE_EOLWS);
   @ <pre>
   @ %h(blob_str(&d))
   @ </pre>
   manifest_destroy(pW1);
   manifest_destroy(pW2);