Fossil SCM

Support arbitrary line lengths with synced horizontal scrolling in side-by-side diffs.

joel 2013-07-06 02:35 UTC trunk
Commit 4081a91c84c181ba77736a6e1bd9156c2a1699d4
+198 -162
--- src/diff.c
+++ src/diff.c
@@ -51,16 +51,13 @@
5151
"cannot compute difference between binary files\n"
5252
5353
#define DIFF_CANNOT_COMPUTE_SYMLINK \
5454
"cannot compute difference between symlink and regular file\n"
5555
56
-#define DIFF_TOO_MANY_CHANGES_TXT \
56
+#define DIFF_TOO_MANY_CHANGES \
5757
"more than 10,000 changes\n"
5858
59
-#define DIFF_TOO_MANY_CHANGES_HTML \
60
- "<p class='generalError'>More than 10,000 changes</p>\n"
61
-
6259
/*
6360
** This macro is designed to return non-zero if the specified blob contains
6461
** data that MAY be binary in nature; otherwise, zero will be returned.
6562
*/
6663
#define looks_like_binary(blob) \
@@ -576,11 +573,11 @@
576573
int mxr; /* Maximum value for r */
577574
int na, nb; /* Number of lines shown from A and B */
578575
int i, j; /* Loop counters */
579576
int m; /* Number of lines to output */
580577
int skip; /* Number of lines to skip */
581
- int nChunk = 0; /* Number of diff chunks seen so far */
578
+ static int nChunk = 0; /* Number of diff chunks seen so far */
582579
int nContext; /* Number of lines of context */
583580
int showLn; /* Show line numbers */
584581
int html; /* Render as HTML */
585582
int showDivider = 0; /* True to show the divider between diff blocks */
586583
@@ -657,14 +654,14 @@
657654
if( !showDivider ){
658655
/* Do not show a top divider */
659656
showDivider = 1;
660657
}else if( html ){
661658
blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.');
662
- blob_appendf(pOut, "<a name=\"chunk%d\"></a>\n", nChunk);
663659
}else{
664660
blob_appendf(pOut, "%.80c\n", '.');
665661
}
662
+ if( html ) blob_appendf(pOut, "<span id=\"chunk%d\"></span>", nChunk);
666663
}else{
667664
if( html ) blob_appendf(pOut, "<span class=\"diffln\">");
668665
/*
669666
* If the patch changes an empty file or results in an empty file,
670667
* the block header must use 0,0 as position indicator and not 1,0.
@@ -727,12 +724,11 @@
727724
/*
728725
** Status of a single output line
729726
*/
730727
typedef struct SbsLine SbsLine;
731728
struct SbsLine {
732
- char *zLine; /* The output line under construction */
733
- int n; /* Index of next unused slot in the zLine[] */
729
+ Blob *apCols[5]; /* Array of pointers to output columns */
734730
int width; /* Maximum width of a column in the output */
735731
unsigned char escHtml; /* True to escape html characters */
736732
int iStart; /* Write zStart prior to character iStart */
737733
const char *zStart; /* A <span> tag */
738734
int iEnd; /* Write </span> prior to character iEnd */
@@ -741,125 +737,154 @@
741737
int iEnd2; /* Write </span> prior to character iEnd2 */
742738
ReCompiled *pRe; /* Only colorize matching lines, if not NULL */
743739
};
744740
745741
/*
746
-** Flags for sbsWriteText()
742
+** Column indices
743
+*/
744
+#define SBS_LNA 0
745
+#define SBS_TXTA 1
746
+#define SBS_MKR 2
747
+#define SBS_LNB 3
748
+#define SBS_TXTB 4
749
+
750
+/*
751
+** Append newlines to all columns.
752
+*/
753
+static void sbsWriteNewlines(SbsLine *p){
754
+ int i;
755
+ for( i=p->escHtml ? SBS_LNA : SBS_TXTB; i<=SBS_TXTB; i++ ){
756
+ blob_append(p->apCols[i], "\n", 1);
757
+ }
758
+}
759
+
760
+/*
761
+** Append n spaces to the column.
747762
*/
748
-#define SBS_NEWLINE 0x0001 /* End with \n\000 */
749
-#define SBS_PAD 0x0002 /* Pad output to width spaces */
763
+static void sbsWriteSpace(SbsLine *p, int n, int col){
764
+ blob_appendf(p->apCols[col], "%*s", n, "");
765
+}
750766
751767
/*
752
-** Write up to width characters of pLine into p->zLine[]. Translate tabs into
753
-** spaces. Add a newline if SBS_NEWLINE is set. Translate HTML characters
754
-** if SBS_HTML is set. Pad the rendering out width bytes if SBS_PAD is set.
768
+** Write pLine to the column. If outputting HTML, write the full line.
769
+** Otherwise, only write up to width characters. Translate tabs into
770
+** spaces. Add newlines if col is SBS_TXTB. Translate HTML characters
771
+** if escHtml is true. Pad the rendering out width bytes if col is
772
+** SBS_TXTA and escHtml is false.
755773
**
756774
** This comment contains multibyte unicode characters (ü, Æ, ð) in order
757775
** to test the ability of the diff code to handle such characters.
758776
*/
759
-static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){
777
+static void sbsWriteText(SbsLine *p, DLine *pLine, int col){
778
+ Blob *pCol = p->apCols[col];
760779
int n = pLine->h & LENGTH_MASK;
761780
int i; /* Number of input characters consumed */
762
- int j; /* Number of output characters generated */
763781
int k; /* Cursor position */
764782
int needEndSpan = 0;
765783
const char *zIn = pLine->z;
766
- char *z = &p->zLine[p->n];
767784
int w = p->width;
768785
int colorize = p->escHtml;
769786
if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
770787
colorize = 0;
771788
}
772
- for(i=j=k=0; k<w && i<n; i++, k++){
789
+ for(i=k=0; (p->escHtml || k<w) && i<n; i++, k++){
773790
char c = zIn[i];
774791
if( colorize ){
775792
if( i==p->iStart ){
776793
int x = strlen(p->zStart);
777
- memcpy(z+j, p->zStart, x);
778
- j += x;
794
+ blob_append(pCol, p->zStart, x);
779795
needEndSpan = 1;
780796
if( p->iStart2 ){
781797
p->iStart = p->iStart2;
782798
p->zStart = p->zStart2;
783799
p->iStart2 = 0;
784800
}
785801
}else if( i==p->iEnd ){
786
- memcpy(z+j, "</span>", 7);
787
- j += 7;
802
+ blob_append(pCol, "</span>", 7);
788803
needEndSpan = 0;
789804
if( p->iEnd2 ){
790805
p->iEnd = p->iEnd2;
791806
p->iEnd2 = 0;
792807
}
793808
}
794809
}
795810
if( c=='\t' ){
796
- z[j++] = ' ';
797
- while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; }
811
+ blob_append(pCol, " ", 1);
812
+ while( (k&7)!=7 && (p->escHtml || k<w) ){
813
+ blob_append(pCol, " ", 1);
814
+ k++;
815
+ }
798816
}else if( c=='\r' || c=='\f' ){
799
- z[j++] = ' ';
817
+ blob_append(pCol, " ", 1);
800818
}else if( c=='<' && p->escHtml ){
801
- memcpy(&z[j], "&lt;", 4);
802
- j += 4;
819
+ blob_append(pCol, "&lt;", 4);
803820
}else if( c=='&' && p->escHtml ){
804
- memcpy(&z[j], "&amp;", 5);
805
- j += 5;
821
+ blob_append(pCol, "&amp;", 5);
806822
}else if( c=='>' && p->escHtml ){
807
- memcpy(&z[j], "&gt;", 4);
808
- j += 4;
823
+ blob_append(pCol, "&gt;", 4);
809824
}else if( c=='"' && p->escHtml ){
810
- memcpy(&z[j], "&quot;", 6);
811
- j += 6;
825
+ blob_append(pCol, "&quot;", 6);
812826
}else{
813
- z[j++] = c;
827
+ blob_append(pCol, &zIn[i], 1);
814828
if( (c&0xc0)==0x80 ) k--;
815829
}
816830
}
817831
if( needEndSpan ){
818
- memcpy(&z[j], "</span>", 7);
819
- j += 7;
820
- }
821
- if( (flags & SBS_PAD)!=0 ){
822
- while( k<w ){ k++; z[j++] = ' '; }
823
- }
824
- if( flags & SBS_NEWLINE ){
825
- z[j++] = '\n';
826
- }
827
- p->n += j;
828
-}
829
-
830
-/*
831
-** Append a string to an SbSLine without coding, interpretation, or padding.
832
-*/
833
-static void sbsWrite(SbsLine *p, const char *zIn, int nIn){
834
- memcpy(p->zLine+p->n, zIn, nIn);
835
- p->n += nIn;
836
-}
837
-
838
-/*
839
-** Append n spaces to the string.
840
-*/
841
-static void sbsWriteSpace(SbsLine *p, int n){
842
- while( n-- ) p->zLine[p->n++] = ' ';
843
-}
844
-
845
-/*
846
-** Append a string to the output only if we are rendering HTML.
847
-*/
848
-static void sbsWriteHtml(SbsLine *p, const char *zIn){
849
- if( p->escHtml ) sbsWrite(p, zIn, strlen(zIn));
850
-}
851
-
852
-/*
853
-** Write a 6-digit line number followed by a single space onto the line.
854
-*/
855
-static void sbsWriteLineno(SbsLine *p, int ln){
856
- sbsWriteHtml(p, "<span class=\"diffln\">");
857
- sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1);
858
- p->n += 6;
859
- sbsWriteHtml(p, "</span>");
860
- p->zLine[p->n++] = ' ';
832
+ blob_append(pCol, "</span>", 7);
833
+ }
834
+ if( col==SBS_TXTB ){
835
+ sbsWriteNewlines(p);
836
+ }else if( !p->escHtml ){
837
+ sbsWriteSpace(p, w-k, SBS_TXTA);
838
+ }
839
+}
840
+
841
+/*
842
+** Append a column to the final output blob.
843
+*/
844
+static void sbsWriteColumn(Blob *pOut, Blob *pCol, int col){
845
+ blob_appendf(pOut,
846
+ "<td><div class=\"diff%scol\">\n"
847
+ "<pre>\n"
848
+ "%s"
849
+ "</pre>\n"
850
+ "</div></td>\n",
851
+ col % 3 ? (col == SBS_MKR ? "mkr" : "txt") : "ln",
852
+ blob_str(pCol)
853
+ );
854
+}
855
+
856
+/*
857
+** Append separator to the column.
858
+*/
859
+static void sbsWriteSep(SbsLine *p, int len, int col){
860
+ char ch = '.';
861
+ if( len<1 ){
862
+ len = 1;
863
+ ch = ' ';
864
+ }
865
+ blob_appendf(p->apCols[col], "<span class=\"diffhr\">%.*c</span>\n", len, ch);
866
+}
867
+
868
+/*
869
+** Append the appropriate marker.
870
+*/
871
+static void sbsWriteMarker(SbsLine *p, const char *zTxt, const char *zHtml){
872
+ blob_append(p->apCols[SBS_MKR], p->escHtml ? zHtml : zTxt, -1);
873
+}
874
+
875
+/*
876
+** Append a line number to the column.
877
+*/
878
+static void sbsWriteLineno(SbsLine *p, int ln, int col){
879
+ if( p->escHtml ){
880
+ blob_appendf(p->apCols[col], "%d", ln+1);
881
+ }else{
882
+ char zLn[7];
883
+ sqlite3_snprintf(7, zLn, "%5d ", ln+1);
884
+ blob_appendf(p->apCols[col], "%s ", zLn);
885
+ }
861886
}
862887
863888
/*
864889
** The two text segments zLeft and zRight are known to be different on
865890
** both ends, but they might have a common segment in the middle. If
@@ -1018,43 +1043,42 @@
10181043
}
10191044
if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
10201045
}
10211046
if( nPrefix+nSuffix > nShort ) nPrefix = nShort - nSuffix;
10221047
1023
-
10241048
/* A single chunk of text inserted on the right */
10251049
if( nPrefix+nSuffix==nLeft ){
1026
- sbsWriteLineno(p, lnLeft);
1050
+ sbsWriteLineno(p, lnLeft, SBS_LNA);
10271051
p->iStart2 = p->iEnd2 = 0;
10281052
p->iStart = p->iEnd = -1;
1029
- sbsWriteText(p, pLeft, SBS_PAD);
1053
+ sbsWriteText(p, pLeft, SBS_TXTA);
10301054
if( nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){
1031
- sbsWrite(p, " ", 3);
1055
+ sbsWriteMarker(p, " ", "");
10321056
}else{
1033
- sbsWrite(p, " | ", 3);
1057
+ sbsWriteMarker(p, " | ", "|");
10341058
}
1035
- sbsWriteLineno(p, lnRight);
1059
+ sbsWriteLineno(p, lnRight, SBS_LNB);
10361060
p->iStart = nPrefix;
10371061
p->iEnd = nRight - nSuffix;
10381062
p->zStart = zClassAdd;
1039
- sbsWriteText(p, pRight, SBS_NEWLINE);
1063
+ sbsWriteText(p, pRight, SBS_TXTB);
10401064
return;
10411065
}
10421066
10431067
/* A single chunk of text deleted from the left */
10441068
if( nPrefix+nSuffix==nRight ){
10451069
/* Text deleted from the left */
1046
- sbsWriteLineno(p, lnLeft);
1070
+ sbsWriteLineno(p, lnLeft, SBS_LNA);
10471071
p->iStart2 = p->iEnd2 = 0;
10481072
p->iStart = nPrefix;
10491073
p->iEnd = nLeft - nSuffix;
10501074
p->zStart = zClassRm;
1051
- sbsWriteText(p, pLeft, SBS_PAD);
1052
- sbsWrite(p, " | ", 3);
1053
- sbsWriteLineno(p, lnRight);
1075
+ sbsWriteText(p, pLeft, SBS_TXTA);
1076
+ sbsWriteMarker(p, " | ", "|");
1077
+ sbsWriteLineno(p, lnRight, SBS_LNB);
10541078
p->iStart = p->iEnd = -1;
1055
- sbsWriteText(p, pRight, SBS_NEWLINE);
1079
+ sbsWriteText(p, pRight, SBS_TXTB);
10561080
return;
10571081
}
10581082
10591083
/* At this point we know that there is a chunk of text that has
10601084
** changed between the left and the right. Check to see if there
@@ -1065,11 +1089,11 @@
10651089
if( p->escHtml
10661090
&& nLeftDiff >= 6
10671091
&& nRightDiff >= 6
10681092
&& textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS)
10691093
){
1070
- sbsWriteLineno(p, lnLeft);
1094
+ sbsWriteLineno(p, lnLeft, SBS_LNA);
10711095
p->iStart = nPrefix;
10721096
p->iEnd = nPrefix + aLCS[0];
10731097
if( aLCS[2]==0 ){
10741098
sbsShiftLeft(p, pLeft->z);
10751099
p->zStart = zClassRm;
@@ -1078,13 +1102,13 @@
10781102
}
10791103
p->iStart2 = nPrefix + aLCS[1];
10801104
p->iEnd2 = nLeft - nSuffix;
10811105
p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng;
10821106
sbsSimplifyLine(p, zLeft+nPrefix);
1083
- sbsWriteText(p, pLeft, SBS_PAD);
1084
- sbsWrite(p, " | ", 3);
1085
- sbsWriteLineno(p, lnRight);
1107
+ sbsWriteText(p, pLeft, SBS_TXTA);
1108
+ sbsWriteMarker(p, " | ", "|");
1109
+ sbsWriteLineno(p, lnRight, SBS_LNB);
10861110
p->iStart = nPrefix;
10871111
p->iEnd = nPrefix + aLCS[2];
10881112
if( aLCS[0]==0 ){
10891113
sbsShiftLeft(p, pRight->z);
10901114
p->zStart = zClassAdd;
@@ -1093,25 +1117,25 @@
10931117
}
10941118
p->iStart2 = nPrefix + aLCS[3];
10951119
p->iEnd2 = nRight - nSuffix;
10961120
p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng;
10971121
sbsSimplifyLine(p, zRight+nPrefix);
1098
- sbsWriteText(p, pRight, SBS_NEWLINE);
1122
+ sbsWriteText(p, pRight, SBS_TXTB);
10991123
return;
11001124
}
11011125
11021126
/* If all else fails, show a single big change between left and right */
1103
- sbsWriteLineno(p, lnLeft);
1127
+ sbsWriteLineno(p, lnLeft, SBS_LNA);
11041128
p->iStart2 = p->iEnd2 = 0;
11051129
p->iStart = nPrefix;
11061130
p->iEnd = nLeft - nSuffix;
11071131
p->zStart = zClassChng;
1108
- sbsWriteText(p, pLeft, SBS_PAD);
1109
- sbsWrite(p, " | ", 3);
1110
- sbsWriteLineno(p, lnRight);
1132
+ sbsWriteText(p, pLeft, SBS_TXTA);
1133
+ sbsWriteMarker(p, " | ", "|");
1134
+ sbsWriteLineno(p, lnRight, SBS_LNB);
11111135
p->iEnd = nRight - nSuffix;
1112
- sbsWriteText(p, pRight, SBS_NEWLINE);
1136
+ sbsWriteText(p, pRight, SBS_TXTB);
11131137
}
11141138
11151139
/*
11161140
** Minimum of two values
11171141
*/
@@ -1357,30 +1381,40 @@
13571381
int mxr; /* Maximum value for r */
13581382
int na, nb; /* Number of lines shown from A and B */
13591383
int i, j; /* Loop counters */
13601384
int m, ma, mb;/* Number of lines to output */
13611385
int skip; /* Number of lines to skip */
1362
- int nChunk = 0; /* Number of chunks of diff output seen so far */
1386
+ static int nChunk = 0; /* Number of chunks of diff output seen so far */
13631387
SbsLine s; /* Output line buffer */
13641388
int nContext; /* Lines of context above and below each change */
13651389
int showDivider = 0; /* True to show the divider */
1390
+ Blob aCols[5]; /* Array of column blobs */
13661391
13671392
memset(&s, 0, sizeof(s));
13681393
s.width = diff_width(diffFlags);
1369
- s.zLine = fossil_malloc( 15*s.width + 200 );
1370
- if( s.zLine==0 ) return;
13711394
nContext = diff_context_lines(diffFlags);
13721395
s.escHtml = (diffFlags & DIFF_HTML)!=0;
1396
+ if( s.escHtml ){
1397
+ for(i=SBS_LNA; i<=SBS_TXTB; i++){
1398
+ blob_zero(&aCols[i]);
1399
+ s.apCols[i] = &aCols[i];
1400
+ }
1401
+ }else{
1402
+ for(i=SBS_LNA; i<=SBS_TXTB; i++){
1403
+ s.apCols[i] = pOut;
1404
+ }
1405
+ }
13731406
s.pRe = pRe;
13741407
s.iStart = -1;
13751408
s.iStart2 = 0;
13761409
s.iEnd = -1;
13771410
A = p->aFrom;
13781411
B = p->aTo;
13791412
R = p->aEdit;
13801413
mxr = p->nEdit;
13811414
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
1415
+
13821416
for(r=0; r<mxr; r += 3*nr){
13831417
/* Figure out how many triples to show in a single block */
13841418
for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
13851419
/* printf("r=%d nr=%d\n", r, nr); */
13861420
@@ -1436,35 +1470,39 @@
14361470
}
14371471
14381472
/* Draw the separator between blocks */
14391473
if( showDivider ){
14401474
if( s.escHtml ){
1441
- blob_appendf(pOut, "<span class=\"diffhr\">%.*c</span>\n",
1442
- s.width*2+16, '.');
1475
+ char zLn[10];
1476
+ sqlite3_snprintf(sizeof(zLn), zLn, "%d", a+skip+1);
1477
+ sbsWriteSep(&s, strlen(zLn), SBS_LNA);
1478
+ sbsWriteSep(&s, s.width, SBS_TXTA);
1479
+ sbsWriteSep(&s, 0, SBS_MKR);
1480
+ sqlite3_snprintf(sizeof(zLn), zLn, "%d", b+skip+1);
1481
+ sbsWriteSep(&s, strlen(zLn), SBS_LNB);
1482
+ sbsWriteSep(&s, s.width, SBS_TXTB);
14431483
}else{
14441484
blob_appendf(pOut, "%.*c\n", s.width*2+16, '.');
14451485
}
14461486
}
14471487
showDivider = 1;
14481488
nChunk++;
14491489
if( s.escHtml ){
1450
- blob_appendf(pOut, "<a name=\"chunk%d\"></a>\n", nChunk);
1490
+ blob_appendf(s.apCols[SBS_LNA], "<span id=\"chunk%d\"></span>", nChunk);
14511491
}
14521492
14531493
/* Show the initial common area */
14541494
a += skip;
14551495
b += skip;
14561496
m = R[r] - skip;
14571497
for(j=0; j<m; j++){
1458
- s.n = 0;
1459
- sbsWriteLineno(&s, a+j);
1498
+ sbsWriteLineno(&s, a+j, SBS_LNA);
14601499
s.iStart = s.iEnd = -1;
1461
- sbsWriteText(&s, &A[a+j], SBS_PAD);
1462
- sbsWrite(&s, " ", 3);
1463
- sbsWriteLineno(&s, b+j);
1464
- sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
1465
- blob_append(pOut, s.zLine, s.n);
1500
+ sbsWriteText(&s, &A[a+j], SBS_TXTA);
1501
+ sbsWriteMarker(&s, " ", "");
1502
+ sbsWriteLineno(&s, b+j, SBS_LNB);
1503
+ sbsWriteText(&s, &B[b+j], SBS_TXTB);
14661504
}
14671505
a += m;
14681506
b += m;
14691507
14701508
/* Show the differences */
@@ -1485,87 +1523,71 @@
14851523
14861524
alignment = sbsAlignment(&A[a], ma, &B[b], mb);
14871525
for(j=0; ma+mb>0; j++){
14881526
if( alignment[j]==1 ){
14891527
/* Delete one line from the left */
1490
- s.n = 0;
1491
- sbsWriteLineno(&s, a);
1528
+ sbsWriteLineno(&s, a, SBS_LNA);
14921529
s.iStart = 0;
14931530
s.zStart = "<span class=\"diffrm\">";
14941531
s.iEnd = LENGTH(&A[a]);
1495
- sbsWriteText(&s, &A[a], SBS_PAD);
1496
- if( s.escHtml ){
1497
- sbsWrite(&s, " &lt;\n", 6);
1498
- }else{
1499
- sbsWrite(&s, " <\n", 3);
1500
- }
1501
- blob_append(pOut, s.zLine, s.n);
1532
+ sbsWriteText(&s, &A[a], SBS_TXTA);
1533
+ sbsWriteMarker(&s, " <", "&lt;");
1534
+ sbsWriteNewlines(&s);
15021535
assert( ma>0 );
15031536
ma--;
15041537
a++;
15051538
}else if( alignment[j]==3 ){
15061539
/* The left line is changed into the right line */
1507
- s.n = 0;
15081540
sbsWriteLineChange(&s, &A[a], a, &B[b], b);
1509
- blob_append(pOut, s.zLine, s.n);
15101541
assert( ma>0 && mb>0 );
15111542
ma--;
15121543
mb--;
15131544
a++;
15141545
b++;
15151546
}else if( alignment[j]==2 ){
15161547
/* Insert one line on the right */
1517
- s.n = 0;
1518
- sbsWriteSpace(&s, s.width + 7);
1519
- if( s.escHtml ){
1520
- sbsWrite(&s, " &gt; ", 6);
1521
- }else{
1522
- sbsWrite(&s, " > ", 3);
1523
- }
1524
- sbsWriteLineno(&s, b);
1548
+ if( !s.escHtml ){
1549
+ sbsWriteSpace(&s, s.width + 7, SBS_TXTA);
1550
+ }
1551
+ sbsWriteMarker(&s, " > ", "&gt;");
1552
+ sbsWriteLineno(&s, b, SBS_LNB);
15251553
s.iStart = 0;
15261554
s.zStart = "<span class=\"diffadd\">";
15271555
s.iEnd = LENGTH(&B[b]);
1528
- sbsWriteText(&s, &B[b], SBS_NEWLINE);
1529
- blob_append(pOut, s.zLine, s.n);
1556
+ sbsWriteText(&s, &B[b], SBS_TXTB);
15301557
assert( mb>0 );
15311558
mb--;
15321559
b++;
15331560
}else{
15341561
/* Delete from the left and insert on the right */
1535
- s.n = 0;
1536
- sbsWriteLineno(&s, a);
1562
+ sbsWriteLineno(&s, a, SBS_LNA);
15371563
s.iStart = 0;
15381564
s.zStart = "<span class=\"diffrm\">";
15391565
s.iEnd = LENGTH(&A[a]);
1540
- sbsWriteText(&s, &A[a], SBS_PAD);
1541
- sbsWrite(&s, " | ", 3);
1542
- sbsWriteLineno(&s, b);
1566
+ sbsWriteText(&s, &A[a], SBS_TXTA);
1567
+ sbsWriteMarker(&s, " | ", "|");
1568
+ sbsWriteLineno(&s, b, SBS_LNB);
15431569
s.iStart = 0;
15441570
s.zStart = "<span class=\"diffadd\">";
15451571
s.iEnd = LENGTH(&B[b]);
1546
- sbsWriteText(&s, &B[b], SBS_NEWLINE);
1547
- blob_append(pOut, s.zLine, s.n);
1572
+ sbsWriteText(&s, &B[b], SBS_TXTB);
15481573
ma--;
15491574
mb--;
15501575
a++;
15511576
b++;
15521577
}
1553
-
15541578
}
15551579
fossil_free(alignment);
15561580
if( i<nr-1 ){
15571581
m = R[r+i*3+3];
15581582
for(j=0; j<m; j++){
1559
- s.n = 0;
1560
- sbsWriteLineno(&s, a+j);
1583
+ sbsWriteLineno(&s, a+j, SBS_LNA);
15611584
s.iStart = s.iEnd = -1;
1562
- sbsWriteText(&s, &A[a+j], SBS_PAD);
1563
- sbsWrite(&s, " ", 3);
1564
- sbsWriteLineno(&s, b+j);
1565
- sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
1566
- blob_append(pOut, s.zLine, s.n);
1585
+ sbsWriteText(&s, &A[a+j], SBS_TXTA);
1586
+ sbsWriteMarker(&s, " ", "");
1587
+ sbsWriteLineno(&s, b+j, SBS_LNB);
1588
+ sbsWriteText(&s, &B[b+j], SBS_TXTB);
15671589
}
15681590
b += m;
15691591
a += m;
15701592
}
15711593
}
@@ -1573,21 +1595,27 @@
15731595
/* Show the final common area */
15741596
assert( nr==i );
15751597
m = R[r+nr*3];
15761598
if( m>nContext ) m = nContext;
15771599
for(j=0; j<m; j++){
1578
- s.n = 0;
1579
- sbsWriteLineno(&s, a+j);
1600
+ sbsWriteLineno(&s, a+j, SBS_LNA);
15801601
s.iStart = s.iEnd = -1;
1581
- sbsWriteText(&s, &A[a+j], SBS_PAD);
1582
- sbsWrite(&s, " ", 3);
1583
- sbsWriteLineno(&s, b+j);
1584
- sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
1585
- blob_append(pOut, s.zLine, s.n);
1602
+ sbsWriteText(&s, &A[a+j], SBS_TXTA);
1603
+ sbsWriteMarker(&s, " ", "");
1604
+ sbsWriteLineno(&s, b+j, SBS_LNB);
1605
+ sbsWriteText(&s, &B[b+j], SBS_TXTB);
15861606
}
15871607
}
1588
- free(s.zLine);
1608
+
1609
+ if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){
1610
+ blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1);
1611
+ for(i=SBS_LNA; i<=SBS_TXTB; i++){
1612
+ sbsWriteColumn(pOut, s.apCols[i], i);
1613
+ blob_reset(s.apCols[i]);
1614
+ }
1615
+ blob_append(pOut, "</tr></table>\n", -1);
1616
+ }
15891617
}
15901618
15911619
/*
15921620
** Compute the optimal longest common subsequence (LCS) using an
15931621
** exhaustive search. This version of the LCS is only used for
@@ -1990,10 +2018,21 @@
19902018
int diff_width(u64 diffFlags){
19912019
int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
19922020
if( w==0 ) w = 80;
19932021
return w;
19942022
}
2023
+
2024
+/*
2025
+** Append the error message to pOut.
2026
+*/
2027
+void diff_errmsg(Blob *pOut, const char *msg, int diffFlags){
2028
+ if( diffFlags & DIFF_HTML ){
2029
+ blob_appendf(pOut, "<p class=\"generalError\">%s</p>", msg);
2030
+ }else{
2031
+ blob_append(pOut, msg, -1);
2032
+ }
2033
+}
19952034
19962035
/*
19972036
** Generate a report of the differences between files pA and pB.
19982037
** If pOut is not NULL then a unified diff is appended there. It
19992038
** is assumed that pOut has already been initialized. If pOut is
@@ -2032,11 +2071,11 @@
20322071
&c.nTo, ignoreEolWs);
20332072
if( c.aFrom==0 || c.aTo==0 ){
20342073
fossil_free(c.aFrom);
20352074
fossil_free(c.aTo);
20362075
if( pOut ){
2037
- blob_appendf(pOut, DIFF_CANNOT_COMPUTE_BINARY);
2076
+ diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags);
20382077
}
20392078
return 0;
20402079
}
20412080
20422081
/* Compute the difference */
@@ -2048,15 +2087,11 @@
20482087
for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
20492088
if( n>10000 ){
20502089
fossil_free(c.aFrom);
20512090
fossil_free(c.aTo);
20522091
fossil_free(c.aEdit);
2053
- if( diffFlags & DIFF_HTML ){
2054
- blob_append(pOut, DIFF_TOO_MANY_CHANGES_HTML, -1);
2055
- }else{
2056
- blob_append(pOut, DIFF_TOO_MANY_CHANGES_TXT, -1);
2057
- }
2092
+ diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags);
20582093
return 0;
20592094
}
20602095
}
20612096
if( (diffFlags & DIFF_NOOPT)==0 ){
20622097
diff_optimize(&c);
@@ -2159,10 +2194,11 @@
21592194
if( find_option("tk",0,0)!=0 ){
21602195
diff_tk("test-diff", 2);
21612196
return;
21622197
}
21632198
find_option("i",0,0);
2199
+ find_option("v",0,0);
21642200
zRe = find_option("regexp","e",1);
21652201
if( zRe ){
21662202
const char *zErr = re_compile(&pRe, zRe, 0);
21672203
if( zErr ) fossil_fatal("regex error: %s", zErr);
21682204
}
21692205
--- src/diff.c
+++ src/diff.c
@@ -51,16 +51,13 @@
51 "cannot compute difference between binary files\n"
52
53 #define DIFF_CANNOT_COMPUTE_SYMLINK \
54 "cannot compute difference between symlink and regular file\n"
55
56 #define DIFF_TOO_MANY_CHANGES_TXT \
57 "more than 10,000 changes\n"
58
59 #define DIFF_TOO_MANY_CHANGES_HTML \
60 "<p class='generalError'>More than 10,000 changes</p>\n"
61
62 /*
63 ** This macro is designed to return non-zero if the specified blob contains
64 ** data that MAY be binary in nature; otherwise, zero will be returned.
65 */
66 #define looks_like_binary(blob) \
@@ -576,11 +573,11 @@
576 int mxr; /* Maximum value for r */
577 int na, nb; /* Number of lines shown from A and B */
578 int i, j; /* Loop counters */
579 int m; /* Number of lines to output */
580 int skip; /* Number of lines to skip */
581 int nChunk = 0; /* Number of diff chunks seen so far */
582 int nContext; /* Number of lines of context */
583 int showLn; /* Show line numbers */
584 int html; /* Render as HTML */
585 int showDivider = 0; /* True to show the divider between diff blocks */
586
@@ -657,14 +654,14 @@
657 if( !showDivider ){
658 /* Do not show a top divider */
659 showDivider = 1;
660 }else if( html ){
661 blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.');
662 blob_appendf(pOut, "<a name=\"chunk%d\"></a>\n", nChunk);
663 }else{
664 blob_appendf(pOut, "%.80c\n", '.');
665 }
 
666 }else{
667 if( html ) blob_appendf(pOut, "<span class=\"diffln\">");
668 /*
669 * If the patch changes an empty file or results in an empty file,
670 * the block header must use 0,0 as position indicator and not 1,0.
@@ -727,12 +724,11 @@
727 /*
728 ** Status of a single output line
729 */
730 typedef struct SbsLine SbsLine;
731 struct SbsLine {
732 char *zLine; /* The output line under construction */
733 int n; /* Index of next unused slot in the zLine[] */
734 int width; /* Maximum width of a column in the output */
735 unsigned char escHtml; /* True to escape html characters */
736 int iStart; /* Write zStart prior to character iStart */
737 const char *zStart; /* A <span> tag */
738 int iEnd; /* Write </span> prior to character iEnd */
@@ -741,125 +737,154 @@
741 int iEnd2; /* Write </span> prior to character iEnd2 */
742 ReCompiled *pRe; /* Only colorize matching lines, if not NULL */
743 };
744
745 /*
746 ** Flags for sbsWriteText()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747 */
748 #define SBS_NEWLINE 0x0001 /* End with \n\000 */
749 #define SBS_PAD 0x0002 /* Pad output to width spaces */
 
750
751 /*
752 ** Write up to width characters of pLine into p->zLine[]. Translate tabs into
753 ** spaces. Add a newline if SBS_NEWLINE is set. Translate HTML characters
754 ** if SBS_HTML is set. Pad the rendering out width bytes if SBS_PAD is set.
 
 
755 **
756 ** This comment contains multibyte unicode characters (ü, Æ, ð) in order
757 ** to test the ability of the diff code to handle such characters.
758 */
759 static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){
 
760 int n = pLine->h & LENGTH_MASK;
761 int i; /* Number of input characters consumed */
762 int j; /* Number of output characters generated */
763 int k; /* Cursor position */
764 int needEndSpan = 0;
765 const char *zIn = pLine->z;
766 char *z = &p->zLine[p->n];
767 int w = p->width;
768 int colorize = p->escHtml;
769 if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
770 colorize = 0;
771 }
772 for(i=j=k=0; k<w && i<n; i++, k++){
773 char c = zIn[i];
774 if( colorize ){
775 if( i==p->iStart ){
776 int x = strlen(p->zStart);
777 memcpy(z+j, p->zStart, x);
778 j += x;
779 needEndSpan = 1;
780 if( p->iStart2 ){
781 p->iStart = p->iStart2;
782 p->zStart = p->zStart2;
783 p->iStart2 = 0;
784 }
785 }else if( i==p->iEnd ){
786 memcpy(z+j, "</span>", 7);
787 j += 7;
788 needEndSpan = 0;
789 if( p->iEnd2 ){
790 p->iEnd = p->iEnd2;
791 p->iEnd2 = 0;
792 }
793 }
794 }
795 if( c=='\t' ){
796 z[j++] = ' ';
797 while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; }
 
 
 
798 }else if( c=='\r' || c=='\f' ){
799 z[j++] = ' ';
800 }else if( c=='<' && p->escHtml ){
801 memcpy(&z[j], "&lt;", 4);
802 j += 4;
803 }else if( c=='&' && p->escHtml ){
804 memcpy(&z[j], "&amp;", 5);
805 j += 5;
806 }else if( c=='>' && p->escHtml ){
807 memcpy(&z[j], "&gt;", 4);
808 j += 4;
809 }else if( c=='"' && p->escHtml ){
810 memcpy(&z[j], "&quot;", 6);
811 j += 6;
812 }else{
813 z[j++] = c;
814 if( (c&0xc0)==0x80 ) k--;
815 }
816 }
817 if( needEndSpan ){
818 memcpy(&z[j], "</span>", 7);
819 j += 7;
820 }
821 if( (flags & SBS_PAD)!=0 ){
822 while( k<w ){ k++; z[j++] = ' '; }
823 }
824 if( flags & SBS_NEWLINE ){
825 z[j++] = '\n';
826 }
827 p->n += j;
828 }
829
830 /*
831 ** Append a string to an SbSLine without coding, interpretation, or padding.
832 */
833 static void sbsWrite(SbsLine *p, const char *zIn, int nIn){
834 memcpy(p->zLine+p->n, zIn, nIn);
835 p->n += nIn;
836 }
837
838 /*
839 ** Append n spaces to the string.
840 */
841 static void sbsWriteSpace(SbsLine *p, int n){
842 while( n-- ) p->zLine[p->n++] = ' ';
843 }
844
845 /*
846 ** Append a string to the output only if we are rendering HTML.
847 */
848 static void sbsWriteHtml(SbsLine *p, const char *zIn){
849 if( p->escHtml ) sbsWrite(p, zIn, strlen(zIn));
850 }
851
852 /*
853 ** Write a 6-digit line number followed by a single space onto the line.
854 */
855 static void sbsWriteLineno(SbsLine *p, int ln){
856 sbsWriteHtml(p, "<span class=\"diffln\">");
857 sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1);
858 p->n += 6;
859 sbsWriteHtml(p, "</span>");
860 p->zLine[p->n++] = ' ';
 
 
 
 
 
 
 
 
 
 
 
861 }
862
863 /*
864 ** The two text segments zLeft and zRight are known to be different on
865 ** both ends, but they might have a common segment in the middle. If
@@ -1018,43 +1043,42 @@
1018 }
1019 if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
1020 }
1021 if( nPrefix+nSuffix > nShort ) nPrefix = nShort - nSuffix;
1022
1023
1024 /* A single chunk of text inserted on the right */
1025 if( nPrefix+nSuffix==nLeft ){
1026 sbsWriteLineno(p, lnLeft);
1027 p->iStart2 = p->iEnd2 = 0;
1028 p->iStart = p->iEnd = -1;
1029 sbsWriteText(p, pLeft, SBS_PAD);
1030 if( nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){
1031 sbsWrite(p, " ", 3);
1032 }else{
1033 sbsWrite(p, " | ", 3);
1034 }
1035 sbsWriteLineno(p, lnRight);
1036 p->iStart = nPrefix;
1037 p->iEnd = nRight - nSuffix;
1038 p->zStart = zClassAdd;
1039 sbsWriteText(p, pRight, SBS_NEWLINE);
1040 return;
1041 }
1042
1043 /* A single chunk of text deleted from the left */
1044 if( nPrefix+nSuffix==nRight ){
1045 /* Text deleted from the left */
1046 sbsWriteLineno(p, lnLeft);
1047 p->iStart2 = p->iEnd2 = 0;
1048 p->iStart = nPrefix;
1049 p->iEnd = nLeft - nSuffix;
1050 p->zStart = zClassRm;
1051 sbsWriteText(p, pLeft, SBS_PAD);
1052 sbsWrite(p, " | ", 3);
1053 sbsWriteLineno(p, lnRight);
1054 p->iStart = p->iEnd = -1;
1055 sbsWriteText(p, pRight, SBS_NEWLINE);
1056 return;
1057 }
1058
1059 /* At this point we know that there is a chunk of text that has
1060 ** changed between the left and the right. Check to see if there
@@ -1065,11 +1089,11 @@
1065 if( p->escHtml
1066 && nLeftDiff >= 6
1067 && nRightDiff >= 6
1068 && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS)
1069 ){
1070 sbsWriteLineno(p, lnLeft);
1071 p->iStart = nPrefix;
1072 p->iEnd = nPrefix + aLCS[0];
1073 if( aLCS[2]==0 ){
1074 sbsShiftLeft(p, pLeft->z);
1075 p->zStart = zClassRm;
@@ -1078,13 +1102,13 @@
1078 }
1079 p->iStart2 = nPrefix + aLCS[1];
1080 p->iEnd2 = nLeft - nSuffix;
1081 p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng;
1082 sbsSimplifyLine(p, zLeft+nPrefix);
1083 sbsWriteText(p, pLeft, SBS_PAD);
1084 sbsWrite(p, " | ", 3);
1085 sbsWriteLineno(p, lnRight);
1086 p->iStart = nPrefix;
1087 p->iEnd = nPrefix + aLCS[2];
1088 if( aLCS[0]==0 ){
1089 sbsShiftLeft(p, pRight->z);
1090 p->zStart = zClassAdd;
@@ -1093,25 +1117,25 @@
1093 }
1094 p->iStart2 = nPrefix + aLCS[3];
1095 p->iEnd2 = nRight - nSuffix;
1096 p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng;
1097 sbsSimplifyLine(p, zRight+nPrefix);
1098 sbsWriteText(p, pRight, SBS_NEWLINE);
1099 return;
1100 }
1101
1102 /* If all else fails, show a single big change between left and right */
1103 sbsWriteLineno(p, lnLeft);
1104 p->iStart2 = p->iEnd2 = 0;
1105 p->iStart = nPrefix;
1106 p->iEnd = nLeft - nSuffix;
1107 p->zStart = zClassChng;
1108 sbsWriteText(p, pLeft, SBS_PAD);
1109 sbsWrite(p, " | ", 3);
1110 sbsWriteLineno(p, lnRight);
1111 p->iEnd = nRight - nSuffix;
1112 sbsWriteText(p, pRight, SBS_NEWLINE);
1113 }
1114
1115 /*
1116 ** Minimum of two values
1117 */
@@ -1357,30 +1381,40 @@
1357 int mxr; /* Maximum value for r */
1358 int na, nb; /* Number of lines shown from A and B */
1359 int i, j; /* Loop counters */
1360 int m, ma, mb;/* Number of lines to output */
1361 int skip; /* Number of lines to skip */
1362 int nChunk = 0; /* Number of chunks of diff output seen so far */
1363 SbsLine s; /* Output line buffer */
1364 int nContext; /* Lines of context above and below each change */
1365 int showDivider = 0; /* True to show the divider */
 
1366
1367 memset(&s, 0, sizeof(s));
1368 s.width = diff_width(diffFlags);
1369 s.zLine = fossil_malloc( 15*s.width + 200 );
1370 if( s.zLine==0 ) return;
1371 nContext = diff_context_lines(diffFlags);
1372 s.escHtml = (diffFlags & DIFF_HTML)!=0;
 
 
 
 
 
 
 
 
 
 
1373 s.pRe = pRe;
1374 s.iStart = -1;
1375 s.iStart2 = 0;
1376 s.iEnd = -1;
1377 A = p->aFrom;
1378 B = p->aTo;
1379 R = p->aEdit;
1380 mxr = p->nEdit;
1381 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
 
1382 for(r=0; r<mxr; r += 3*nr){
1383 /* Figure out how many triples to show in a single block */
1384 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
1385 /* printf("r=%d nr=%d\n", r, nr); */
1386
@@ -1436,35 +1470,39 @@
1436 }
1437
1438 /* Draw the separator between blocks */
1439 if( showDivider ){
1440 if( s.escHtml ){
1441 blob_appendf(pOut, "<span class=\"diffhr\">%.*c</span>\n",
1442 s.width*2+16, '.');
 
 
 
 
 
 
1443 }else{
1444 blob_appendf(pOut, "%.*c\n", s.width*2+16, '.');
1445 }
1446 }
1447 showDivider = 1;
1448 nChunk++;
1449 if( s.escHtml ){
1450 blob_appendf(pOut, "<a name=\"chunk%d\"></a>\n", nChunk);
1451 }
1452
1453 /* Show the initial common area */
1454 a += skip;
1455 b += skip;
1456 m = R[r] - skip;
1457 for(j=0; j<m; j++){
1458 s.n = 0;
1459 sbsWriteLineno(&s, a+j);
1460 s.iStart = s.iEnd = -1;
1461 sbsWriteText(&s, &A[a+j], SBS_PAD);
1462 sbsWrite(&s, " ", 3);
1463 sbsWriteLineno(&s, b+j);
1464 sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
1465 blob_append(pOut, s.zLine, s.n);
1466 }
1467 a += m;
1468 b += m;
1469
1470 /* Show the differences */
@@ -1485,87 +1523,71 @@
1485
1486 alignment = sbsAlignment(&A[a], ma, &B[b], mb);
1487 for(j=0; ma+mb>0; j++){
1488 if( alignment[j]==1 ){
1489 /* Delete one line from the left */
1490 s.n = 0;
1491 sbsWriteLineno(&s, a);
1492 s.iStart = 0;
1493 s.zStart = "<span class=\"diffrm\">";
1494 s.iEnd = LENGTH(&A[a]);
1495 sbsWriteText(&s, &A[a], SBS_PAD);
1496 if( s.escHtml ){
1497 sbsWrite(&s, " &lt;\n", 6);
1498 }else{
1499 sbsWrite(&s, " <\n", 3);
1500 }
1501 blob_append(pOut, s.zLine, s.n);
1502 assert( ma>0 );
1503 ma--;
1504 a++;
1505 }else if( alignment[j]==3 ){
1506 /* The left line is changed into the right line */
1507 s.n = 0;
1508 sbsWriteLineChange(&s, &A[a], a, &B[b], b);
1509 blob_append(pOut, s.zLine, s.n);
1510 assert( ma>0 && mb>0 );
1511 ma--;
1512 mb--;
1513 a++;
1514 b++;
1515 }else if( alignment[j]==2 ){
1516 /* Insert one line on the right */
1517 s.n = 0;
1518 sbsWriteSpace(&s, s.width + 7);
1519 if( s.escHtml ){
1520 sbsWrite(&s, " &gt; ", 6);
1521 }else{
1522 sbsWrite(&s, " > ", 3);
1523 }
1524 sbsWriteLineno(&s, b);
1525 s.iStart = 0;
1526 s.zStart = "<span class=\"diffadd\">";
1527 s.iEnd = LENGTH(&B[b]);
1528 sbsWriteText(&s, &B[b], SBS_NEWLINE);
1529 blob_append(pOut, s.zLine, s.n);
1530 assert( mb>0 );
1531 mb--;
1532 b++;
1533 }else{
1534 /* Delete from the left and insert on the right */
1535 s.n = 0;
1536 sbsWriteLineno(&s, a);
1537 s.iStart = 0;
1538 s.zStart = "<span class=\"diffrm\">";
1539 s.iEnd = LENGTH(&A[a]);
1540 sbsWriteText(&s, &A[a], SBS_PAD);
1541 sbsWrite(&s, " | ", 3);
1542 sbsWriteLineno(&s, b);
1543 s.iStart = 0;
1544 s.zStart = "<span class=\"diffadd\">";
1545 s.iEnd = LENGTH(&B[b]);
1546 sbsWriteText(&s, &B[b], SBS_NEWLINE);
1547 blob_append(pOut, s.zLine, s.n);
1548 ma--;
1549 mb--;
1550 a++;
1551 b++;
1552 }
1553
1554 }
1555 fossil_free(alignment);
1556 if( i<nr-1 ){
1557 m = R[r+i*3+3];
1558 for(j=0; j<m; j++){
1559 s.n = 0;
1560 sbsWriteLineno(&s, a+j);
1561 s.iStart = s.iEnd = -1;
1562 sbsWriteText(&s, &A[a+j], SBS_PAD);
1563 sbsWrite(&s, " ", 3);
1564 sbsWriteLineno(&s, b+j);
1565 sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
1566 blob_append(pOut, s.zLine, s.n);
1567 }
1568 b += m;
1569 a += m;
1570 }
1571 }
@@ -1573,21 +1595,27 @@
1573 /* Show the final common area */
1574 assert( nr==i );
1575 m = R[r+nr*3];
1576 if( m>nContext ) m = nContext;
1577 for(j=0; j<m; j++){
1578 s.n = 0;
1579 sbsWriteLineno(&s, a+j);
1580 s.iStart = s.iEnd = -1;
1581 sbsWriteText(&s, &A[a+j], SBS_PAD);
1582 sbsWrite(&s, " ", 3);
1583 sbsWriteLineno(&s, b+j);
1584 sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
1585 blob_append(pOut, s.zLine, s.n);
1586 }
1587 }
1588 free(s.zLine);
 
 
 
 
 
 
 
 
1589 }
1590
1591 /*
1592 ** Compute the optimal longest common subsequence (LCS) using an
1593 ** exhaustive search. This version of the LCS is only used for
@@ -1990,10 +2018,21 @@
1990 int diff_width(u64 diffFlags){
1991 int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
1992 if( w==0 ) w = 80;
1993 return w;
1994 }
 
 
 
 
 
 
 
 
 
 
 
1995
1996 /*
1997 ** Generate a report of the differences between files pA and pB.
1998 ** If pOut is not NULL then a unified diff is appended there. It
1999 ** is assumed that pOut has already been initialized. If pOut is
@@ -2032,11 +2071,11 @@
2032 &c.nTo, ignoreEolWs);
2033 if( c.aFrom==0 || c.aTo==0 ){
2034 fossil_free(c.aFrom);
2035 fossil_free(c.aTo);
2036 if( pOut ){
2037 blob_appendf(pOut, DIFF_CANNOT_COMPUTE_BINARY);
2038 }
2039 return 0;
2040 }
2041
2042 /* Compute the difference */
@@ -2048,15 +2087,11 @@
2048 for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
2049 if( n>10000 ){
2050 fossil_free(c.aFrom);
2051 fossil_free(c.aTo);
2052 fossil_free(c.aEdit);
2053 if( diffFlags & DIFF_HTML ){
2054 blob_append(pOut, DIFF_TOO_MANY_CHANGES_HTML, -1);
2055 }else{
2056 blob_append(pOut, DIFF_TOO_MANY_CHANGES_TXT, -1);
2057 }
2058 return 0;
2059 }
2060 }
2061 if( (diffFlags & DIFF_NOOPT)==0 ){
2062 diff_optimize(&c);
@@ -2159,10 +2194,11 @@
2159 if( find_option("tk",0,0)!=0 ){
2160 diff_tk("test-diff", 2);
2161 return;
2162 }
2163 find_option("i",0,0);
 
2164 zRe = find_option("regexp","e",1);
2165 if( zRe ){
2166 const char *zErr = re_compile(&pRe, zRe, 0);
2167 if( zErr ) fossil_fatal("regex error: %s", zErr);
2168 }
2169
--- src/diff.c
+++ src/diff.c
@@ -51,16 +51,13 @@
51 "cannot compute difference between binary files\n"
52
53 #define DIFF_CANNOT_COMPUTE_SYMLINK \
54 "cannot compute difference between symlink and regular file\n"
55
56 #define DIFF_TOO_MANY_CHANGES \
57 "more than 10,000 changes\n"
58
 
 
 
59 /*
60 ** This macro is designed to return non-zero if the specified blob contains
61 ** data that MAY be binary in nature; otherwise, zero will be returned.
62 */
63 #define looks_like_binary(blob) \
@@ -576,11 +573,11 @@
573 int mxr; /* Maximum value for r */
574 int na, nb; /* Number of lines shown from A and B */
575 int i, j; /* Loop counters */
576 int m; /* Number of lines to output */
577 int skip; /* Number of lines to skip */
578 static int nChunk = 0; /* Number of diff chunks seen so far */
579 int nContext; /* Number of lines of context */
580 int showLn; /* Show line numbers */
581 int html; /* Render as HTML */
582 int showDivider = 0; /* True to show the divider between diff blocks */
583
@@ -657,14 +654,14 @@
654 if( !showDivider ){
655 /* Do not show a top divider */
656 showDivider = 1;
657 }else if( html ){
658 blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.');
 
659 }else{
660 blob_appendf(pOut, "%.80c\n", '.');
661 }
662 if( html ) blob_appendf(pOut, "<span id=\"chunk%d\"></span>", nChunk);
663 }else{
664 if( html ) blob_appendf(pOut, "<span class=\"diffln\">");
665 /*
666 * If the patch changes an empty file or results in an empty file,
667 * the block header must use 0,0 as position indicator and not 1,0.
@@ -727,12 +724,11 @@
724 /*
725 ** Status of a single output line
726 */
727 typedef struct SbsLine SbsLine;
728 struct SbsLine {
729 Blob *apCols[5]; /* Array of pointers to output columns */
 
730 int width; /* Maximum width of a column in the output */
731 unsigned char escHtml; /* True to escape html characters */
732 int iStart; /* Write zStart prior to character iStart */
733 const char *zStart; /* A <span> tag */
734 int iEnd; /* Write </span> prior to character iEnd */
@@ -741,125 +737,154 @@
737 int iEnd2; /* Write </span> prior to character iEnd2 */
738 ReCompiled *pRe; /* Only colorize matching lines, if not NULL */
739 };
740
741 /*
742 ** Column indices
743 */
744 #define SBS_LNA 0
745 #define SBS_TXTA 1
746 #define SBS_MKR 2
747 #define SBS_LNB 3
748 #define SBS_TXTB 4
749
750 /*
751 ** Append newlines to all columns.
752 */
753 static void sbsWriteNewlines(SbsLine *p){
754 int i;
755 for( i=p->escHtml ? SBS_LNA : SBS_TXTB; i<=SBS_TXTB; i++ ){
756 blob_append(p->apCols[i], "\n", 1);
757 }
758 }
759
760 /*
761 ** Append n spaces to the column.
762 */
763 static void sbsWriteSpace(SbsLine *p, int n, int col){
764 blob_appendf(p->apCols[col], "%*s", n, "");
765 }
766
767 /*
768 ** Write pLine to the column. If outputting HTML, write the full line.
769 ** Otherwise, only write up to width characters. Translate tabs into
770 ** spaces. Add newlines if col is SBS_TXTB. Translate HTML characters
771 ** if escHtml is true. Pad the rendering out width bytes if col is
772 ** SBS_TXTA and escHtml is false.
773 **
774 ** This comment contains multibyte unicode characters (ü, Æ, ð) in order
775 ** to test the ability of the diff code to handle such characters.
776 */
777 static void sbsWriteText(SbsLine *p, DLine *pLine, int col){
778 Blob *pCol = p->apCols[col];
779 int n = pLine->h & LENGTH_MASK;
780 int i; /* Number of input characters consumed */
 
781 int k; /* Cursor position */
782 int needEndSpan = 0;
783 const char *zIn = pLine->z;
 
784 int w = p->width;
785 int colorize = p->escHtml;
786 if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
787 colorize = 0;
788 }
789 for(i=k=0; (p->escHtml || k<w) && i<n; i++, k++){
790 char c = zIn[i];
791 if( colorize ){
792 if( i==p->iStart ){
793 int x = strlen(p->zStart);
794 blob_append(pCol, p->zStart, x);
 
795 needEndSpan = 1;
796 if( p->iStart2 ){
797 p->iStart = p->iStart2;
798 p->zStart = p->zStart2;
799 p->iStart2 = 0;
800 }
801 }else if( i==p->iEnd ){
802 blob_append(pCol, "</span>", 7);
 
803 needEndSpan = 0;
804 if( p->iEnd2 ){
805 p->iEnd = p->iEnd2;
806 p->iEnd2 = 0;
807 }
808 }
809 }
810 if( c=='\t' ){
811 blob_append(pCol, " ", 1);
812 while( (k&7)!=7 && (p->escHtml || k<w) ){
813 blob_append(pCol, " ", 1);
814 k++;
815 }
816 }else if( c=='\r' || c=='\f' ){
817 blob_append(pCol, " ", 1);
818 }else if( c=='<' && p->escHtml ){
819 blob_append(pCol, "&lt;", 4);
 
820 }else if( c=='&' && p->escHtml ){
821 blob_append(pCol, "&amp;", 5);
 
822 }else if( c=='>' && p->escHtml ){
823 blob_append(pCol, "&gt;", 4);
 
824 }else if( c=='"' && p->escHtml ){
825 blob_append(pCol, "&quot;", 6);
 
826 }else{
827 blob_append(pCol, &zIn[i], 1);
828 if( (c&0xc0)==0x80 ) k--;
829 }
830 }
831 if( needEndSpan ){
832 blob_append(pCol, "</span>", 7);
833 }
834 if( col==SBS_TXTB ){
835 sbsWriteNewlines(p);
836 }else if( !p->escHtml ){
837 sbsWriteSpace(p, w-k, SBS_TXTA);
838 }
839 }
840
841 /*
842 ** Append a column to the final output blob.
843 */
844 static void sbsWriteColumn(Blob *pOut, Blob *pCol, int col){
845 blob_appendf(pOut,
846 "<td><div class=\"diff%scol\">\n"
847 "<pre>\n"
848 "%s"
849 "</pre>\n"
850 "</div></td>\n",
851 col % 3 ? (col == SBS_MKR ? "mkr" : "txt") : "ln",
852 blob_str(pCol)
853 );
854 }
855
856 /*
857 ** Append separator to the column.
858 */
859 static void sbsWriteSep(SbsLine *p, int len, int col){
860 char ch = '.';
861 if( len<1 ){
862 len = 1;
863 ch = ' ';
864 }
865 blob_appendf(p->apCols[col], "<span class=\"diffhr\">%.*c</span>\n", len, ch);
866 }
867
868 /*
869 ** Append the appropriate marker.
870 */
871 static void sbsWriteMarker(SbsLine *p, const char *zTxt, const char *zHtml){
872 blob_append(p->apCols[SBS_MKR], p->escHtml ? zHtml : zTxt, -1);
873 }
874
875 /*
876 ** Append a line number to the column.
877 */
878 static void sbsWriteLineno(SbsLine *p, int ln, int col){
879 if( p->escHtml ){
880 blob_appendf(p->apCols[col], "%d", ln+1);
881 }else{
882 char zLn[7];
883 sqlite3_snprintf(7, zLn, "%5d ", ln+1);
884 blob_appendf(p->apCols[col], "%s ", zLn);
885 }
886 }
887
888 /*
889 ** The two text segments zLeft and zRight are known to be different on
890 ** both ends, but they might have a common segment in the middle. If
@@ -1018,43 +1043,42 @@
1043 }
1044 if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
1045 }
1046 if( nPrefix+nSuffix > nShort ) nPrefix = nShort - nSuffix;
1047
 
1048 /* A single chunk of text inserted on the right */
1049 if( nPrefix+nSuffix==nLeft ){
1050 sbsWriteLineno(p, lnLeft, SBS_LNA);
1051 p->iStart2 = p->iEnd2 = 0;
1052 p->iStart = p->iEnd = -1;
1053 sbsWriteText(p, pLeft, SBS_TXTA);
1054 if( nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){
1055 sbsWriteMarker(p, " ", "");
1056 }else{
1057 sbsWriteMarker(p, " | ", "|");
1058 }
1059 sbsWriteLineno(p, lnRight, SBS_LNB);
1060 p->iStart = nPrefix;
1061 p->iEnd = nRight - nSuffix;
1062 p->zStart = zClassAdd;
1063 sbsWriteText(p, pRight, SBS_TXTB);
1064 return;
1065 }
1066
1067 /* A single chunk of text deleted from the left */
1068 if( nPrefix+nSuffix==nRight ){
1069 /* Text deleted from the left */
1070 sbsWriteLineno(p, lnLeft, SBS_LNA);
1071 p->iStart2 = p->iEnd2 = 0;
1072 p->iStart = nPrefix;
1073 p->iEnd = nLeft - nSuffix;
1074 p->zStart = zClassRm;
1075 sbsWriteText(p, pLeft, SBS_TXTA);
1076 sbsWriteMarker(p, " | ", "|");
1077 sbsWriteLineno(p, lnRight, SBS_LNB);
1078 p->iStart = p->iEnd = -1;
1079 sbsWriteText(p, pRight, SBS_TXTB);
1080 return;
1081 }
1082
1083 /* At this point we know that there is a chunk of text that has
1084 ** changed between the left and the right. Check to see if there
@@ -1065,11 +1089,11 @@
1089 if( p->escHtml
1090 && nLeftDiff >= 6
1091 && nRightDiff >= 6
1092 && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS)
1093 ){
1094 sbsWriteLineno(p, lnLeft, SBS_LNA);
1095 p->iStart = nPrefix;
1096 p->iEnd = nPrefix + aLCS[0];
1097 if( aLCS[2]==0 ){
1098 sbsShiftLeft(p, pLeft->z);
1099 p->zStart = zClassRm;
@@ -1078,13 +1102,13 @@
1102 }
1103 p->iStart2 = nPrefix + aLCS[1];
1104 p->iEnd2 = nLeft - nSuffix;
1105 p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng;
1106 sbsSimplifyLine(p, zLeft+nPrefix);
1107 sbsWriteText(p, pLeft, SBS_TXTA);
1108 sbsWriteMarker(p, " | ", "|");
1109 sbsWriteLineno(p, lnRight, SBS_LNB);
1110 p->iStart = nPrefix;
1111 p->iEnd = nPrefix + aLCS[2];
1112 if( aLCS[0]==0 ){
1113 sbsShiftLeft(p, pRight->z);
1114 p->zStart = zClassAdd;
@@ -1093,25 +1117,25 @@
1117 }
1118 p->iStart2 = nPrefix + aLCS[3];
1119 p->iEnd2 = nRight - nSuffix;
1120 p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng;
1121 sbsSimplifyLine(p, zRight+nPrefix);
1122 sbsWriteText(p, pRight, SBS_TXTB);
1123 return;
1124 }
1125
1126 /* If all else fails, show a single big change between left and right */
1127 sbsWriteLineno(p, lnLeft, SBS_LNA);
1128 p->iStart2 = p->iEnd2 = 0;
1129 p->iStart = nPrefix;
1130 p->iEnd = nLeft - nSuffix;
1131 p->zStart = zClassChng;
1132 sbsWriteText(p, pLeft, SBS_TXTA);
1133 sbsWriteMarker(p, " | ", "|");
1134 sbsWriteLineno(p, lnRight, SBS_LNB);
1135 p->iEnd = nRight - nSuffix;
1136 sbsWriteText(p, pRight, SBS_TXTB);
1137 }
1138
1139 /*
1140 ** Minimum of two values
1141 */
@@ -1357,30 +1381,40 @@
1381 int mxr; /* Maximum value for r */
1382 int na, nb; /* Number of lines shown from A and B */
1383 int i, j; /* Loop counters */
1384 int m, ma, mb;/* Number of lines to output */
1385 int skip; /* Number of lines to skip */
1386 static int nChunk = 0; /* Number of chunks of diff output seen so far */
1387 SbsLine s; /* Output line buffer */
1388 int nContext; /* Lines of context above and below each change */
1389 int showDivider = 0; /* True to show the divider */
1390 Blob aCols[5]; /* Array of column blobs */
1391
1392 memset(&s, 0, sizeof(s));
1393 s.width = diff_width(diffFlags);
 
 
1394 nContext = diff_context_lines(diffFlags);
1395 s.escHtml = (diffFlags & DIFF_HTML)!=0;
1396 if( s.escHtml ){
1397 for(i=SBS_LNA; i<=SBS_TXTB; i++){
1398 blob_zero(&aCols[i]);
1399 s.apCols[i] = &aCols[i];
1400 }
1401 }else{
1402 for(i=SBS_LNA; i<=SBS_TXTB; i++){
1403 s.apCols[i] = pOut;
1404 }
1405 }
1406 s.pRe = pRe;
1407 s.iStart = -1;
1408 s.iStart2 = 0;
1409 s.iEnd = -1;
1410 A = p->aFrom;
1411 B = p->aTo;
1412 R = p->aEdit;
1413 mxr = p->nEdit;
1414 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
1415
1416 for(r=0; r<mxr; r += 3*nr){
1417 /* Figure out how many triples to show in a single block */
1418 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
1419 /* printf("r=%d nr=%d\n", r, nr); */
1420
@@ -1436,35 +1470,39 @@
1470 }
1471
1472 /* Draw the separator between blocks */
1473 if( showDivider ){
1474 if( s.escHtml ){
1475 char zLn[10];
1476 sqlite3_snprintf(sizeof(zLn), zLn, "%d", a+skip+1);
1477 sbsWriteSep(&s, strlen(zLn), SBS_LNA);
1478 sbsWriteSep(&s, s.width, SBS_TXTA);
1479 sbsWriteSep(&s, 0, SBS_MKR);
1480 sqlite3_snprintf(sizeof(zLn), zLn, "%d", b+skip+1);
1481 sbsWriteSep(&s, strlen(zLn), SBS_LNB);
1482 sbsWriteSep(&s, s.width, SBS_TXTB);
1483 }else{
1484 blob_appendf(pOut, "%.*c\n", s.width*2+16, '.');
1485 }
1486 }
1487 showDivider = 1;
1488 nChunk++;
1489 if( s.escHtml ){
1490 blob_appendf(s.apCols[SBS_LNA], "<span id=\"chunk%d\"></span>", nChunk);
1491 }
1492
1493 /* Show the initial common area */
1494 a += skip;
1495 b += skip;
1496 m = R[r] - skip;
1497 for(j=0; j<m; j++){
1498 sbsWriteLineno(&s, a+j, SBS_LNA);
 
1499 s.iStart = s.iEnd = -1;
1500 sbsWriteText(&s, &A[a+j], SBS_TXTA);
1501 sbsWriteMarker(&s, " ", "");
1502 sbsWriteLineno(&s, b+j, SBS_LNB);
1503 sbsWriteText(&s, &B[b+j], SBS_TXTB);
 
1504 }
1505 a += m;
1506 b += m;
1507
1508 /* Show the differences */
@@ -1485,87 +1523,71 @@
1523
1524 alignment = sbsAlignment(&A[a], ma, &B[b], mb);
1525 for(j=0; ma+mb>0; j++){
1526 if( alignment[j]==1 ){
1527 /* Delete one line from the left */
1528 sbsWriteLineno(&s, a, SBS_LNA);
 
1529 s.iStart = 0;
1530 s.zStart = "<span class=\"diffrm\">";
1531 s.iEnd = LENGTH(&A[a]);
1532 sbsWriteText(&s, &A[a], SBS_TXTA);
1533 sbsWriteMarker(&s, " <", "&lt;");
1534 sbsWriteNewlines(&s);
 
 
 
 
1535 assert( ma>0 );
1536 ma--;
1537 a++;
1538 }else if( alignment[j]==3 ){
1539 /* The left line is changed into the right line */
 
1540 sbsWriteLineChange(&s, &A[a], a, &B[b], b);
 
1541 assert( ma>0 && mb>0 );
1542 ma--;
1543 mb--;
1544 a++;
1545 b++;
1546 }else if( alignment[j]==2 ){
1547 /* Insert one line on the right */
1548 if( !s.escHtml ){
1549 sbsWriteSpace(&s, s.width + 7, SBS_TXTA);
1550 }
1551 sbsWriteMarker(&s, " > ", "&gt;");
1552 sbsWriteLineno(&s, b, SBS_LNB);
 
 
 
1553 s.iStart = 0;
1554 s.zStart = "<span class=\"diffadd\">";
1555 s.iEnd = LENGTH(&B[b]);
1556 sbsWriteText(&s, &B[b], SBS_TXTB);
 
1557 assert( mb>0 );
1558 mb--;
1559 b++;
1560 }else{
1561 /* Delete from the left and insert on the right */
1562 sbsWriteLineno(&s, a, SBS_LNA);
 
1563 s.iStart = 0;
1564 s.zStart = "<span class=\"diffrm\">";
1565 s.iEnd = LENGTH(&A[a]);
1566 sbsWriteText(&s, &A[a], SBS_TXTA);
1567 sbsWriteMarker(&s, " | ", "|");
1568 sbsWriteLineno(&s, b, SBS_LNB);
1569 s.iStart = 0;
1570 s.zStart = "<span class=\"diffadd\">";
1571 s.iEnd = LENGTH(&B[b]);
1572 sbsWriteText(&s, &B[b], SBS_TXTB);
 
1573 ma--;
1574 mb--;
1575 a++;
1576 b++;
1577 }
 
1578 }
1579 fossil_free(alignment);
1580 if( i<nr-1 ){
1581 m = R[r+i*3+3];
1582 for(j=0; j<m; j++){
1583 sbsWriteLineno(&s, a+j, SBS_LNA);
 
1584 s.iStart = s.iEnd = -1;
1585 sbsWriteText(&s, &A[a+j], SBS_TXTA);
1586 sbsWriteMarker(&s, " ", "");
1587 sbsWriteLineno(&s, b+j, SBS_LNB);
1588 sbsWriteText(&s, &B[b+j], SBS_TXTB);
 
1589 }
1590 b += m;
1591 a += m;
1592 }
1593 }
@@ -1573,21 +1595,27 @@
1595 /* Show the final common area */
1596 assert( nr==i );
1597 m = R[r+nr*3];
1598 if( m>nContext ) m = nContext;
1599 for(j=0; j<m; j++){
1600 sbsWriteLineno(&s, a+j, SBS_LNA);
 
1601 s.iStart = s.iEnd = -1;
1602 sbsWriteText(&s, &A[a+j], SBS_TXTA);
1603 sbsWriteMarker(&s, " ", "");
1604 sbsWriteLineno(&s, b+j, SBS_LNB);
1605 sbsWriteText(&s, &B[b+j], SBS_TXTB);
 
1606 }
1607 }
1608
1609 if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){
1610 blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1);
1611 for(i=SBS_LNA; i<=SBS_TXTB; i++){
1612 sbsWriteColumn(pOut, s.apCols[i], i);
1613 blob_reset(s.apCols[i]);
1614 }
1615 blob_append(pOut, "</tr></table>\n", -1);
1616 }
1617 }
1618
1619 /*
1620 ** Compute the optimal longest common subsequence (LCS) using an
1621 ** exhaustive search. This version of the LCS is only used for
@@ -1990,10 +2018,21 @@
2018 int diff_width(u64 diffFlags){
2019 int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
2020 if( w==0 ) w = 80;
2021 return w;
2022 }
2023
2024 /*
2025 ** Append the error message to pOut.
2026 */
2027 void diff_errmsg(Blob *pOut, const char *msg, int diffFlags){
2028 if( diffFlags & DIFF_HTML ){
2029 blob_appendf(pOut, "<p class=\"generalError\">%s</p>", msg);
2030 }else{
2031 blob_append(pOut, msg, -1);
2032 }
2033 }
2034
2035 /*
2036 ** Generate a report of the differences between files pA and pB.
2037 ** If pOut is not NULL then a unified diff is appended there. It
2038 ** is assumed that pOut has already been initialized. If pOut is
@@ -2032,11 +2071,11 @@
2071 &c.nTo, ignoreEolWs);
2072 if( c.aFrom==0 || c.aTo==0 ){
2073 fossil_free(c.aFrom);
2074 fossil_free(c.aTo);
2075 if( pOut ){
2076 diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags);
2077 }
2078 return 0;
2079 }
2080
2081 /* Compute the difference */
@@ -2048,15 +2087,11 @@
2087 for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
2088 if( n>10000 ){
2089 fossil_free(c.aFrom);
2090 fossil_free(c.aTo);
2091 fossil_free(c.aEdit);
2092 diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags);
 
 
 
 
2093 return 0;
2094 }
2095 }
2096 if( (diffFlags & DIFF_NOOPT)==0 ){
2097 diff_optimize(&c);
@@ -2159,10 +2194,11 @@
2194 if( find_option("tk",0,0)!=0 ){
2195 diff_tk("test-diff", 2);
2196 return;
2197 }
2198 find_option("i",0,0);
2199 find_option("v",0,0);
2200 zRe = find_option("regexp","e",1);
2201 if( zRe ){
2202 const char *zErr = re_compile(&pRe, zRe, 0);
2203 if( zErr ) fossil_fatal("regex error: %s", zErr);
2204 }
2205
+236 -39
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -597,52 +597,249 @@
597597
return db_get(zName, zDefault);
598598
}
599599
600600
/* A Tcl/Tk script used to render diff output.
601601
*/
602
-static const char zDiffScript[] =
602
+static const char zDiffScript[] =
603603
@ package require Tk
604
-@ wm withdraw .
605
-@ wm title . {Fossil Diff}
606
-@ wm iconname . {Fossil Diff}
607
-@ bind . <q> exit
608
-@ set body {}
609
-@ set mx 80 ;# Length of the longest line of text
610
-@ set nLine 0 ;# Number of lines of text
611
-@ text .t -width 180 -yscroll {.sb set}
612
-@ if {$tcl_platform(platform)=="windows"} {.t config -font {courier 9}}
613
-@ .t tag config ln -foreground gray
614
-@ .t tag config chng -background {#d0d0ff}
615
-@ .t tag config add -background {#c0ffc0}
616
-@ .t tag config rm -background {#ffc0c0}
604
+@
605
+@ array set CFG {
606
+@ TITLE {Fossil Diff}
607
+@ LN_COL_BG #dddddd
608
+@ LN_COL_FG #444444
609
+@ TXT_COL_BG #ffffff
610
+@ TXT_COL_FG #000000
611
+@ MKR_COL_BG #444444
612
+@ MKR_COL_FG #dddddd
613
+@ CHNG_BG #d0d0ff
614
+@ ADD_BG #c0ffc0
615
+@ RM_BG #ffc0c0
616
+@ HR_FG #888888
617
+@ HR_PAD_TOP 4
618
+@ HR_PAD_BTM 8
619
+@ FONTS {{DejaVu Sans Mono} Consolas Monaco fixed}
620
+@ FONT_SIZE 9
621
+@ PADX 5
622
+@ WIDTH 80
623
+@ HEIGHT 45
624
+@ }
625
+@
626
+@ if {![namespace exists ttk]} {
627
+@ interp alias {} ::ttk::scrollbar {} ::scrollbar
628
+@ interp alias {} ::ttk::menubutton {} ::menubutton
629
+@ }
630
+@
617631
@ proc dehtml {x} {
632
+@ set x [regsub -all {<[^>]*>} $x {}]
618633
@ return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
619634
@ }
620
-@ # puts $cmd
621
-@ set in [open $cmd r]
622
-@ while {![eof $in]} {
623
-@ set line [gets $in]
624
-@ if {[regexp {^<a name="chunk.*"></a>} $line]} continue
625
-@ if {[regexp {^===} $line]} {
626
-@ set n [string length $line]
627
-@ if {$n>$mx} {set mx $n}
628
-@ }
629
-@ incr nLine
630
-@ while {[regexp {^(.*?)<span class="diff([a-z]+)">(.*?)</span>(.*)$} $line \
631
-@ all pre class mid tail]} {
632
-@ .t insert end [dehtml $pre] {} [dehtml $mid] $class
633
-@ set line $tail
634
-@ }
635
-@ .t insert end [dehtml $line]\n {}
636
-@ }
637
-@ close $in
638
-@ if {$mx>250} {set mx 250} ;# Limit window width to 200 characters
639
-@ if {$nLine>55} {set nLine 55} ;# Limit window height to 55 lines
640
-@ .t config -height $nLine -width $mx
641
-@ pack .t -side left -fill both -expand 1
642
-@ scrollbar .sb -command {.t yview} -orient vertical
643
-@ pack .sb -side left -fill y
635
+@
636
+@ proc cols {} {
637
+@ return [list .lnA .txtA .mkr .lnB .txtB]
638
+@ }
639
+@
640
+@ proc colType {c} {
641
+@ regexp {[a-z]+} $c type
642
+@ return $type
643
+@ }
644
+@
645
+@ proc readDiffs {cmd} {
646
+@ global gDiffs
647
+@ set in [open $cmd r]
648
+@ set idx -1
649
+@ while {[gets $in line] != -1} {
650
+@ if {![regexp {^=+\s+(.*?)\s+=+$} $line all fn]} {
651
+@ continue
652
+@ }
653
+@
654
+@ if {[string compare -length 6 [gets $in] "<table"]} {
655
+@ continue
656
+@ }
657
+@
658
+@ incr idx
659
+@ .files.menu add radiobutton -variable gIdx -value $idx -label $fn \
660
+@ -command "viewDiff $idx"
661
+@ array set widths {txt 0 ln 0 mkr 0}
662
+@
663
+@ foreach c [cols] {
664
+@ while {[gets $in] ne "<pre>"} continue
665
+@ set type [colType $c]
666
+@ set str {}
667
+@ while {[set line [gets $in]] ne "</pre>"} {
668
+@ set len [string length [dehtml $line]]
669
+@ if {$len > $widths($type)} {
670
+@ set widths($type) $len
671
+@ }
672
+@ append str $line\n
673
+@ }
674
+@
675
+@ set str [string range $str 0 end-1]
676
+@ set colData {}
677
+@ set re {<span class="diff([a-z]+)">([^<]*)</span>}
678
+@ # Use \r as separator since it can't appear in the diff output (it gets
679
+@ # converted to a space).
680
+@ set str [regsub -all $re $str "\r\\1\r\\2\r"]
681
+@ foreach {pre class mid} [split $str \r] {
682
+@ if {$class ne ""} {
683
+@ lappend colData [dehtml $pre] - [dehtml $mid] [list $class -]
684
+@ } else {
685
+@ lappend colData [dehtml $pre] -
686
+@ }
687
+@ }
688
+@ set gDiffs($idx,$c) $colData
689
+@ }
690
+@
691
+@ foreach {type width} [array get widths] {
692
+@ set gDiffs($idx,$type-width) $width
693
+@ }
694
+@ }
695
+@ close $in
696
+@ }
697
+@
698
+@ proc viewDiff {idx} {
699
+@ global gDiffs
700
+@ .files config -text [.files.menu entrycget $idx -label]
701
+@
702
+@ foreach c [cols] {
703
+@ $c config -state normal
704
+@ $c delete 1.0 end
705
+@ foreach {content tag} $gDiffs($idx,$c) {
706
+@ $c insert end $content $tag
707
+@ }
708
+@ }
709
+@
710
+@ foreach c {.lnA .lnB .mkr} {
711
+@ $c config -width $gDiffs($idx,[colType $c]-width)
712
+@ }
713
+@
714
+@ # Add whitespace to equalize line lengths.
715
+@ regexp {\d+} [.txtA index {end -1c}] numLines
716
+@ set width $gDiffs($idx,txt-width)
717
+@ foreach c {.txtA .txtB} {
718
+@ for {set ln 1} {$ln <= $numLines} {incr ln} {
719
+@ regexp {\d+$} [$c index $ln.end] len
720
+@ $c insert $ln.end [string repeat " " [expr {$width-$len}]] ws
721
+@ }
722
+@ }
723
+@
724
+@ foreach c [cols] {
725
+@ $c config -state disabled
726
+@ }
727
+@ }
728
+@
729
+@ proc cycleDiffs {{inc 1}} {
730
+@ global gIdx
731
+@ .files.menu invoke [expr {($gIdx+$inc) % ([.files.menu index last]+1)}]
732
+@ }
733
+@
734
+@ proc scrollSync {axis sbs first last} {
735
+@ foreach c [cols] {
736
+@ $c ${axis}view moveto $first
737
+@ }
738
+@ foreach sb $sbs {
739
+@ if {$first <= 0 && $last >= 1} {
740
+@ grid remove $sb
741
+@ } else {
742
+@ grid $sb
743
+@ $sb set $first $last
744
+@ }
745
+@ }
746
+@ }
747
+@
748
+@ proc copyText {c} {
749
+@ set txt ""
750
+@ # Copy selection without excess trailing whitespace
751
+@ $c tag config ws -elide 1
752
+@ catch {
753
+@ $c tag add sel sel.first sel.last
754
+@ set txt [selection get]
755
+@ }
756
+@ $c tag config ws -elide 0
757
+@ clipboard clear
758
+@ clipboard append $txt
759
+@ }
760
+@
761
+@ wm withdraw .
762
+@ wm title . $CFG(TITLE)
763
+@ wm iconname . $CFG(TITLE)
764
+@ bind . <q> exit
765
+@ bind . <Tab> {cycleDiffs; break}
766
+@ bind . <<PrevWindow>> {cycleDiffs -1; break}
767
+@ bind . <Return> {
768
+@ event generate .files <1>
769
+@ event generate .files <ButtonRelease-1>
770
+@ break
771
+@ }
772
+@ foreach {key axis args} {
773
+@ Up y {scroll -5 units}
774
+@ Down y {scroll 5 units}
775
+@ Left x {scroll -5 units}
776
+@ Right x {scroll 5 units}
777
+@ Prior y {scroll -1 page}
778
+@ Next y {scroll 1 page}
779
+@ Home y {moveto 0}
780
+@ End y {moveto 1}
781
+@ } {
782
+@ bind . <$key> ".txtA ${axis}view $args; break"
783
+@ bind . <Shift-$key> continue
784
+@ }
785
+@
786
+@ ::ttk::menubutton .files -menu .files.menu
787
+@ menu .files.menu -tearoff 0
788
+@ readDiffs $cmd
789
+@ if {[.files.menu index 0] eq "none"} {
790
+@ tk_messageBox -type ok -title $CFG(TITLE) -message "No changes"
791
+@ exit
792
+@ }
793
+@
794
+@ foreach side {A B} {
795
+@ set ln .ln$side
796
+@ text $ln
797
+@ $ln tag config - -justify right
798
+@
799
+@ set txt .txt$side
800
+@ text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
801
+@ -xscroll {scrollSync x {.sbxA .sbxB}}
802
+@ foreach tag {add rm chng} {
803
+@ $txt tag config $tag -background $CFG([string toupper $tag]_BG)
804
+@ $txt tag lower $tag
805
+@ }
806
+@ bind $txt <<Copy>> {copyText %W; break}
807
+@ bind $txt <<Cut>> {copyText %W; break}
808
+@ }
809
+@ text .mkr
810
+@
811
+@ font create mono -family courier -size $CFG(FONT_SIZE)
812
+@ foreach font $CFG(FONTS) {
813
+@ if {[lsearch -exact [font families] $font] != -1} {
814
+@ font config mono -family $font
815
+@ break
816
+@ }
817
+@ }
818
+@ foreach c [cols] {
819
+@ set keyPrefix [string toupper [colType $c]]_COL_
820
+@ $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
821
+@ -font mono -padx $CFG(PADX) -insertontime 0 -yscroll {scrollSync y .sby}
822
+@ $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
823
+@ -foreground $CFG(HR_FG)
824
+@ bindtags $c ". $c Text all"
825
+@ bind $c <1> {focus %W}
826
+@ }
827
+@
828
+@ ::ttk::scrollbar .sby -command {.txtA yview} -orient vertical
829
+@ ::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal
830
+@ ::ttk::scrollbar .sbxB -command {.txtA xview} -orient horizontal
831
+@
832
+@ grid rowconfigure . 1 -weight 1
833
+@ grid columnconfigure . 1 -weight 1
834
+@ grid columnconfigure . 4 -weight 1
835
+@ grid .files -columnspan 6
836
+@ eval grid [cols] .sby -sticky nsew
837
+@ grid .sbxA -row 2 -column 0 -columnspan 2 -sticky ew
838
+@ grid .sbxB -row 2 -column 3 -columnspan 2 -sticky ew
839
+@
840
+@ .files.menu invoke 0
644841
@ wm deiconify .
645842
;
646843
647844
/*
648845
** Show diff output in a Tcl/Tk window, in response to the --tk option
@@ -657,11 +854,11 @@
657854
int i;
658855
Blob script;
659856
char *zTempFile;
660857
char *zCmd;
661858
blob_zero(&script);
662
- blob_appendf(&script, "set cmd {| \"%/\" %s --html -y -i",
859
+ blob_appendf(&script, "set cmd {| \"%/\" %s --html -y -i -v",
663860
g.nameOfExe, zSubCmd);
664861
for(i=firstArg; i<g.argc; i++){
665862
const char *z = g.argv[i];
666863
if( z[0]=='-' ){
667864
if( strglob("*-html",z) ) continue;
668865
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -597,52 +597,249 @@
597 return db_get(zName, zDefault);
598 }
599
600 /* A Tcl/Tk script used to render diff output.
601 */
602 static const char zDiffScript[] =
603 @ package require Tk
604 @ wm withdraw .
605 @ wm title . {Fossil Diff}
606 @ wm iconname . {Fossil Diff}
607 @ bind . <q> exit
608 @ set body {}
609 @ set mx 80 ;# Length of the longest line of text
610 @ set nLine 0 ;# Number of lines of text
611 @ text .t -width 180 -yscroll {.sb set}
612 @ if {$tcl_platform(platform)=="windows"} {.t config -font {courier 9}}
613 @ .t tag config ln -foreground gray
614 @ .t tag config chng -background {#d0d0ff}
615 @ .t tag config add -background {#c0ffc0}
616 @ .t tag config rm -background {#ffc0c0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617 @ proc dehtml {x} {
 
618 @ return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
619 @ }
620 @ # puts $cmd
621 @ set in [open $cmd r]
622 @ while {![eof $in]} {
623 @ set line [gets $in]
624 @ if {[regexp {^<a name="chunk.*"></a>} $line]} continue
625 @ if {[regexp {^===} $line]} {
626 @ set n [string length $line]
627 @ if {$n>$mx} {set mx $n}
628 @ }
629 @ incr nLine
630 @ while {[regexp {^(.*?)<span class="diff([a-z]+)">(.*?)</span>(.*)$} $line \
631 @ all pre class mid tail]} {
632 @ .t insert end [dehtml $pre] {} [dehtml $mid] $class
633 @ set line $tail
634 @ }
635 @ .t insert end [dehtml $line]\n {}
636 @ }
637 @ close $in
638 @ if {$mx>250} {set mx 250} ;# Limit window width to 200 characters
639 @ if {$nLine>55} {set nLine 55} ;# Limit window height to 55 lines
640 @ .t config -height $nLine -width $mx
641 @ pack .t -side left -fill both -expand 1
642 @ scrollbar .sb -command {.t yview} -orient vertical
643 @ pack .sb -side left -fill y
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644 @ wm deiconify .
645 ;
646
647 /*
648 ** Show diff output in a Tcl/Tk window, in response to the --tk option
@@ -657,11 +854,11 @@
657 int i;
658 Blob script;
659 char *zTempFile;
660 char *zCmd;
661 blob_zero(&script);
662 blob_appendf(&script, "set cmd {| \"%/\" %s --html -y -i",
663 g.nameOfExe, zSubCmd);
664 for(i=firstArg; i<g.argc; i++){
665 const char *z = g.argv[i];
666 if( z[0]=='-' ){
667 if( strglob("*-html",z) ) continue;
668
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -597,52 +597,249 @@
597 return db_get(zName, zDefault);
598 }
599
600 /* A Tcl/Tk script used to render diff output.
601 */
602 static const char zDiffScript[] =
603 @ package require Tk
604 @
605 @ array set CFG {
606 @ TITLE {Fossil Diff}
607 @ LN_COL_BG #dddddd
608 @ LN_COL_FG #444444
609 @ TXT_COL_BG #ffffff
610 @ TXT_COL_FG #000000
611 @ MKR_COL_BG #444444
612 @ MKR_COL_FG #dddddd
613 @ CHNG_BG #d0d0ff
614 @ ADD_BG #c0ffc0
615 @ RM_BG #ffc0c0
616 @ HR_FG #888888
617 @ HR_PAD_TOP 4
618 @ HR_PAD_BTM 8
619 @ FONTS {{DejaVu Sans Mono} Consolas Monaco fixed}
620 @ FONT_SIZE 9
621 @ PADX 5
622 @ WIDTH 80
623 @ HEIGHT 45
624 @ }
625 @
626 @ if {![namespace exists ttk]} {
627 @ interp alias {} ::ttk::scrollbar {} ::scrollbar
628 @ interp alias {} ::ttk::menubutton {} ::menubutton
629 @ }
630 @
631 @ proc dehtml {x} {
632 @ set x [regsub -all {<[^>]*>} $x {}]
633 @ return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
634 @ }
635 @
636 @ proc cols {} {
637 @ return [list .lnA .txtA .mkr .lnB .txtB]
638 @ }
639 @
640 @ proc colType {c} {
641 @ regexp {[a-z]+} $c type
642 @ return $type
643 @ }
644 @
645 @ proc readDiffs {cmd} {
646 @ global gDiffs
647 @ set in [open $cmd r]
648 @ set idx -1
649 @ while {[gets $in line] != -1} {
650 @ if {![regexp {^=+\s+(.*?)\s+=+$} $line all fn]} {
651 @ continue
652 @ }
653 @
654 @ if {[string compare -length 6 [gets $in] "<table"]} {
655 @ continue
656 @ }
657 @
658 @ incr idx
659 @ .files.menu add radiobutton -variable gIdx -value $idx -label $fn \
660 @ -command "viewDiff $idx"
661 @ array set widths {txt 0 ln 0 mkr 0}
662 @
663 @ foreach c [cols] {
664 @ while {[gets $in] ne "<pre>"} continue
665 @ set type [colType $c]
666 @ set str {}
667 @ while {[set line [gets $in]] ne "</pre>"} {
668 @ set len [string length [dehtml $line]]
669 @ if {$len > $widths($type)} {
670 @ set widths($type) $len
671 @ }
672 @ append str $line\n
673 @ }
674 @
675 @ set str [string range $str 0 end-1]
676 @ set colData {}
677 @ set re {<span class="diff([a-z]+)">([^<]*)</span>}
678 @ # Use \r as separator since it can't appear in the diff output (it gets
679 @ # converted to a space).
680 @ set str [regsub -all $re $str "\r\\1\r\\2\r"]
681 @ foreach {pre class mid} [split $str \r] {
682 @ if {$class ne ""} {
683 @ lappend colData [dehtml $pre] - [dehtml $mid] [list $class -]
684 @ } else {
685 @ lappend colData [dehtml $pre] -
686 @ }
687 @ }
688 @ set gDiffs($idx,$c) $colData
689 @ }
690 @
691 @ foreach {type width} [array get widths] {
692 @ set gDiffs($idx,$type-width) $width
693 @ }
694 @ }
695 @ close $in
696 @ }
697 @
698 @ proc viewDiff {idx} {
699 @ global gDiffs
700 @ .files config -text [.files.menu entrycget $idx -label]
701 @
702 @ foreach c [cols] {
703 @ $c config -state normal
704 @ $c delete 1.0 end
705 @ foreach {content tag} $gDiffs($idx,$c) {
706 @ $c insert end $content $tag
707 @ }
708 @ }
709 @
710 @ foreach c {.lnA .lnB .mkr} {
711 @ $c config -width $gDiffs($idx,[colType $c]-width)
712 @ }
713 @
714 @ # Add whitespace to equalize line lengths.
715 @ regexp {\d+} [.txtA index {end -1c}] numLines
716 @ set width $gDiffs($idx,txt-width)
717 @ foreach c {.txtA .txtB} {
718 @ for {set ln 1} {$ln <= $numLines} {incr ln} {
719 @ regexp {\d+$} [$c index $ln.end] len
720 @ $c insert $ln.end [string repeat " " [expr {$width-$len}]] ws
721 @ }
722 @ }
723 @
724 @ foreach c [cols] {
725 @ $c config -state disabled
726 @ }
727 @ }
728 @
729 @ proc cycleDiffs {{inc 1}} {
730 @ global gIdx
731 @ .files.menu invoke [expr {($gIdx+$inc) % ([.files.menu index last]+1)}]
732 @ }
733 @
734 @ proc scrollSync {axis sbs first last} {
735 @ foreach c [cols] {
736 @ $c ${axis}view moveto $first
737 @ }
738 @ foreach sb $sbs {
739 @ if {$first <= 0 && $last >= 1} {
740 @ grid remove $sb
741 @ } else {
742 @ grid $sb
743 @ $sb set $first $last
744 @ }
745 @ }
746 @ }
747 @
748 @ proc copyText {c} {
749 @ set txt ""
750 @ # Copy selection without excess trailing whitespace
751 @ $c tag config ws -elide 1
752 @ catch {
753 @ $c tag add sel sel.first sel.last
754 @ set txt [selection get]
755 @ }
756 @ $c tag config ws -elide 0
757 @ clipboard clear
758 @ clipboard append $txt
759 @ }
760 @
761 @ wm withdraw .
762 @ wm title . $CFG(TITLE)
763 @ wm iconname . $CFG(TITLE)
764 @ bind . <q> exit
765 @ bind . <Tab> {cycleDiffs; break}
766 @ bind . <<PrevWindow>> {cycleDiffs -1; break}
767 @ bind . <Return> {
768 @ event generate .files <1>
769 @ event generate .files <ButtonRelease-1>
770 @ break
771 @ }
772 @ foreach {key axis args} {
773 @ Up y {scroll -5 units}
774 @ Down y {scroll 5 units}
775 @ Left x {scroll -5 units}
776 @ Right x {scroll 5 units}
777 @ Prior y {scroll -1 page}
778 @ Next y {scroll 1 page}
779 @ Home y {moveto 0}
780 @ End y {moveto 1}
781 @ } {
782 @ bind . <$key> ".txtA ${axis}view $args; break"
783 @ bind . <Shift-$key> continue
784 @ }
785 @
786 @ ::ttk::menubutton .files -menu .files.menu
787 @ menu .files.menu -tearoff 0
788 @ readDiffs $cmd
789 @ if {[.files.menu index 0] eq "none"} {
790 @ tk_messageBox -type ok -title $CFG(TITLE) -message "No changes"
791 @ exit
792 @ }
793 @
794 @ foreach side {A B} {
795 @ set ln .ln$side
796 @ text $ln
797 @ $ln tag config - -justify right
798 @
799 @ set txt .txt$side
800 @ text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
801 @ -xscroll {scrollSync x {.sbxA .sbxB}}
802 @ foreach tag {add rm chng} {
803 @ $txt tag config $tag -background $CFG([string toupper $tag]_BG)
804 @ $txt tag lower $tag
805 @ }
806 @ bind $txt <<Copy>> {copyText %W; break}
807 @ bind $txt <<Cut>> {copyText %W; break}
808 @ }
809 @ text .mkr
810 @
811 @ font create mono -family courier -size $CFG(FONT_SIZE)
812 @ foreach font $CFG(FONTS) {
813 @ if {[lsearch -exact [font families] $font] != -1} {
814 @ font config mono -family $font
815 @ break
816 @ }
817 @ }
818 @ foreach c [cols] {
819 @ set keyPrefix [string toupper [colType $c]]_COL_
820 @ $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
821 @ -font mono -padx $CFG(PADX) -insertontime 0 -yscroll {scrollSync y .sby}
822 @ $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
823 @ -foreground $CFG(HR_FG)
824 @ bindtags $c ". $c Text all"
825 @ bind $c <1> {focus %W}
826 @ }
827 @
828 @ ::ttk::scrollbar .sby -command {.txtA yview} -orient vertical
829 @ ::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal
830 @ ::ttk::scrollbar .sbxB -command {.txtA xview} -orient horizontal
831 @
832 @ grid rowconfigure . 1 -weight 1
833 @ grid columnconfigure . 1 -weight 1
834 @ grid columnconfigure . 4 -weight 1
835 @ grid .files -columnspan 6
836 @ eval grid [cols] .sby -sticky nsew
837 @ grid .sbxA -row 2 -column 0 -columnspan 2 -sticky ew
838 @ grid .sbxB -row 2 -column 3 -columnspan 2 -sticky ew
839 @
840 @ .files.menu invoke 0
841 @ wm deiconify .
842 ;
843
844 /*
845 ** Show diff output in a Tcl/Tk window, in response to the --tk option
@@ -657,11 +854,11 @@
854 int i;
855 Blob script;
856 char *zTempFile;
857 char *zCmd;
858 blob_zero(&script);
859 blob_appendf(&script, "set cmd {| \"%/\" %s --html -y -i -v",
860 g.nameOfExe, zSubCmd);
861 for(i=firstArg; i<g.argc; i++){
862 const char *z = g.argv[i];
863 if( z[0]=='-' ){
864 if( strglob("*-html",z) ) continue;
865
+85 -70
--- src/info.c
+++ src/info.c
@@ -316,25 +316,22 @@
316316
blob_zero(&to);
317317
}
318318
blob_zero(&out);
319319
if( diffFlags & DIFF_SIDEBYSIDE ){
320320
text_diff(&from, &to, &out, pRe, diffFlags | DIFF_HTML | DIFF_NOTTOOBIG);
321
- @ <div class="sbsdiff">
322321
@ %s(blob_str(&out))
323
- @ </div>
324322
}else{
325323
text_diff(&from, &to, &out, pRe,
326324
diffFlags | DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG);
327
- @ <div class="udiff">
325
+ @ <pre class="udiff">
328326
@ %s(blob_str(&out))
329
- @ </div>
327
+ @ </pre>
330328
}
331329
blob_reset(&from);
332330
blob_reset(&to);
333331
blob_reset(&out);
334332
}
335
-
336333
337334
/*
338335
** Write a line of web-page output that shows changes that have occurred
339336
** to a file between two check-ins.
340337
*/
@@ -359,13 +356,11 @@
359356
@ for %h(zName)</p>
360357
}else{
361358
@ <p>Changes to %h(zName)</p>
362359
}
363360
if( diffFlags ){
364
- @ <pre style="white-space:pre;">
365361
append_diff(zOld, zNew, diffFlags, pRe);
366
- @ </pre>
367362
}
368363
}else{
369364
if( zOld && zNew ){
370365
if( fossil_strcmp(zOld, zNew)!=0 ){
371366
@ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
@@ -385,20 +380,50 @@
385380
}else{
386381
@ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
387382
@ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
388383
}
389384
if( diffFlags ){
390
- @ <pre style="white-space:pre;">
391385
append_diff(zOld, zNew, diffFlags, pRe);
392
- @ </pre>
393386
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
394387
@ &nbsp;&nbsp;
395388
@ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a>
396389
}
397
- @ </p>
398390
}
399391
}
392
+
393
+/*
394
+** Generate javascript to enhance HTML diffs.
395
+*/
396
+void append_diff_javascript(int sideBySide){
397
+ if( !sideBySide ) return;
398
+ @ <script>(function(){
399
+ @ var SCROLL_LEN = 25;
400
+ @ function initSbsDiff(diff){
401
+ @ var txtCols = diff.querySelectorAll('.difftxtcol');
402
+ @ var width = Math.max(txtCols[0].scrollWidth, txtCols[1].scrollWidth);
403
+ @ for(var i=0; i<2; i++){
404
+ @ txtCols[i].children[0].style.width = width + 'px';
405
+ @ txtCols[i].onscroll = function(e){
406
+ @ txtCols[0].scrollLeft = txtCols[1].scrollLeft = this.scrollLeft;
407
+ @ };
408
+ @ }
409
+ @ diff.tabIndex = 0;
410
+ @ diff.onkeydown = function(e){
411
+ @ e = e || event;
412
+ @ var len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
413
+ @ if( !len ) return;
414
+ @ txtCols[0].scrollLeft += len;
415
+ @ return false;
416
+ @ };
417
+ @ }
418
+ @
419
+ @ var diffs = document.querySelectorAll('.sbsdiffcols');
420
+ @ for(var i=0; i<diffs.length; i++){
421
+ @ initSbsDiff(diffs[i]);
422
+ @ }
423
+ @ }())</script>
424
+}
400425
401426
/*
402427
** Construct an appropriate diffFlag for text_diff() based on query
403428
** parameters and the to boolean arguments.
404429
*/
@@ -675,10 +700,11 @@
675700
const char *zOldName = db_column_text(&q, 4);
676701
append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
677702
}
678703
db_finalize(&q);
679704
}
705
+ append_diff_javascript(sideBySide);
680706
style_footer();
681707
}
682708
683709
/*
684710
** WEBPAGE: winfo
@@ -988,11 +1014,11 @@
9881014
pFileTo = manifest_file_next(pTo, 0);
9891015
}
9901016
}
9911017
manifest_destroy(pFrom);
9921018
manifest_destroy(pTo);
993
-
1019
+ append_diff_javascript(sideBySide);
9941020
style_footer();
9951021
}
9961022
9971023
#if INTERFACE
9981024
/*
@@ -1244,85 +1270,74 @@
12441270
*/
12451271
void diff_page(void){
12461272
int v1, v2;
12471273
int isPatch;
12481274
int sideBySide;
1249
- Blob c1, c2, diff, *pOut;
12501275
char *zV1;
12511276
char *zV2;
12521277
const char *zRe;
12531278
ReCompiled *pRe = 0;
12541279
u64 diffFlags;
1255
- const char *zStyle = "sbsdiff";
12561280
12571281
login_check_credentials();
12581282
if( !g.perm.Read ){ login_needed(); return; }
12591283
v1 = name_to_rid_www("v1");
12601284
v2 = name_to_rid_www("v2");
12611285
if( v1==0 || v2==0 ) fossil_redirect_home();
1262
- sideBySide = !is_false(PD("sbs","1"));
1263
- zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
1264
- zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
1286
+ zRe = P("regex");
1287
+ if( zRe ) re_compile(&pRe, zRe, 0);
12651288
isPatch = P("patch")!=0;
12661289
if( isPatch ){
1290
+ Blob c1, c2, *pOut;
12671291
pOut = cgi_output_blob();
12681292
cgi_set_content_type("text/plain");
12691293
diffFlags = 4;
1270
- }else{
1271
- blob_zero(&diff);
1272
- pOut = &diff;
1273
- diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;
1274
- if( sideBySide ){
1275
- zStyle = "sbsdiff";
1276
- }else{
1277
- diffFlags |= DIFF_LINENO;
1278
- zStyle = "udiff";
1279
- }
1280
- }
1281
- zRe = P("regex");
1282
- if( zRe ) re_compile(&pRe, zRe, 0);
1283
- content_get(v1, &c1);
1284
- content_get(v2, &c2);
1285
- text_diff(&c1, &c2, pOut, pRe, diffFlags);
1286
- blob_reset(&c1);
1287
- blob_reset(&c2);
1288
- if( !isPatch ){
1289
- style_header("Diff");
1290
- style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
1291
- g.zTop, P("v1"), P("v2"));
1292
- if( !sideBySide ){
1293
- style_submenu_element("Side-by-side Diff", "sbsdiff",
1294
- "%s/fdiff?v1=%T&v2=%T&sbs=1",
1295
- g.zTop, P("v1"), P("v2"));
1296
- }else{
1297
- style_submenu_element("Unified Diff", "udiff",
1298
- "%s/fdiff?v1=%T&v2=%T&sbs=0",
1299
- g.zTop, P("v1"), P("v2"));
1300
- }
1301
-
1302
- if( P("smhdr")!=0 ){
1303
- @ <h2>Differences From Artifact
1304
- @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To
1305
- @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
1306
- }else{
1307
- @ <h2>Differences From
1308
- @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
1309
- object_description(v1, 0, 0);
1310
- @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
1311
- object_description(v2, 0, 0);
1312
- }
1313
- if( pRe ){
1314
- @ <b>Only differences that match regular expression "%h(zRe)"
1315
- @ are shown.</b>
1316
- }
1317
- @ <hr />
1318
- @ <div class="%s(zStyle)">
1319
- @ %s(blob_str(&diff))
1320
- @ </div>
1321
- blob_reset(&diff);
1322
- style_footer();
1323
- }
1294
+ content_get(v1, &c1);
1295
+ content_get(v2, &c2);
1296
+ text_diff(&c1, &c2, pOut, pRe, diffFlags);
1297
+ blob_reset(&c1);
1298
+ blob_reset(&c2);
1299
+ return;
1300
+ }
1301
+
1302
+ sideBySide = !is_false(PD("sbs","1"));
1303
+ zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
1304
+ zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
1305
+ diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;
1306
+
1307
+ style_header("Diff");
1308
+ style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
1309
+ g.zTop, P("v1"), P("v2"));
1310
+ if( !sideBySide ){
1311
+ style_submenu_element("Side-by-side Diff", "sbsdiff",
1312
+ "%s/fdiff?v1=%T&v2=%T&sbs=1",
1313
+ g.zTop, P("v1"), P("v2"));
1314
+ }else{
1315
+ style_submenu_element("Unified Diff", "udiff",
1316
+ "%s/fdiff?v1=%T&v2=%T&sbs=0",
1317
+ g.zTop, P("v1"), P("v2"));
1318
+ }
1319
+
1320
+ if( P("smhdr")!=0 ){
1321
+ @ <h2>Differences From Artifact
1322
+ @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To
1323
+ @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
1324
+ }else{
1325
+ @ <h2>Differences From
1326
+ @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
1327
+ object_description(v1, 0, 0);
1328
+ @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
1329
+ object_description(v2, 0, 0);
1330
+ }
1331
+ if( pRe ){
1332
+ @ <b>Only differences that match regular expression "%h(zRe)"
1333
+ @ are shown.</b>
1334
+ }
1335
+ @ <hr />
1336
+ append_diff(zV1, zV2, diffFlags, pRe);
1337
+ append_diff_javascript(sideBySide);
1338
+ style_footer();
13241339
}
13251340
13261341
/*
13271342
** WEBPAGE: raw
13281343
** URL: /raw?name=ARTIFACTID&m=TYPE
13291344
--- src/info.c
+++ src/info.c
@@ -316,25 +316,22 @@
316 blob_zero(&to);
317 }
318 blob_zero(&out);
319 if( diffFlags & DIFF_SIDEBYSIDE ){
320 text_diff(&from, &to, &out, pRe, diffFlags | DIFF_HTML | DIFF_NOTTOOBIG);
321 @ <div class="sbsdiff">
322 @ %s(blob_str(&out))
323 @ </div>
324 }else{
325 text_diff(&from, &to, &out, pRe,
326 diffFlags | DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG);
327 @ <div class="udiff">
328 @ %s(blob_str(&out))
329 @ </div>
330 }
331 blob_reset(&from);
332 blob_reset(&to);
333 blob_reset(&out);
334 }
335
336
337 /*
338 ** Write a line of web-page output that shows changes that have occurred
339 ** to a file between two check-ins.
340 */
@@ -359,13 +356,11 @@
359 @ for %h(zName)</p>
360 }else{
361 @ <p>Changes to %h(zName)</p>
362 }
363 if( diffFlags ){
364 @ <pre style="white-space:pre;">
365 append_diff(zOld, zNew, diffFlags, pRe);
366 @ </pre>
367 }
368 }else{
369 if( zOld && zNew ){
370 if( fossil_strcmp(zOld, zNew)!=0 ){
371 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
@@ -385,20 +380,50 @@
385 }else{
386 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
387 @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
388 }
389 if( diffFlags ){
390 @ <pre style="white-space:pre;">
391 append_diff(zOld, zNew, diffFlags, pRe);
392 @ </pre>
393 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
394 @ &nbsp;&nbsp;
395 @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a>
396 }
397 @ </p>
398 }
399 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
401 /*
402 ** Construct an appropriate diffFlag for text_diff() based on query
403 ** parameters and the to boolean arguments.
404 */
@@ -675,10 +700,11 @@
675 const char *zOldName = db_column_text(&q, 4);
676 append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
677 }
678 db_finalize(&q);
679 }
 
680 style_footer();
681 }
682
683 /*
684 ** WEBPAGE: winfo
@@ -988,11 +1014,11 @@
988 pFileTo = manifest_file_next(pTo, 0);
989 }
990 }
991 manifest_destroy(pFrom);
992 manifest_destroy(pTo);
993
994 style_footer();
995 }
996
997 #if INTERFACE
998 /*
@@ -1244,85 +1270,74 @@
1244 */
1245 void diff_page(void){
1246 int v1, v2;
1247 int isPatch;
1248 int sideBySide;
1249 Blob c1, c2, diff, *pOut;
1250 char *zV1;
1251 char *zV2;
1252 const char *zRe;
1253 ReCompiled *pRe = 0;
1254 u64 diffFlags;
1255 const char *zStyle = "sbsdiff";
1256
1257 login_check_credentials();
1258 if( !g.perm.Read ){ login_needed(); return; }
1259 v1 = name_to_rid_www("v1");
1260 v2 = name_to_rid_www("v2");
1261 if( v1==0 || v2==0 ) fossil_redirect_home();
1262 sideBySide = !is_false(PD("sbs","1"));
1263 zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
1264 zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
1265 isPatch = P("patch")!=0;
1266 if( isPatch ){
 
1267 pOut = cgi_output_blob();
1268 cgi_set_content_type("text/plain");
1269 diffFlags = 4;
1270 }else{
1271 blob_zero(&diff);
1272 pOut = &diff;
1273 diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;
1274 if( sideBySide ){
1275 zStyle = "sbsdiff";
1276 }else{
1277 diffFlags |= DIFF_LINENO;
1278 zStyle = "udiff";
1279 }
1280 }
1281 zRe = P("regex");
1282 if( zRe ) re_compile(&pRe, zRe, 0);
1283 content_get(v1, &c1);
1284 content_get(v2, &c2);
1285 text_diff(&c1, &c2, pOut, pRe, diffFlags);
1286 blob_reset(&c1);
1287 blob_reset(&c2);
1288 if( !isPatch ){
1289 style_header("Diff");
1290 style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
1291 g.zTop, P("v1"), P("v2"));
1292 if( !sideBySide ){
1293 style_submenu_element("Side-by-side Diff", "sbsdiff",
1294 "%s/fdiff?v1=%T&v2=%T&sbs=1",
1295 g.zTop, P("v1"), P("v2"));
1296 }else{
1297 style_submenu_element("Unified Diff", "udiff",
1298 "%s/fdiff?v1=%T&v2=%T&sbs=0",
1299 g.zTop, P("v1"), P("v2"));
1300 }
1301
1302 if( P("smhdr")!=0 ){
1303 @ <h2>Differences From Artifact
1304 @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To
1305 @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
1306 }else{
1307 @ <h2>Differences From
1308 @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
1309 object_description(v1, 0, 0);
1310 @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
1311 object_description(v2, 0, 0);
1312 }
1313 if( pRe ){
1314 @ <b>Only differences that match regular expression "%h(zRe)"
1315 @ are shown.</b>
1316 }
1317 @ <hr />
1318 @ <div class="%s(zStyle)">
1319 @ %s(blob_str(&diff))
1320 @ </div>
1321 blob_reset(&diff);
1322 style_footer();
1323 }
1324 }
1325
1326 /*
1327 ** WEBPAGE: raw
1328 ** URL: /raw?name=ARTIFACTID&m=TYPE
1329
--- src/info.c
+++ src/info.c
@@ -316,25 +316,22 @@
316 blob_zero(&to);
317 }
318 blob_zero(&out);
319 if( diffFlags & DIFF_SIDEBYSIDE ){
320 text_diff(&from, &to, &out, pRe, diffFlags | DIFF_HTML | DIFF_NOTTOOBIG);
 
321 @ %s(blob_str(&out))
 
322 }else{
323 text_diff(&from, &to, &out, pRe,
324 diffFlags | DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG);
325 @ <pre class="udiff">
326 @ %s(blob_str(&out))
327 @ </pre>
328 }
329 blob_reset(&from);
330 blob_reset(&to);
331 blob_reset(&out);
332 }
 
333
334 /*
335 ** Write a line of web-page output that shows changes that have occurred
336 ** to a file between two check-ins.
337 */
@@ -359,13 +356,11 @@
356 @ for %h(zName)</p>
357 }else{
358 @ <p>Changes to %h(zName)</p>
359 }
360 if( diffFlags ){
 
361 append_diff(zOld, zNew, diffFlags, pRe);
 
362 }
363 }else{
364 if( zOld && zNew ){
365 if( fossil_strcmp(zOld, zNew)!=0 ){
366 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
@@ -385,20 +380,50 @@
380 }else{
381 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
382 @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
383 }
384 if( diffFlags ){
 
385 append_diff(zOld, zNew, diffFlags, pRe);
 
386 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
387 @ &nbsp;&nbsp;
388 @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a>
389 }
 
390 }
391 }
392
393 /*
394 ** Generate javascript to enhance HTML diffs.
395 */
396 void append_diff_javascript(int sideBySide){
397 if( !sideBySide ) return;
398 @ <script>(function(){
399 @ var SCROLL_LEN = 25;
400 @ function initSbsDiff(diff){
401 @ var txtCols = diff.querySelectorAll('.difftxtcol');
402 @ var width = Math.max(txtCols[0].scrollWidth, txtCols[1].scrollWidth);
403 @ for(var i=0; i<2; i++){
404 @ txtCols[i].children[0].style.width = width + 'px';
405 @ txtCols[i].onscroll = function(e){
406 @ txtCols[0].scrollLeft = txtCols[1].scrollLeft = this.scrollLeft;
407 @ };
408 @ }
409 @ diff.tabIndex = 0;
410 @ diff.onkeydown = function(e){
411 @ e = e || event;
412 @ var len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
413 @ if( !len ) return;
414 @ txtCols[0].scrollLeft += len;
415 @ return false;
416 @ };
417 @ }
418 @
419 @ var diffs = document.querySelectorAll('.sbsdiffcols');
420 @ for(var i=0; i<diffs.length; i++){
421 @ initSbsDiff(diffs[i]);
422 @ }
423 @ }())</script>
424 }
425
426 /*
427 ** Construct an appropriate diffFlag for text_diff() based on query
428 ** parameters and the to boolean arguments.
429 */
@@ -675,10 +700,11 @@
700 const char *zOldName = db_column_text(&q, 4);
701 append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
702 }
703 db_finalize(&q);
704 }
705 append_diff_javascript(sideBySide);
706 style_footer();
707 }
708
709 /*
710 ** WEBPAGE: winfo
@@ -988,11 +1014,11 @@
1014 pFileTo = manifest_file_next(pTo, 0);
1015 }
1016 }
1017 manifest_destroy(pFrom);
1018 manifest_destroy(pTo);
1019 append_diff_javascript(sideBySide);
1020 style_footer();
1021 }
1022
1023 #if INTERFACE
1024 /*
@@ -1244,85 +1270,74 @@
1270 */
1271 void diff_page(void){
1272 int v1, v2;
1273 int isPatch;
1274 int sideBySide;
 
1275 char *zV1;
1276 char *zV2;
1277 const char *zRe;
1278 ReCompiled *pRe = 0;
1279 u64 diffFlags;
 
1280
1281 login_check_credentials();
1282 if( !g.perm.Read ){ login_needed(); return; }
1283 v1 = name_to_rid_www("v1");
1284 v2 = name_to_rid_www("v2");
1285 if( v1==0 || v2==0 ) fossil_redirect_home();
1286 zRe = P("regex");
1287 if( zRe ) re_compile(&pRe, zRe, 0);
 
1288 isPatch = P("patch")!=0;
1289 if( isPatch ){
1290 Blob c1, c2, *pOut;
1291 pOut = cgi_output_blob();
1292 cgi_set_content_type("text/plain");
1293 diffFlags = 4;
1294 content_get(v1, &c1);
1295 content_get(v2, &c2);
1296 text_diff(&c1, &c2, pOut, pRe, diffFlags);
1297 blob_reset(&c1);
1298 blob_reset(&c2);
1299 return;
1300 }
1301
1302 sideBySide = !is_false(PD("sbs","1"));
1303 zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
1304 zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
1305 diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;
1306
1307 style_header("Diff");
1308 style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
1309 g.zTop, P("v1"), P("v2"));
1310 if( !sideBySide ){
1311 style_submenu_element("Side-by-side Diff", "sbsdiff",
1312 "%s/fdiff?v1=%T&v2=%T&sbs=1",
1313 g.zTop, P("v1"), P("v2"));
1314 }else{
1315 style_submenu_element("Unified Diff", "udiff",
1316 "%s/fdiff?v1=%T&v2=%T&sbs=0",
1317 g.zTop, P("v1"), P("v2"));
1318 }
1319
1320 if( P("smhdr")!=0 ){
1321 @ <h2>Differences From Artifact
1322 @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To
1323 @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
1324 }else{
1325 @ <h2>Differences From
1326 @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
1327 object_description(v1, 0, 0);
1328 @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
1329 object_description(v2, 0, 0);
1330 }
1331 if( pRe ){
1332 @ <b>Only differences that match regular expression "%h(zRe)"
1333 @ are shown.</b>
1334 }
1335 @ <hr />
1336 append_diff(zV1, zV2, diffFlags, pRe);
1337 append_diff_javascript(sideBySide);
1338 style_footer();
 
 
 
 
 
 
 
 
 
1339 }
1340
1341 /*
1342 ** WEBPAGE: raw
1343 ** URL: /raw?name=ARTIFACTID&m=TYPE
1344
+34 -9
--- src/style.c
+++ src/style.c
@@ -462,11 +462,11 @@
462462
;
463463
464464
/*
465465
** The default Cascading Style Sheet.
466466
** It's assembled by different strings for each class.
467
-** The default css conatains all definitions.
467
+** The default css contains all definitions.
468468
** The style sheet, send to the client only contains the ones,
469469
** not defined in the user defined css.
470470
*/
471471
const char zDefaultCSS[] =
472472
@ /* General settings for the entire page */
@@ -965,20 +965,43 @@
965965
{ "ul.filelist",
966966
"List of files in a timeline",
967967
@ margin-top: 3px;
968968
@ line-height: 100%;
969969
},
970
- { "div.sbsdiff",
971
- "side-by-side diff display",
972
- @ font-family: monospace;
970
+ { "table.sbsdiffcols",
971
+ "side-by-side diff display (column-based)",
972
+ @ border-spacing: 0;
973973
@ font-size: xx-small;
974
- @ white-space: pre;
974
+ },
975
+ { "table.sbsdiffcols td",
976
+ "sbs diff table cell",
977
+ @ padding: 0;
978
+ @ vertical-align: top;
979
+ },
980
+ { "table.sbsdiffcols pre",
981
+ "sbs diff pre block",
982
+ @ margin: 0;
983
+ @ padding: 0;
984
+ @ border: 0;
985
+ @ font-size: inherit;
986
+ @ background: inherit;
987
+ @ color: inherit;
988
+ },
989
+ { "div.difflncol",
990
+ "diff line number column",
991
+ @ padding-right: 1em;
992
+ @ text-align: right;
993
+ @ color: #a0a0a0;
994
+ },
995
+ { "div.difftxtcol",
996
+ "diff text column",
997
+ @ width: 45em;
998
+ @ overflow-x: auto;
975999
},
976
- { "div.udiff",
977
- "context diff display",
978
- @ font-family: monospace;
979
- @ white-space: pre;
1000
+ { "div.diffmkrcol",
1001
+ "diff marker column",
1002
+ @ padding: 0 1em;
9801003
},
9811004
{ "span.diffchng",
9821005
"changes in a diff",
9831006
@ background-color: #c0c0ff;
9841007
},
@@ -990,10 +1013,12 @@
9901013
"deleted in a diff",
9911014
@ background-color: #ffc8c8;
9921015
},
9931016
{ "span.diffhr",
9941017
"suppressed lines in a diff",
1018
+ @ display: inline-block;
1019
+ @ margin: .5em 0 1em;
9951020
@ color: #0000ff;
9961021
},
9971022
{ "span.diffln",
9981023
"line numbers in a diff",
9991024
@ color: #a0a0a0;
10001025
--- src/style.c
+++ src/style.c
@@ -462,11 +462,11 @@
462 ;
463
464 /*
465 ** The default Cascading Style Sheet.
466 ** It's assembled by different strings for each class.
467 ** The default css conatains all definitions.
468 ** The style sheet, send to the client only contains the ones,
469 ** not defined in the user defined css.
470 */
471 const char zDefaultCSS[] =
472 @ /* General settings for the entire page */
@@ -965,20 +965,43 @@
965 { "ul.filelist",
966 "List of files in a timeline",
967 @ margin-top: 3px;
968 @ line-height: 100%;
969 },
970 { "div.sbsdiff",
971 "side-by-side diff display",
972 @ font-family: monospace;
973 @ font-size: xx-small;
974 @ white-space: pre;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
975 },
976 { "div.udiff",
977 "context diff display",
978 @ font-family: monospace;
979 @ white-space: pre;
980 },
981 { "span.diffchng",
982 "changes in a diff",
983 @ background-color: #c0c0ff;
984 },
@@ -990,10 +1013,12 @@
990 "deleted in a diff",
991 @ background-color: #ffc8c8;
992 },
993 { "span.diffhr",
994 "suppressed lines in a diff",
 
 
995 @ color: #0000ff;
996 },
997 { "span.diffln",
998 "line numbers in a diff",
999 @ color: #a0a0a0;
1000
--- src/style.c
+++ src/style.c
@@ -462,11 +462,11 @@
462 ;
463
464 /*
465 ** The default Cascading Style Sheet.
466 ** It's assembled by different strings for each class.
467 ** The default css contains all definitions.
468 ** The style sheet, send to the client only contains the ones,
469 ** not defined in the user defined css.
470 */
471 const char zDefaultCSS[] =
472 @ /* General settings for the entire page */
@@ -965,20 +965,43 @@
965 { "ul.filelist",
966 "List of files in a timeline",
967 @ margin-top: 3px;
968 @ line-height: 100%;
969 },
970 { "table.sbsdiffcols",
971 "side-by-side diff display (column-based)",
972 @ border-spacing: 0;
973 @ font-size: xx-small;
974 },
975 { "table.sbsdiffcols td",
976 "sbs diff table cell",
977 @ padding: 0;
978 @ vertical-align: top;
979 },
980 { "table.sbsdiffcols pre",
981 "sbs diff pre block",
982 @ margin: 0;
983 @ padding: 0;
984 @ border: 0;
985 @ font-size: inherit;
986 @ background: inherit;
987 @ color: inherit;
988 },
989 { "div.difflncol",
990 "diff line number column",
991 @ padding-right: 1em;
992 @ text-align: right;
993 @ color: #a0a0a0;
994 },
995 { "div.difftxtcol",
996 "diff text column",
997 @ width: 45em;
998 @ overflow-x: auto;
999 },
1000 { "div.diffmkrcol",
1001 "diff marker column",
1002 @ padding: 0 1em;
 
1003 },
1004 { "span.diffchng",
1005 "changes in a diff",
1006 @ background-color: #c0c0ff;
1007 },
@@ -990,10 +1013,12 @@
1013 "deleted in a diff",
1014 @ background-color: #ffc8c8;
1015 },
1016 { "span.diffhr",
1017 "suppressed lines in a diff",
1018 @ display: inline-block;
1019 @ margin: .5em 0 1em;
1020 @ color: #0000ff;
1021 },
1022 { "span.diffln",
1023 "line numbers in a diff",
1024 @ color: #a0a0a0;
1025
+2 -2
--- src/wiki.c
+++ src/wiki.c
@@ -786,13 +786,13 @@
786786
blob_init(&w2, pW2->zWiki, -1);
787787
}
788788
blob_zero(&d);
789789
diffFlags = construct_diff_flags(1,0);
790790
text_diff(&w2, &w1, &d, 0, diffFlags | DIFF_HTML | DIFF_LINENO);
791
- @ <div class="udiff">
791
+ @ <pre class="udiff">
792792
@ %s(blob_str(&d))
793
- @ </div>
793
+ @ <pre>
794794
manifest_destroy(pW1);
795795
manifest_destroy(pW2);
796796
style_footer();
797797
}
798798
799799
--- src/wiki.c
+++ src/wiki.c
@@ -786,13 +786,13 @@
786 blob_init(&w2, pW2->zWiki, -1);
787 }
788 blob_zero(&d);
789 diffFlags = construct_diff_flags(1,0);
790 text_diff(&w2, &w1, &d, 0, diffFlags | DIFF_HTML | DIFF_LINENO);
791 @ <div class="udiff">
792 @ %s(blob_str(&d))
793 @ </div>
794 manifest_destroy(pW1);
795 manifest_destroy(pW2);
796 style_footer();
797 }
798
799
--- src/wiki.c
+++ src/wiki.c
@@ -786,13 +786,13 @@
786 blob_init(&w2, pW2->zWiki, -1);
787 }
788 blob_zero(&d);
789 diffFlags = construct_diff_flags(1,0);
790 text_diff(&w2, &w1, &d, 0, diffFlags | DIFF_HTML | DIFF_LINENO);
791 @ <pre class="udiff">
792 @ %s(blob_str(&d))
793 @ <pre>
794 manifest_destroy(pW1);
795 manifest_destroy(pW2);
796 style_footer();
797 }
798
799
--- test/diff-test-1.wiki
+++ test/diff-test-1.wiki
@@ -2,11 +2,11 @@
22
33
This page contains list of URLs of interesting diffs.
44
Click on all URLs, one by one, to verify
55
the correct operation of the diff logic.
66
7
- * <a href="../../../info/030035345c#chunk59" target="testwindow">
7
+ * <a href="../../../info/030035345c#chunk73" target="testwindow">
88
Multiple edits on a single line.</a> This is an SQLite version
99
update diff. It is a large diff and contains many other interesting
1010
features. Scan the whole diff.
1111
* <a href="../../../fdiff?v1=6da016415dc52d61&v2=af6df3466e3c4a88"
1212
target="testwindow">Tricky alignment and multiple edits per line</a>.
@@ -15,12 +15,12 @@
1515
* <a href="../../../fdiff?v1=d1c60722e0b9d775&v2=58d1a8991bacb113"
1616
target="testwindow">Column alignment with multibyte characters.</a>
1717
The edit of a line with multibyte characters is the first chunk.
1818
* <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"
1919
target="testwindow">Large diff of sqlite3.c</a>. This diff was very
20
- slow prior to the preformance enhancement change [9e15437e97].
21
- * <a href="../../../info/bda00cbada#chunk42" target="testwindow">
20
+ slow prior to the performance enhancement change [9e15437e97].
21
+ * <a href="../../../info/bda00cbada#chunk49" target="testwindow">
2222
A difficult indentation change.
2323
* <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"
2424
target="testwindow">Another tricky indentation.</a> Notice especially
2525
lines 59398 and 59407 on the left.
2626
* <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"
2727
--- test/diff-test-1.wiki
+++ test/diff-test-1.wiki
@@ -2,11 +2,11 @@
2
3 This page contains list of URLs of interesting diffs.
4 Click on all URLs, one by one, to verify
5 the correct operation of the diff logic.
6
7 * <a href="../../../info/030035345c#chunk59" target="testwindow">
8 Multiple edits on a single line.</a> This is an SQLite version
9 update diff. It is a large diff and contains many other interesting
10 features. Scan the whole diff.
11 * <a href="../../../fdiff?v1=6da016415dc52d61&v2=af6df3466e3c4a88"
12 target="testwindow">Tricky alignment and multiple edits per line</a>.
@@ -15,12 +15,12 @@
15 * <a href="../../../fdiff?v1=d1c60722e0b9d775&v2=58d1a8991bacb113"
16 target="testwindow">Column alignment with multibyte characters.</a>
17 The edit of a line with multibyte characters is the first chunk.
18 * <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"
19 target="testwindow">Large diff of sqlite3.c</a>. This diff was very
20 slow prior to the preformance enhancement change [9e15437e97].
21 * <a href="../../../info/bda00cbada#chunk42" target="testwindow">
22 A difficult indentation change.
23 * <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"
24 target="testwindow">Another tricky indentation.</a> Notice especially
25 lines 59398 and 59407 on the left.
26 * <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"
27
--- test/diff-test-1.wiki
+++ test/diff-test-1.wiki
@@ -2,11 +2,11 @@
2
3 This page contains list of URLs of interesting diffs.
4 Click on all URLs, one by one, to verify
5 the correct operation of the diff logic.
6
7 * <a href="../../../info/030035345c#chunk73" target="testwindow">
8 Multiple edits on a single line.</a> This is an SQLite version
9 update diff. It is a large diff and contains many other interesting
10 features. Scan the whole diff.
11 * <a href="../../../fdiff?v1=6da016415dc52d61&v2=af6df3466e3c4a88"
12 target="testwindow">Tricky alignment and multiple edits per line</a>.
@@ -15,12 +15,12 @@
15 * <a href="../../../fdiff?v1=d1c60722e0b9d775&v2=58d1a8991bacb113"
16 target="testwindow">Column alignment with multibyte characters.</a>
17 The edit of a line with multibyte characters is the first chunk.
18 * <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"
19 target="testwindow">Large diff of sqlite3.c</a>. This diff was very
20 slow prior to the performance enhancement change [9e15437e97].
21 * <a href="../../../info/bda00cbada#chunk49" target="testwindow">
22 A difficult indentation change.
23 * <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"
24 target="testwindow">Another tricky indentation.</a> Notice especially
25 lines 59398 and 59407 on the left.
26 * <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"
27

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button