Fossil SCM

Refactor the diff logic for improved modularity and performance. This check-in transitions the diff improvement efforts from a branch to trunk. The effort is not yet complete, but it is stable enough to continue going on trunk. User-visible changes include (1) improved diff display, especially for unified diffs, (2) the new "fossil xdiff" command (promoting the older "test-diff" command into a supported command), and improved performance. Behind the scenes, (3) the class names DOM module used for for HTML diffs is simplified - which may break custom skins, (4) the internal processing of diffs is simplified in many ways.

drh 2021-09-05 19:02 trunk merge
Commit 9e330740ccfc62862b02b7bce69fb65631ac8771beaeec454bd0f4ebc1656416
--- skins/ardoise/css.txt
+++ skins/ardoise/css.txt
@@ -83,14 +83,10 @@
8383
color: #ff8000;
8484
text-decoration: unset
8585
}
8686
a:active,
8787
a:hover,
88
-pre.udiff:focus,
89
-table.sbsdiffcols:focus {
90
- outline: 0
91
-}
9288
abbr[title] {
9389
border-bottom: 1px dotted
9490
}
9591
b,
9692
optgroup,
@@ -1119,55 +1115,57 @@
11191115
}
11201116
table.login_out .login_out_label {
11211117
font-weight: 700;
11221118
text-align: right
11231119
}
1124
-pre.udiff,
1125
-table.sbsdiffcols {
1120
+table.diff {
11261121
width: 100%;
11271122
overflow: auto;
11281123
padding: 0 5px;
11291124
font-size: 1rem;
11301125
background: #000;
1131
- border-radius: 5px
1132
-}
1133
-pre.udiff,
1134
-pre.udiff pre,
1135
-table.sbsdiffcols pre {
1136
- font-size: 1.15rem
1137
-}
1138
-pre.udiff {
1126
+ border-radius: 5px;
1127
+}
1128
+table.diff pre {
1129
+ font-size: 1.15rem;
1130
+ scrollbar-color: black #999;
1131
+}
1132
+table.udiff pre {
11391133
padding: 10px 0
11401134
}
1141
-div.difftxtcol {
1135
+td.difftxt {
11421136
width: 52rem;
1143
- overflow-x: auto
11441137
}
1145
-span.diffchng {
1146
- background-color: #8080e8;
1147
- color: #000
1148
-}
1149
-span.diffadd {
1138
+td.diffln ins {
11501139
background-color: #559855;
1151
- color: #000
1140
+ color: #000;
1141
+ text-decoration: none;
1142
+}
1143
+td.diffln del {
1144
+ background-color: #c55;
1145
+ color: #000;
1146
+ text-decoration: none;
1147
+}
1148
+td.difftxt del {
1149
+ background-color: inherit;
1150
+ text-decoration: none;
11521151
}
1153
-span.diffrm {
1152
+td.difftxt del > del {
11541153
background-color: #c55;
1155
- color: #000
1156
-}
1157
-div.diffmkrcol {
1158
- padding: 0 1em;
1159
- background: #111
1160
-}
1161
-span.diffhr {
1162
- display: inline-block;
1163
- margin: .5em 0 1em;
1164
- color: #555
1165
-}
1166
-span.diffln {
1167
- color: #666
1168
-}
1154
+ color: #000;
1155
+ text-decoration: none;
1156
+}
1157
+td.difftxt ins {
1158
+ background-color: inherit;
1159
+ text-decoration: none;
1160
+}
1161
+td.difftxt ins > ins {
1162
+ background-color: #559855;
1163
+ color: #000;
1164
+ text-decoration: none;
1165
+}
1166
+
11691167
table.report {
11701168
width: 100%;
11711169
cursor: auto;
11721170
margin: 0 0 1em;
11731171
color: #000
11741172
--- skins/ardoise/css.txt
+++ skins/ardoise/css.txt
@@ -83,14 +83,10 @@
83 color: #ff8000;
84 text-decoration: unset
85 }
86 a:active,
87 a:hover,
88 pre.udiff:focus,
89 table.sbsdiffcols:focus {
90 outline: 0
91 }
92 abbr[title] {
93 border-bottom: 1px dotted
94 }
95 b,
96 optgroup,
@@ -1119,55 +1115,57 @@
1119 }
1120 table.login_out .login_out_label {
1121 font-weight: 700;
1122 text-align: right
1123 }
1124 pre.udiff,
1125 table.sbsdiffcols {
1126 width: 100%;
1127 overflow: auto;
1128 padding: 0 5px;
1129 font-size: 1rem;
1130 background: #000;
1131 border-radius: 5px
1132 }
1133 pre.udiff,
1134 pre.udiff pre,
1135 table.sbsdiffcols pre {
1136 font-size: 1.15rem
1137 }
1138 pre.udiff {
1139 padding: 10px 0
1140 }
1141 div.difftxtcol {
1142 width: 52rem;
1143 overflow-x: auto
1144 }
1145 span.diffchng {
1146 background-color: #8080e8;
1147 color: #000
1148 }
1149 span.diffadd {
1150 background-color: #559855;
1151 color: #000
 
 
 
 
 
 
 
 
 
 
1152 }
1153 span.diffrm {
1154 background-color: #c55;
1155 color: #000
1156 }
1157 div.diffmkrcol {
1158 padding: 0 1em;
1159 background: #111
1160 }
1161 span.diffhr {
1162 display: inline-block;
1163 margin: .5em 0 1em;
1164 color: #555
1165 }
1166 span.diffln {
1167 color: #666
1168 }
1169 table.report {
1170 width: 100%;
1171 cursor: auto;
1172 margin: 0 0 1em;
1173 color: #000
1174
--- skins/ardoise/css.txt
+++ skins/ardoise/css.txt
@@ -83,14 +83,10 @@
83 color: #ff8000;
84 text-decoration: unset
85 }
86 a:active,
87 a:hover,
 
 
 
 
88 abbr[title] {
89 border-bottom: 1px dotted
90 }
91 b,
92 optgroup,
@@ -1119,55 +1115,57 @@
1115 }
1116 table.login_out .login_out_label {
1117 font-weight: 700;
1118 text-align: right
1119 }
1120 table.diff {
 
1121 width: 100%;
1122 overflow: auto;
1123 padding: 0 5px;
1124 font-size: 1rem;
1125 background: #000;
1126 border-radius: 5px;
1127 }
1128 table.diff pre {
1129 font-size: 1.15rem;
1130 scrollbar-color: black #999;
1131 }
1132 table.udiff pre {
 
1133 padding: 10px 0
1134 }
1135 td.difftxt {
1136 width: 52rem;
 
1137 }
1138 td.diffln ins {
 
 
 
 
1139 background-color: #559855;
1140 color: #000;
1141 text-decoration: none;
1142 }
1143 td.diffln del {
1144 background-color: #c55;
1145 color: #000;
1146 text-decoration: none;
1147 }
1148 td.difftxt del {
1149 background-color: inherit;
1150 text-decoration: none;
1151 }
1152 td.difftxt del > del {
1153 background-color: #c55;
1154 color: #000;
1155 text-decoration: none;
1156 }
1157 td.difftxt ins {
1158 background-color: inherit;
1159 text-decoration: none;
1160 }
1161 td.difftxt ins > ins {
1162 background-color: #559855;
1163 color: #000;
1164 text-decoration: none;
1165 }
1166
 
1167 table.report {
1168 width: 100%;
1169 cursor: auto;
1170 margin: 0 0 1em;
1171 color: #000
1172
--- skins/blitz/css.txt
+++ skins/blitz/css.txt
@@ -1131,19 +1131,19 @@
11311131
}
11321132
11331133
11341134
/* Diff displays
11351135
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */
1136
-pre.udiff, table.sbsdiffcols {
1136
+table.diff {
11371137
width: 100%;
11381138
overflow: auto;
11391139
border: 1px solid #ccc;
11401140
padding: 5px;
11411141
font-size: 1rem;
11421142
}
11431143
1144
-pre.udiff:focus, table.sbsdiffcols:focus {
1144
+table.diff:focus {
11451145
outline: none;
11461146
}
11471147
11481148
11491149
/* Ticket Reports
11501150
--- skins/blitz/css.txt
+++ skins/blitz/css.txt
@@ -1131,19 +1131,19 @@
1131 }
1132
1133
1134 /* Diff displays
1135 ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */
1136 pre.udiff, table.sbsdiffcols {
1137 width: 100%;
1138 overflow: auto;
1139 border: 1px solid #ccc;
1140 padding: 5px;
1141 font-size: 1rem;
1142 }
1143
1144 pre.udiff:focus, table.sbsdiffcols:focus {
1145 outline: none;
1146 }
1147
1148
1149 /* Ticket Reports
1150
--- skins/blitz/css.txt
+++ skins/blitz/css.txt
@@ -1131,19 +1131,19 @@
1131 }
1132
1133
1134 /* Diff displays
1135 ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */
1136 table.diff {
1137 width: 100%;
1138 overflow: auto;
1139 border: 1px solid #ccc;
1140 padding: 5px;
1141 font-size: 1rem;
1142 }
1143
1144 table.diff:focus {
1145 outline: none;
1146 }
1147
1148
1149 /* Ticket Reports
1150
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -451,31 +451,39 @@
451451
}
452452
453453
/************************************************************************
454454
diffs...
455455
************************************************************************/
456
-span.diffchng {
457
- background-color: #8080e8;
458
- color: #000
459
-}
460
-span.diffadd {
456
+td.diffln ins {
461457
background-color: #559855;
462
- color: #000
458
+ color: #000;
459
+ text-decoration: none;
460
+}
461
+td.diffln del {
462
+ background-color: #c55;
463
+ color: #000;
464
+ text-decoration: none;
465
+}
466
+td.difftxt del {
467
+ background-color: inherit;
468
+ text-decoration: none;
463469
}
464
-span.diffrm {
470
+td.difftxt del > del {
465471
background-color: #c55;
466
- color: #000
467
-}
468
-div.diffmkrcol {
469
- background: #111
470
-}
471
-span.diffhr {
472
- color: #555
473
-}
474
-span.diffln {
475
- color: #666
476
-}
472
+ color: #000;
473
+ text-decoration: none;
474
+}
475
+td.difftxt ins {
476
+ background-color: inherit;
477
+ text-decoration: none;
478
+}
479
+td.difftxt ins > ins {
480
+ background-color: #559855;
481
+ color: #000;
482
+ text-decoration: none;
483
+}
484
+
477485
478486
/************************************************************************
479487
************************************************************************/
480488
body.wikiedit #fossil-status-bar,
481489
body.fileedit #fossil-status-bar{
@@ -547,18 +555,14 @@
547555
body.forum .forumPostBody > div blockquote {
548556
border: 1px inset;
549557
padding: 0 0.5em;
550558
}
551559
552
-pre.udiff {
553
- overflow-x: auto;
554
-}
555
-
556560
body.report table.report tr td { color: black }
557561
body.report table.report a { color: blue }
558562
body.tkt td.tktDspValue { color: black }
559563
body.tkt td.tktDspValue a { color: blue }
560564
561565
body.branch .brlist > table > tbody > tr:hover:not(.selected),
562566
body.branch .brlist > table > tbody > tr.selected {
563567
background-color: #442800;
564568
}
565569
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -451,31 +451,39 @@
451 }
452
453 /************************************************************************
454 diffs...
455 ************************************************************************/
456 span.diffchng {
457 background-color: #8080e8;
458 color: #000
459 }
460 span.diffadd {
461 background-color: #559855;
462 color: #000
 
 
 
 
 
 
 
 
 
 
463 }
464 span.diffrm {
465 background-color: #c55;
466 color: #000
467 }
468 div.diffmkrcol {
469 background: #111
470 }
471 span.diffhr {
472 color: #555
473 }
474 span.diffln {
475 color: #666
476 }
 
 
477
478 /************************************************************************
479 ************************************************************************/
480 body.wikiedit #fossil-status-bar,
481 body.fileedit #fossil-status-bar{
@@ -547,18 +555,14 @@
547 body.forum .forumPostBody > div blockquote {
548 border: 1px inset;
549 padding: 0 0.5em;
550 }
551
552 pre.udiff {
553 overflow-x: auto;
554 }
555
556 body.report table.report tr td { color: black }
557 body.report table.report a { color: blue }
558 body.tkt td.tktDspValue { color: black }
559 body.tkt td.tktDspValue a { color: blue }
560
561 body.branch .brlist > table > tbody > tr:hover:not(.selected),
562 body.branch .brlist > table > tbody > tr.selected {
563 background-color: #442800;
564 }
565
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -451,31 +451,39 @@
451 }
452
453 /************************************************************************
454 diffs...
455 ************************************************************************/
456 td.diffln ins {
 
 
 
 
457 background-color: #559855;
458 color: #000;
459 text-decoration: none;
460 }
461 td.diffln del {
462 background-color: #c55;
463 color: #000;
464 text-decoration: none;
465 }
466 td.difftxt del {
467 background-color: inherit;
468 text-decoration: none;
469 }
470 td.difftxt del > del {
471 background-color: #c55;
472 color: #000;
473 text-decoration: none;
474 }
475 td.difftxt ins {
476 background-color: inherit;
477 text-decoration: none;
478 }
479 td.difftxt ins > ins {
480 background-color: #559855;
481 color: #000;
482 text-decoration: none;
483 }
484
485
486 /************************************************************************
487 ************************************************************************/
488 body.wikiedit #fossil-status-bar,
489 body.fileedit #fossil-status-bar{
@@ -547,18 +555,14 @@
555 body.forum .forumPostBody > div blockquote {
556 border: 1px inset;
557 padding: 0 0.5em;
558 }
559
 
 
 
 
560 body.report table.report tr td { color: black }
561 body.report table.report a { color: blue }
562 body.tkt td.tktDspValue { color: black }
563 body.tkt td.tktDspValue a { color: blue }
564
565 body.branch .brlist > table > tbody > tr:hover:not(.selected),
566 body.branch .brlist > table > tbody > tr.selected {
567 background-color: #442800;
568 }
569
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -158,21 +158,10 @@
158158
padding: 10px;
159159
font-size: 0.7em;
160160
margin-top: 10px;
161161
color: #ccc;
162162
}
163
-
164
-
165
-/* Exceptions for /info diff views */
166
-
167
-.udiff, .sbsdiff {
168
- font-size: .85em !important;
169
- overflow: auto;
170
- border: 1px solid #ccc;
171
- border-radius: 5px;
172
-}
173
-
174163
175164
/* Forum */
176165
177166
.forum a:visited {
178167
color: #6A7F94;
179168
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -158,21 +158,10 @@
158 padding: 10px;
159 font-size: 0.7em;
160 margin-top: 10px;
161 color: #ccc;
162 }
163
164
165 /* Exceptions for /info diff views */
166
167 .udiff, .sbsdiff {
168 font-size: .85em !important;
169 overflow: auto;
170 border: 1px solid #ccc;
171 border-radius: 5px;
172 }
173
174
175 /* Forum */
176
177 .forum a:visited {
178 color: #6A7F94;
179
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -158,21 +158,10 @@
158 padding: 10px;
159 font-size: 0.7em;
160 margin-top: 10px;
161 color: #ccc;
162 }
 
 
 
 
 
 
 
 
 
 
 
163
164 /* Forum */
165
166 .forum a:visited {
167 color: #6A7F94;
168
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -257,11 +257,11 @@
257257
background: rgba(255,255,255,0);
258258
}
259259
260260
261261
/* Side-by-side diff */
262
-table.sbsdiff {
262
+table.splitdiff {
263263
background-color: #485D7B;
264264
font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
265265
font-size: 8pt;
266266
border-collapse:collapse;
267267
white-space: pre;
@@ -320,47 +320,36 @@
320320
margin-top: 3px;
321321
line-height: 100%;
322322
}
323323
324324
/* side-by-side diff display */
325
-div.sbsdiff {
325
+div.splitdiff {
326326
font-family: monospace;
327327
font-size: smaller;
328328
white-space: pre;
329329
}
330330
331331
/* context diff display */
332
-div.udiff {
332
+table.udiff {
333333
font-family: monospace;
334334
white-space: pre;
335335
}
336336
337
-/* changes in a diff */
338
-span.diffchng {
339
- background-color: rgb(170, 170, 140);
340
-}
341
-
342337
/* added code in a diff */
343
-span.diffadd {
338
+td.difftxt ins > ins, td.diffln ins {
344339
background-color: rgb(100, 200, 100);
345340
}
341
+td.difftxt ins {
342
+ background-color: inherit;
343
+}
346344
347345
/* deleted in a diff */
348
-span.diffrm {
346
+td.difftxt del > del, td.diffln del {
349347
background-color: rgb(230, 110, 110);
350348
}
351
-
352
-/* suppressed lines in a diff */
353
-span.diffhr {
354
- display: inline-block;
355
- margin: .5em 0 1em;
356
- color: rgb(150, 150, 140);
357
-}
358
-
359
-/* line numbers in a diff */
360
-span.diffln {
361
- color: white;
349
+td.difftxt del {
350
+ background-color: inherit;
362351
}
363352
364353
.fileage tr:hover {
365354
background-color: #7EA2D9;
366355
}
@@ -419,15 +408,10 @@
419408
420409
.timelineModernCell[id], .timelineColumnarCell[id], .timelineDetailCell[id] {
421410
background-color: #455978;
422411
}
423412
424
-div.difflncol {
425
- padding-right: 1em;
426
- text-align: right;
427
- color: white;
428
-}
429413
.capsumOff {
430414
background-color: #bbbbbb;
431415
}
432416
.capsumRead {
433417
background-color: #006d00;
434418
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -257,11 +257,11 @@
257 background: rgba(255,255,255,0);
258 }
259
260
261 /* Side-by-side diff */
262 table.sbsdiff {
263 background-color: #485D7B;
264 font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
265 font-size: 8pt;
266 border-collapse:collapse;
267 white-space: pre;
@@ -320,47 +320,36 @@
320 margin-top: 3px;
321 line-height: 100%;
322 }
323
324 /* side-by-side diff display */
325 div.sbsdiff {
326 font-family: monospace;
327 font-size: smaller;
328 white-space: pre;
329 }
330
331 /* context diff display */
332 div.udiff {
333 font-family: monospace;
334 white-space: pre;
335 }
336
337 /* changes in a diff */
338 span.diffchng {
339 background-color: rgb(170, 170, 140);
340 }
341
342 /* added code in a diff */
343 span.diffadd {
344 background-color: rgb(100, 200, 100);
345 }
 
 
 
346
347 /* deleted in a diff */
348 span.diffrm {
349 background-color: rgb(230, 110, 110);
350 }
351
352 /* suppressed lines in a diff */
353 span.diffhr {
354 display: inline-block;
355 margin: .5em 0 1em;
356 color: rgb(150, 150, 140);
357 }
358
359 /* line numbers in a diff */
360 span.diffln {
361 color: white;
362 }
363
364 .fileage tr:hover {
365 background-color: #7EA2D9;
366 }
@@ -419,15 +408,10 @@
419
420 .timelineModernCell[id], .timelineColumnarCell[id], .timelineDetailCell[id] {
421 background-color: #455978;
422 }
423
424 div.difflncol {
425 padding-right: 1em;
426 text-align: right;
427 color: white;
428 }
429 .capsumOff {
430 background-color: #bbbbbb;
431 }
432 .capsumRead {
433 background-color: #006d00;
434
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -257,11 +257,11 @@
257 background: rgba(255,255,255,0);
258 }
259
260
261 /* Side-by-side diff */
262 table.splitdiff {
263 background-color: #485D7B;
264 font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
265 font-size: 8pt;
266 border-collapse:collapse;
267 white-space: pre;
@@ -320,47 +320,36 @@
320 margin-top: 3px;
321 line-height: 100%;
322 }
323
324 /* side-by-side diff display */
325 div.splitdiff {
326 font-family: monospace;
327 font-size: smaller;
328 white-space: pre;
329 }
330
331 /* context diff display */
332 table.udiff {
333 font-family: monospace;
334 white-space: pre;
335 }
336
 
 
 
 
 
337 /* added code in a diff */
338 td.difftxt ins > ins, td.diffln ins {
339 background-color: rgb(100, 200, 100);
340 }
341 td.difftxt ins {
342 background-color: inherit;
343 }
344
345 /* deleted in a diff */
346 td.difftxt del > del, td.diffln del {
347 background-color: rgb(230, 110, 110);
348 }
349 td.difftxt del {
350 background-color: inherit;
 
 
 
 
 
 
 
 
 
351 }
352
353 .fileage tr:hover {
354 background-color: #7EA2D9;
355 }
@@ -419,15 +408,10 @@
408
409 .timelineModernCell[id], .timelineColumnarCell[id], .timelineDetailCell[id] {
410 background-color: #455978;
411 }
412
 
 
 
 
 
413 .capsumOff {
414 background-color: #bbbbbb;
415 }
416 .capsumRead {
417 background-color: #006d00;
418
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -265,91 +265,81 @@
265265
/**************************************
266266
* Diffs
267267
*/
268268
269269
/* Code Added */
270
-span.diffadd {
270
+td.diffln ins,
271
+td.difftxt ins > ins {
271272
background-color: #7f7;
272273
color: #000;
273274
}
274
-
275
-/* Code Changed */
276
-span.diffchng {
277
- background-color: #77f;
278
- color: #000;
275
+td.difftxt ins {
276
+ background-color: inherit;
279277
}
280278
281279
/* Code Deleted */
282
-span.diffrm {
280
+td.diffln del,
281
+td.difftxt del > del {
283282
background-color: #f77;
284283
color: #000;
285284
}
285
+td.difftxt del {
286
+ background-color: inherit;
287
+}
286288
287289
288290
/**************************************
289291
* Diffs : Side-By-Side
290292
*/
291293
292294
/* display (column-based) */
293
-table.sbsdiffcols {
295
+table.splitdiff {
294296
border-spacing: 0;
295297
font-size: 0.85rem;
296298
width: 90%;
297299
}
298300
299
-table.sbsdiffcols pre {
301
+table.splitdiff pre {
300302
border: 0;
301
- margin: 0;
303
+ margin: 0 0.5em;
302304
padding: 0;
303305
}
304306
305
-table.sbsdiffcols td {
307
+table.splitdiff td {
306308
padding: 0;
307309
vertical-align: top;
308310
}
309311
310312
/* line number column */
311
-div.difflncol {
313
+td.diffln {
312314
color: #ee0;
313315
padding-right: 0.75em;
314316
text-align: right;
315317
}
316318
317319
/* diff text column */
318
-div.difftxtcol {
320
+td.difftxt {
319321
background-color: #111;
320322
overflow-x: auto;
321323
width: 45em;
322324
}
323325
324
-/* suppressed lines */
325
-span.diffhr {
326
- display: inline-block;
327
- margin-bottom: 0.75em;
328
- color: #ff0;
329
-}
330326
331327
/* diff marker column */
332
-div.diffmkrcol {
328
+td.diffsep {
333329
padding: 0 0.5em;
334330
}
335331
336332
337333
/**************************************
338334
* Diffs : Unified
339335
*/
340336
341
-pre.udiff {
337
+table.udiff pre {
342338
background-color: #111;
343339
}
344340
345
-/* line numbers */
346
-span.diffln {
347
- background-color: #222;
348
- color: #ee0;
349
-}
350
-
351341
352342
/**************************************
353343
* File List : Flat
354344
*/
355345
356346
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -265,91 +265,81 @@
265 /**************************************
266 * Diffs
267 */
268
269 /* Code Added */
270 span.diffadd {
 
271 background-color: #7f7;
272 color: #000;
273 }
274
275 /* Code Changed */
276 span.diffchng {
277 background-color: #77f;
278 color: #000;
279 }
280
281 /* Code Deleted */
282 span.diffrm {
 
283 background-color: #f77;
284 color: #000;
285 }
 
 
 
286
287
288 /**************************************
289 * Diffs : Side-By-Side
290 */
291
292 /* display (column-based) */
293 table.sbsdiffcols {
294 border-spacing: 0;
295 font-size: 0.85rem;
296 width: 90%;
297 }
298
299 table.sbsdiffcols pre {
300 border: 0;
301 margin: 0;
302 padding: 0;
303 }
304
305 table.sbsdiffcols td {
306 padding: 0;
307 vertical-align: top;
308 }
309
310 /* line number column */
311 div.difflncol {
312 color: #ee0;
313 padding-right: 0.75em;
314 text-align: right;
315 }
316
317 /* diff text column */
318 div.difftxtcol {
319 background-color: #111;
320 overflow-x: auto;
321 width: 45em;
322 }
323
324 /* suppressed lines */
325 span.diffhr {
326 display: inline-block;
327 margin-bottom: 0.75em;
328 color: #ff0;
329 }
330
331 /* diff marker column */
332 div.diffmkrcol {
333 padding: 0 0.5em;
334 }
335
336
337 /**************************************
338 * Diffs : Unified
339 */
340
341 pre.udiff {
342 background-color: #111;
343 }
344
345 /* line numbers */
346 span.diffln {
347 background-color: #222;
348 color: #ee0;
349 }
350
351
352 /**************************************
353 * File List : Flat
354 */
355
356
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -265,91 +265,81 @@
265 /**************************************
266 * Diffs
267 */
268
269 /* Code Added */
270 td.diffln ins,
271 td.difftxt ins > ins {
272 background-color: #7f7;
273 color: #000;
274 }
275 td.difftxt ins {
276 background-color: inherit;
 
 
 
277 }
278
279 /* Code Deleted */
280 td.diffln del,
281 td.difftxt del > del {
282 background-color: #f77;
283 color: #000;
284 }
285 td.difftxt del {
286 background-color: inherit;
287 }
288
289
290 /**************************************
291 * Diffs : Side-By-Side
292 */
293
294 /* display (column-based) */
295 table.splitdiff {
296 border-spacing: 0;
297 font-size: 0.85rem;
298 width: 90%;
299 }
300
301 table.splitdiff pre {
302 border: 0;
303 margin: 0 0.5em;
304 padding: 0;
305 }
306
307 table.splitdiff td {
308 padding: 0;
309 vertical-align: top;
310 }
311
312 /* line number column */
313 td.diffln {
314 color: #ee0;
315 padding-right: 0.75em;
316 text-align: right;
317 }
318
319 /* diff text column */
320 td.difftxt {
321 background-color: #111;
322 overflow-x: auto;
323 width: 45em;
324 }
325
 
 
 
 
 
 
326
327 /* diff marker column */
328 td.diffsep {
329 padding: 0 0.5em;
330 }
331
332
333 /**************************************
334 * Diffs : Unified
335 */
336
337 table.udiff pre {
338 background-color: #111;
339 }
340
 
 
 
 
 
 
341
342 /**************************************
343 * File List : Flat
344 */
345
346
+51
--- src/blob.c
+++ src/blob.c
@@ -347,10 +347,61 @@
347347
void blob_copy(Blob *pTo, Blob *pFrom){
348348
blob_is_init(pFrom);
349349
blob_zero(pTo);
350350
blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom));
351351
}
352
+
353
+/*
354
+** Append the second blob onto the end of the first blob and reset the
355
+** second blob.
356
+*/
357
+void blob_append_xfer(Blob *pTo, Blob *pFrom){
358
+ blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom));
359
+ blob_reset(pFrom);
360
+}
361
+
362
+/*
363
+** Write into pOut, a string literal representation for the first n bytes
364
+** of z[]. The string literal representation is compatible with C, TCL,
365
+** and JSON. Double-quotes are added to both ends. Double-quote and
366
+** backslash characters are escaped.
367
+*/
368
+void blob_append_tcl_literal(Blob *pOut, const char *z, int n){
369
+ int i;
370
+ blob_append_char(pOut, '"');
371
+ for(i=0; i<n; i++){
372
+ char c = z[i];
373
+ switch( c ){
374
+ case '\r': c = 'r';
375
+ case '[':
376
+ case ']':
377
+ case '$':
378
+ case '"':
379
+ case '\\':
380
+ blob_append_char(pOut, '\\');
381
+ default:
382
+ blob_append_char(pOut, c);
383
+ }
384
+ }
385
+ blob_append_char(pOut, '"');
386
+}
387
+void blob_append_json_literal(Blob *pOut, const char *z, int n){
388
+ int i;
389
+ blob_append_char(pOut, '"');
390
+ for(i=0; i<n; i++){
391
+ char c = z[i];
392
+ switch( c ){
393
+ case '"':
394
+ case '\\':
395
+ blob_append_char(pOut, '\\');
396
+ default:
397
+ blob_append_char(pOut, c);
398
+ }
399
+ }
400
+ blob_append_char(pOut, '"');
401
+}
402
+
352403
353404
/*
354405
** Return a pointer to a null-terminated string for a blob.
355406
*/
356407
char *blob_str(Blob *p){
357408
--- src/blob.c
+++ src/blob.c
@@ -347,10 +347,61 @@
347 void blob_copy(Blob *pTo, Blob *pFrom){
348 blob_is_init(pFrom);
349 blob_zero(pTo);
350 blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom));
351 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
353 /*
354 ** Return a pointer to a null-terminated string for a blob.
355 */
356 char *blob_str(Blob *p){
357
--- src/blob.c
+++ src/blob.c
@@ -347,10 +347,61 @@
347 void blob_copy(Blob *pTo, Blob *pFrom){
348 blob_is_init(pFrom);
349 blob_zero(pTo);
350 blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom));
351 }
352
353 /*
354 ** Append the second blob onto the end of the first blob and reset the
355 ** second blob.
356 */
357 void blob_append_xfer(Blob *pTo, Blob *pFrom){
358 blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom));
359 blob_reset(pFrom);
360 }
361
362 /*
363 ** Write into pOut, a string literal representation for the first n bytes
364 ** of z[]. The string literal representation is compatible with C, TCL,
365 ** and JSON. Double-quotes are added to both ends. Double-quote and
366 ** backslash characters are escaped.
367 */
368 void blob_append_tcl_literal(Blob *pOut, const char *z, int n){
369 int i;
370 blob_append_char(pOut, '"');
371 for(i=0; i<n; i++){
372 char c = z[i];
373 switch( c ){
374 case '\r': c = 'r';
375 case '[':
376 case ']':
377 case '$':
378 case '"':
379 case '\\':
380 blob_append_char(pOut, '\\');
381 default:
382 blob_append_char(pOut, c);
383 }
384 }
385 blob_append_char(pOut, '"');
386 }
387 void blob_append_json_literal(Blob *pOut, const char *z, int n){
388 int i;
389 blob_append_char(pOut, '"');
390 for(i=0; i<n; i++){
391 char c = z[i];
392 switch( c ){
393 case '"':
394 case '\\':
395 blob_append_char(pOut, '\\');
396 default:
397 blob_append_char(pOut, c);
398 }
399 }
400 blob_append_char(pOut, '"');
401 }
402
403
404 /*
405 ** Return a pointer to a null-terminated string for a blob.
406 */
407 char *blob_str(Blob *p){
408
+56 -36
--- src/default.css
+++ src/default.css
@@ -529,56 +529,76 @@
529529
line-height: 100%;
530530
}
531531
ul.filelist li {
532532
padding-top: 1px;
533533
}
534
-table.sbsdiffcols {
535
- width: 90%;
534
+
535
+/* Rules governing diff layout and colors */
536
+table.diff {
537
+ width: 98%;
536538
border-spacing: 0;
537
- font-size: xx-small;
539
+ border: 1px solid black;
538540
}
539
-table.sbsdiffcols td {
540
- padding: 0;
541
+table.diff td {
541542
vertical-align: top;
542543
}
543
-table.sbsdiffcols pre {
544
- margin: 0;
545
- padding: 0;
546
- border: 0;
547
- font-size: inherit;
548
- background: inherit;
549
- color: inherit;
550
-}
551
-div.difflncol {
552
- padding-right: 1em;
544
+table.diff pre {
545
+ margin: 0 0 0 0;
546
+}
547
+td.diffln {
548
+ width: 1px;
553549
text-align: right;
554
- color: #a0a0a0;
550
+ padding: 0 1em 0 0;
555551
}
556
-div.difftxtcol {
557
- width: 45em;
552
+td.difflne {
553
+ padding-bottom: 0.4em;
554
+}
555
+td.diffsep {
556
+ width: 1px;
557
+ padding: 0 0.3em 0 1em;
558
+}
559
+td.difftxt pre {
558560
overflow-x: auto;
559561
}
560
-div.diffmkrcol {
561
- padding: 0 1em;
562
+td.diffln ins {
563
+ background-color: #a0e4b2;
564
+ text-decoration: none;
562565
}
563
-span.diffchng {
566
+td.diffln del {
567
+ background-color: #ffc0c0;
568
+ text-decoration: none;
569
+}
570
+td.difftxt del {
571
+ background-color: #ffe8e8;
572
+ text-decoration: none;
573
+}
574
+td.difftxt del > del {
575
+ background-color: #ffc0c0;
576
+ text-decoration: none;
577
+ font-weight: bold;
578
+}
579
+td.difftxt del > del.edit {
580
+ background-color: #c0c0ff;
581
+ text-decoration: none;
582
+ font-weight: bold;
583
+}
584
+td.difftxt ins {
585
+ background-color: #dafbe1;
586
+ text-decoration: none;
587
+}
588
+td.difftxt ins > ins {
589
+ background-color: #a0e4b2;
590
+ text-decoration: none;
591
+ font-weight: bold;
592
+}
593
+td.difftxt ins > ins.edit {
564594
background-color: #c0c0ff;
565
-}
566
-span.diffadd {
567
- background-color: #c0ffc0;
568
-}
569
-span.diffrm {
570
- background-color: #ffc8c8;
571
-}
572
-span.diffhr {
573
- display: inline-block;
574
- margin: .5em 0 1em;
575
- color: #0000ff;
576
-}
577
-span.diffln {
578
- color: #a0a0a0;
579
-}
595
+ text-decoration: none;
596
+ font-weight: bold;
597
+}
598
+
599
+
580600
span.modpending {
581601
color: #b03800;
582602
font-style: italic;
583603
}
584604
pre.th1result {
585605
--- src/default.css
+++ src/default.css
@@ -529,56 +529,76 @@
529 line-height: 100%;
530 }
531 ul.filelist li {
532 padding-top: 1px;
533 }
534 table.sbsdiffcols {
535 width: 90%;
 
 
536 border-spacing: 0;
537 font-size: xx-small;
538 }
539 table.sbsdiffcols td {
540 padding: 0;
541 vertical-align: top;
542 }
543 table.sbsdiffcols pre {
544 margin: 0;
545 padding: 0;
546 border: 0;
547 font-size: inherit;
548 background: inherit;
549 color: inherit;
550 }
551 div.difflncol {
552 padding-right: 1em;
553 text-align: right;
554 color: #a0a0a0;
555 }
556 div.difftxtcol {
557 width: 45em;
 
 
 
 
 
 
558 overflow-x: auto;
559 }
560 div.diffmkrcol {
561 padding: 0 1em;
 
562 }
563 span.diffchng {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564 background-color: #c0c0ff;
565 }
566 span.diffadd {
567 background-color: #c0ffc0;
568 }
569 span.diffrm {
570 background-color: #ffc8c8;
571 }
572 span.diffhr {
573 display: inline-block;
574 margin: .5em 0 1em;
575 color: #0000ff;
576 }
577 span.diffln {
578 color: #a0a0a0;
579 }
580 span.modpending {
581 color: #b03800;
582 font-style: italic;
583 }
584 pre.th1result {
585
--- src/default.css
+++ src/default.css
@@ -529,56 +529,76 @@
529 line-height: 100%;
530 }
531 ul.filelist li {
532 padding-top: 1px;
533 }
534
535 /* Rules governing diff layout and colors */
536 table.diff {
537 width: 98%;
538 border-spacing: 0;
539 border: 1px solid black;
540 }
541 table.diff td {
 
542 vertical-align: top;
543 }
544 table.diff pre {
545 margin: 0 0 0 0;
546 }
547 td.diffln {
548 width: 1px;
 
 
 
 
 
549 text-align: right;
550 padding: 0 1em 0 0;
551 }
552 td.difflne {
553 padding-bottom: 0.4em;
554 }
555 td.diffsep {
556 width: 1px;
557 padding: 0 0.3em 0 1em;
558 }
559 td.difftxt pre {
560 overflow-x: auto;
561 }
562 td.diffln ins {
563 background-color: #a0e4b2;
564 text-decoration: none;
565 }
566 td.diffln del {
567 background-color: #ffc0c0;
568 text-decoration: none;
569 }
570 td.difftxt del {
571 background-color: #ffe8e8;
572 text-decoration: none;
573 }
574 td.difftxt del > del {
575 background-color: #ffc0c0;
576 text-decoration: none;
577 font-weight: bold;
578 }
579 td.difftxt del > del.edit {
580 background-color: #c0c0ff;
581 text-decoration: none;
582 font-weight: bold;
583 }
584 td.difftxt ins {
585 background-color: #dafbe1;
586 text-decoration: none;
587 }
588 td.difftxt ins > ins {
589 background-color: #a0e4b2;
590 text-decoration: none;
591 font-weight: bold;
592 }
593 td.difftxt ins > ins.edit {
594 background-color: #c0c0ff;
595 text-decoration: none;
596 font-weight: bold;
597 }
598
599
 
 
 
 
 
 
 
 
 
 
600 span.modpending {
601 color: #b03800;
602 font-style: italic;
603 }
604 pre.th1result {
605
+1383 -664
--- src/diff.c
+++ src/diff.c
@@ -42,12 +42,16 @@
4242
#define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */
4343
#define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */
4444
#define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */
4545
#define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */
4646
#define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */
47
-#define DIFF_WEBPAGE (((u64)0x40)<<32) /* Complete webpage */
48
-#define DIFF_BROWSER (((u64)0x80)<<32) /* The --browser option */
47
+#define DIFF_WEBPAGE (((u64)0x00040)<<32) /* Complete webpage */
48
+#define DIFF_BROWSER (((u64)0x00080)<<32) /* The --browser option */
49
+#define DIFF_JSON (((u64)0x00100)<<32) /* JSON output */
50
+#define DIFF_DEBUG (((u64)0x00200)<<32) /* Debugging diff output */
51
+#define DIFF_RAW (((u64)0x00400)<<32) /* Raw triples - for debugging */
52
+#define DIFF_TCL (((u64)0x00800)<<32) /* For the --tk option */
4953
5054
/*
5155
** These error messages are shared in multiple locations. They are defined
5256
** here for consistency.
5357
*/
@@ -95,10 +99,15 @@
9599
/*
96100
** Length of a dline
97101
*/
98102
#define LENGTH(X) ((X)->n)
99103
104
+/*
105
+** Number of diff chunks generated
106
+*/
107
+static int nChunk = 0;
108
+
100109
/*
101110
** A context for running a raw diff.
102111
**
103112
** The aEdit[] array describes the raw diff. Each triple of integers in
104113
** aEdit[] means:
@@ -273,13 +282,13 @@
273282
/*
274283
** Return true if the regular expression *pRe matches any of the
275284
** N dlines
276285
*/
277286
static int re_dline_match(
278
- ReCompiled *pRe, /* The regular expression to be matched */
279
- DLine *aDLine, /* First of N DLines to compare against */
280
- int N /* Number of DLines to check */
287
+ ReCompiled *pRe, /* The regular expression to be matched */
288
+ const DLine *aDLine, /* First of N DLines to compare against */
289
+ int N /* Number of DLines to check */
281290
){
282291
while( N-- ){
283292
if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){
284293
return 1;
285294
}
@@ -292,41 +301,23 @@
292301
** Append a single line of context-diff output to pOut.
293302
*/
294303
static void appendDiffLine(
295304
Blob *pOut, /* Where to write the line of output */
296305
char cPrefix, /* One of " ", "+", or "-" */
297
- DLine *pLine, /* The line to be output */
298
- int html, /* True if generating HTML. False for plain text */
299
- ReCompiled *pRe /* Colorize only if line matches this Regex */
306
+ DLine *pLine /* The line to be output */
300307
){
301
- blob_append(pOut, &cPrefix, 1);
302
- if( html ){
303
- if( pRe && re_dline_match(pRe, pLine, 1)==0 ){
304
- cPrefix = ' ';
305
- }else if( cPrefix=='+' ){
306
- blob_append(pOut, "<span class=\"diffadd\">", -1);
307
- }else if( cPrefix=='-' ){
308
- blob_append(pOut, "<span class=\"diffrm\">", -1);
309
- }
310
- htmlize_to_blob(pOut, pLine->z, pLine->n);
311
- if( cPrefix!=' ' ){
312
- blob_append(pOut, "</span>", -1);
313
- }
314
- }else{
315
- blob_append(pOut, pLine->z, pLine->n);
316
- }
317
- blob_append(pOut, "\n", 1);
308
+ blob_append_char(pOut, cPrefix);
309
+ blob_append(pOut, pLine->z, pLine->n);
310
+ blob_append_char(pOut, '\n');
318311
}
319312
320313
/*
321314
** Add two line numbers to the beginning of an output line for a context
322315
** diff. One or the other of the two numbers might be zero, which means
323
-** to leave that number field blank. The "html" parameter means to format
324
-** the output for HTML.
316
+** to leave that number field blank.
325317
*/
326
-static void appendDiffLineno(Blob *pOut, int lnA, int lnB, int html){
327
- if( html ) blob_append(pOut, "<span class=\"diffln\">", -1);
318
+static void appendDiffLineno(Blob *pOut, int lnA, int lnB){
328319
if( lnA>0 ){
329320
blob_appendf(pOut, "%6d ", lnA);
330321
}else{
331322
blob_append(pOut, " ", 7);
332323
}
@@ -333,21 +324,18 @@
333324
if( lnB>0 ){
334325
blob_appendf(pOut, "%6d ", lnB);
335326
}else{
336327
blob_append(pOut, " ", 8);
337328
}
338
- if( html ) blob_append(pOut, "</span>", -1);
339329
}
340330
341331
/*
342
-** Given a raw diff p[] in which the p->aEdit[] array has been filled
343
-** in, compute a context diff into pOut.
332
+** Output a patch-style text diff.
344333
*/
345334
static void contextDiff(
346335
DContext *p, /* The difference */
347336
Blob *pOut, /* Output a context diff to here */
348
- ReCompiled *pRe, /* Only show changes that match this regex */
349337
u64 diffFlags /* Flags controlling the diff format */
350338
){
351339
DLine *A; /* Left side of the diff */
352340
DLine *B; /* Right side of the diff */
353341
int a = 0; /* Index of next line in A[] */
@@ -361,17 +349,14 @@
361349
int m; /* Number of lines to output */
362350
int skip; /* Number of lines to skip */
363351
static int nChunk = 0; /* Number of diff chunks seen so far */
364352
int nContext; /* Number of lines of context */
365353
int showLn; /* Show line numbers */
366
- int html; /* Render as HTML */
367354
int showDivider = 0; /* True to show the divider between diff blocks */
368355
369356
nContext = diff_context_lines(diffFlags);
370357
showLn = (diffFlags & DIFF_LINENO)!=0;
371
- html = (diffFlags & DIFF_HTML)!=0;
372
- if( html ) blob_append(pOut, "<pre class=\"udiff\">\n", -1);
373358
A = p->aFrom;
374359
B = p->aTo;
375360
R = p->aEdit;
376361
mxr = p->nEdit;
377362
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
@@ -378,35 +363,10 @@
378363
for(r=0; r<mxr; r += 3*nr){
379364
/* Figure out how many triples to show in a single block */
380365
for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
381366
/* printf("r=%d nr=%d\n", r, nr); */
382367
383
- /* If there is a regex, skip this block (generate no diff output)
384
- ** if the regex matches or does not match both insert and delete.
385
- ** Only display the block if one side matches but the other side does
386
- ** not.
387
- */
388
- if( pRe ){
389
- int hideBlock = 1;
390
- int xa = a, xb = b;
391
- for(i=0; hideBlock && i<nr; i++){
392
- int c1, c2;
393
- xa += R[r+i*3];
394
- xb += R[r+i*3];
395
- c1 = re_dline_match(pRe, &A[xa], R[r+i*3+1]);
396
- c2 = re_dline_match(pRe, &B[xb], R[r+i*3+2]);
397
- hideBlock = c1==c2;
398
- xa += R[r+i*3+1];
399
- xb += R[r+i*3+2];
400
- }
401
- if( hideBlock ){
402
- a = xa;
403
- b = xb;
404
- continue;
405
- }
406
- }
407
-
408368
/* For the current block comprising nr triples, figure out
409369
** how many lines of A and B are to be displayed
410370
*/
411371
if( R[r]>nContext ){
412372
na = nb = nContext;
@@ -438,60 +398,55 @@
438398
nChunk++;
439399
if( showLn ){
440400
if( !showDivider ){
441401
/* Do not show a top divider */
442402
showDivider = 1;
443
- }else if( html ){
444
- blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.');
445403
}else{
446404
blob_appendf(pOut, "%.80c\n", '.');
447405
}
448
- if( html ) blob_appendf(pOut, "<span id=\"chunk%d\"></span>", nChunk);
449406
}else{
450
- if( html ) blob_appendf(pOut, "<span class=\"diffln\">");
451407
/*
452408
* If the patch changes an empty file or results in an empty file,
453409
* the block header must use 0,0 as position indicator and not 1,0.
454410
* Otherwise, patch would be confused and may reject the diff.
455411
*/
456412
blob_appendf(pOut,"@@ -%d,%d +%d,%d @@",
457413
na ? a+skip+1 : a+skip, na,
458414
nb ? b+skip+1 : b+skip, nb);
459
- if( html ) blob_appendf(pOut, "</span>");
460415
blob_append(pOut, "\n", 1);
461416
}
462417
463418
/* Show the initial common area */
464419
a += skip;
465420
b += skip;
466421
m = R[r] - skip;
467422
for(j=0; j<m; j++){
468
- if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
469
- appendDiffLine(pOut, ' ', &A[a+j], html, 0);
423
+ if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
424
+ appendDiffLine(pOut, ' ', &A[a+j]);
470425
}
471426
a += m;
472427
b += m;
473428
474429
/* Show the differences */
475430
for(i=0; i<nr; i++){
476431
m = R[r+i*3+1];
477432
for(j=0; j<m; j++){
478
- if( showLn ) appendDiffLineno(pOut, a+j+1, 0, html);
479
- appendDiffLine(pOut, '-', &A[a+j], html, pRe);
433
+ if( showLn ) appendDiffLineno(pOut, a+j+1, 0);
434
+ appendDiffLine(pOut, '-', &A[a+j]);
480435
}
481436
a += m;
482437
m = R[r+i*3+2];
483438
for(j=0; j<m; j++){
484
- if( showLn ) appendDiffLineno(pOut, 0, b+j+1, html);
485
- appendDiffLine(pOut, '+', &B[b+j], html, pRe);
439
+ if( showLn ) appendDiffLineno(pOut, 0, b+j+1);
440
+ appendDiffLine(pOut, '+', &B[b+j]);
486441
}
487442
b += m;
488443
if( i<nr-1 ){
489444
m = R[r+i*3+3];
490445
for(j=0; j<m; j++){
491
- if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
492
- appendDiffLine(pOut, ' ', &A[a+j], html, 0);
446
+ if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
447
+ appendDiffLine(pOut, ' ', &A[a+j]);
493448
}
494449
b += m;
495450
a += m;
496451
}
497452
}
@@ -499,181 +454,33 @@
499454
/* Show the final common area */
500455
assert( nr==i );
501456
m = R[r+nr*3];
502457
if( m>nContext ) m = nContext;
503458
for(j=0; j<m; j++){
504
- if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
505
- appendDiffLine(pOut, ' ', &A[a+j], html, 0);
506
- }
507
- }
508
- if( html ) blob_append(pOut, "</pre>\n", -1);
509
-}
510
-
511
-/*
512
-** Status of a single output line
513
-*/
514
-typedef struct SbsLine SbsLine;
515
-struct SbsLine {
516
- Blob *apCols[5]; /* Array of pointers to output columns */
517
- int width; /* Maximum width of a column in the output */
518
- unsigned char escHtml; /* True to escape html characters */
519
- int iStart; /* Write zStart prior to character iStart */
520
- const char *zStart; /* A <span> tag */
521
- int iEnd; /* Write </span> prior to character iEnd */
522
- int iStart2; /* Write zStart2 prior to character iStart2 */
523
- const char *zStart2; /* A <span> tag */
524
- int iEnd2; /* Write </span> prior to character iEnd2 */
525
- ReCompiled *pRe; /* Only colorize matching lines, if not NULL */
526
-};
527
-
528
-/*
529
-** Column indices for SbsLine.apCols[]
530
-*/
531
-#define SBS_LNA 0 /* Left line number */
532
-#define SBS_TXTA 1 /* Left text */
533
-#define SBS_MKR 2 /* Middle separator column */
534
-#define SBS_LNB 3 /* Right line number */
535
-#define SBS_TXTB 4 /* Right text */
536
-
537
-/*
538
-** Append newlines to all columns.
539
-*/
540
-static void sbsWriteNewlines(SbsLine *p){
541
- int i;
542
- for( i=p->escHtml ? SBS_LNA : SBS_TXTB; i<=SBS_TXTB; i++ ){
543
- blob_append(p->apCols[i], "\n", 1);
544
- }
545
-}
546
-
547
-/*
548
-** Append n spaces to the column.
549
-*/
550
-static void sbsWriteSpace(SbsLine *p, int n, int col){
551
- blob_appendf(p->apCols[col], "%*s", n, "");
552
-}
553
-
554
-/*
555
-** Write the text of pLine into column iCol of p.
556
-**
557
-** If outputting HTML, write the full line. Otherwise, only write the
558
-** width characters. Translate tabs into spaces. Add newlines if col
559
-** is SBS_TXTB. Translate HTML characters if escHtml is true. Pad the
560
-** rendering to width bytes if col is SBS_TXTA and escHtml is false.
561
-**
562
-** This comment contains multibyte unicode characters (ü, Æ, ð) in order
563
-** to test the ability of the diff code to handle such characters.
564
-*/
565
-static void sbsWriteText(SbsLine *p, DLine *pLine, int col){
566
- Blob *pCol = p->apCols[col];
567
- int n = pLine->n;
568
- int i; /* Number of input characters consumed */
569
- int k; /* Cursor position */
570
- int needEndSpan = 0;
571
- const char *zIn = pLine->z;
572
- int w = p->width;
573
- int colorize = p->escHtml;
574
- if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
575
- colorize = 0;
576
- }
577
- for(i=k=0; (p->escHtml || k<w) && i<n; i++, k++){
578
- char c = zIn[i];
579
- if( colorize ){
580
- if( i==p->iStart ){
581
- int x = strlen(p->zStart);
582
- blob_append(pCol, p->zStart, x);
583
- needEndSpan = 1;
584
- if( p->iStart2 ){
585
- p->iStart = p->iStart2;
586
- p->zStart = p->zStart2;
587
- p->iStart2 = 0;
588
- }
589
- }else if( i==p->iEnd ){
590
- blob_append(pCol, "</span>", 7);
591
- needEndSpan = 0;
592
- if( p->iEnd2 ){
593
- p->iEnd = p->iEnd2;
594
- p->iEnd2 = 0;
595
- }
596
- }
597
- }
598
- if( c=='\t' && !p->escHtml ){
599
- blob_append(pCol, " ", 1);
600
- while( (k&7)!=7 && (p->escHtml || k<w) ){
601
- blob_append(pCol, " ", 1);
602
- k++;
603
- }
604
- }else if( c=='\r' || c=='\f' ){
605
- blob_append(pCol, " ", 1);
606
- }else if( c=='<' && p->escHtml ){
607
- blob_append(pCol, "&lt;", 4);
608
- }else if( c=='&' && p->escHtml ){
609
- blob_append(pCol, "&amp;", 5);
610
- }else if( c=='>' && p->escHtml ){
611
- blob_append(pCol, "&gt;", 4);
612
- }else if( c=='"' && p->escHtml ){
613
- blob_append(pCol, "&quot;", 6);
614
- }else{
615
- blob_append(pCol, &zIn[i], 1);
616
- if( (c&0xc0)==0x80 ) k--;
617
- }
618
- }
619
- if( needEndSpan ){
620
- blob_append(pCol, "</span>", 7);
621
- }
622
- if( col==SBS_TXTB ){
623
- sbsWriteNewlines(p);
624
- }else if( !p->escHtml ){
625
- sbsWriteSpace(p, w-k, SBS_TXTA);
626
- }
627
-}
628
-
629
-/*
630
-** Append a column to the final output blob.
631
-*/
632
-static void sbsWriteColumn(Blob *pOut, Blob *pCol, int col){
633
- blob_appendf(pOut,
634
- "<td><div class=\"diff%scol\">\n"
635
- "<pre>\n"
636
- "%s"
637
- "</pre>\n"
638
- "</div></td>\n",
639
- (col % 3) ? (col == SBS_MKR ? "mkr" : "txt") : "ln",
640
- blob_str(pCol)
641
- );
642
-}
643
-
644
-/*
645
-** Append a separator line to column iCol
646
-*/
647
-static void sbsWriteSep(SbsLine *p, int len, int col){
648
- char ch = '.';
649
- if( len<1 ){
650
- len = 1;
651
- ch = ' ';
652
- }
653
- blob_appendf(p->apCols[col], "<span class=\"diffhr\">%.*c</span>\n", len, ch);
654
-}
655
-
656
-/*
657
-** Append the appropriate marker into the center column of the diff.
658
-*/
659
-static void sbsWriteMarker(SbsLine *p, const char *zTxt, const char *zHtml){
660
- blob_append(p->apCols[SBS_MKR], p->escHtml ? zHtml : zTxt, -1);
661
-}
662
-
663
-/*
664
-** Append a line number to the column.
665
-*/
666
-static void sbsWriteLineno(SbsLine *p, int ln, int col){
667
- if( p->escHtml ){
668
- blob_appendf(p->apCols[col], "%d", ln+1);
669
- }else{
670
- char zLn[7];
671
- sqlite3_snprintf(7, zLn, "%5d ", ln+1);
672
- blob_appendf(p->apCols[col], "%s ", zLn);
673
- }
674
-}
459
+ if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
460
+ appendDiffLine(pOut, ' ', &A[a+j]);
461
+ }
462
+ }
463
+}
464
+
465
+#define MX_CSN 8 /* Maximum number of change spans across a change region */
466
+
467
+/*
468
+** A description of zero or more (up to MX_CSN) areas of difference
469
+** between two lines of text.
470
+*/
471
+typedef struct LineChange LineChange;
472
+struct LineChange {
473
+ int n; /* Number of change spans */
474
+ struct Span {
475
+ int iStart1; /* Byte offset to start of a change on the left */
476
+ int iLen1; /* Length of the left change in bytes */
477
+ int iStart2; /* Byte offset to start of a change on the right */
478
+ int iLen2; /* Length of the change on the right in bytes */
479
+ int isMin; /* True if this change is known to have no useful subdivs */
480
+ } a[MX_CSN]; /* Array of change spans, sorted order */
481
+};
675482
676483
/*
677484
** The two text segments zLeft and zRight are known to be different on
678485
** both ends, but they might have a common segment in the middle. If
679486
** they do not have a common segment, return 0. If they do have a large
@@ -692,123 +499,177 @@
692499
const char *zRight, int nB, /* String on the right */
693500
int *aLCS /* Identify bounds of LCS here */
694501
){
695502
const unsigned char *zA = (const unsigned char*)zLeft; /* left string */
696503
const unsigned char *zB = (const unsigned char*)zRight; /* right string */
697
- int nt; /* Number of target points */
698
- int ti[3]; /* Index for start of each 4-byte target */
699
- unsigned int target[3]; /* 4-byte alignment targets */
700
- unsigned int probe; /* probe to compare against target */
701
- int iAS, iAE, iBS, iBE; /* Range of common segment */
702
- int i, j; /* Loop counters */
703
- int rc = 0; /* Result code. 1 for success */
704
-
705
- if( nA<6 || nB<6 ) return 0;
706
- memset(aLCS, 0, sizeof(int)*4);
707
- ti[0] = i = nB/2-2;
708
- target[0] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
709
- probe = 0;
710
- if( nB<16 ){
711
- nt = 1;
712
- }else{
713
- ti[1] = i = nB/4-2;
714
- target[1] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
715
- ti[2] = i = (nB*3)/4-2;
716
- target[2] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
717
- nt = 3;
718
- }
719
- probe = (zA[0]<<16) | (zA[1]<<8) | zA[2];
720
- for(i=3; i<nA; i++){
721
- probe = (probe<<8) | zA[i];
722
- for(j=0; j<nt; j++){
723
- if( probe==target[j] ){
724
- iAS = i-3;
725
- iAE = i+1;
726
- iBS = ti[j];
727
- iBE = ti[j]+4;
728
- while( iAE<nA && iBE<nB && zA[iAE]==zB[iBE] ){ iAE++; iBE++; }
729
- while( iAS>0 && iBS>0 && zA[iAS-1]==zB[iBS-1] ){ iAS--; iBS--; }
730
- if( iAE-iAS > aLCS[1] - aLCS[0] ){
731
- aLCS[0] = iAS;
732
- aLCS[1] = iAE;
733
- aLCS[2] = iBS;
734
- aLCS[3] = iBE;
735
- rc = 1;
736
- }
737
- }
738
- }
739
- }
740
- return rc;
741
-}
742
-
743
-/*
744
-** Try to shift iStart as far as possible to the left.
745
-*/
746
-static void sbsShiftLeft(SbsLine *p, const char *z){
747
- int i, j;
748
- while( (i=p->iStart)>0 && z[i-1]==z[i] ){
749
- for(j=i+1; j<p->iEnd && z[j-1]==z[j]; j++){}
750
- if( j<p->iEnd ) break;
751
- p->iStart--;
752
- p->iEnd--;
753
- }
754
-}
755
-
756
-/*
757
-** Simplify iStart and iStart2:
758
-**
759
-** * If iStart is a null-change then move iStart2 into iStart
760
-** * Make sure any null-changes are in canonoical form.
761
-** * Make sure all changes are at character boundaries for
762
-** multi-byte characters.
763
-*/
764
-static void sbsSimplifyLine(SbsLine *p, const char *z){
765
- if( p->iStart2==p->iEnd2 ){
766
- p->iStart2 = p->iEnd2 = 0;
767
- }else if( p->iStart2 ){
768
- while( p->iStart2>0 && (z[p->iStart2]&0xc0)==0x80 ) p->iStart2--;
769
- while( (z[p->iEnd2]&0xc0)==0x80 ) p->iEnd2++;
770
- }
771
- if( p->iStart==p->iEnd ){
772
- p->iStart = p->iStart2;
773
- p->iEnd = p->iEnd2;
774
- p->zStart = p->zStart2;
775
- p->iStart2 = 0;
776
- p->iEnd2 = 0;
777
- }
778
- if( p->iStart==p->iEnd ){
779
- p->iStart = p->iEnd = -1;
780
- }else if( p->iStart>0 ){
781
- while( p->iStart>0 && (z[p->iStart]&0xc0)==0x80 ) p->iStart--;
782
- while( (z[p->iEnd]&0xc0)==0x80 ) p->iEnd++;
783
- }
784
-}
785
-
786
-/*
787
-** Write out lines that have been edited. Adjust the highlight to cover
788
-** only those parts of the line that actually changed.
789
-*/
790
-static void sbsWriteLineChange(
791
- SbsLine *p, /* The SBS output line */
792
- DLine *pLeft, /* Left line of the change */
793
- int lnLeft, /* Line number for the left line */
794
- DLine *pRight, /* Right line of the change */
795
- int lnRight /* Line number of the right line */
504
+ int i, j, k; /* Loop counters */
505
+ int lenBest = 0; /* Match length to beat */
506
+
507
+ for(i=0; i<nA-lenBest; i++){
508
+ unsigned char cA = zA[i];
509
+ if( (cA&0xc0)==0x80 ) continue;
510
+ for(j=0; j<nB-lenBest; j++ ){
511
+ if( zB[j]==cA ){
512
+ for(k=1; j+k<nB && i+k<nA && zB[j+k]==zA[i+k]; k++){}
513
+ while( (zB[j+k]&0xc0)==0x80 ){ k--; }
514
+ if( k>lenBest ){
515
+ lenBest = k;
516
+ aLCS[0] = i;
517
+ aLCS[1] = i+k;
518
+ aLCS[2] = j;
519
+ aLCS[3] = j+k;
520
+ }
521
+ }
522
+ }
523
+ }
524
+ return lenBest>0;
525
+}
526
+
527
+/*
528
+** Find the smallest spans that are different between two text strings that
529
+** are known to be different on both ends.
530
+*/
531
+static int textLineChanges(
532
+ const char *zLeft, int nA, /* String on the left */
533
+ const char *zRight, int nB, /* String on the right */
534
+ LineChange *p /* Write results here */
535
+){
536
+ p->n = 1;
537
+ p->a[0].iStart1 = 0;
538
+ p->a[0].iLen1 = nA;
539
+ p->a[0].iStart2 = 0;
540
+ p->a[0].iLen2 = nB;
541
+ p->a[0].isMin = 0;
542
+ while( p->n<MX_CSN-1 ){
543
+ int mxi = -1;
544
+ int mxLen = -1;
545
+ int x, i;
546
+ int aLCS[4];
547
+ struct Span *a, *b;
548
+ for(i=0; i<p->n; i++){
549
+ if( p->a[i].isMin ) continue;
550
+ x = p->a[i].iLen1;
551
+ if( p->a[i].iLen2<x ) x = p->a[i].iLen2;
552
+ if( x>mxLen ){
553
+ mxLen = x;
554
+ mxi = i;
555
+ }
556
+ }
557
+ if( mxLen<6 ) break;
558
+ x = textLCS(zLeft + p->a[mxi].iStart1, p->a[mxi].iLen1,
559
+ zRight + p->a[mxi].iStart2, p->a[mxi].iLen2, aLCS);
560
+ if( x==0 ){
561
+ p->a[mxi].isMin = 1;
562
+ continue;
563
+ }
564
+ a = p->a+mxi;
565
+ b = a+1;
566
+ if( mxi<p->n-1 ){
567
+ memmove(b+1, b, sizeof(*b)*(p->n-mxi-1));
568
+ }
569
+ p->n++;
570
+ b->iStart1 = a->iStart1 + aLCS[1];
571
+ b->iLen1 = a->iLen1 - aLCS[1];
572
+ a->iLen1 = aLCS[0];
573
+ b->iStart2 = a->iStart2 + aLCS[3];
574
+ b->iLen2 = a->iLen2 - aLCS[3];
575
+ a->iLen2 = aLCS[2];
576
+ b->isMin = 0;
577
+ }
578
+ return p->n;
579
+}
580
+
581
+/*
582
+** Return true if the string starts with n spaces
583
+*/
584
+static int allSpaces(const char *z, int n){
585
+ int i;
586
+ for(i=0; i<n && fossil_isspace(z[i]); i++){}
587
+ return i==n;
588
+}
589
+
590
+/*
591
+** Try to improve the human-readability of the LineChange p.
592
+**
593
+** (1) If the first change span shows a change of indentation, try to
594
+** move that indentation change to the left margin.
595
+**
596
+** (2) Try to shift changes so that they begin or end with a space.
597
+*/
598
+static void improveReadability(
599
+ const char *zA, /* Left line of the change */
600
+ const char *zB, /* Right line of the change */
601
+ LineChange *p /* The LineChange to be adjusted */
602
+){
603
+ int j, n, len;
604
+ if( p->n<1 ) return;
605
+
606
+ /* (1) Attempt to move indentation changes to the left margin */
607
+ if( p->a[0].iLen1==0
608
+ && (len = p->a[0].iLen2)>0
609
+ && (j = p->a[0].iStart2)>0
610
+ && zB[0]==zB[j]
611
+ && allSpaces(zB, j)
612
+ ){
613
+ for(n=1; n<len && n<j && zB[j]==zB[j+n]; n++){}
614
+ if( n<len ){
615
+ memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n);
616
+ p->n++;
617
+ p->a[0] = p->a[1];
618
+ p->a[1].iStart2 += n;
619
+ p->a[1].iLen2 -= n;
620
+ p->a[0].iLen2 = n;
621
+ }
622
+ p->a[0].iStart1 = 0;
623
+ p->a[0].iStart2 = 0;
624
+ }else
625
+ if( p->a[0].iLen2==0
626
+ && (len = p->a[0].iLen1)>0
627
+ && (j = p->a[0].iStart1)>0
628
+ && zA[0]==zA[j]
629
+ && allSpaces(zA, j)
630
+ ){
631
+ for(n=1; n<len && n<j && zA[j]==zA[j+n]; n++){}
632
+ if( n<len ){
633
+ memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n);
634
+ p->n++;
635
+ p->a[0] = p->a[1];
636
+ p->a[1].iStart1 += n;
637
+ p->a[1].iLen1 -= n;
638
+ p->a[0].iLen1 = n;
639
+ }
640
+ p->a[0].iStart1 = 0;
641
+ p->a[0].iStart2 = 0;
642
+ }
643
+
644
+ /* (2) Try to shift changes so that they begin or end with a
645
+ ** space. (TBD) */
646
+}
647
+
648
+
649
+/*
650
+** Given two lines of text, pFrom and pTo, compute a set of changes
651
+** between those two lines, for enhanced display purposes.
652
+**
653
+** The result is written into the LineChange object given by the
654
+** third parameter.
655
+*/
656
+static void oneLineChange(
657
+ const DLine *pLeft, /* Left line of the change */
658
+ const DLine *pRight, /* Right line of the change */
659
+ LineChange *p /* OUTPUT: Write the results here */
796660
){
797661
int nLeft; /* Length of left line in bytes */
798662
int nRight; /* Length of right line in bytes */
799663
int nShort; /* Shortest of left and right */
800664
int nPrefix; /* Length of common prefix */
801665
int nSuffix; /* Length of common suffix */
666
+ int nCommon; /* Total byte length of suffix and prefix */
802667
const char *zLeft; /* Text of the left line */
803668
const char *zRight; /* Text of the right line */
804669
int nLeftDiff; /* nLeft - nPrefix - nSuffix */
805670
int nRightDiff; /* nRight - nPrefix - nSuffix */
806
- int aLCS[4]; /* Bounds of common middle segment */
807
- static const char zClassRm[] = "<span class=\"diffrm\">";
808
- static const char zClassAdd[] = "<span class=\"diffadd\">";
809
- static const char zClassChng[] = "<span class=\"diffchng\">";
810671
811672
nLeft = pLeft->n;
812673
zLeft = pLeft->z;
813674
nRight = pRight->n;
814675
zRight = pRight->z;
@@ -821,25 +682,27 @@
821682
if( nPrefix<nShort ){
822683
while( nPrefix>0 && (zLeft[nPrefix]&0xc0)==0x80 ) nPrefix--;
823684
}
824685
nSuffix = 0;
825686
if( nPrefix<nShort ){
826
- while( nSuffix<nShort && zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
687
+ while( nSuffix<nShort
688
+ && zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
827689
nSuffix++;
828690
}
829691
if( nSuffix<nShort ){
830692
while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--;
831693
}
832694
if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
833695
}
696
+ nCommon = nPrefix + nSuffix;
834697
835698
/* If the prefix and suffix overlap, that means that we are dealing with
836699
** a pure insertion or deletion of text that can have multiple alignments.
837700
** Try to find an alignment to begins and ends on whitespace, or on
838701
** punctuation, rather than in the middle of a name or number.
839702
*/
840
- if( nPrefix+nSuffix > nShort ){
703
+ if( nCommon > nShort ){
841704
int iBest = -1;
842705
int iBestVal = -1;
843706
int i;
844707
int nLong = nLeft<nRight ? nRight : nLeft;
845708
int nGap = nLong - nShort;
@@ -862,108 +725,931 @@
862725
iBest = i;
863726
}
864727
}
865728
nPrefix = iBest;
866729
nSuffix = nShort - nPrefix;
867
- }
868
-
869
- /* A single chunk of text inserted on the right */
870
- if( nPrefix+nSuffix==nLeft ){
871
- sbsWriteLineno(p, lnLeft, SBS_LNA);
872
- p->iStart2 = p->iEnd2 = 0;
873
- p->iStart = p->iEnd = -1;
874
- sbsWriteText(p, pLeft, SBS_TXTA);
875
- if( nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){
876
- sbsWriteMarker(p, " ", "");
877
- }else{
878
- sbsWriteMarker(p, " | ", "|");
879
- }
880
- sbsWriteLineno(p, lnRight, SBS_LNB);
881
- p->iStart = nPrefix;
882
- p->iEnd = nRight - nSuffix;
883
- p->zStart = zClassAdd;
884
- sbsWriteText(p, pRight, SBS_TXTB);
730
+ nCommon = nPrefix + nSuffix;
731
+ }
732
+
733
+ /* A single chunk of text inserted */
734
+ if( nCommon==nLeft ){
735
+ p->n = 1;
736
+ p->a[0].iStart1 = nPrefix;
737
+ p->a[0].iLen1 = 0;
738
+ p->a[0].iStart2 = nPrefix;
739
+ p->a[0].iLen2 = nRight - nCommon;
740
+ improveReadability(zLeft, zRight, p);
885741
return;
886742
}
887743
888
- /* A single chunk of text deleted from the left */
889
- if( nPrefix+nSuffix==nRight ){
890
- /* Text deleted from the left */
891
- sbsWriteLineno(p, lnLeft, SBS_LNA);
892
- p->iStart2 = p->iEnd2 = 0;
893
- p->iStart = nPrefix;
894
- p->iEnd = nLeft - nSuffix;
895
- p->zStart = zClassRm;
896
- sbsWriteText(p, pLeft, SBS_TXTA);
897
- sbsWriteMarker(p, " | ", "|");
898
- sbsWriteLineno(p, lnRight, SBS_LNB);
899
- p->iStart = p->iEnd = -1;
900
- sbsWriteText(p, pRight, SBS_TXTB);
744
+ /* A single chunk of text deleted */
745
+ if( nCommon==nRight ){
746
+ p->n = 1;
747
+ p->a[0].iStart1 = nPrefix;
748
+ p->a[0].iLen1 = nLeft - nCommon;
749
+ p->a[0].iStart2 = nPrefix;
750
+ p->a[0].iLen2 = 0;
751
+ improveReadability(zLeft, zRight, p);
901752
return;
902753
}
903754
904755
/* At this point we know that there is a chunk of text that has
905756
** changed between the left and the right. Check to see if there
906757
** is a large unchanged section in the middle of that changed block.
907758
*/
908
- nLeftDiff = nLeft - nSuffix - nPrefix;
909
- nRightDiff = nRight - nSuffix - nPrefix;
910
- if( p->escHtml
911
- && nLeftDiff >= 6
912
- && nRightDiff >= 6
913
- && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS)
759
+ nLeftDiff = nLeft - nCommon;
760
+ nRightDiff = nRight - nCommon;
761
+ if( nLeftDiff >= 4
762
+ && nRightDiff >= 4
763
+ && textLineChanges(&zLeft[nPrefix], nLeftDiff,
764
+ &zRight[nPrefix], nRightDiff, p)>1
914765
){
915
- sbsWriteLineno(p, lnLeft, SBS_LNA);
916
- p->iStart = nPrefix;
917
- p->iEnd = nPrefix + aLCS[0];
918
- if( aLCS[2]==0 ){
919
- sbsShiftLeft(p, pLeft->z);
920
- p->zStart = zClassRm;
921
- }else{
922
- p->zStart = zClassChng;
923
- }
924
- p->iStart2 = nPrefix + aLCS[1];
925
- p->iEnd2 = nLeft - nSuffix;
926
- p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng;
927
- sbsSimplifyLine(p, zLeft);
928
- sbsWriteText(p, pLeft, SBS_TXTA);
929
- sbsWriteMarker(p, " | ", "|");
930
- sbsWriteLineno(p, lnRight, SBS_LNB);
931
- p->iStart = nPrefix;
932
- p->iEnd = nPrefix + aLCS[2];
933
- if( aLCS[0]==0 ){
934
- sbsShiftLeft(p, pRight->z);
935
- p->zStart = zClassAdd;
936
- }else{
937
- p->zStart = zClassChng;
938
- }
939
- p->iStart2 = nPrefix + aLCS[3];
940
- p->iEnd2 = nRight - nSuffix;
941
- p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng;
942
- sbsSimplifyLine(p, zRight);
943
- sbsWriteText(p, pRight, SBS_TXTB);
766
+ int i;
767
+ for(i=0; i<p->n; i++){
768
+ p->a[i].iStart1 += nPrefix;
769
+ p->a[i].iStart2 += nPrefix;
770
+ }
771
+ improveReadability(zLeft, zRight, p);
944772
return;
945773
}
946774
947775
/* If all else fails, show a single big change between left and right */
948
- sbsWriteLineno(p, lnLeft, SBS_LNA);
949
- p->iStart2 = p->iEnd2 = 0;
950
- p->iStart = nPrefix;
951
- p->iEnd = nLeft - nSuffix;
952
- p->zStart = zClassChng;
953
- sbsWriteText(p, pLeft, SBS_TXTA);
954
- sbsWriteMarker(p, " | ", "|");
955
- sbsWriteLineno(p, lnRight, SBS_LNB);
956
- p->iEnd = nRight - nSuffix;
957
- sbsWriteText(p, pRight, SBS_TXTB);
776
+ p->n = 1;
777
+ p->a[0].iStart1 = nPrefix;
778
+ p->a[0].iLen1 = nLeft - nCommon;
779
+ p->a[0].iStart2 = nPrefix;
780
+ p->a[0].iLen2 = nRight - nCommon;
781
+ improveReadability(zLeft, zRight, p);
782
+}
783
+
784
+/*
785
+** COMMAND: test-line-diff
786
+** Usage: %fossil% test-line-diff STRING1 STRING2
787
+**
788
+** Show the differences between the two strings. Used for testing
789
+** the oneLineChange() routine in the diff logic.
790
+*/
791
+void test_line_diff(void){
792
+ DLine a, b;
793
+ LineChange chng;
794
+ int i, j, x;
795
+ if( g.argc!=4 ) usage("STRING1 STRING2");
796
+ a.z = g.argv[2];
797
+ a.n = (int)strlen(a.z);
798
+ b.z = g.argv[3];
799
+ b.n = (int)strlen(b.z);
800
+ oneLineChange(&a, &b, &chng);
801
+ fossil_print("left: [%s]\n", a.z);
802
+ for(i=x=0; i<chng.n; i++){
803
+ int ofst = chng.a[i].iStart1;
804
+ int len = chng.a[i].iLen1;
805
+ if( len ){
806
+ if( x==0 ){ fossil_print("%*s", 8, ""); }
807
+ while( ofst > x ){
808
+ if( (a.z[x]&0xc0)!=0x80 ) fossil_print(" ");
809
+ x++;
810
+ }
811
+ for(j=0; j<len; j++, x++){
812
+ if( (a.z[x]&0xc0)!=0x80 ) fossil_print("%d",i);
813
+ }
814
+ }
815
+ }
816
+ if( x ) fossil_print("\n");
817
+ fossil_print("right: [%s]\n", b.z);
818
+ for(i=x=0; i<chng.n; i++){
819
+ int ofst = chng.a[i].iStart2;
820
+ int len = chng.a[i].iLen2;
821
+ if( len ){
822
+ if( x==0 ){ fossil_print("%*s", 8, ""); }
823
+ while( ofst > x ){
824
+ if( (b.z[x]&0xc0)!=0x80 ) fossil_print(" ");
825
+ x++;
826
+ }
827
+ for(j=0; j<len; j++, x++){
828
+ if( (b.z[x]&0xc0)!=0x80 ) fossil_print("%d",i);
829
+ }
830
+ }
831
+ }
832
+ if( x ) fossil_print("\n");
958833
}
959834
960835
/*
961836
** Minimum of two values
962837
*/
963838
static int minInt(int a, int b){ return a<b ? a : b; }
964839
840
+
841
+
842
+/*
843
+** This is an abstract superclass for an object that accepts difference
844
+** lines and formats them for display. Subclasses of this object format
845
+** the diff output in different ways.
846
+**
847
+** To subclass, create an instance of the DiffBuilder object and fill
848
+** in appropriate method implementations.
849
+*/
850
+typedef struct DiffBuilder DiffBuilder;
851
+struct DiffBuilder {
852
+ void (*xSkip)(DiffBuilder*, unsigned int, int);
853
+ void (*xCommon)(DiffBuilder*,const DLine*);
854
+ void (*xInsert)(DiffBuilder*,const DLine*);
855
+ void (*xDelete)(DiffBuilder*,const DLine*);
856
+ void (*xReplace)(DiffBuilder*,const DLine*, const DLine*);
857
+ void (*xEdit)(DiffBuilder*,const DLine*,const DLine*);
858
+ void (*xEnd)(DiffBuilder*);
859
+ unsigned int lnLeft; /* Lines seen on the left (delete) side */
860
+ unsigned int lnRight; /* Lines seen on the right (insert) side */
861
+ unsigned int nPending; /* Number of pending lines */
862
+ int eState; /* State of the output */
863
+ int width; /* Display width */
864
+ Blob *pOut; /* Output blob */
865
+ Blob aCol[5]; /* Holding blobs */
866
+};
867
+
868
+/************************* DiffBuilderDebug ********************************/
869
+/* This version of DiffBuilder is used for debugging the diff and diff
870
+** diff formatter logic. It is accessed using the (undocumented) --debug
871
+** option to the diff command. The output is human-readable text that
872
+** describes the various method calls that are invoked agains the DiffBuilder
873
+** object.
874
+*/
875
+static void dfdebugSkip(DiffBuilder *p, unsigned int n, int isFinal){
876
+ blob_appendf(p->pOut, "SKIP %d (%d..%d left and %d..%d right)%s\n",
877
+ n, p->lnLeft+1, p->lnLeft+n, p->lnRight+1, p->lnRight+n,
878
+ isFinal ? " FINAL" : "");
879
+ p->lnLeft += n;
880
+ p->lnRight += n;
881
+}
882
+static void dfdebugCommon(DiffBuilder *p, const DLine *pLine){
883
+ p->lnLeft++;
884
+ p->lnRight++;
885
+ blob_appendf(p->pOut, "COMMON %8u %8u %.*s\n",
886
+ p->lnLeft, p->lnRight, (int)pLine->n, pLine->z);
887
+}
888
+static void dfdebugInsert(DiffBuilder *p, const DLine *pLine){
889
+ p->lnRight++;
890
+ blob_appendf(p->pOut, "INSERT %8d %.*s\n",
891
+ p->lnRight, (int)pLine->n, pLine->z);
892
+}
893
+static void dfdebugDelete(DiffBuilder *p, const DLine *pLine){
894
+ p->lnLeft++;
895
+ blob_appendf(p->pOut, "DELETE %8u %.*s\n",
896
+ p->lnLeft, (int)pLine->n, pLine->z);
897
+}
898
+static void dfdebugReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
899
+ p->lnLeft++;
900
+ p->lnRight++;
901
+ blob_appendf(p->pOut, "REPLACE %8u %.*s\n",
902
+ p->lnLeft, (int)pX->n, pX->z);
903
+ blob_appendf(p->pOut, " %8u %.*s\n",
904
+ p->lnRight, (int)pY->n, pY->z);
905
+}
906
+static void dfdebugEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
907
+ int i, j;
908
+ int x;
909
+ LineChange chng;
910
+ p->lnLeft++;
911
+ p->lnRight++;
912
+ blob_appendf(p->pOut, "EDIT %8u %.*s\n",
913
+ p->lnLeft, (int)pX->n, pX->z);
914
+ oneLineChange(pX, pY, &chng);
915
+ for(i=x=0; i<chng.n; i++){
916
+ int ofst = chng.a[i].iStart1;
917
+ int len = chng.a[i].iLen1;
918
+ if( len ){
919
+ if( x==0 ){ blob_appendf(p->pOut, "%*s", 25, ""); }
920
+ while( ofst > x ){
921
+ if( (pX->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, ' ');
922
+ x++;
923
+ }
924
+ for(j=0; j<len; j++, x++){
925
+ if( (pX->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, '^');
926
+ }
927
+ }
928
+ }
929
+ if( x ) blob_append_char(p->pOut, '\n');
930
+ blob_appendf(p->pOut, " %8u %.*s\n",
931
+ p->lnRight, (int)pY->n, pY->z);
932
+ for(i=x=0; i<chng.n; i++){
933
+ int ofst = chng.a[i].iStart2;
934
+ int len = chng.a[i].iLen2;
935
+ if( len ){
936
+ if( x==0 ){ blob_appendf(p->pOut, "%*s", 25, ""); }
937
+ while( ofst > x ){
938
+ if( (pY->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, ' ');
939
+ x++;
940
+ }
941
+ for(j=0; j<len; j++, x++){
942
+ if( (pY->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, '^');
943
+ }
944
+ }
945
+ }
946
+ if( x ) blob_append_char(p->pOut, '\n');
947
+}
948
+static void dfdebugEnd(DiffBuilder *p){
949
+ blob_appendf(p->pOut, "END with %u lines left and %u lines right\n",
950
+ p->lnLeft, p->lnRight);
951
+ fossil_free(p);
952
+}
953
+static DiffBuilder *dfdebugNew(Blob *pOut){
954
+ DiffBuilder *p = fossil_malloc(sizeof(*p));
955
+ p->xSkip = dfdebugSkip;
956
+ p->xCommon = dfdebugCommon;
957
+ p->xInsert = dfdebugInsert;
958
+ p->xDelete = dfdebugDelete;
959
+ p->xReplace = dfdebugReplace;
960
+ p->xEdit = dfdebugEdit;
961
+ p->xEnd = dfdebugEnd;
962
+ p->lnLeft = p->lnRight = 0;
963
+ p->pOut = pOut;
964
+ return p;
965
+}
966
+
967
+/************************* DiffBuilderTcl ********************************/
968
+/*
969
+** This formatter outputs a description of the diff formatted as TCL, for
970
+** use by the --tk option to "diff". See also the "diff.tcl" file. The
971
+** output can be viewed directly using the --tcl option.
972
+**
973
+** There is one line per method call:
974
+**
975
+** SKIP n -- Skip "n" lines of input
976
+** COM string -- "string" is an unchanged context line
977
+** INS string -- "string" is in the right file only
978
+** DEL string -- "string" is in the left file only
979
+** EDIT string .... -- Complex edit between left and right
980
+**
981
+** The EDIT verb will be followed by 3*N or 3*N+1 strings. The triples
982
+** each show:
983
+**
984
+** 1. Common text
985
+** 2. Text from the left side
986
+** 3. Text on the right that replaces (2) from the left
987
+**
988
+** For inserted text (2) will be an empty string. For deleted text, (3)
989
+** will be an empty string. (1) might be empty for the first triple if
990
+** the line begins with an edit. After all triples, there might be one
991
+** additional string which is a common suffix.
992
+*/
993
+static void dftclSkip(DiffBuilder *p, unsigned int n, int isFinal){
994
+ blob_appendf(p->pOut, "SKIP %u\n", n);
995
+}
996
+static void dftclCommon(DiffBuilder *p, const DLine *pLine){
997
+ blob_appendf(p->pOut, "COM ");
998
+ blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
999
+ blob_append_char(p->pOut, '\n');
1000
+}
1001
+static void dftclInsert(DiffBuilder *p, const DLine *pLine){
1002
+ blob_append(p->pOut, "INS ", -1);
1003
+ blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1004
+ blob_append_char(p->pOut, '\n');
1005
+}
1006
+static void dftclDelete(DiffBuilder *p, const DLine *pLine){
1007
+ blob_append(p->pOut, "DEL ", -1);
1008
+ blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1009
+ blob_append_char(p->pOut, '\n');
1010
+}
1011
+static void dftclReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1012
+ blob_append(p->pOut, "EDIT \"\" ", -1);
1013
+ blob_append_tcl_literal(p->pOut, pX->z, pX->n);
1014
+ blob_append_char(p->pOut, ' ');
1015
+ blob_append_tcl_literal(p->pOut, pY->z, pY->n);
1016
+ blob_append_char(p->pOut, '\n');
1017
+}
1018
+static void dftclEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1019
+ int i, x;
1020
+ LineChange chng;
1021
+ blob_append(p->pOut, "EDIT", 4);
1022
+ oneLineChange(pX, pY, &chng);
1023
+ for(i=x=0; i<chng.n; i++){
1024
+ blob_append_char(p->pOut, ' ');
1025
+ blob_append_tcl_literal(p->pOut, pX->z + x, chng.a[i].iStart1 - x);
1026
+ x = chng.a[i].iStart1;
1027
+ blob_append_char(p->pOut, ' ');
1028
+ blob_append_tcl_literal(p->pOut, pX->z + x, chng.a[i].iLen1);
1029
+ x += chng.a[i].iLen1;
1030
+ blob_append_char(p->pOut, ' ');
1031
+ blob_append_tcl_literal(p->pOut,
1032
+ pY->z + chng.a[i].iStart2, chng.a[i].iLen2);
1033
+ }
1034
+ if( x<pX->n ){
1035
+ blob_append_char(p->pOut, ' ');
1036
+ blob_append_tcl_literal(p->pOut, pX->z + x, pX->n - x);
1037
+ }
1038
+ blob_append_char(p->pOut, '\n');
1039
+}
1040
+static void dftclEnd(DiffBuilder *p){
1041
+ fossil_free(p);
1042
+}
1043
+static DiffBuilder *dftclNew(Blob *pOut){
1044
+ DiffBuilder *p = fossil_malloc(sizeof(*p));
1045
+ p->xSkip = dftclSkip;
1046
+ p->xCommon = dftclCommon;
1047
+ p->xInsert = dftclInsert;
1048
+ p->xDelete = dftclDelete;
1049
+ p->xReplace = dftclReplace;
1050
+ p->xEdit = dftclEdit;
1051
+ p->xEnd = dftclEnd;
1052
+ p->pOut = pOut;
1053
+ return p;
1054
+}
1055
+
1056
+/************************* DiffBuilderJson ********************************/
1057
+/*
1058
+** This formatter generates a JSON array that describes the difference.
1059
+**
1060
+** The Json array consists of integer opcodes with each opcode followed
1061
+** by zero or more arguments:
1062
+**
1063
+** Syntax Mnemonic Description
1064
+** ----------- -------- --------------------------
1065
+** 0 END This is the end of the diff
1066
+** 1 INTEGER SKIP Skip N lines from both files
1067
+** 2 STRING COMMON The line show by STRING is in both files
1068
+** 3 STRING INSERT The line STRING is in only the right file
1069
+** 4 STRING DELETE The STRING line is in only the left file
1070
+** 5 SUBARRAY EDIT One line is different on left and right.
1071
+**
1072
+** The SUBARRAY is an array of 3*N+1 strings with N>=0. The triples
1073
+** represent common-text, left-text, and right-text. The last string
1074
+** in SUBARRAY is the common-suffix. Any string can be empty if it does
1075
+** not apply.
1076
+*/
1077
+static void dfjsonSkip(DiffBuilder *p, unsigned int n, int isFinal){
1078
+ blob_appendf(p->pOut, "1,%u,\n", n);
1079
+}
1080
+static void dfjsonCommon(DiffBuilder *p, const DLine *pLine){
1081
+ blob_append(p->pOut, "2,",2);
1082
+ blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1083
+ blob_append(p->pOut, ",\n",2);
1084
+}
1085
+static void dfjsonInsert(DiffBuilder *p, const DLine *pLine){
1086
+ blob_append(p->pOut, "3,",2);
1087
+ blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1088
+ blob_append(p->pOut, ",\n",2);
1089
+}
1090
+static void dfjsonDelete(DiffBuilder *p, const DLine *pLine){
1091
+ blob_append(p->pOut, "4,",2);
1092
+ blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1093
+ blob_append(p->pOut, ",\n",2);
1094
+}
1095
+static void dfjsonReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1096
+ blob_append(p->pOut, "5,[\"\",",-1);
1097
+ blob_append_json_literal(p->pOut, pX->z, (int)pX->n);
1098
+ blob_append(p->pOut, ",",1);
1099
+ blob_append_json_literal(p->pOut, pY->z, (int)pY->n);
1100
+ blob_append(p->pOut, ",\"\"],\n",-1);
1101
+}
1102
+static void dfjsonEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1103
+ int i, x;
1104
+ LineChange chng;
1105
+ blob_append(p->pOut, "5,[", 3);
1106
+ oneLineChange(pX, pY, &chng);
1107
+ for(i=x=0; i<chng.n; i++){
1108
+ blob_append_json_literal(p->pOut, pX->z + x, chng.a[i].iStart1 - x);
1109
+ x = chng.a[i].iStart1;
1110
+ blob_append_char(p->pOut, ',');
1111
+ blob_append_json_literal(p->pOut, pX->z + x, chng.a[i].iLen1);
1112
+ x += chng.a[i].iLen1;
1113
+ blob_append_char(p->pOut, ',');
1114
+ blob_append_json_literal(p->pOut,
1115
+ pY->z + chng.a[i].iStart2, chng.a[i].iLen2);
1116
+ }
1117
+ blob_append_char(p->pOut, ',');
1118
+ blob_append_json_literal(p->pOut, pX->z + x, pX->n - x);
1119
+ blob_append(p->pOut, "],\n",3);
1120
+}
1121
+static void dfjsonEnd(DiffBuilder *p){
1122
+ blob_append(p->pOut, "0]", 2);
1123
+ fossil_free(p);
1124
+}
1125
+static DiffBuilder *dfjsonNew(Blob *pOut){
1126
+ DiffBuilder *p = fossil_malloc(sizeof(*p));
1127
+ p->xSkip = dfjsonSkip;
1128
+ p->xCommon = dfjsonCommon;
1129
+ p->xInsert = dfjsonInsert;
1130
+ p->xDelete = dfjsonDelete;
1131
+ p->xReplace = dfjsonReplace;
1132
+ p->xEdit = dfjsonEdit;
1133
+ p->xEnd = dfjsonEnd;
1134
+ p->lnLeft = p->lnRight = 0;
1135
+ p->pOut = pOut;
1136
+ blob_append_char(pOut, '[');
1137
+ return p;
1138
+}
1139
+
1140
+/************************* DiffBuilderUnified********************************/
1141
+/* This formatter generates a unified diff for HTML.
1142
+**
1143
+** The result is a <table> with four columns. The four columns hold:
1144
+**
1145
+** 1. The line numbers for the first file.
1146
+** 2. The line numbers for the second file.
1147
+** 3. The "diff mark": "+" or "-" or just a space
1148
+** 4. Text of the line
1149
+**
1150
+** Inserted lines are marked with <ins> and deleted lines are marked
1151
+** with <del>. The whole line is marked this way, not just the part that
1152
+** changed. The part that change has an additional nested <ins> or <del>.
1153
+** The CSS needs to be set up such that a single <ins> or <del> gives a
1154
+** light background and a nested <ins> or <del> gives a darker background.
1155
+** Additional attributes (like bold font) might also be added to nested
1156
+** <ins> and <del> since those are the characters that have actually
1157
+** changed.
1158
+**
1159
+** Accumulator strategy:
1160
+**
1161
+** * Delete line numbers are output directly to p->pOut
1162
+** * Insert line numbers accumulate in p->aCol[0].
1163
+** * Separator marks accumulate in p->aCol[1].
1164
+** * Change text accumulates in p->aCol[2].
1165
+** * Pending insert line numbers go into p->aCol[3].
1166
+** * Pending insert text goes into p->aCol[4].
1167
+**
1168
+** eState is 1 if text has an open <del>
1169
+*/
1170
+static void dfunifiedFinishDelete(DiffBuilder *p){
1171
+ if( p->eState==0 ) return;
1172
+ blob_append(p->pOut, "</del>", 6);
1173
+ blob_append(&p->aCol[2], "</del>", 6);
1174
+ p->eState = 0;
1175
+}
1176
+static void dfunifiedFinishInsert(DiffBuilder *p){
1177
+ unsigned int i;
1178
+ if( p->nPending==0 ) return;
1179
+ dfunifiedFinishDelete(p);
1180
+
1181
+ /* Blank lines for delete line numbers for each inserted line */
1182
+ for(i=0; i<p->nPending; i++) blob_append_char(p->pOut, '\n');
1183
+
1184
+ /* Insert line numbers */
1185
+ blob_append(&p->aCol[0], "<ins>", 5);
1186
+ blob_append_xfer(&p->aCol[0], &p->aCol[3]);
1187
+ blob_append(&p->aCol[0], "</ins>", 6);
1188
+
1189
+ /* "+" marks for the separator on inserted lines */
1190
+ for(i=0; i<p->nPending; i++) blob_append(&p->aCol[1], "+\n", 2);
1191
+
1192
+ /* Text of the inserted lines */
1193
+ blob_append(&p->aCol[2], "<ins>", 5);
1194
+ blob_append_xfer(&p->aCol[2], &p->aCol[4]);
1195
+ blob_append(&p->aCol[2], "</ins>", 6);
1196
+
1197
+ p->nPending = 0;
1198
+}
1199
+static void dfunifiedFinishRow(DiffBuilder *p){
1200
+ dfunifiedFinishDelete(p);
1201
+ dfunifiedFinishInsert(p);
1202
+ if( blob_size(&p->aCol[0])==0 ) return;
1203
+ blob_append(p->pOut, "</pre></td><td class=\"diffln difflnr\"><pre>\n", -1);
1204
+ blob_append_xfer(p->pOut, &p->aCol[0]);
1205
+ blob_append(p->pOut, "</pre></td><td class=\"diffsep\"><pre>\n", -1);
1206
+ blob_append_xfer(p->pOut, &p->aCol[1]);
1207
+ blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtu\"><pre>\n",-1);
1208
+ blob_append_xfer(p->pOut, &p->aCol[2]);
1209
+ blob_append(p->pOut, "</pre></td></tr>\n", -1);
1210
+}
1211
+static void dfunifiedStartRow(DiffBuilder *p){
1212
+ if( blob_size(&p->aCol[0])>0 ) return;
1213
+ blob_appendf(p->pOut,"<tr id=\"chunk%d\">"
1214
+ "<td class=\"diffln difflnl\"><pre>\n", ++nChunk);
1215
+ p->eState = 0;
1216
+ p->nPending = 0;
1217
+}
1218
+static void dfunifiedSkip(DiffBuilder *p, unsigned int n, int isFinal){
1219
+ dfunifiedFinishRow(p);
1220
+ blob_append(p->pOut, "<tr><td class=\"diffln difflne\">"
1221
+ "&#xfe19;</td><td></td><td></td></tr>\n", -1);
1222
+ p->lnLeft += n;
1223
+ p->lnRight += n;
1224
+}
1225
+static void dfunifiedCommon(DiffBuilder *p, const DLine *pLine){
1226
+ dfunifiedStartRow(p);
1227
+ dfunifiedFinishDelete(p);
1228
+ dfunifiedFinishInsert(p);
1229
+ p->lnLeft++;
1230
+ p->lnRight++;
1231
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1232
+ blob_appendf(&p->aCol[0],"%d\n",p->lnRight);
1233
+ blob_append_char(&p->aCol[1], '\n');
1234
+ htmlize_to_blob(&p->aCol[2], pLine->z, (int)pLine->n);
1235
+ blob_append_char(&p->aCol[2], '\n');
1236
+}
1237
+static void dfunifiedInsert(DiffBuilder *p, const DLine *pLine){
1238
+ dfunifiedStartRow(p);
1239
+ p->lnRight++;
1240
+ blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1241
+ blob_append(&p->aCol[4], "<ins>", 5);
1242
+ htmlize_to_blob(&p->aCol[4], pLine->z, (int)pLine->n);
1243
+ blob_append(&p->aCol[4], "</ins>\n", 7);
1244
+ p->nPending++;
1245
+}
1246
+static void dfunifiedDelete(DiffBuilder *p, const DLine *pLine){
1247
+ dfunifiedStartRow(p);
1248
+ dfunifiedFinishInsert(p);
1249
+ if( p->eState==0 ){
1250
+ dfunifiedFinishInsert(p);
1251
+ blob_append(p->pOut, "<del>", 5);
1252
+ blob_append(&p->aCol[2], "<del>", 5);
1253
+ p->eState = 1;
1254
+ }
1255
+ p->lnLeft++;
1256
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1257
+ blob_append_char(&p->aCol[0],'\n');
1258
+ blob_append(&p->aCol[1],"-\n",2);
1259
+ blob_append(&p->aCol[2], "<del>", 5);
1260
+ htmlize_to_blob(&p->aCol[2], pLine->z, (int)pLine->n);
1261
+ blob_append(&p->aCol[2], "</del>\n", 7);
1262
+}
1263
+static void dfunifiedReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1264
+ dfunifiedStartRow(p);
1265
+ if( p->eState==0 ){
1266
+ dfunifiedFinishInsert(p);
1267
+ blob_append(p->pOut, "<del>", 5);
1268
+ blob_append(&p->aCol[2], "<del>", 5);
1269
+ p->eState = 1;
1270
+ }
1271
+ p->lnLeft++;
1272
+ p->lnRight++;
1273
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1274
+ blob_append_char(&p->aCol[0], '\n');
1275
+ blob_append(&p->aCol[1], "-\n", 2);
1276
+
1277
+ htmlize_to_blob(&p->aCol[2], pX->z, pX->n);
1278
+ blob_append_char(&p->aCol[2], '\n');
1279
+
1280
+ blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1281
+
1282
+ htmlize_to_blob(&p->aCol[4], pY->z, pY->n);
1283
+ blob_append_char(&p->aCol[4], '\n');
1284
+ p->nPending++;
1285
+}
1286
+static void dfunifiedEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1287
+ int i;
1288
+ int x;
1289
+ LineChange chng;
1290
+ oneLineChange(pX, pY, &chng);
1291
+ dfunifiedStartRow(p);
1292
+ if( p->eState==0 ){
1293
+ dfunifiedFinishInsert(p);
1294
+ blob_append(p->pOut, "<del>", 5);
1295
+ blob_append(&p->aCol[2], "<del>", 5);
1296
+ p->eState = 1;
1297
+ }
1298
+ p->lnLeft++;
1299
+ p->lnRight++;
1300
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1301
+ blob_append_char(&p->aCol[0], '\n');
1302
+ blob_append(&p->aCol[1], "-\n", 2);
1303
+
1304
+ for(i=x=0; i<chng.n; i++){
1305
+ int ofst = chng.a[i].iStart1;
1306
+ int len = chng.a[i].iLen1;
1307
+ if( len ){
1308
+ htmlize_to_blob(&p->aCol[2], pX->z+x, ofst - x);
1309
+ x = ofst;
1310
+ blob_append(&p->aCol[2], "<del>", 5);
1311
+ htmlize_to_blob(&p->aCol[2], pX->z+x, len);
1312
+ x += len;
1313
+ blob_append(&p->aCol[2], "</del>", 6);
1314
+ }
1315
+ }
1316
+ htmlize_to_blob(&p->aCol[2], pX->z+x, pX->n - x);
1317
+ blob_append_char(&p->aCol[2], '\n');
1318
+
1319
+ blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1320
+ for(i=x=0; i<chng.n; i++){
1321
+ int ofst = chng.a[i].iStart2;
1322
+ int len = chng.a[i].iLen2;
1323
+ if( len ){
1324
+ htmlize_to_blob(&p->aCol[4], pY->z+x, ofst - x);
1325
+ x = ofst;
1326
+ blob_append(&p->aCol[4], "<ins>", 5);
1327
+ htmlize_to_blob(&p->aCol[4], pY->z+x, len);
1328
+ x += len;
1329
+ blob_append(&p->aCol[4], "</ins>", 6);
1330
+ }
1331
+ }
1332
+ htmlize_to_blob(&p->aCol[4], pY->z+x, pY->n - x);
1333
+ blob_append_char(&p->aCol[4], '\n');
1334
+ p->nPending++;
1335
+}
1336
+static void dfunifiedEnd(DiffBuilder *p){
1337
+ dfunifiedFinishRow(p);
1338
+ blob_append(p->pOut, "</table>\n",-1);
1339
+ fossil_free(p);
1340
+}
1341
+static DiffBuilder *dfunifiedNew(Blob *pOut){
1342
+ DiffBuilder *p = fossil_malloc(sizeof(*p));
1343
+ p->xSkip = dfunifiedSkip;
1344
+ p->xCommon = dfunifiedCommon;
1345
+ p->xInsert = dfunifiedInsert;
1346
+ p->xDelete = dfunifiedDelete;
1347
+ p->xReplace = dfunifiedReplace;
1348
+ p->xEdit = dfunifiedEdit;
1349
+ p->xEnd = dfunifiedEnd;
1350
+ p->lnLeft = p->lnRight = 0;
1351
+ p->eState = 0;
1352
+ p->nPending = 0;
1353
+ p->pOut = pOut;
1354
+ blob_append(pOut, "<table class=\"diff udiff\">\n", -1);
1355
+ blob_init(&p->aCol[0], 0, 0);
1356
+ blob_init(&p->aCol[1], 0, 0);
1357
+ blob_init(&p->aCol[2], 0, 0);
1358
+ blob_init(&p->aCol[3], 0, 0);
1359
+ blob_init(&p->aCol[4], 0, 0);
1360
+ return p;
1361
+}
1362
+
1363
+/************************* DiffBuilderSplit ******************************/
1364
+/* This formatter creates a side-by-side diff in HTML. The output is a
1365
+** <table> with 5 columns:
1366
+**
1367
+** 1. Line numbers for the first file.
1368
+** 2. Text for the first file.
1369
+** 3. The difference mark. "<", ">", "|" or blank
1370
+** 4. Line numbers for the second file.
1371
+** 5. Text for the second file.
1372
+**
1373
+** The <ins> and <del> strategy is the same as for unified diff above.
1374
+** In fact, the same CSS can be used for both.
1375
+**
1376
+** Accumulator strategy:
1377
+**
1378
+** * Left line numbers are output directly to p->pOut
1379
+** * Left text accumulates in p->aCol[0].
1380
+** * Edit marks accumulates in p->aCol[1].
1381
+** * Right line numbers accumulate in p->aCol[2].
1382
+** * Right text accumulates in p->aCol[3].
1383
+**
1384
+** eState:
1385
+** 0 In common block
1386
+** 1 Have <del> on the left
1387
+** 2 Have <ins> on the right
1388
+** 3 Have <del> on left and <ins> on the right
1389
+*/
1390
+static void dfsplitChangeState(DiffBuilder *p, int newState){
1391
+ if( p->eState == newState ) return;
1392
+ if( (p->eState&1)==0 && (newState & 1)!=0 ){
1393
+ blob_append(p->pOut, "<del>", 5);
1394
+ blob_append(&p->aCol[0], "<del>", 5);
1395
+ p->eState |= 1;
1396
+ }else if( (p->eState&1)!=0 && (newState & 1)==0 ){
1397
+ blob_append(p->pOut, "</del>", 6);
1398
+ blob_append(&p->aCol[0], "</del>", 6);
1399
+ p->eState &= ~1;
1400
+ }
1401
+ if( (p->eState&2)==0 && (newState & 2)!=0 ){
1402
+ blob_append(&p->aCol[2], "<ins>", 5);
1403
+ blob_append(&p->aCol[3], "<ins>", 5);
1404
+ p->eState |= 2;
1405
+ }else if( (p->eState&2)!=0 && (newState & 2)==0 ){
1406
+ blob_append(&p->aCol[2], "</ins>", 6);
1407
+ blob_append(&p->aCol[3], "</ins>", 6);
1408
+ p->eState &= ~2;
1409
+ }
1410
+}
1411
+static void dfsplitFinishRow(DiffBuilder *p){
1412
+ if( blob_size(&p->aCol[0])==0 ) return;
1413
+ dfsplitChangeState(p, 0);
1414
+ blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtl\"><pre>\n",-1);
1415
+ blob_append_xfer(p->pOut, &p->aCol[0]);
1416
+ blob_append(p->pOut, "</pre></td><td class=\"diffsep\"><pre>\n", -1);
1417
+ blob_append_xfer(p->pOut, &p->aCol[1]);
1418
+ blob_append(p->pOut, "</pre></td><td class=\"diffln difflnr\"><pre>\n",-1);
1419
+ blob_append_xfer(p->pOut, &p->aCol[2]);
1420
+ blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtr\"><pre>\n",-1);
1421
+ blob_append_xfer(p->pOut, &p->aCol[3]);
1422
+ blob_append(p->pOut, "</pre></td></tr>\n", -1);
1423
+}
1424
+static void dfsplitStartRow(DiffBuilder *p){
1425
+ if( blob_size(&p->aCol[0])>0 ) return;
1426
+ blob_appendf(p->pOut,"<tr id=\"chunk%d\">"
1427
+ "<td class=\"diffln difflnl\"><pre>\n", ++nChunk);
1428
+ p->eState = 0;
1429
+}
1430
+static void dfsplitSkip(DiffBuilder *p, unsigned int n, int isFinal){
1431
+ dfsplitFinishRow(p);
1432
+ blob_append(p->pOut,
1433
+ "<tr><td class=\"diffln difflnl difflne\">&#xfe19;</td>"
1434
+ "<td></td><td></td>"
1435
+ "<td class=\"diffln difflnr difflne\">&#xfe19;</td>"
1436
+ "<td/td></tr>\n", -1);
1437
+ p->lnLeft += n;
1438
+ p->lnRight += n;
1439
+}
1440
+static void dfsplitCommon(DiffBuilder *p, const DLine *pLine){
1441
+ dfsplitStartRow(p);
1442
+ dfsplitChangeState(p, 0);
1443
+ p->lnLeft++;
1444
+ p->lnRight++;
1445
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1446
+ htmlize_to_blob(&p->aCol[0], pLine->z, (int)pLine->n);
1447
+ blob_append_char(&p->aCol[0], '\n');
1448
+ blob_append_char(&p->aCol[1], '\n');
1449
+ blob_appendf(&p->aCol[2],"%d\n",p->lnRight);
1450
+ htmlize_to_blob(&p->aCol[3], pLine->z, (int)pLine->n);
1451
+ blob_append_char(&p->aCol[3], '\n');
1452
+}
1453
+static void dfsplitInsert(DiffBuilder *p, const DLine *pLine){
1454
+ dfsplitStartRow(p);
1455
+ dfsplitChangeState(p, 2);
1456
+ p->lnRight++;
1457
+ blob_append_char(p->pOut, '\n');
1458
+ blob_append_char(&p->aCol[0], '\n');
1459
+ blob_append(&p->aCol[1], "&gt;\n", -1);
1460
+ blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1461
+ blob_append(&p->aCol[3], "<ins>", 5);
1462
+ htmlize_to_blob(&p->aCol[3], pLine->z, (int)pLine->n);
1463
+ blob_append(&p->aCol[3], "</ins>\n", 7);
1464
+}
1465
+static void dfsplitDelete(DiffBuilder *p, const DLine *pLine){
1466
+ dfsplitStartRow(p);
1467
+ dfsplitChangeState(p, 1);
1468
+ p->lnLeft++;
1469
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1470
+ blob_append(&p->aCol[0], "<del>", 5);
1471
+ htmlize_to_blob(&p->aCol[0], pLine->z, (int)pLine->n);
1472
+ blob_append(&p->aCol[0], "</del>\n", 7);
1473
+ blob_append(&p->aCol[1], "&lt;\n", -1);
1474
+ blob_append_char(&p->aCol[2],'\n');
1475
+ blob_append_char(&p->aCol[3],'\n');
1476
+}
1477
+static void dfsplitReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1478
+ dfsplitStartRow(p);
1479
+ dfsplitChangeState(p, 3);
1480
+ p->lnLeft++;
1481
+ p->lnRight++;
1482
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1483
+ htmlize_to_blob(&p->aCol[0], pX->z, pX->n);
1484
+ blob_append_char(&p->aCol[0], '\n');
1485
+
1486
+ blob_append(&p->aCol[1], "|\n", 2);
1487
+
1488
+ blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1489
+
1490
+ htmlize_to_blob(&p->aCol[3], pY->z, pY->n);
1491
+ blob_append_char(&p->aCol[3], '\n');
1492
+}
1493
+static void dfsplitEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1494
+ int i;
1495
+ int x;
1496
+ LineChange chng;
1497
+ oneLineChange(pX, pY, &chng);
1498
+ dfsplitStartRow(p);
1499
+ dfsplitChangeState(p, 3);
1500
+ p->lnLeft++;
1501
+ p->lnRight++;
1502
+ blob_appendf(p->pOut,"%d\n", p->lnLeft);
1503
+ for(i=x=0; i<chng.n; i++){
1504
+ int ofst = chng.a[i].iStart1;
1505
+ int len = chng.a[i].iLen1;
1506
+ if( len ){
1507
+ htmlize_to_blob(&p->aCol[0], pX->z+x, ofst - x);
1508
+ x = ofst;
1509
+ if( chng.a[i].iLen2 ){
1510
+ blob_append(&p->aCol[0], "<del class='edit'>", -1);
1511
+ }else{
1512
+ blob_append(&p->aCol[0], "<del>", 5);
1513
+ }
1514
+ htmlize_to_blob(&p->aCol[0], pX->z+x, len);
1515
+ x += len;
1516
+ blob_append(&p->aCol[0], "</del>", 6);
1517
+ }
1518
+ }
1519
+ htmlize_to_blob(&p->aCol[0], pX->z+x, pX->n - x);
1520
+ blob_append_char(&p->aCol[0], '\n');
1521
+
1522
+ blob_append(&p->aCol[1], "|\n", 2);
1523
+
1524
+ blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1525
+ for(i=x=0; i<chng.n; i++){
1526
+ int ofst = chng.a[i].iStart2;
1527
+ int len = chng.a[i].iLen2;
1528
+ if( len ){
1529
+ htmlize_to_blob(&p->aCol[3], pY->z+x, ofst - x);
1530
+ x = ofst;
1531
+ if( chng.a[i].iLen1 ){
1532
+ blob_append(&p->aCol[3], "<ins class='edit'>", -1);
1533
+ }else{
1534
+ blob_append(&p->aCol[3], "<ins>", 5);
1535
+ }
1536
+ htmlize_to_blob(&p->aCol[3], pY->z+x, len);
1537
+ x += len;
1538
+ blob_append(&p->aCol[3], "</ins>", 6);
1539
+ }
1540
+ }
1541
+ htmlize_to_blob(&p->aCol[3], pY->z+x, pY->n - x);
1542
+ blob_append_char(&p->aCol[3], '\n');
1543
+}
1544
+static void dfsplitEnd(DiffBuilder *p){
1545
+ dfsplitFinishRow(p);
1546
+ blob_append(p->pOut, "</table>\n",-1);
1547
+ fossil_free(p);
1548
+}
1549
+static DiffBuilder *dfsplitNew(Blob *pOut){
1550
+ DiffBuilder *p = fossil_malloc(sizeof(*p));
1551
+ p->xSkip = dfsplitSkip;
1552
+ p->xCommon = dfsplitCommon;
1553
+ p->xInsert = dfsplitInsert;
1554
+ p->xDelete = dfsplitDelete;
1555
+ p->xReplace = dfsplitReplace;
1556
+ p->xEdit = dfsplitEdit;
1557
+ p->xEnd = dfsplitEnd;
1558
+ p->lnLeft = p->lnRight = 0;
1559
+ p->eState = 0;
1560
+ p->pOut = pOut;
1561
+ blob_append(pOut, "<table class=\"diff splitdiff\">\n", -1);
1562
+ blob_init(&p->aCol[0], 0, 0);
1563
+ blob_init(&p->aCol[1], 0, 0);
1564
+ blob_init(&p->aCol[2], 0, 0);
1565
+ blob_init(&p->aCol[3], 0, 0);
1566
+ blob_init(&p->aCol[4], 0, 0);
1567
+ return p;
1568
+}
1569
+
1570
+/************************* DiffBuilderSbs ******************************/
1571
+/* This formatter creates a side-by-side diff in text.
1572
+*/
1573
+static void dfsbsSkip(DiffBuilder *p, unsigned int n, int isFinal){
1574
+ if( (p->lnLeft || p->lnRight) && !isFinal ){
1575
+ blob_appendf(p->pOut, "%.*c\n", p->width*2 + 16, '.');
1576
+ }
1577
+ p->lnLeft += n;
1578
+ p->lnRight += n;
1579
+}
1580
+
1581
+/*
1582
+** Append at least iMin characters (not bytes) and at most iMax characters
1583
+** from pX onto the into of p.
1584
+**
1585
+** This comment contains multibyte unicode characters (ü, Æ, ð) in order
1586
+** to test the ability of the diff code to handle such characters.
1587
+*/
1588
+static void sbs_append_chars(Blob *p, int iMin, int iMax, const DLine *pX){
1589
+ int i;
1590
+ const char *z = pX->z;
1591
+ for(i=0; i<iMax && i<pX->n; i++){
1592
+ char c = z[i];
1593
+ blob_append_char(p, c);
1594
+ if( (c&0xc0)==0x80 ){ iMin++; iMax++; }
1595
+ }
1596
+ while( i<iMin ){
1597
+ blob_append_char(p, ' ');
1598
+ i++;
1599
+ }
1600
+}
1601
+
1602
+static void dfsbsCommon(DiffBuilder *p, const DLine *pLine){
1603
+ p->lnLeft++;
1604
+ p->lnRight++;
1605
+ blob_appendf(p->pOut,"%6u ", p->lnLeft);
1606
+ sbs_append_chars(p->pOut, p->width, p->width, pLine);
1607
+ blob_appendf(p->pOut," %6u ", p->lnRight);
1608
+ sbs_append_chars(p->pOut, 0, p->width, pLine);
1609
+ blob_append_char(p->pOut, '\n');
1610
+}
1611
+static void dfsbsInsert(DiffBuilder *p, const DLine *pLine){
1612
+ p->lnRight++;
1613
+ blob_appendf(p->pOut,"%6s %*s > %6u ",
1614
+ "", p->width, "", p->lnRight);
1615
+ sbs_append_chars(p->pOut, 0, p->width, pLine);
1616
+ blob_append_char(p->pOut, '\n');
1617
+}
1618
+static void dfsbsDelete(DiffBuilder *p, const DLine *pLine){
1619
+ p->lnLeft++;
1620
+ blob_appendf(p->pOut,"%6u ", p->lnLeft);
1621
+ sbs_append_chars(p->pOut, p->width, p->width, pLine);
1622
+ blob_append(p->pOut," <\n", 3);
1623
+}
1624
+static void dfsbsEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1625
+ p->lnLeft++;
1626
+ p->lnRight++;
1627
+ blob_appendf(p->pOut,"%6u ", p->lnLeft);
1628
+ sbs_append_chars(p->pOut, p->width, p->width, pX);
1629
+ blob_appendf(p->pOut, " | %6u ", p->lnRight);
1630
+ sbs_append_chars(p->pOut, 0, p->width, pY);
1631
+ blob_append_char(p->pOut, '\n');
1632
+}
1633
+static void dfsbsEnd(DiffBuilder *p){
1634
+ fossil_free(p);
1635
+}
1636
+static DiffBuilder *dfsbsNew(Blob *pOut, u64 diffFlags){
1637
+ DiffBuilder *p = fossil_malloc(sizeof(*p));
1638
+ p->xSkip = dfsbsSkip;
1639
+ p->xCommon = dfsbsCommon;
1640
+ p->xInsert = dfsbsInsert;
1641
+ p->xDelete = dfsbsDelete;
1642
+ p->xReplace = dfsbsEdit;
1643
+ p->xEdit = dfsbsEdit;
1644
+ p->xEnd = dfsbsEnd;
1645
+ p->lnLeft = p->lnRight = 0;
1646
+ p->width = diff_width(diffFlags);
1647
+ p->pOut = pOut;
1648
+ return p;
1649
+}
1650
+/****************************************************************************/
9651651
/*
9661652
** Return the number between 0 and 100 that is smaller the closer pA and
9671653
** pB match. Return 0 for a perfect match. Return 100 if pA and pB are
9681654
** completely different.
9691655
**
@@ -972,11 +1658,11 @@
9721658
** (1) Remove leading and trailing whitespace.
9731659
** (2) Truncate both strings to at most 250 characters
9741660
** (3) Find the length of the longest common subsequence
9751661
** (4) Longer common subsequences yield lower scores.
9761662
*/
977
-static int match_dline(DLine *pA, DLine *pB){
1663
+static int match_dline(const DLine *pA, const DLine *pB){
9781664
const char *zA; /* Left string */
9791665
const char *zB; /* right string */
9801666
int nA; /* Bytes in zA[] */
9811667
int nB; /* Bytes in zB[] */
9821668
int avg; /* Average length of A and B */
@@ -1024,10 +1710,32 @@
10241710
#endif
10251711
10261712
/* Return the result */
10271713
return score;
10281714
}
1715
+
1716
+/*
1717
+** COMMAND: test-line-match
1718
+** Usage: %fossil test-line-match STRING1 STRING2
1719
+**
1720
+** Return a score from 0 to 100 that is how similar STRING1 is to
1721
+** STRING2. Smaller numbers mean more similar. 0 is an exact match.
1722
+**
1723
+** This command is used to test to match_dline() function in the
1724
+** internal Fossil diff logic.
1725
+*/
1726
+void test_dline_match(void){
1727
+ DLine a, b;
1728
+ int x;
1729
+ if( g.argc!=4 ) usage("STRING1 STRING2");
1730
+ a.z = g.argv[2];
1731
+ a.n = (int)strlen(a.z);
1732
+ b.z = g.argv[3];
1733
+ b.n = (int)strlen(b.z);
1734
+ x = match_dline(&a, &b);
1735
+ fossil_print("%d\n", x);
1736
+}
10291737
10301738
/*
10311739
** There is a change block in which nLeft lines of text on the left are
10321740
** converted into nRight lines of text on the right. This routine computes
10331741
** how the lines on the left line up with the lines on the right.
@@ -1039,61 +1747,113 @@
10391747
** 1. Delete the next line of pLeft.
10401748
** 2. Insert the next line of pRight.
10411749
** 3. The next line of pLeft changes into the next line of pRight.
10421750
** 4. Delete one line from pLeft and add one line to pRight.
10431751
**
1044
-** Values larger than three indicate better matches.
1045
-**
1046
-** The length of the returned array will be just large enough to cause
1047
-** all elements of pLeft and pRight to be consumed.
1752
+** The length of the returned array will be at most nLeft+nRight bytes.
1753
+** If the first bytes is 4, that means we could not compute reasonable
1754
+** alignment between the two blocks.
10481755
**
10491756
** Algorithm: Wagner's minimum edit-distance algorithm, modified by
10501757
** adding a cost to each match based on how well the two rows match
10511758
** each other. Insertion and deletion costs are 50. Match costs
10521759
** are between 0 and 100 where 0 is a perfect match 100 is a complete
10531760
** mismatch.
10541761
*/
1055
-static unsigned char *sbsAlignment(
1056
- DLine *aLeft, int nLeft, /* Text on the left */
1057
- DLine *aRight, int nRight, /* Text on the right */
1058
- u64 diffFlags /* Flags passed into the original diff */
1762
+static unsigned char *diffBlockAlignment(
1763
+ const DLine *aLeft, int nLeft, /* Text on the left */
1764
+ const DLine *aRight, int nRight, /* Text on the right */
1765
+ u64 diffFlags, /* Flags passed into the original diff */
1766
+ int *pNResult /* OUTPUT: Bytes of result */
10591767
){
10601768
int i, j, k; /* Loop counters */
10611769
int *a; /* One row of the Wagner matrix */
10621770
int *pToFree; /* Space that needs to be freed */
10631771
unsigned char *aM; /* Wagner result matrix */
10641772
int nMatch, iMatch; /* Number of matching lines and match score */
1065
- int mnLen; /* MIN(nLeft, nRight) */
1066
- int mxLen; /* MAX(nLeft, nRight) */
1067
- int aBuf[100]; /* Stack space for a[] if nRight not to big */
1068
-
1069
- aM = fossil_malloc( (nLeft+1)*(nRight+1) );
1070
- if( nLeft==0 ){
1071
- memset(aM, 2, nRight);
1072
- return aM;
1073
- }
1074
- if( nRight==0 ){
1075
- memset(aM, 1, nLeft);
1076
- return aM;
1077
- }
1078
-
1079
- /* This algorithm is O(N**2). So if N is too big, bail out with a
1080
- ** simple (but stupid and ugly) result that doesn't take too long. */
1081
- mnLen = nLeft<nRight ? nLeft : nRight;
1082
- if( nLeft*nRight>100000 && (diffFlags & DIFF_SLOW_SBS)==0 ){
1083
- memset(aM, 4, mnLen);
1084
- if( nLeft>mnLen ) memset(aM+mnLen, 1, nLeft-mnLen);
1085
- if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen);
1086
- return aM;
1087
- }
1088
-
1773
+ int mnLen; /* minInt(nLeft, nRight) */
1774
+ int mxLen; /* MAX(nLeft, nRight) */
1775
+ int aBuf[100]; /* Stack space for a[] if nRight not to big */
1776
+
1777
+ if( nLeft==0 ){
1778
+ aM = fossil_malloc( nRight + 2 );
1779
+ memset(aM, 2, nRight);
1780
+ *pNResult = nRight;
1781
+ return aM;
1782
+ }
1783
+ if( nRight==0 ){
1784
+ aM = fossil_malloc( nLeft + 2 );
1785
+ memset(aM, 1, nLeft);
1786
+ *pNResult = nLeft;
1787
+ return aM;
1788
+ }
1789
+
1790
+ /* For large alignments, use a divide and conquer algorithm that is
1791
+ ** O(NlogN). The result is not as precise, but this whole thing is an
1792
+ ** approximation anyhow, and the faster response time is an acceptable
1793
+ ** trade-off for reduced precision.
1794
+ */
1795
+ mnLen = nLeft<nRight ? nLeft : nRight;
1796
+ if( nLeft*nRight>1000 && (diffFlags & DIFF_SLOW_SBS)==0 ){
1797
+ const DLine *aSmall; /* The smaller of aLeft and aRight */
1798
+ const DLine *aBig; /* The larger of aLeft and aRight */
1799
+ int nSmall, nBig; /* Size of aSmall and aBig. nSmall<=nBig */
1800
+ int iDivSmall, iDivBig; /* Divider point for aSmall and aBig */
1801
+ int iDivLeft, iDivRight; /* Divider point for aLeft and aRight */
1802
+ unsigned char *a1, *a2; /* Results of the alignments on two halves */
1803
+ int n1, n2; /* Number of entries in a1 and a2 */
1804
+ int score, bestScore; /* Score and best score seen so far */
1805
+ if( nLeft>nRight ){
1806
+ aSmall = aRight;
1807
+ nSmall = nRight;
1808
+ aBig = aLeft;
1809
+ nBig = nLeft;
1810
+ }else{
1811
+ aSmall = aLeft;
1812
+ nSmall = nLeft;
1813
+ aBig = aRight;
1814
+ nBig = nRight;
1815
+ }
1816
+ iDivBig = nBig/2;
1817
+ iDivSmall = nSmall/2;
1818
+ bestScore = 10000;
1819
+ for(i=0; i<nSmall; i++){
1820
+ score = match_dline(aBig+iDivBig, aSmall+i) + abs(i-nSmall/2)*2;
1821
+ if( score<bestScore ){
1822
+ bestScore = score;
1823
+ iDivSmall = i;
1824
+ }
1825
+ }
1826
+ if( aSmall==aRight ){
1827
+ iDivRight = iDivSmall;
1828
+ iDivLeft = iDivBig;
1829
+ }else{
1830
+ iDivRight = iDivBig;
1831
+ iDivLeft = iDivSmall;
1832
+ }
1833
+ a1 = diffBlockAlignment(aLeft,iDivLeft,aRight,iDivRight,diffFlags,&n1);
1834
+ a2 = diffBlockAlignment(aLeft+iDivLeft, nLeft-iDivLeft,
1835
+ aRight+iDivRight, nRight-iDivRight,
1836
+ diffFlags, &n2);
1837
+ a1 = fossil_realloc(a1, n1+n2 );
1838
+ memcpy(a1+n1,a2,n2);
1839
+ fossil_free(a2);
1840
+ *pNResult = n1+n2;
1841
+ return a1;
1842
+ }
1843
+
1844
+ /* If we reach this point, we will be doing an O(N*N) Wagner minimum
1845
+ ** edit distance to compute the alignment.
1846
+ */
10891847
if( nRight < count(aBuf)-1 ){
10901848
pToFree = 0;
10911849
a = aBuf;
10921850
}else{
10931851
a = pToFree = fossil_malloc( sizeof(a[0])*(nRight+1) );
10941852
}
1853
+ aM = fossil_malloc( (nLeft+1)*(nRight+1) );
1854
+
10951855
10961856
/* Compute the best alignment */
10971857
for(i=0; i<=nRight; i++){
10981858
aM[i] = 2;
10991859
a[i] = i*50;
@@ -1148,10 +1908,11 @@
11481908
aM[k] = aM[j*(nRight+1)+i];
11491909
}
11501910
k++;
11511911
i = (nRight+1)*(nLeft+1) - k;
11521912
memmove(aM, &aM[k], i);
1913
+ *pNResult = i;
11531914
11541915
/* If:
11551916
** (1) the alignment is more than 25% longer than the longest side, and
11561917
** (2) the average match cost exceeds 15
11571918
** Then this is probably an alignment that will be difficult for humans
@@ -1161,13 +1922,13 @@
11611922
** The coefficients for conditions (1) and (2) above are determined by
11621923
** experimentation.
11631924
*/
11641925
mxLen = nLeft>nRight ? nLeft : nRight;
11651926
if( i*4>mxLen*5 && (nMatch==0 || iMatch/nMatch>15) ){
1166
- memset(aM, 4, mnLen);
1167
- if( nLeft>mnLen ) memset(aM+mnLen, 1, nLeft-mnLen);
1168
- if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen);
1927
+ memset(aM, 4, mnLen); *pNResult = mnLen;
1928
+ if( nLeft>mnLen ){ memset(aM+mnLen, 1, nLeft-mnLen); *pNResult = nLeft; }
1929
+ if( nRight>mnLen ){ memset(aM+mnLen, 2, nRight-mnLen); *pNResult = nRight; }
11691930
}
11701931
11711932
/* Return the result */
11721933
fossil_free(pToFree);
11731934
return aM;
@@ -1177,70 +1938,47 @@
11771938
** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a
11781939
** pair of adjacent differences. Return true if the gap between these
11791940
** two differences is so small that they should be rendered as a single
11801941
** edit.
11811942
*/
1182
-static int smallGap(int *R){
1943
+static int smallGap(const int *R){
11831944
return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8;
11841945
}
11851946
11861947
/*
1187
-** Given a diff context in which the aEdit[] array has been filled
1188
-** in, compute a side-by-side diff into pOut.
1948
+** Format a diff using a DiffBuilder object
11891949
*/
1190
-static void sbsDiff(
1191
- DContext *p, /* The computed diff */
1192
- Blob *pOut, /* Write the results here */
1193
- ReCompiled *pRe, /* Only show changes that match this regex */
1194
- u64 diffFlags /* Flags controlling the diff */
1950
+static void formatDiff(
1951
+ DContext *p, /* The computed diff */
1952
+ ReCompiled *pRe, /* Only show changes that match this regex */
1953
+ u64 diffFlags, /* Flags controlling the diff */
1954
+ DiffBuilder *pBuilder /* The formatter object */
11951955
){
1196
- DLine *A; /* Left side of the diff */
1197
- DLine *B; /* Right side of the diff */
1198
- int a = 0; /* Index of next line in A[] */
1199
- int b = 0; /* Index of next line in B[] */
1200
- int *R; /* Array of COPY/DELETE/INSERT triples */
1201
- int r; /* Index into R[] */
1202
- int nr; /* Number of COPY/DELETE/INSERT triples to process */
1203
- int mxr; /* Maximum value for r */
1204
- int na, nb; /* Number of lines shown from A and B */
1205
- int i, j; /* Loop counters */
1206
- int m, ma, mb;/* Number of lines to output */
1207
- int skip; /* Number of lines to skip */
1208
- static int nChunk = 0; /* Number of chunks of diff output seen so far */
1209
- SbsLine s; /* Output line buffer */
1210
- int nContext; /* Lines of context above and below each change */
1211
- int showDivider = 0; /* True to show the divider */
1212
- Blob aCols[5]; /* Array of column blobs */
1213
-
1214
- memset(&s, 0, sizeof(s));
1215
- s.width = diff_width(diffFlags);
1216
- nContext = diff_context_lines(diffFlags);
1217
- s.escHtml = (diffFlags & DIFF_HTML)!=0;
1218
- if( s.escHtml ){
1219
- for(i=SBS_LNA; i<=SBS_TXTB; i++){
1220
- blob_zero(&aCols[i]);
1221
- s.apCols[i] = &aCols[i];
1222
- }
1223
- }else{
1224
- for(i=SBS_LNA; i<=SBS_TXTB; i++){
1225
- s.apCols[i] = pOut;
1226
- }
1227
- }
1228
- s.pRe = pRe;
1229
- s.iStart = -1;
1230
- s.iStart2 = 0;
1231
- s.iEnd = -1;
1956
+ const DLine *A; /* Left side of the diff */
1957
+ const DLine *B; /* Right side of the diff */
1958
+ unsigned int a = 0; /* Index of next line in A[] */
1959
+ unsigned int b = 0; /* Index of next line in B[] */
1960
+ const int *R; /* Array of COPY/DELETE/INSERT triples */
1961
+ unsigned int r; /* Index into R[] */
1962
+ unsigned int nr; /* Number of COPY/DELETE/INSERT triples to process */
1963
+ unsigned int mxr; /* Maximum value for r */
1964
+ unsigned int na, nb; /* Number of lines shown from A and B */
1965
+ unsigned int i, j; /* Loop counters */
1966
+ unsigned int m, ma, mb;/* Number of lines to output */
1967
+ signed int skip = 0; /* Number of lines to skip */
1968
+ unsigned int nContext; /* Lines of context above and below each change */
1969
+
1970
+ nContext = diff_context_lines(diffFlags);
12321971
A = p->aFrom;
12331972
B = p->aTo;
12341973
R = p->aEdit;
12351974
mxr = p->nEdit;
12361975
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
12371976
12381977
for(r=0; r<mxr; r += 3*nr){
12391978
/* Figure out how many triples to show in a single block */
12401979
for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
1241
- /* printf("r=%d nr=%d\n", r, nr); */
12421980
12431981
/* If there is a regex, skip this block (generate no diff output)
12441982
** if the regex matches or does not match both insert and delete.
12451983
** Only display the block if one side matches but the other side does
12461984
** not.
@@ -1263,12 +2001,12 @@
12632001
b = xb;
12642002
continue;
12652003
}
12662004
}
12672005
1268
- /* For the current block comprising nr triples, figure out
1269
- ** how many lines of A and B are to be displayed
2006
+ /* Figure out how many lines of A and B are to be displayed
2007
+ ** for this change block.
12702008
*/
12712009
if( R[r]>nContext ){
12722010
na = nb = nContext;
12732011
skip = R[r] - nContext;
12742012
}else{
@@ -1289,127 +2027,91 @@
12892027
for(i=1; i<nr; i++){
12902028
na += R[r+i*3];
12912029
nb += R[r+i*3];
12922030
}
12932031
1294
- /* Draw the separator between blocks */
1295
- if( showDivider ){
1296
- if( s.escHtml ){
1297
- char zLn[10];
1298
- sqlite3_snprintf(sizeof(zLn), zLn, "%d", a+skip+1);
1299
- sbsWriteSep(&s, strlen(zLn), SBS_LNA);
1300
- sbsWriteSep(&s, s.width, SBS_TXTA);
1301
- sbsWriteSep(&s, 0, SBS_MKR);
1302
- sqlite3_snprintf(sizeof(zLn), zLn, "%d", b+skip+1);
1303
- sbsWriteSep(&s, strlen(zLn), SBS_LNB);
1304
- sbsWriteSep(&s, s.width, SBS_TXTB);
1305
- }else{
1306
- blob_appendf(pOut, "%.*c\n", s.width*2+16, '.');
1307
- }
1308
- }
1309
- showDivider = 1;
1310
- nChunk++;
1311
- if( s.escHtml ){
1312
- blob_appendf(s.apCols[SBS_LNA], "<span id=\"chunk%d\"></span>", nChunk);
1313
- }
1314
-
13152032
/* Show the initial common area */
13162033
a += skip;
13172034
b += skip;
13182035
m = R[r] - skip;
2036
+ if( r ) skip -= nContext;
2037
+ if( skip>0 ){
2038
+ pBuilder->xSkip(pBuilder, skip, 0);
2039
+ }
13192040
for(j=0; j<m; j++){
1320
- sbsWriteLineno(&s, a+j, SBS_LNA);
1321
- s.iStart = s.iEnd = -1;
1322
- sbsWriteText(&s, &A[a+j], SBS_TXTA);
1323
- sbsWriteMarker(&s, " ", "");
1324
- sbsWriteLineno(&s, b+j, SBS_LNB);
1325
- sbsWriteText(&s, &B[b+j], SBS_TXTB);
2041
+ pBuilder->xCommon(pBuilder, &A[a+j]);
13262042
}
13272043
a += m;
13282044
b += m;
13292045
13302046
/* Show the differences */
13312047
for(i=0; i<nr; i++){
2048
+ int nAlign;
13322049
unsigned char *alignment;
13332050
ma = R[r+i*3+1]; /* Lines on left but not on right */
13342051
mb = R[r+i*3+2]; /* Lines on right but not on left */
13352052
1336
- /* If the gap between the current diff and then next diff within the
1337
- ** same block is not too great, then render them as if they are a
1338
- ** single diff. */
1339
- while( i<nr-1 && smallGap(&R[r+i*3]) ){
2053
+ /* Try to find an alignment for the lines within this one block */
2054
+ alignment = diffBlockAlignment(&A[a], ma, &B[b], mb, diffFlags, &nAlign);
2055
+
2056
+ /* If we could not get a good alignment, try merging the current
2057
+ ** block with subsequent blocks, if the subsequent blocks are
2058
+ ** nearby */
2059
+ while( alignment[0]==4 && i<nr-1 && smallGap(&R[r+i*3]) ){
13402060
i++;
13412061
m = R[r+i*3];
13422062
ma += R[r+i*3+1] + m;
13432063
mb += R[r+i*3+2] + m;
1344
- }
1345
-
1346
- alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags);
1347
- for(j=0; ma+mb>0; j++){
1348
- if( alignment[j]==1 ){
1349
- /* Delete one line from the left */
1350
- sbsWriteLineno(&s, a, SBS_LNA);
1351
- s.iStart = 0;
1352
- s.zStart = "<span class=\"diffrm\">";
1353
- s.iEnd = LENGTH(&A[a]);
1354
- sbsWriteText(&s, &A[a], SBS_TXTA);
1355
- sbsWriteMarker(&s, " <", "&lt;");
1356
- sbsWriteNewlines(&s);
1357
- assert( ma>0 );
1358
- ma--;
1359
- a++;
1360
- }else if( alignment[j]==3 ){
1361
- /* The left line is changed into the right line */
1362
- sbsWriteLineChange(&s, &A[a], a, &B[b], b);
1363
- assert( ma>0 && mb>0 );
1364
- ma--;
1365
- mb--;
1366
- a++;
1367
- b++;
1368
- }else if( alignment[j]==2 ){
1369
- /* Insert one line on the right */
1370
- if( !s.escHtml ){
1371
- sbsWriteSpace(&s, s.width + 7, SBS_TXTA);
1372
- }
1373
- sbsWriteMarker(&s, " > ", "&gt;");
1374
- sbsWriteLineno(&s, b, SBS_LNB);
1375
- s.iStart = 0;
1376
- s.zStart = "<span class=\"diffadd\">";
1377
- s.iEnd = LENGTH(&B[b]);
1378
- sbsWriteText(&s, &B[b], SBS_TXTB);
1379
- assert( mb>0 );
1380
- mb--;
1381
- b++;
1382
- }else{
1383
- /* Delete from the left and insert on the right */
1384
- sbsWriteLineno(&s, a, SBS_LNA);
1385
- s.iStart = 0;
1386
- s.zStart = "<span class=\"diffrm\">";
1387
- s.iEnd = LENGTH(&A[a]);
1388
- sbsWriteText(&s, &A[a], SBS_TXTA);
1389
- sbsWriteMarker(&s, " | ", "|");
1390
- sbsWriteLineno(&s, b, SBS_LNB);
1391
- s.iStart = 0;
1392
- s.zStart = "<span class=\"diffadd\">";
1393
- s.iEnd = LENGTH(&B[b]);
1394
- sbsWriteText(&s, &B[b], SBS_TXTB);
1395
- ma--;
1396
- mb--;
1397
- a++;
1398
- b++;
1399
- }
1400
- }
2064
+ fossil_free(alignment);
2065
+ alignment = diffBlockAlignment(&A[a], ma, &B[b], mb, diffFlags,&nAlign);
2066
+ }
2067
+
2068
+ for(j=0; ma+mb>0; j++){
2069
+ assert( j<nAlign );
2070
+ switch( alignment[j] ){
2071
+ case 1: {
2072
+ /* Delete one line from the left */
2073
+ pBuilder->xDelete(pBuilder, &A[a]);
2074
+ ma--;
2075
+ a++;
2076
+ break;
2077
+ }
2078
+ case 2: {
2079
+ /* Insert one line on the right */
2080
+ pBuilder->xInsert(pBuilder, &B[b]);
2081
+ assert( mb>0 );
2082
+ mb--;
2083
+ b++;
2084
+ break;
2085
+ }
2086
+ case 3: {
2087
+ /* The left line is changed into the right line */
2088
+ pBuilder->xEdit(pBuilder, &A[a], &B[b]);
2089
+ assert( ma>0 && mb>0 );
2090
+ ma--;
2091
+ mb--;
2092
+ a++;
2093
+ b++;
2094
+ break;
2095
+ }
2096
+ case 4: {
2097
+ /* Delete from left then separately insert on the right */
2098
+ pBuilder->xReplace(pBuilder, &A[a], &B[b]);
2099
+ ma--;
2100
+ a++;
2101
+ mb--;
2102
+ b++;
2103
+ break;
2104
+ }
2105
+ }
2106
+ }
2107
+ assert( nAlign==j );
14012108
fossil_free(alignment);
14022109
if( i<nr-1 ){
14032110
m = R[r+i*3+3];
14042111
for(j=0; j<m; j++){
1405
- sbsWriteLineno(&s, a+j, SBS_LNA);
1406
- s.iStart = s.iEnd = -1;
1407
- sbsWriteText(&s, &A[a+j], SBS_TXTA);
1408
- sbsWriteMarker(&s, " ", "");
1409
- sbsWriteLineno(&s, b+j, SBS_LNB);
1410
- sbsWriteText(&s, &B[b+j], SBS_TXTB);
2112
+ pBuilder->xCommon(pBuilder, &A[a+j]);
14112113
}
14122114
b += m;
14132115
a += m;
14142116
}
14152117
}
@@ -1416,29 +2118,20 @@
14162118
14172119
/* Show the final common area */
14182120
assert( nr==i );
14192121
m = R[r+nr*3];
14202122
if( m>nContext ) m = nContext;
1421
- for(j=0; j<m; j++){
1422
- sbsWriteLineno(&s, a+j, SBS_LNA);
1423
- s.iStart = s.iEnd = -1;
1424
- sbsWriteText(&s, &A[a+j], SBS_TXTA);
1425
- sbsWriteMarker(&s, " ", "");
1426
- sbsWriteLineno(&s, b+j, SBS_LNB);
1427
- sbsWriteText(&s, &B[b+j], SBS_TXTB);
1428
- }
1429
- }
1430
-
1431
- if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){
1432
- blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1);
1433
- for(i=SBS_LNA; i<=SBS_TXTB; i++){
1434
- sbsWriteColumn(pOut, s.apCols[i], i);
1435
- blob_reset(s.apCols[i]);
1436
- }
1437
- blob_append(pOut, "</tr></table>\n", -1);
1438
- }
1439
-}
2123
+ for(j=0; j<m && j<nContext; j++){
2124
+ pBuilder->xCommon(pBuilder, &A[a+j]);
2125
+ }
2126
+ }
2127
+ if( R[r]>nContext ){
2128
+ pBuilder->xSkip(pBuilder, R[r] - nContext, 1);
2129
+ }
2130
+ pBuilder->xEnd(pBuilder);
2131
+}
2132
+
14402133
14412134
/*
14422135
** Compute the optimal longest common subsequence (LCS) using an
14432136
** exhaustive search. This version of the LCS is only used for
14442137
** shorter input strings since runtime is O(N*N) where N is the
@@ -1509,11 +2202,11 @@
15092202
int n; /* Loop limit */
15102203
DLine *pA, *pB; /* Pointers to lines */
15112204
int iSX, iSY, iEX, iEY; /* Current match */
15122205
int skew = 0; /* How lopsided is the match */
15132206
int dist = 0; /* Distance of match from center */
1514
- int mid; /* Center of the span */
2207
+ int mid; /* Center of the chng */
15152208
int iSXb, iSYb, iEXb, iEYb; /* Best match so far */
15162209
int iSXp, iSYp, iEXp, iEYp; /* Previous match */
15172210
sqlite3_int64 bestScore; /* Best score so far */
15182211
sqlite3_int64 score; /* Score for current candidate LCS */
15192212
int span; /* combined width of the input sequences */
@@ -1878,18 +2571,26 @@
18782571
blob_append(pOut, msg, -1);
18792572
}
18802573
}
18812574
18822575
/*
1883
-** Generate a report of the differences between files pA and pB.
1884
-** If pOut is not NULL then a unified diff is appended there. It
1885
-** is assumed that pOut has already been initialized. If pOut is
1886
-** NULL, then a pointer to an array of integers is returned.
1887
-** The integers come in triples. For each triple,
1888
-** the elements are the number of lines copied, the number of
1889
-** lines deleted, and the number of lines inserted. The vector
1890
-** is terminated by a triple of all zeros.
2576
+** Generate a report of the differences between files pA_Blob and pB_Blob.
2577
+**
2578
+** If pOut!=NULL then append text to pOut that will be the difference,
2579
+** formatted according to flags in diffFlags. The pOut Blob must have
2580
+** already been initialized.
2581
+**
2582
+** If pOut==NULL then no formatting occurs. Instead, this routine
2583
+** returns a pointer to an array of integers. The integers come in
2584
+** triples. The elements of each triple are:
2585
+**
2586
+** 1. The number of lines to copy
2587
+** 2. The number of lines to delete
2588
+** 3. The number of lines to insert
2589
+**
2590
+** The return vector is terminated bin a triple of all zeros. The caller
2591
+** should free the returned vector using fossil_free().
18912592
**
18922593
** This diff utility does not work on binary files. If a binary
18932594
** file is encountered, 0 is returned and pOut is written with
18942595
** text "cannot compute difference between binary files".
18952596
*/
@@ -1969,14 +2670,40 @@
19692670
g.diffCnt[2] += nDel;
19702671
if( nIns+nDel ){
19712672
g.diffCnt[0]++;
19722673
blob_appendf(pOut, "%10d %10d", nIns, nDel);
19732674
}
2675
+ }else if( diffFlags & DIFF_RAW ){
2676
+ const int *R = c.aEdit;
2677
+ unsigned int r;
2678
+ for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
2679
+ blob_appendf(pOut, " copy %6d delete %6d insert %6d\n",
2680
+ R[r], R[r+1], R[r+2]);
2681
+ }
2682
+ }else if( diffFlags & DIFF_JSON ){
2683
+ DiffBuilder *pBuilder = dfjsonNew(pOut);
2684
+ formatDiff(&c, pRe, diffFlags, pBuilder);
2685
+ blob_append_char(pOut, '\n');
2686
+ }else if( diffFlags & DIFF_TCL ){
2687
+ DiffBuilder *pBuilder = dftclNew(pOut);
2688
+ formatDiff(&c, pRe, diffFlags, pBuilder);
19742689
}else if( diffFlags & DIFF_SIDEBYSIDE ){
1975
- sbsDiff(&c, pOut, pRe, diffFlags);
2690
+ DiffBuilder *pBuilder;
2691
+ if( diffFlags & DIFF_HTML ){
2692
+ pBuilder = dfsplitNew(pOut);
2693
+ }else{
2694
+ pBuilder = dfsbsNew(pOut, diffFlags);
2695
+ }
2696
+ formatDiff(&c, pRe, diffFlags, pBuilder);
2697
+ }else if( diffFlags & DIFF_DEBUG ){
2698
+ DiffBuilder *pBuilder = dfdebugNew(pOut);
2699
+ formatDiff(&c, pRe, diffFlags, pBuilder);
2700
+ }else if( diffFlags & DIFF_HTML ){
2701
+ DiffBuilder *pBuilder = dfunifiedNew(pOut);
2702
+ formatDiff(&c, pRe, diffFlags, pBuilder);
19762703
}else{
1977
- contextDiff(&c, pOut, pRe, diffFlags);
2704
+ contextDiff(&c, pOut, diffFlags);
19782705
}
19792706
fossil_free(c.aFrom);
19802707
fossil_free(c.aTo);
19812708
fossil_free(c.aEdit);
19822709
return 0;
@@ -2049,48 +2776,40 @@
20492776
}
20502777
if( find_option("by",0,0)!=0 ){
20512778
diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER
20522779
|DIFF_SIDEBYSIDE;
20532780
}
2781
+ if( find_option("json",0,0)!=0 ){
2782
+ diffFlags |= DIFF_JSON;
2783
+ }
2784
+ if( find_option("tcl",0,0)!=0 ){
2785
+ diffFlags |= DIFF_TCL;
2786
+ }
2787
+
2788
+ /* Undocumented and unsupported flags used for development
2789
+ ** debugging and analysis: */
2790
+ if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
2791
+ if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
20542792
return diffFlags;
20552793
}
20562794
2057
-/*
2058
-** COMMAND: test-rawdiff
2059
-**
2060
-** Usage: %fossil test-rawdiff FILE1 FILE2
2061
-**
2062
-** Show a minimal sequence of Copy/Delete/Insert operations needed to convert
2063
-** FILE1 into FILE2. This command is intended for use in testing and debugging
2064
-** the built-in difference engine of Fossil.
2065
-*/
2066
-void test_rawdiff_cmd(void){
2067
- Blob a, b;
2068
- int r;
2069
- int i;
2070
- int *R;
2071
- u64 diffFlags = diff_options();
2072
- if( g.argc<4 ) usage("FILE1 FILE2 ...");
2073
- blob_read_from_file(&a, g.argv[2], ExtFILE);
2074
- for(i=3; i<g.argc; i++){
2075
- if( i>3 ) fossil_print("-------------------------------\n");
2076
- blob_read_from_file(&b, g.argv[i], ExtFILE);
2077
- R = text_diff(&a, &b, 0, 0, diffFlags);
2078
- for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
2079
- fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
2080
- }
2081
- /* free(R); */
2082
- blob_reset(&b);
2083
- }
2084
-}
2085
-
20862795
/*
20872796
** COMMAND: test-diff
2797
+** COMMAND: xdiff
2798
+**
2799
+** Usage: %fossil xdiff [options] FILE1 FILE2
2800
+**
2801
+** This is the "external diff" feature. By "external" here we mean a diff
2802
+** applied to files that are not under version control. See the "diff"
2803
+** command for computing differences between files that are under control.
20882804
**
2089
-** Usage: %fossil [options] FILE1 FILE2
2805
+** This command prints the differences between the two files FILE1 and FILE2.
2806
+** all of the usual diff command-line options apply. See the "diff" command
2807
+** for a full list of command-line options.
20902808
**
2091
-** Print the difference between two files. The usual diff options apply.
2809
+** This command used to be called "test-diff". The older "test-diff" spelling
2810
+** still works, for compatibility.
20922811
*/
20932812
void test_diff_cmd(void){
20942813
Blob a, b, out;
20952814
u64 diffFlag;
20962815
const char *zRe; /* Regex filter for diff output */
20972816
20982817
ADDED src/diff.js
--- src/diff.c
+++ src/diff.c
@@ -42,12 +42,16 @@
42 #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */
43 #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */
44 #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */
45 #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */
46 #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */
47 #define DIFF_WEBPAGE (((u64)0x40)<<32) /* Complete webpage */
48 #define DIFF_BROWSER (((u64)0x80)<<32) /* The --browser option */
 
 
 
 
49
50 /*
51 ** These error messages are shared in multiple locations. They are defined
52 ** here for consistency.
53 */
@@ -95,10 +99,15 @@
95 /*
96 ** Length of a dline
97 */
98 #define LENGTH(X) ((X)->n)
99
 
 
 
 
 
100 /*
101 ** A context for running a raw diff.
102 **
103 ** The aEdit[] array describes the raw diff. Each triple of integers in
104 ** aEdit[] means:
@@ -273,13 +282,13 @@
273 /*
274 ** Return true if the regular expression *pRe matches any of the
275 ** N dlines
276 */
277 static int re_dline_match(
278 ReCompiled *pRe, /* The regular expression to be matched */
279 DLine *aDLine, /* First of N DLines to compare against */
280 int N /* Number of DLines to check */
281 ){
282 while( N-- ){
283 if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){
284 return 1;
285 }
@@ -292,41 +301,23 @@
292 ** Append a single line of context-diff output to pOut.
293 */
294 static void appendDiffLine(
295 Blob *pOut, /* Where to write the line of output */
296 char cPrefix, /* One of " ", "+", or "-" */
297 DLine *pLine, /* The line to be output */
298 int html, /* True if generating HTML. False for plain text */
299 ReCompiled *pRe /* Colorize only if line matches this Regex */
300 ){
301 blob_append(pOut, &cPrefix, 1);
302 if( html ){
303 if( pRe && re_dline_match(pRe, pLine, 1)==0 ){
304 cPrefix = ' ';
305 }else if( cPrefix=='+' ){
306 blob_append(pOut, "<span class=\"diffadd\">", -1);
307 }else if( cPrefix=='-' ){
308 blob_append(pOut, "<span class=\"diffrm\">", -1);
309 }
310 htmlize_to_blob(pOut, pLine->z, pLine->n);
311 if( cPrefix!=' ' ){
312 blob_append(pOut, "</span>", -1);
313 }
314 }else{
315 blob_append(pOut, pLine->z, pLine->n);
316 }
317 blob_append(pOut, "\n", 1);
318 }
319
320 /*
321 ** Add two line numbers to the beginning of an output line for a context
322 ** diff. One or the other of the two numbers might be zero, which means
323 ** to leave that number field blank. The "html" parameter means to format
324 ** the output for HTML.
325 */
326 static void appendDiffLineno(Blob *pOut, int lnA, int lnB, int html){
327 if( html ) blob_append(pOut, "<span class=\"diffln\">", -1);
328 if( lnA>0 ){
329 blob_appendf(pOut, "%6d ", lnA);
330 }else{
331 blob_append(pOut, " ", 7);
332 }
@@ -333,21 +324,18 @@
333 if( lnB>0 ){
334 blob_appendf(pOut, "%6d ", lnB);
335 }else{
336 blob_append(pOut, " ", 8);
337 }
338 if( html ) blob_append(pOut, "</span>", -1);
339 }
340
341 /*
342 ** Given a raw diff p[] in which the p->aEdit[] array has been filled
343 ** in, compute a context diff into pOut.
344 */
345 static void contextDiff(
346 DContext *p, /* The difference */
347 Blob *pOut, /* Output a context diff to here */
348 ReCompiled *pRe, /* Only show changes that match this regex */
349 u64 diffFlags /* Flags controlling the diff format */
350 ){
351 DLine *A; /* Left side of the diff */
352 DLine *B; /* Right side of the diff */
353 int a = 0; /* Index of next line in A[] */
@@ -361,17 +349,14 @@
361 int m; /* Number of lines to output */
362 int skip; /* Number of lines to skip */
363 static int nChunk = 0; /* Number of diff chunks seen so far */
364 int nContext; /* Number of lines of context */
365 int showLn; /* Show line numbers */
366 int html; /* Render as HTML */
367 int showDivider = 0; /* True to show the divider between diff blocks */
368
369 nContext = diff_context_lines(diffFlags);
370 showLn = (diffFlags & DIFF_LINENO)!=0;
371 html = (diffFlags & DIFF_HTML)!=0;
372 if( html ) blob_append(pOut, "<pre class=\"udiff\">\n", -1);
373 A = p->aFrom;
374 B = p->aTo;
375 R = p->aEdit;
376 mxr = p->nEdit;
377 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
@@ -378,35 +363,10 @@
378 for(r=0; r<mxr; r += 3*nr){
379 /* Figure out how many triples to show in a single block */
380 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
381 /* printf("r=%d nr=%d\n", r, nr); */
382
383 /* If there is a regex, skip this block (generate no diff output)
384 ** if the regex matches or does not match both insert and delete.
385 ** Only display the block if one side matches but the other side does
386 ** not.
387 */
388 if( pRe ){
389 int hideBlock = 1;
390 int xa = a, xb = b;
391 for(i=0; hideBlock && i<nr; i++){
392 int c1, c2;
393 xa += R[r+i*3];
394 xb += R[r+i*3];
395 c1 = re_dline_match(pRe, &A[xa], R[r+i*3+1]);
396 c2 = re_dline_match(pRe, &B[xb], R[r+i*3+2]);
397 hideBlock = c1==c2;
398 xa += R[r+i*3+1];
399 xb += R[r+i*3+2];
400 }
401 if( hideBlock ){
402 a = xa;
403 b = xb;
404 continue;
405 }
406 }
407
408 /* For the current block comprising nr triples, figure out
409 ** how many lines of A and B are to be displayed
410 */
411 if( R[r]>nContext ){
412 na = nb = nContext;
@@ -438,60 +398,55 @@
438 nChunk++;
439 if( showLn ){
440 if( !showDivider ){
441 /* Do not show a top divider */
442 showDivider = 1;
443 }else if( html ){
444 blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.');
445 }else{
446 blob_appendf(pOut, "%.80c\n", '.');
447 }
448 if( html ) blob_appendf(pOut, "<span id=\"chunk%d\"></span>", nChunk);
449 }else{
450 if( html ) blob_appendf(pOut, "<span class=\"diffln\">");
451 /*
452 * If the patch changes an empty file or results in an empty file,
453 * the block header must use 0,0 as position indicator and not 1,0.
454 * Otherwise, patch would be confused and may reject the diff.
455 */
456 blob_appendf(pOut,"@@ -%d,%d +%d,%d @@",
457 na ? a+skip+1 : a+skip, na,
458 nb ? b+skip+1 : b+skip, nb);
459 if( html ) blob_appendf(pOut, "</span>");
460 blob_append(pOut, "\n", 1);
461 }
462
463 /* Show the initial common area */
464 a += skip;
465 b += skip;
466 m = R[r] - skip;
467 for(j=0; j<m; j++){
468 if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
469 appendDiffLine(pOut, ' ', &A[a+j], html, 0);
470 }
471 a += m;
472 b += m;
473
474 /* Show the differences */
475 for(i=0; i<nr; i++){
476 m = R[r+i*3+1];
477 for(j=0; j<m; j++){
478 if( showLn ) appendDiffLineno(pOut, a+j+1, 0, html);
479 appendDiffLine(pOut, '-', &A[a+j], html, pRe);
480 }
481 a += m;
482 m = R[r+i*3+2];
483 for(j=0; j<m; j++){
484 if( showLn ) appendDiffLineno(pOut, 0, b+j+1, html);
485 appendDiffLine(pOut, '+', &B[b+j], html, pRe);
486 }
487 b += m;
488 if( i<nr-1 ){
489 m = R[r+i*3+3];
490 for(j=0; j<m; j++){
491 if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
492 appendDiffLine(pOut, ' ', &A[a+j], html, 0);
493 }
494 b += m;
495 a += m;
496 }
497 }
@@ -499,181 +454,33 @@
499 /* Show the final common area */
500 assert( nr==i );
501 m = R[r+nr*3];
502 if( m>nContext ) m = nContext;
503 for(j=0; j<m; j++){
504 if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
505 appendDiffLine(pOut, ' ', &A[a+j], html, 0);
506 }
507 }
508 if( html ) blob_append(pOut, "</pre>\n", -1);
509 }
510
511 /*
512 ** Status of a single output line
513 */
514 typedef struct SbsLine SbsLine;
515 struct SbsLine {
516 Blob *apCols[5]; /* Array of pointers to output columns */
517 int width; /* Maximum width of a column in the output */
518 unsigned char escHtml; /* True to escape html characters */
519 int iStart; /* Write zStart prior to character iStart */
520 const char *zStart; /* A <span> tag */
521 int iEnd; /* Write </span> prior to character iEnd */
522 int iStart2; /* Write zStart2 prior to character iStart2 */
523 const char *zStart2; /* A <span> tag */
524 int iEnd2; /* Write </span> prior to character iEnd2 */
525 ReCompiled *pRe; /* Only colorize matching lines, if not NULL */
526 };
527
528 /*
529 ** Column indices for SbsLine.apCols[]
530 */
531 #define SBS_LNA 0 /* Left line number */
532 #define SBS_TXTA 1 /* Left text */
533 #define SBS_MKR 2 /* Middle separator column */
534 #define SBS_LNB 3 /* Right line number */
535 #define SBS_TXTB 4 /* Right text */
536
537 /*
538 ** Append newlines to all columns.
539 */
540 static void sbsWriteNewlines(SbsLine *p){
541 int i;
542 for( i=p->escHtml ? SBS_LNA : SBS_TXTB; i<=SBS_TXTB; i++ ){
543 blob_append(p->apCols[i], "\n", 1);
544 }
545 }
546
547 /*
548 ** Append n spaces to the column.
549 */
550 static void sbsWriteSpace(SbsLine *p, int n, int col){
551 blob_appendf(p->apCols[col], "%*s", n, "");
552 }
553
554 /*
555 ** Write the text of pLine into column iCol of p.
556 **
557 ** If outputting HTML, write the full line. Otherwise, only write the
558 ** width characters. Translate tabs into spaces. Add newlines if col
559 ** is SBS_TXTB. Translate HTML characters if escHtml is true. Pad the
560 ** rendering to width bytes if col is SBS_TXTA and escHtml is false.
561 **
562 ** This comment contains multibyte unicode characters (ü, Æ, ð) in order
563 ** to test the ability of the diff code to handle such characters.
564 */
565 static void sbsWriteText(SbsLine *p, DLine *pLine, int col){
566 Blob *pCol = p->apCols[col];
567 int n = pLine->n;
568 int i; /* Number of input characters consumed */
569 int k; /* Cursor position */
570 int needEndSpan = 0;
571 const char *zIn = pLine->z;
572 int w = p->width;
573 int colorize = p->escHtml;
574 if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
575 colorize = 0;
576 }
577 for(i=k=0; (p->escHtml || k<w) && i<n; i++, k++){
578 char c = zIn[i];
579 if( colorize ){
580 if( i==p->iStart ){
581 int x = strlen(p->zStart);
582 blob_append(pCol, p->zStart, x);
583 needEndSpan = 1;
584 if( p->iStart2 ){
585 p->iStart = p->iStart2;
586 p->zStart = p->zStart2;
587 p->iStart2 = 0;
588 }
589 }else if( i==p->iEnd ){
590 blob_append(pCol, "</span>", 7);
591 needEndSpan = 0;
592 if( p->iEnd2 ){
593 p->iEnd = p->iEnd2;
594 p->iEnd2 = 0;
595 }
596 }
597 }
598 if( c=='\t' && !p->escHtml ){
599 blob_append(pCol, " ", 1);
600 while( (k&7)!=7 && (p->escHtml || k<w) ){
601 blob_append(pCol, " ", 1);
602 k++;
603 }
604 }else if( c=='\r' || c=='\f' ){
605 blob_append(pCol, " ", 1);
606 }else if( c=='<' && p->escHtml ){
607 blob_append(pCol, "&lt;", 4);
608 }else if( c=='&' && p->escHtml ){
609 blob_append(pCol, "&amp;", 5);
610 }else if( c=='>' && p->escHtml ){
611 blob_append(pCol, "&gt;", 4);
612 }else if( c=='"' && p->escHtml ){
613 blob_append(pCol, "&quot;", 6);
614 }else{
615 blob_append(pCol, &zIn[i], 1);
616 if( (c&0xc0)==0x80 ) k--;
617 }
618 }
619 if( needEndSpan ){
620 blob_append(pCol, "</span>", 7);
621 }
622 if( col==SBS_TXTB ){
623 sbsWriteNewlines(p);
624 }else if( !p->escHtml ){
625 sbsWriteSpace(p, w-k, SBS_TXTA);
626 }
627 }
628
629 /*
630 ** Append a column to the final output blob.
631 */
632 static void sbsWriteColumn(Blob *pOut, Blob *pCol, int col){
633 blob_appendf(pOut,
634 "<td><div class=\"diff%scol\">\n"
635 "<pre>\n"
636 "%s"
637 "</pre>\n"
638 "</div></td>\n",
639 (col % 3) ? (col == SBS_MKR ? "mkr" : "txt") : "ln",
640 blob_str(pCol)
641 );
642 }
643
644 /*
645 ** Append a separator line to column iCol
646 */
647 static void sbsWriteSep(SbsLine *p, int len, int col){
648 char ch = '.';
649 if( len<1 ){
650 len = 1;
651 ch = ' ';
652 }
653 blob_appendf(p->apCols[col], "<span class=\"diffhr\">%.*c</span>\n", len, ch);
654 }
655
656 /*
657 ** Append the appropriate marker into the center column of the diff.
658 */
659 static void sbsWriteMarker(SbsLine *p, const char *zTxt, const char *zHtml){
660 blob_append(p->apCols[SBS_MKR], p->escHtml ? zHtml : zTxt, -1);
661 }
662
663 /*
664 ** Append a line number to the column.
665 */
666 static void sbsWriteLineno(SbsLine *p, int ln, int col){
667 if( p->escHtml ){
668 blob_appendf(p->apCols[col], "%d", ln+1);
669 }else{
670 char zLn[7];
671 sqlite3_snprintf(7, zLn, "%5d ", ln+1);
672 blob_appendf(p->apCols[col], "%s ", zLn);
673 }
674 }
675
676 /*
677 ** The two text segments zLeft and zRight are known to be different on
678 ** both ends, but they might have a common segment in the middle. If
679 ** they do not have a common segment, return 0. If they do have a large
@@ -692,123 +499,177 @@
692 const char *zRight, int nB, /* String on the right */
693 int *aLCS /* Identify bounds of LCS here */
694 ){
695 const unsigned char *zA = (const unsigned char*)zLeft; /* left string */
696 const unsigned char *zB = (const unsigned char*)zRight; /* right string */
697 int nt; /* Number of target points */
698 int ti[3]; /* Index for start of each 4-byte target */
699 unsigned int target[3]; /* 4-byte alignment targets */
700 unsigned int probe; /* probe to compare against target */
701 int iAS, iAE, iBS, iBE; /* Range of common segment */
702 int i, j; /* Loop counters */
703 int rc = 0; /* Result code. 1 for success */
704
705 if( nA<6 || nB<6 ) return 0;
706 memset(aLCS, 0, sizeof(int)*4);
707 ti[0] = i = nB/2-2;
708 target[0] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
709 probe = 0;
710 if( nB<16 ){
711 nt = 1;
712 }else{
713 ti[1] = i = nB/4-2;
714 target[1] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
715 ti[2] = i = (nB*3)/4-2;
716 target[2] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
717 nt = 3;
718 }
719 probe = (zA[0]<<16) | (zA[1]<<8) | zA[2];
720 for(i=3; i<nA; i++){
721 probe = (probe<<8) | zA[i];
722 for(j=0; j<nt; j++){
723 if( probe==target[j] ){
724 iAS = i-3;
725 iAE = i+1;
726 iBS = ti[j];
727 iBE = ti[j]+4;
728 while( iAE<nA && iBE<nB && zA[iAE]==zB[iBE] ){ iAE++; iBE++; }
729 while( iAS>0 && iBS>0 && zA[iAS-1]==zB[iBS-1] ){ iAS--; iBS--; }
730 if( iAE-iAS > aLCS[1] - aLCS[0] ){
731 aLCS[0] = iAS;
732 aLCS[1] = iAE;
733 aLCS[2] = iBS;
734 aLCS[3] = iBE;
735 rc = 1;
736 }
737 }
738 }
739 }
740 return rc;
741 }
742
743 /*
744 ** Try to shift iStart as far as possible to the left.
745 */
746 static void sbsShiftLeft(SbsLine *p, const char *z){
747 int i, j;
748 while( (i=p->iStart)>0 && z[i-1]==z[i] ){
749 for(j=i+1; j<p->iEnd && z[j-1]==z[j]; j++){}
750 if( j<p->iEnd ) break;
751 p->iStart--;
752 p->iEnd--;
753 }
754 }
755
756 /*
757 ** Simplify iStart and iStart2:
758 **
759 ** * If iStart is a null-change then move iStart2 into iStart
760 ** * Make sure any null-changes are in canonoical form.
761 ** * Make sure all changes are at character boundaries for
762 ** multi-byte characters.
763 */
764 static void sbsSimplifyLine(SbsLine *p, const char *z){
765 if( p->iStart2==p->iEnd2 ){
766 p->iStart2 = p->iEnd2 = 0;
767 }else if( p->iStart2 ){
768 while( p->iStart2>0 && (z[p->iStart2]&0xc0)==0x80 ) p->iStart2--;
769 while( (z[p->iEnd2]&0xc0)==0x80 ) p->iEnd2++;
770 }
771 if( p->iStart==p->iEnd ){
772 p->iStart = p->iStart2;
773 p->iEnd = p->iEnd2;
774 p->zStart = p->zStart2;
775 p->iStart2 = 0;
776 p->iEnd2 = 0;
777 }
778 if( p->iStart==p->iEnd ){
779 p->iStart = p->iEnd = -1;
780 }else if( p->iStart>0 ){
781 while( p->iStart>0 && (z[p->iStart]&0xc0)==0x80 ) p->iStart--;
782 while( (z[p->iEnd]&0xc0)==0x80 ) p->iEnd++;
783 }
784 }
785
786 /*
787 ** Write out lines that have been edited. Adjust the highlight to cover
788 ** only those parts of the line that actually changed.
789 */
790 static void sbsWriteLineChange(
791 SbsLine *p, /* The SBS output line */
792 DLine *pLeft, /* Left line of the change */
793 int lnLeft, /* Line number for the left line */
794 DLine *pRight, /* Right line of the change */
795 int lnRight /* Line number of the right line */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796 ){
797 int nLeft; /* Length of left line in bytes */
798 int nRight; /* Length of right line in bytes */
799 int nShort; /* Shortest of left and right */
800 int nPrefix; /* Length of common prefix */
801 int nSuffix; /* Length of common suffix */
 
802 const char *zLeft; /* Text of the left line */
803 const char *zRight; /* Text of the right line */
804 int nLeftDiff; /* nLeft - nPrefix - nSuffix */
805 int nRightDiff; /* nRight - nPrefix - nSuffix */
806 int aLCS[4]; /* Bounds of common middle segment */
807 static const char zClassRm[] = "<span class=\"diffrm\">";
808 static const char zClassAdd[] = "<span class=\"diffadd\">";
809 static const char zClassChng[] = "<span class=\"diffchng\">";
810
811 nLeft = pLeft->n;
812 zLeft = pLeft->z;
813 nRight = pRight->n;
814 zRight = pRight->z;
@@ -821,25 +682,27 @@
821 if( nPrefix<nShort ){
822 while( nPrefix>0 && (zLeft[nPrefix]&0xc0)==0x80 ) nPrefix--;
823 }
824 nSuffix = 0;
825 if( nPrefix<nShort ){
826 while( nSuffix<nShort && zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
 
827 nSuffix++;
828 }
829 if( nSuffix<nShort ){
830 while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--;
831 }
832 if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
833 }
 
834
835 /* If the prefix and suffix overlap, that means that we are dealing with
836 ** a pure insertion or deletion of text that can have multiple alignments.
837 ** Try to find an alignment to begins and ends on whitespace, or on
838 ** punctuation, rather than in the middle of a name or number.
839 */
840 if( nPrefix+nSuffix > nShort ){
841 int iBest = -1;
842 int iBestVal = -1;
843 int i;
844 int nLong = nLeft<nRight ? nRight : nLeft;
845 int nGap = nLong - nShort;
@@ -862,108 +725,931 @@
862 iBest = i;
863 }
864 }
865 nPrefix = iBest;
866 nSuffix = nShort - nPrefix;
867 }
868
869 /* A single chunk of text inserted on the right */
870 if( nPrefix+nSuffix==nLeft ){
871 sbsWriteLineno(p, lnLeft, SBS_LNA);
872 p->iStart2 = p->iEnd2 = 0;
873 p->iStart = p->iEnd = -1;
874 sbsWriteText(p, pLeft, SBS_TXTA);
875 if( nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){
876 sbsWriteMarker(p, " ", "");
877 }else{
878 sbsWriteMarker(p, " | ", "|");
879 }
880 sbsWriteLineno(p, lnRight, SBS_LNB);
881 p->iStart = nPrefix;
882 p->iEnd = nRight - nSuffix;
883 p->zStart = zClassAdd;
884 sbsWriteText(p, pRight, SBS_TXTB);
885 return;
886 }
887
888 /* A single chunk of text deleted from the left */
889 if( nPrefix+nSuffix==nRight ){
890 /* Text deleted from the left */
891 sbsWriteLineno(p, lnLeft, SBS_LNA);
892 p->iStart2 = p->iEnd2 = 0;
893 p->iStart = nPrefix;
894 p->iEnd = nLeft - nSuffix;
895 p->zStart = zClassRm;
896 sbsWriteText(p, pLeft, SBS_TXTA);
897 sbsWriteMarker(p, " | ", "|");
898 sbsWriteLineno(p, lnRight, SBS_LNB);
899 p->iStart = p->iEnd = -1;
900 sbsWriteText(p, pRight, SBS_TXTB);
901 return;
902 }
903
904 /* At this point we know that there is a chunk of text that has
905 ** changed between the left and the right. Check to see if there
906 ** is a large unchanged section in the middle of that changed block.
907 */
908 nLeftDiff = nLeft - nSuffix - nPrefix;
909 nRightDiff = nRight - nSuffix - nPrefix;
910 if( p->escHtml
911 && nLeftDiff >= 6
912 && nRightDiff >= 6
913 && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS)
914 ){
915 sbsWriteLineno(p, lnLeft, SBS_LNA);
916 p->iStart = nPrefix;
917 p->iEnd = nPrefix + aLCS[0];
918 if( aLCS[2]==0 ){
919 sbsShiftLeft(p, pLeft->z);
920 p->zStart = zClassRm;
921 }else{
922 p->zStart = zClassChng;
923 }
924 p->iStart2 = nPrefix + aLCS[1];
925 p->iEnd2 = nLeft - nSuffix;
926 p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng;
927 sbsSimplifyLine(p, zLeft);
928 sbsWriteText(p, pLeft, SBS_TXTA);
929 sbsWriteMarker(p, " | ", "|");
930 sbsWriteLineno(p, lnRight, SBS_LNB);
931 p->iStart = nPrefix;
932 p->iEnd = nPrefix + aLCS[2];
933 if( aLCS[0]==0 ){
934 sbsShiftLeft(p, pRight->z);
935 p->zStart = zClassAdd;
936 }else{
937 p->zStart = zClassChng;
938 }
939 p->iStart2 = nPrefix + aLCS[3];
940 p->iEnd2 = nRight - nSuffix;
941 p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng;
942 sbsSimplifyLine(p, zRight);
943 sbsWriteText(p, pRight, SBS_TXTB);
944 return;
945 }
946
947 /* If all else fails, show a single big change between left and right */
948 sbsWriteLineno(p, lnLeft, SBS_LNA);
949 p->iStart2 = p->iEnd2 = 0;
950 p->iStart = nPrefix;
951 p->iEnd = nLeft - nSuffix;
952 p->zStart = zClassChng;
953 sbsWriteText(p, pLeft, SBS_TXTA);
954 sbsWriteMarker(p, " | ", "|");
955 sbsWriteLineno(p, lnRight, SBS_LNB);
956 p->iEnd = nRight - nSuffix;
957 sbsWriteText(p, pRight, SBS_TXTB);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
958 }
959
960 /*
961 ** Minimum of two values
962 */
963 static int minInt(int a, int b){ return a<b ? a : b; }
964
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
965 /*
966 ** Return the number between 0 and 100 that is smaller the closer pA and
967 ** pB match. Return 0 for a perfect match. Return 100 if pA and pB are
968 ** completely different.
969 **
@@ -972,11 +1658,11 @@
972 ** (1) Remove leading and trailing whitespace.
973 ** (2) Truncate both strings to at most 250 characters
974 ** (3) Find the length of the longest common subsequence
975 ** (4) Longer common subsequences yield lower scores.
976 */
977 static int match_dline(DLine *pA, DLine *pB){
978 const char *zA; /* Left string */
979 const char *zB; /* right string */
980 int nA; /* Bytes in zA[] */
981 int nB; /* Bytes in zB[] */
982 int avg; /* Average length of A and B */
@@ -1024,10 +1710,32 @@
1024 #endif
1025
1026 /* Return the result */
1027 return score;
1028 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1029
1030 /*
1031 ** There is a change block in which nLeft lines of text on the left are
1032 ** converted into nRight lines of text on the right. This routine computes
1033 ** how the lines on the left line up with the lines on the right.
@@ -1039,61 +1747,113 @@
1039 ** 1. Delete the next line of pLeft.
1040 ** 2. Insert the next line of pRight.
1041 ** 3. The next line of pLeft changes into the next line of pRight.
1042 ** 4. Delete one line from pLeft and add one line to pRight.
1043 **
1044 ** Values larger than three indicate better matches.
1045 **
1046 ** The length of the returned array will be just large enough to cause
1047 ** all elements of pLeft and pRight to be consumed.
1048 **
1049 ** Algorithm: Wagner's minimum edit-distance algorithm, modified by
1050 ** adding a cost to each match based on how well the two rows match
1051 ** each other. Insertion and deletion costs are 50. Match costs
1052 ** are between 0 and 100 where 0 is a perfect match 100 is a complete
1053 ** mismatch.
1054 */
1055 static unsigned char *sbsAlignment(
1056 DLine *aLeft, int nLeft, /* Text on the left */
1057 DLine *aRight, int nRight, /* Text on the right */
1058 u64 diffFlags /* Flags passed into the original diff */
 
1059 ){
1060 int i, j, k; /* Loop counters */
1061 int *a; /* One row of the Wagner matrix */
1062 int *pToFree; /* Space that needs to be freed */
1063 unsigned char *aM; /* Wagner result matrix */
1064 int nMatch, iMatch; /* Number of matching lines and match score */
1065 int mnLen; /* MIN(nLeft, nRight) */
1066 int mxLen; /* MAX(nLeft, nRight) */
1067 int aBuf[100]; /* Stack space for a[] if nRight not to big */
1068
1069 aM = fossil_malloc( (nLeft+1)*(nRight+1) );
1070 if( nLeft==0 ){
1071 memset(aM, 2, nRight);
1072 return aM;
1073 }
1074 if( nRight==0 ){
1075 memset(aM, 1, nLeft);
1076 return aM;
1077 }
1078
1079 /* This algorithm is O(N**2). So if N is too big, bail out with a
1080 ** simple (but stupid and ugly) result that doesn't take too long. */
1081 mnLen = nLeft<nRight ? nLeft : nRight;
1082 if( nLeft*nRight>100000 && (diffFlags & DIFF_SLOW_SBS)==0 ){
1083 memset(aM, 4, mnLen);
1084 if( nLeft>mnLen ) memset(aM+mnLen, 1, nLeft-mnLen);
1085 if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen);
1086 return aM;
1087 }
1088
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1089 if( nRight < count(aBuf)-1 ){
1090 pToFree = 0;
1091 a = aBuf;
1092 }else{
1093 a = pToFree = fossil_malloc( sizeof(a[0])*(nRight+1) );
1094 }
 
 
1095
1096 /* Compute the best alignment */
1097 for(i=0; i<=nRight; i++){
1098 aM[i] = 2;
1099 a[i] = i*50;
@@ -1148,10 +1908,11 @@
1148 aM[k] = aM[j*(nRight+1)+i];
1149 }
1150 k++;
1151 i = (nRight+1)*(nLeft+1) - k;
1152 memmove(aM, &aM[k], i);
 
1153
1154 /* If:
1155 ** (1) the alignment is more than 25% longer than the longest side, and
1156 ** (2) the average match cost exceeds 15
1157 ** Then this is probably an alignment that will be difficult for humans
@@ -1161,13 +1922,13 @@
1161 ** The coefficients for conditions (1) and (2) above are determined by
1162 ** experimentation.
1163 */
1164 mxLen = nLeft>nRight ? nLeft : nRight;
1165 if( i*4>mxLen*5 && (nMatch==0 || iMatch/nMatch>15) ){
1166 memset(aM, 4, mnLen);
1167 if( nLeft>mnLen ) memset(aM+mnLen, 1, nLeft-mnLen);
1168 if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen);
1169 }
1170
1171 /* Return the result */
1172 fossil_free(pToFree);
1173 return aM;
@@ -1177,70 +1938,47 @@
1177 ** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a
1178 ** pair of adjacent differences. Return true if the gap between these
1179 ** two differences is so small that they should be rendered as a single
1180 ** edit.
1181 */
1182 static int smallGap(int *R){
1183 return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8;
1184 }
1185
1186 /*
1187 ** Given a diff context in which the aEdit[] array has been filled
1188 ** in, compute a side-by-side diff into pOut.
1189 */
1190 static void sbsDiff(
1191 DContext *p, /* The computed diff */
1192 Blob *pOut, /* Write the results here */
1193 ReCompiled *pRe, /* Only show changes that match this regex */
1194 u64 diffFlags /* Flags controlling the diff */
1195 ){
1196 DLine *A; /* Left side of the diff */
1197 DLine *B; /* Right side of the diff */
1198 int a = 0; /* Index of next line in A[] */
1199 int b = 0; /* Index of next line in B[] */
1200 int *R; /* Array of COPY/DELETE/INSERT triples */
1201 int r; /* Index into R[] */
1202 int nr; /* Number of COPY/DELETE/INSERT triples to process */
1203 int mxr; /* Maximum value for r */
1204 int na, nb; /* Number of lines shown from A and B */
1205 int i, j; /* Loop counters */
1206 int m, ma, mb;/* Number of lines to output */
1207 int skip; /* Number of lines to skip */
1208 static int nChunk = 0; /* Number of chunks of diff output seen so far */
1209 SbsLine s; /* Output line buffer */
1210 int nContext; /* Lines of context above and below each change */
1211 int showDivider = 0; /* True to show the divider */
1212 Blob aCols[5]; /* Array of column blobs */
1213
1214 memset(&s, 0, sizeof(s));
1215 s.width = diff_width(diffFlags);
1216 nContext = diff_context_lines(diffFlags);
1217 s.escHtml = (diffFlags & DIFF_HTML)!=0;
1218 if( s.escHtml ){
1219 for(i=SBS_LNA; i<=SBS_TXTB; i++){
1220 blob_zero(&aCols[i]);
1221 s.apCols[i] = &aCols[i];
1222 }
1223 }else{
1224 for(i=SBS_LNA; i<=SBS_TXTB; i++){
1225 s.apCols[i] = pOut;
1226 }
1227 }
1228 s.pRe = pRe;
1229 s.iStart = -1;
1230 s.iStart2 = 0;
1231 s.iEnd = -1;
1232 A = p->aFrom;
1233 B = p->aTo;
1234 R = p->aEdit;
1235 mxr = p->nEdit;
1236 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
1237
1238 for(r=0; r<mxr; r += 3*nr){
1239 /* Figure out how many triples to show in a single block */
1240 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
1241 /* printf("r=%d nr=%d\n", r, nr); */
1242
1243 /* If there is a regex, skip this block (generate no diff output)
1244 ** if the regex matches or does not match both insert and delete.
1245 ** Only display the block if one side matches but the other side does
1246 ** not.
@@ -1263,12 +2001,12 @@
1263 b = xb;
1264 continue;
1265 }
1266 }
1267
1268 /* For the current block comprising nr triples, figure out
1269 ** how many lines of A and B are to be displayed
1270 */
1271 if( R[r]>nContext ){
1272 na = nb = nContext;
1273 skip = R[r] - nContext;
1274 }else{
@@ -1289,127 +2027,91 @@
1289 for(i=1; i<nr; i++){
1290 na += R[r+i*3];
1291 nb += R[r+i*3];
1292 }
1293
1294 /* Draw the separator between blocks */
1295 if( showDivider ){
1296 if( s.escHtml ){
1297 char zLn[10];
1298 sqlite3_snprintf(sizeof(zLn), zLn, "%d", a+skip+1);
1299 sbsWriteSep(&s, strlen(zLn), SBS_LNA);
1300 sbsWriteSep(&s, s.width, SBS_TXTA);
1301 sbsWriteSep(&s, 0, SBS_MKR);
1302 sqlite3_snprintf(sizeof(zLn), zLn, "%d", b+skip+1);
1303 sbsWriteSep(&s, strlen(zLn), SBS_LNB);
1304 sbsWriteSep(&s, s.width, SBS_TXTB);
1305 }else{
1306 blob_appendf(pOut, "%.*c\n", s.width*2+16, '.');
1307 }
1308 }
1309 showDivider = 1;
1310 nChunk++;
1311 if( s.escHtml ){
1312 blob_appendf(s.apCols[SBS_LNA], "<span id=\"chunk%d\"></span>", nChunk);
1313 }
1314
1315 /* Show the initial common area */
1316 a += skip;
1317 b += skip;
1318 m = R[r] - skip;
 
 
 
 
1319 for(j=0; j<m; j++){
1320 sbsWriteLineno(&s, a+j, SBS_LNA);
1321 s.iStart = s.iEnd = -1;
1322 sbsWriteText(&s, &A[a+j], SBS_TXTA);
1323 sbsWriteMarker(&s, " ", "");
1324 sbsWriteLineno(&s, b+j, SBS_LNB);
1325 sbsWriteText(&s, &B[b+j], SBS_TXTB);
1326 }
1327 a += m;
1328 b += m;
1329
1330 /* Show the differences */
1331 for(i=0; i<nr; i++){
 
1332 unsigned char *alignment;
1333 ma = R[r+i*3+1]; /* Lines on left but not on right */
1334 mb = R[r+i*3+2]; /* Lines on right but not on left */
1335
1336 /* If the gap between the current diff and then next diff within the
1337 ** same block is not too great, then render them as if they are a
1338 ** single diff. */
1339 while( i<nr-1 && smallGap(&R[r+i*3]) ){
 
 
 
1340 i++;
1341 m = R[r+i*3];
1342 ma += R[r+i*3+1] + m;
1343 mb += R[r+i*3+2] + m;
1344 }
1345
1346 alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags);
1347 for(j=0; ma+mb>0; j++){
1348 if( alignment[j]==1 ){
1349 /* Delete one line from the left */
1350 sbsWriteLineno(&s, a, SBS_LNA);
1351 s.iStart = 0;
1352 s.zStart = "<span class=\"diffrm\">";
1353 s.iEnd = LENGTH(&A[a]);
1354 sbsWriteText(&s, &A[a], SBS_TXTA);
1355 sbsWriteMarker(&s, " <", "&lt;");
1356 sbsWriteNewlines(&s);
1357 assert( ma>0 );
1358 ma--;
1359 a++;
1360 }else if( alignment[j]==3 ){
1361 /* The left line is changed into the right line */
1362 sbsWriteLineChange(&s, &A[a], a, &B[b], b);
1363 assert( ma>0 && mb>0 );
1364 ma--;
1365 mb--;
1366 a++;
1367 b++;
1368 }else if( alignment[j]==2 ){
1369 /* Insert one line on the right */
1370 if( !s.escHtml ){
1371 sbsWriteSpace(&s, s.width + 7, SBS_TXTA);
1372 }
1373 sbsWriteMarker(&s, " > ", "&gt;");
1374 sbsWriteLineno(&s, b, SBS_LNB);
1375 s.iStart = 0;
1376 s.zStart = "<span class=\"diffadd\">";
1377 s.iEnd = LENGTH(&B[b]);
1378 sbsWriteText(&s, &B[b], SBS_TXTB);
1379 assert( mb>0 );
1380 mb--;
1381 b++;
1382 }else{
1383 /* Delete from the left and insert on the right */
1384 sbsWriteLineno(&s, a, SBS_LNA);
1385 s.iStart = 0;
1386 s.zStart = "<span class=\"diffrm\">";
1387 s.iEnd = LENGTH(&A[a]);
1388 sbsWriteText(&s, &A[a], SBS_TXTA);
1389 sbsWriteMarker(&s, " | ", "|");
1390 sbsWriteLineno(&s, b, SBS_LNB);
1391 s.iStart = 0;
1392 s.zStart = "<span class=\"diffadd\">";
1393 s.iEnd = LENGTH(&B[b]);
1394 sbsWriteText(&s, &B[b], SBS_TXTB);
1395 ma--;
1396 mb--;
1397 a++;
1398 b++;
1399 }
1400 }
1401 fossil_free(alignment);
1402 if( i<nr-1 ){
1403 m = R[r+i*3+3];
1404 for(j=0; j<m; j++){
1405 sbsWriteLineno(&s, a+j, SBS_LNA);
1406 s.iStart = s.iEnd = -1;
1407 sbsWriteText(&s, &A[a+j], SBS_TXTA);
1408 sbsWriteMarker(&s, " ", "");
1409 sbsWriteLineno(&s, b+j, SBS_LNB);
1410 sbsWriteText(&s, &B[b+j], SBS_TXTB);
1411 }
1412 b += m;
1413 a += m;
1414 }
1415 }
@@ -1416,29 +2118,20 @@
1416
1417 /* Show the final common area */
1418 assert( nr==i );
1419 m = R[r+nr*3];
1420 if( m>nContext ) m = nContext;
1421 for(j=0; j<m; j++){
1422 sbsWriteLineno(&s, a+j, SBS_LNA);
1423 s.iStart = s.iEnd = -1;
1424 sbsWriteText(&s, &A[a+j], SBS_TXTA);
1425 sbsWriteMarker(&s, " ", "");
1426 sbsWriteLineno(&s, b+j, SBS_LNB);
1427 sbsWriteText(&s, &B[b+j], SBS_TXTB);
1428 }
1429 }
1430
1431 if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){
1432 blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1);
1433 for(i=SBS_LNA; i<=SBS_TXTB; i++){
1434 sbsWriteColumn(pOut, s.apCols[i], i);
1435 blob_reset(s.apCols[i]);
1436 }
1437 blob_append(pOut, "</tr></table>\n", -1);
1438 }
1439 }
1440
1441 /*
1442 ** Compute the optimal longest common subsequence (LCS) using an
1443 ** exhaustive search. This version of the LCS is only used for
1444 ** shorter input strings since runtime is O(N*N) where N is the
@@ -1509,11 +2202,11 @@
1509 int n; /* Loop limit */
1510 DLine *pA, *pB; /* Pointers to lines */
1511 int iSX, iSY, iEX, iEY; /* Current match */
1512 int skew = 0; /* How lopsided is the match */
1513 int dist = 0; /* Distance of match from center */
1514 int mid; /* Center of the span */
1515 int iSXb, iSYb, iEXb, iEYb; /* Best match so far */
1516 int iSXp, iSYp, iEXp, iEYp; /* Previous match */
1517 sqlite3_int64 bestScore; /* Best score so far */
1518 sqlite3_int64 score; /* Score for current candidate LCS */
1519 int span; /* combined width of the input sequences */
@@ -1878,18 +2571,26 @@
1878 blob_append(pOut, msg, -1);
1879 }
1880 }
1881
1882 /*
1883 ** Generate a report of the differences between files pA and pB.
1884 ** If pOut is not NULL then a unified diff is appended there. It
1885 ** is assumed that pOut has already been initialized. If pOut is
1886 ** NULL, then a pointer to an array of integers is returned.
1887 ** The integers come in triples. For each triple,
1888 ** the elements are the number of lines copied, the number of
1889 ** lines deleted, and the number of lines inserted. The vector
1890 ** is terminated by a triple of all zeros.
 
 
 
 
 
 
 
 
1891 **
1892 ** This diff utility does not work on binary files. If a binary
1893 ** file is encountered, 0 is returned and pOut is written with
1894 ** text "cannot compute difference between binary files".
1895 */
@@ -1969,14 +2670,40 @@
1969 g.diffCnt[2] += nDel;
1970 if( nIns+nDel ){
1971 g.diffCnt[0]++;
1972 blob_appendf(pOut, "%10d %10d", nIns, nDel);
1973 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1974 }else if( diffFlags & DIFF_SIDEBYSIDE ){
1975 sbsDiff(&c, pOut, pRe, diffFlags);
 
 
 
 
 
 
 
 
 
 
 
 
1976 }else{
1977 contextDiff(&c, pOut, pRe, diffFlags);
1978 }
1979 fossil_free(c.aFrom);
1980 fossil_free(c.aTo);
1981 fossil_free(c.aEdit);
1982 return 0;
@@ -2049,48 +2776,40 @@
2049 }
2050 if( find_option("by",0,0)!=0 ){
2051 diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER
2052 |DIFF_SIDEBYSIDE;
2053 }
 
 
 
 
 
 
 
 
 
 
 
2054 return diffFlags;
2055 }
2056
2057 /*
2058 ** COMMAND: test-rawdiff
2059 **
2060 ** Usage: %fossil test-rawdiff FILE1 FILE2
2061 **
2062 ** Show a minimal sequence of Copy/Delete/Insert operations needed to convert
2063 ** FILE1 into FILE2. This command is intended for use in testing and debugging
2064 ** the built-in difference engine of Fossil.
2065 */
2066 void test_rawdiff_cmd(void){
2067 Blob a, b;
2068 int r;
2069 int i;
2070 int *R;
2071 u64 diffFlags = diff_options();
2072 if( g.argc<4 ) usage("FILE1 FILE2 ...");
2073 blob_read_from_file(&a, g.argv[2], ExtFILE);
2074 for(i=3; i<g.argc; i++){
2075 if( i>3 ) fossil_print("-------------------------------\n");
2076 blob_read_from_file(&b, g.argv[i], ExtFILE);
2077 R = text_diff(&a, &b, 0, 0, diffFlags);
2078 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
2079 fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
2080 }
2081 /* free(R); */
2082 blob_reset(&b);
2083 }
2084 }
2085
2086 /*
2087 ** COMMAND: test-diff
 
 
 
 
 
 
 
2088 **
2089 ** Usage: %fossil [options] FILE1 FILE2
 
 
2090 **
2091 ** Print the difference between two files. The usual diff options apply.
 
2092 */
2093 void test_diff_cmd(void){
2094 Blob a, b, out;
2095 u64 diffFlag;
2096 const char *zRe; /* Regex filter for diff output */
2097
2098 DDED src/diff.js
--- src/diff.c
+++ src/diff.c
@@ -42,12 +42,16 @@
42 #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */
43 #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */
44 #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */
45 #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */
46 #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */
47 #define DIFF_WEBPAGE (((u64)0x00040)<<32) /* Complete webpage */
48 #define DIFF_BROWSER (((u64)0x00080)<<32) /* The --browser option */
49 #define DIFF_JSON (((u64)0x00100)<<32) /* JSON output */
50 #define DIFF_DEBUG (((u64)0x00200)<<32) /* Debugging diff output */
51 #define DIFF_RAW (((u64)0x00400)<<32) /* Raw triples - for debugging */
52 #define DIFF_TCL (((u64)0x00800)<<32) /* For the --tk option */
53
54 /*
55 ** These error messages are shared in multiple locations. They are defined
56 ** here for consistency.
57 */
@@ -95,10 +99,15 @@
99 /*
100 ** Length of a dline
101 */
102 #define LENGTH(X) ((X)->n)
103
104 /*
105 ** Number of diff chunks generated
106 */
107 static int nChunk = 0;
108
109 /*
110 ** A context for running a raw diff.
111 **
112 ** The aEdit[] array describes the raw diff. Each triple of integers in
113 ** aEdit[] means:
@@ -273,13 +282,13 @@
282 /*
283 ** Return true if the regular expression *pRe matches any of the
284 ** N dlines
285 */
286 static int re_dline_match(
287 ReCompiled *pRe, /* The regular expression to be matched */
288 const DLine *aDLine, /* First of N DLines to compare against */
289 int N /* Number of DLines to check */
290 ){
291 while( N-- ){
292 if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){
293 return 1;
294 }
@@ -292,41 +301,23 @@
301 ** Append a single line of context-diff output to pOut.
302 */
303 static void appendDiffLine(
304 Blob *pOut, /* Where to write the line of output */
305 char cPrefix, /* One of " ", "+", or "-" */
306 DLine *pLine /* The line to be output */
 
 
307 ){
308 blob_append_char(pOut, cPrefix);
309 blob_append(pOut, pLine->z, pLine->n);
310 blob_append_char(pOut, '\n');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311 }
312
313 /*
314 ** Add two line numbers to the beginning of an output line for a context
315 ** diff. One or the other of the two numbers might be zero, which means
316 ** to leave that number field blank.
 
317 */
318 static void appendDiffLineno(Blob *pOut, int lnA, int lnB){
 
319 if( lnA>0 ){
320 blob_appendf(pOut, "%6d ", lnA);
321 }else{
322 blob_append(pOut, " ", 7);
323 }
@@ -333,21 +324,18 @@
324 if( lnB>0 ){
325 blob_appendf(pOut, "%6d ", lnB);
326 }else{
327 blob_append(pOut, " ", 8);
328 }
 
329 }
330
331 /*
332 ** Output a patch-style text diff.
 
333 */
334 static void contextDiff(
335 DContext *p, /* The difference */
336 Blob *pOut, /* Output a context diff to here */
 
337 u64 diffFlags /* Flags controlling the diff format */
338 ){
339 DLine *A; /* Left side of the diff */
340 DLine *B; /* Right side of the diff */
341 int a = 0; /* Index of next line in A[] */
@@ -361,17 +349,14 @@
349 int m; /* Number of lines to output */
350 int skip; /* Number of lines to skip */
351 static int nChunk = 0; /* Number of diff chunks seen so far */
352 int nContext; /* Number of lines of context */
353 int showLn; /* Show line numbers */
 
354 int showDivider = 0; /* True to show the divider between diff blocks */
355
356 nContext = diff_context_lines(diffFlags);
357 showLn = (diffFlags & DIFF_LINENO)!=0;
 
 
358 A = p->aFrom;
359 B = p->aTo;
360 R = p->aEdit;
361 mxr = p->nEdit;
362 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
@@ -378,35 +363,10 @@
363 for(r=0; r<mxr; r += 3*nr){
364 /* Figure out how many triples to show in a single block */
365 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
366 /* printf("r=%d nr=%d\n", r, nr); */
367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368 /* For the current block comprising nr triples, figure out
369 ** how many lines of A and B are to be displayed
370 */
371 if( R[r]>nContext ){
372 na = nb = nContext;
@@ -438,60 +398,55 @@
398 nChunk++;
399 if( showLn ){
400 if( !showDivider ){
401 /* Do not show a top divider */
402 showDivider = 1;
 
 
403 }else{
404 blob_appendf(pOut, "%.80c\n", '.');
405 }
 
406 }else{
 
407 /*
408 * If the patch changes an empty file or results in an empty file,
409 * the block header must use 0,0 as position indicator and not 1,0.
410 * Otherwise, patch would be confused and may reject the diff.
411 */
412 blob_appendf(pOut,"@@ -%d,%d +%d,%d @@",
413 na ? a+skip+1 : a+skip, na,
414 nb ? b+skip+1 : b+skip, nb);
 
415 blob_append(pOut, "\n", 1);
416 }
417
418 /* Show the initial common area */
419 a += skip;
420 b += skip;
421 m = R[r] - skip;
422 for(j=0; j<m; j++){
423 if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
424 appendDiffLine(pOut, ' ', &A[a+j]);
425 }
426 a += m;
427 b += m;
428
429 /* Show the differences */
430 for(i=0; i<nr; i++){
431 m = R[r+i*3+1];
432 for(j=0; j<m; j++){
433 if( showLn ) appendDiffLineno(pOut, a+j+1, 0);
434 appendDiffLine(pOut, '-', &A[a+j]);
435 }
436 a += m;
437 m = R[r+i*3+2];
438 for(j=0; j<m; j++){
439 if( showLn ) appendDiffLineno(pOut, 0, b+j+1);
440 appendDiffLine(pOut, '+', &B[b+j]);
441 }
442 b += m;
443 if( i<nr-1 ){
444 m = R[r+i*3+3];
445 for(j=0; j<m; j++){
446 if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
447 appendDiffLine(pOut, ' ', &A[a+j]);
448 }
449 b += m;
450 a += m;
451 }
452 }
@@ -499,181 +454,33 @@
454 /* Show the final common area */
455 assert( nr==i );
456 m = R[r+nr*3];
457 if( m>nContext ) m = nContext;
458 for(j=0; j<m; j++){
459 if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
460 appendDiffLine(pOut, ' ', &A[a+j]);
461 }
462 }
463 }
464
465 #define MX_CSN 8 /* Maximum number of change spans across a change region */
466
467 /*
468 ** A description of zero or more (up to MX_CSN) areas of difference
469 ** between two lines of text.
470 */
471 typedef struct LineChange LineChange;
472 struct LineChange {
473 int n; /* Number of change spans */
474 struct Span {
475 int iStart1; /* Byte offset to start of a change on the left */
476 int iLen1; /* Length of the left change in bytes */
477 int iStart2; /* Byte offset to start of a change on the right */
478 int iLen2; /* Length of the change on the right in bytes */
479 int isMin; /* True if this change is known to have no useful subdivs */
480 } a[MX_CSN]; /* Array of change spans, sorted order */
481 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
483 /*
484 ** The two text segments zLeft and zRight are known to be different on
485 ** both ends, but they might have a common segment in the middle. If
486 ** they do not have a common segment, return 0. If they do have a large
@@ -692,123 +499,177 @@
499 const char *zRight, int nB, /* String on the right */
500 int *aLCS /* Identify bounds of LCS here */
501 ){
502 const unsigned char *zA = (const unsigned char*)zLeft; /* left string */
503 const unsigned char *zB = (const unsigned char*)zRight; /* right string */
504 int i, j, k; /* Loop counters */
505 int lenBest = 0; /* Match length to beat */
506
507 for(i=0; i<nA-lenBest; i++){
508 unsigned char cA = zA[i];
509 if( (cA&0xc0)==0x80 ) continue;
510 for(j=0; j<nB-lenBest; j++ ){
511 if( zB[j]==cA ){
512 for(k=1; j+k<nB && i+k<nA && zB[j+k]==zA[i+k]; k++){}
513 while( (zB[j+k]&0xc0)==0x80 ){ k--; }
514 if( k>lenBest ){
515 lenBest = k;
516 aLCS[0] = i;
517 aLCS[1] = i+k;
518 aLCS[2] = j;
519 aLCS[3] = j+k;
520 }
521 }
522 }
523 }
524 return lenBest>0;
525 }
526
527 /*
528 ** Find the smallest spans that are different between two text strings that
529 ** are known to be different on both ends.
530 */
531 static int textLineChanges(
532 const char *zLeft, int nA, /* String on the left */
533 const char *zRight, int nB, /* String on the right */
534 LineChange *p /* Write results here */
535 ){
536 p->n = 1;
537 p->a[0].iStart1 = 0;
538 p->a[0].iLen1 = nA;
539 p->a[0].iStart2 = 0;
540 p->a[0].iLen2 = nB;
541 p->a[0].isMin = 0;
542 while( p->n<MX_CSN-1 ){
543 int mxi = -1;
544 int mxLen = -1;
545 int x, i;
546 int aLCS[4];
547 struct Span *a, *b;
548 for(i=0; i<p->n; i++){
549 if( p->a[i].isMin ) continue;
550 x = p->a[i].iLen1;
551 if( p->a[i].iLen2<x ) x = p->a[i].iLen2;
552 if( x>mxLen ){
553 mxLen = x;
554 mxi = i;
555 }
556 }
557 if( mxLen<6 ) break;
558 x = textLCS(zLeft + p->a[mxi].iStart1, p->a[mxi].iLen1,
559 zRight + p->a[mxi].iStart2, p->a[mxi].iLen2, aLCS);
560 if( x==0 ){
561 p->a[mxi].isMin = 1;
562 continue;
563 }
564 a = p->a+mxi;
565 b = a+1;
566 if( mxi<p->n-1 ){
567 memmove(b+1, b, sizeof(*b)*(p->n-mxi-1));
568 }
569 p->n++;
570 b->iStart1 = a->iStart1 + aLCS[1];
571 b->iLen1 = a->iLen1 - aLCS[1];
572 a->iLen1 = aLCS[0];
573 b->iStart2 = a->iStart2 + aLCS[3];
574 b->iLen2 = a->iLen2 - aLCS[3];
575 a->iLen2 = aLCS[2];
576 b->isMin = 0;
577 }
578 return p->n;
579 }
580
581 /*
582 ** Return true if the string starts with n spaces
583 */
584 static int allSpaces(const char *z, int n){
585 int i;
586 for(i=0; i<n && fossil_isspace(z[i]); i++){}
587 return i==n;
588 }
589
590 /*
591 ** Try to improve the human-readability of the LineChange p.
592 **
593 ** (1) If the first change span shows a change of indentation, try to
594 ** move that indentation change to the left margin.
595 **
596 ** (2) Try to shift changes so that they begin or end with a space.
597 */
598 static void improveReadability(
599 const char *zA, /* Left line of the change */
600 const char *zB, /* Right line of the change */
601 LineChange *p /* The LineChange to be adjusted */
602 ){
603 int j, n, len;
604 if( p->n<1 ) return;
605
606 /* (1) Attempt to move indentation changes to the left margin */
607 if( p->a[0].iLen1==0
608 && (len = p->a[0].iLen2)>0
609 && (j = p->a[0].iStart2)>0
610 && zB[0]==zB[j]
611 && allSpaces(zB, j)
612 ){
613 for(n=1; n<len && n<j && zB[j]==zB[j+n]; n++){}
614 if( n<len ){
615 memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n);
616 p->n++;
617 p->a[0] = p->a[1];
618 p->a[1].iStart2 += n;
619 p->a[1].iLen2 -= n;
620 p->a[0].iLen2 = n;
621 }
622 p->a[0].iStart1 = 0;
623 p->a[0].iStart2 = 0;
624 }else
625 if( p->a[0].iLen2==0
626 && (len = p->a[0].iLen1)>0
627 && (j = p->a[0].iStart1)>0
628 && zA[0]==zA[j]
629 && allSpaces(zA, j)
630 ){
631 for(n=1; n<len && n<j && zA[j]==zA[j+n]; n++){}
632 if( n<len ){
633 memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n);
634 p->n++;
635 p->a[0] = p->a[1];
636 p->a[1].iStart1 += n;
637 p->a[1].iLen1 -= n;
638 p->a[0].iLen1 = n;
639 }
640 p->a[0].iStart1 = 0;
641 p->a[0].iStart2 = 0;
642 }
643
644 /* (2) Try to shift changes so that they begin or end with a
645 ** space. (TBD) */
646 }
647
648
649 /*
650 ** Given two lines of text, pFrom and pTo, compute a set of changes
651 ** between those two lines, for enhanced display purposes.
652 **
653 ** The result is written into the LineChange object given by the
654 ** third parameter.
655 */
656 static void oneLineChange(
657 const DLine *pLeft, /* Left line of the change */
658 const DLine *pRight, /* Right line of the change */
659 LineChange *p /* OUTPUT: Write the results here */
660 ){
661 int nLeft; /* Length of left line in bytes */
662 int nRight; /* Length of right line in bytes */
663 int nShort; /* Shortest of left and right */
664 int nPrefix; /* Length of common prefix */
665 int nSuffix; /* Length of common suffix */
666 int nCommon; /* Total byte length of suffix and prefix */
667 const char *zLeft; /* Text of the left line */
668 const char *zRight; /* Text of the right line */
669 int nLeftDiff; /* nLeft - nPrefix - nSuffix */
670 int nRightDiff; /* nRight - nPrefix - nSuffix */
 
 
 
 
671
672 nLeft = pLeft->n;
673 zLeft = pLeft->z;
674 nRight = pRight->n;
675 zRight = pRight->z;
@@ -821,25 +682,27 @@
682 if( nPrefix<nShort ){
683 while( nPrefix>0 && (zLeft[nPrefix]&0xc0)==0x80 ) nPrefix--;
684 }
685 nSuffix = 0;
686 if( nPrefix<nShort ){
687 while( nSuffix<nShort
688 && zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
689 nSuffix++;
690 }
691 if( nSuffix<nShort ){
692 while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--;
693 }
694 if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
695 }
696 nCommon = nPrefix + nSuffix;
697
698 /* If the prefix and suffix overlap, that means that we are dealing with
699 ** a pure insertion or deletion of text that can have multiple alignments.
700 ** Try to find an alignment to begins and ends on whitespace, or on
701 ** punctuation, rather than in the middle of a name or number.
702 */
703 if( nCommon > nShort ){
704 int iBest = -1;
705 int iBestVal = -1;
706 int i;
707 int nLong = nLeft<nRight ? nRight : nLeft;
708 int nGap = nLong - nShort;
@@ -862,108 +725,931 @@
725 iBest = i;
726 }
727 }
728 nPrefix = iBest;
729 nSuffix = nShort - nPrefix;
730 nCommon = nPrefix + nSuffix;
731 }
732
733 /* A single chunk of text inserted */
734 if( nCommon==nLeft ){
735 p->n = 1;
736 p->a[0].iStart1 = nPrefix;
737 p->a[0].iLen1 = 0;
738 p->a[0].iStart2 = nPrefix;
739 p->a[0].iLen2 = nRight - nCommon;
740 improveReadability(zLeft, zRight, p);
 
 
 
 
 
 
 
741 return;
742 }
743
744 /* A single chunk of text deleted */
745 if( nCommon==nRight ){
746 p->n = 1;
747 p->a[0].iStart1 = nPrefix;
748 p->a[0].iLen1 = nLeft - nCommon;
749 p->a[0].iStart2 = nPrefix;
750 p->a[0].iLen2 = 0;
751 improveReadability(zLeft, zRight, p);
 
 
 
 
 
752 return;
753 }
754
755 /* At this point we know that there is a chunk of text that has
756 ** changed between the left and the right. Check to see if there
757 ** is a large unchanged section in the middle of that changed block.
758 */
759 nLeftDiff = nLeft - nCommon;
760 nRightDiff = nRight - nCommon;
761 if( nLeftDiff >= 4
762 && nRightDiff >= 4
763 && textLineChanges(&zLeft[nPrefix], nLeftDiff,
764 &zRight[nPrefix], nRightDiff, p)>1
765 ){
766 int i;
767 for(i=0; i<p->n; i++){
768 p->a[i].iStart1 += nPrefix;
769 p->a[i].iStart2 += nPrefix;
770 }
771 improveReadability(zLeft, zRight, p);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772 return;
773 }
774
775 /* If all else fails, show a single big change between left and right */
776 p->n = 1;
777 p->a[0].iStart1 = nPrefix;
778 p->a[0].iLen1 = nLeft - nCommon;
779 p->a[0].iStart2 = nPrefix;
780 p->a[0].iLen2 = nRight - nCommon;
781 improveReadability(zLeft, zRight, p);
782 }
783
784 /*
785 ** COMMAND: test-line-diff
786 ** Usage: %fossil% test-line-diff STRING1 STRING2
787 **
788 ** Show the differences between the two strings. Used for testing
789 ** the oneLineChange() routine in the diff logic.
790 */
791 void test_line_diff(void){
792 DLine a, b;
793 LineChange chng;
794 int i, j, x;
795 if( g.argc!=4 ) usage("STRING1 STRING2");
796 a.z = g.argv[2];
797 a.n = (int)strlen(a.z);
798 b.z = g.argv[3];
799 b.n = (int)strlen(b.z);
800 oneLineChange(&a, &b, &chng);
801 fossil_print("left: [%s]\n", a.z);
802 for(i=x=0; i<chng.n; i++){
803 int ofst = chng.a[i].iStart1;
804 int len = chng.a[i].iLen1;
805 if( len ){
806 if( x==0 ){ fossil_print("%*s", 8, ""); }
807 while( ofst > x ){
808 if( (a.z[x]&0xc0)!=0x80 ) fossil_print(" ");
809 x++;
810 }
811 for(j=0; j<len; j++, x++){
812 if( (a.z[x]&0xc0)!=0x80 ) fossil_print("%d",i);
813 }
814 }
815 }
816 if( x ) fossil_print("\n");
817 fossil_print("right: [%s]\n", b.z);
818 for(i=x=0; i<chng.n; i++){
819 int ofst = chng.a[i].iStart2;
820 int len = chng.a[i].iLen2;
821 if( len ){
822 if( x==0 ){ fossil_print("%*s", 8, ""); }
823 while( ofst > x ){
824 if( (b.z[x]&0xc0)!=0x80 ) fossil_print(" ");
825 x++;
826 }
827 for(j=0; j<len; j++, x++){
828 if( (b.z[x]&0xc0)!=0x80 ) fossil_print("%d",i);
829 }
830 }
831 }
832 if( x ) fossil_print("\n");
833 }
834
835 /*
836 ** Minimum of two values
837 */
838 static int minInt(int a, int b){ return a<b ? a : b; }
839
840
841
842 /*
843 ** This is an abstract superclass for an object that accepts difference
844 ** lines and formats them for display. Subclasses of this object format
845 ** the diff output in different ways.
846 **
847 ** To subclass, create an instance of the DiffBuilder object and fill
848 ** in appropriate method implementations.
849 */
850 typedef struct DiffBuilder DiffBuilder;
851 struct DiffBuilder {
852 void (*xSkip)(DiffBuilder*, unsigned int, int);
853 void (*xCommon)(DiffBuilder*,const DLine*);
854 void (*xInsert)(DiffBuilder*,const DLine*);
855 void (*xDelete)(DiffBuilder*,const DLine*);
856 void (*xReplace)(DiffBuilder*,const DLine*, const DLine*);
857 void (*xEdit)(DiffBuilder*,const DLine*,const DLine*);
858 void (*xEnd)(DiffBuilder*);
859 unsigned int lnLeft; /* Lines seen on the left (delete) side */
860 unsigned int lnRight; /* Lines seen on the right (insert) side */
861 unsigned int nPending; /* Number of pending lines */
862 int eState; /* State of the output */
863 int width; /* Display width */
864 Blob *pOut; /* Output blob */
865 Blob aCol[5]; /* Holding blobs */
866 };
867
868 /************************* DiffBuilderDebug ********************************/
869 /* This version of DiffBuilder is used for debugging the diff and diff
870 ** diff formatter logic. It is accessed using the (undocumented) --debug
871 ** option to the diff command. The output is human-readable text that
872 ** describes the various method calls that are invoked agains the DiffBuilder
873 ** object.
874 */
875 static void dfdebugSkip(DiffBuilder *p, unsigned int n, int isFinal){
876 blob_appendf(p->pOut, "SKIP %d (%d..%d left and %d..%d right)%s\n",
877 n, p->lnLeft+1, p->lnLeft+n, p->lnRight+1, p->lnRight+n,
878 isFinal ? " FINAL" : "");
879 p->lnLeft += n;
880 p->lnRight += n;
881 }
882 static void dfdebugCommon(DiffBuilder *p, const DLine *pLine){
883 p->lnLeft++;
884 p->lnRight++;
885 blob_appendf(p->pOut, "COMMON %8u %8u %.*s\n",
886 p->lnLeft, p->lnRight, (int)pLine->n, pLine->z);
887 }
888 static void dfdebugInsert(DiffBuilder *p, const DLine *pLine){
889 p->lnRight++;
890 blob_appendf(p->pOut, "INSERT %8d %.*s\n",
891 p->lnRight, (int)pLine->n, pLine->z);
892 }
893 static void dfdebugDelete(DiffBuilder *p, const DLine *pLine){
894 p->lnLeft++;
895 blob_appendf(p->pOut, "DELETE %8u %.*s\n",
896 p->lnLeft, (int)pLine->n, pLine->z);
897 }
898 static void dfdebugReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
899 p->lnLeft++;
900 p->lnRight++;
901 blob_appendf(p->pOut, "REPLACE %8u %.*s\n",
902 p->lnLeft, (int)pX->n, pX->z);
903 blob_appendf(p->pOut, " %8u %.*s\n",
904 p->lnRight, (int)pY->n, pY->z);
905 }
906 static void dfdebugEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
907 int i, j;
908 int x;
909 LineChange chng;
910 p->lnLeft++;
911 p->lnRight++;
912 blob_appendf(p->pOut, "EDIT %8u %.*s\n",
913 p->lnLeft, (int)pX->n, pX->z);
914 oneLineChange(pX, pY, &chng);
915 for(i=x=0; i<chng.n; i++){
916 int ofst = chng.a[i].iStart1;
917 int len = chng.a[i].iLen1;
918 if( len ){
919 if( x==0 ){ blob_appendf(p->pOut, "%*s", 25, ""); }
920 while( ofst > x ){
921 if( (pX->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, ' ');
922 x++;
923 }
924 for(j=0; j<len; j++, x++){
925 if( (pX->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, '^');
926 }
927 }
928 }
929 if( x ) blob_append_char(p->pOut, '\n');
930 blob_appendf(p->pOut, " %8u %.*s\n",
931 p->lnRight, (int)pY->n, pY->z);
932 for(i=x=0; i<chng.n; i++){
933 int ofst = chng.a[i].iStart2;
934 int len = chng.a[i].iLen2;
935 if( len ){
936 if( x==0 ){ blob_appendf(p->pOut, "%*s", 25, ""); }
937 while( ofst > x ){
938 if( (pY->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, ' ');
939 x++;
940 }
941 for(j=0; j<len; j++, x++){
942 if( (pY->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, '^');
943 }
944 }
945 }
946 if( x ) blob_append_char(p->pOut, '\n');
947 }
948 static void dfdebugEnd(DiffBuilder *p){
949 blob_appendf(p->pOut, "END with %u lines left and %u lines right\n",
950 p->lnLeft, p->lnRight);
951 fossil_free(p);
952 }
953 static DiffBuilder *dfdebugNew(Blob *pOut){
954 DiffBuilder *p = fossil_malloc(sizeof(*p));
955 p->xSkip = dfdebugSkip;
956 p->xCommon = dfdebugCommon;
957 p->xInsert = dfdebugInsert;
958 p->xDelete = dfdebugDelete;
959 p->xReplace = dfdebugReplace;
960 p->xEdit = dfdebugEdit;
961 p->xEnd = dfdebugEnd;
962 p->lnLeft = p->lnRight = 0;
963 p->pOut = pOut;
964 return p;
965 }
966
967 /************************* DiffBuilderTcl ********************************/
968 /*
969 ** This formatter outputs a description of the diff formatted as TCL, for
970 ** use by the --tk option to "diff". See also the "diff.tcl" file. The
971 ** output can be viewed directly using the --tcl option.
972 **
973 ** There is one line per method call:
974 **
975 ** SKIP n -- Skip "n" lines of input
976 ** COM string -- "string" is an unchanged context line
977 ** INS string -- "string" is in the right file only
978 ** DEL string -- "string" is in the left file only
979 ** EDIT string .... -- Complex edit between left and right
980 **
981 ** The EDIT verb will be followed by 3*N or 3*N+1 strings. The triples
982 ** each show:
983 **
984 ** 1. Common text
985 ** 2. Text from the left side
986 ** 3. Text on the right that replaces (2) from the left
987 **
988 ** For inserted text (2) will be an empty string. For deleted text, (3)
989 ** will be an empty string. (1) might be empty for the first triple if
990 ** the line begins with an edit. After all triples, there might be one
991 ** additional string which is a common suffix.
992 */
993 static void dftclSkip(DiffBuilder *p, unsigned int n, int isFinal){
994 blob_appendf(p->pOut, "SKIP %u\n", n);
995 }
996 static void dftclCommon(DiffBuilder *p, const DLine *pLine){
997 blob_appendf(p->pOut, "COM ");
998 blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
999 blob_append_char(p->pOut, '\n');
1000 }
1001 static void dftclInsert(DiffBuilder *p, const DLine *pLine){
1002 blob_append(p->pOut, "INS ", -1);
1003 blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1004 blob_append_char(p->pOut, '\n');
1005 }
1006 static void dftclDelete(DiffBuilder *p, const DLine *pLine){
1007 blob_append(p->pOut, "DEL ", -1);
1008 blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1009 blob_append_char(p->pOut, '\n');
1010 }
1011 static void dftclReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1012 blob_append(p->pOut, "EDIT \"\" ", -1);
1013 blob_append_tcl_literal(p->pOut, pX->z, pX->n);
1014 blob_append_char(p->pOut, ' ');
1015 blob_append_tcl_literal(p->pOut, pY->z, pY->n);
1016 blob_append_char(p->pOut, '\n');
1017 }
1018 static void dftclEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1019 int i, x;
1020 LineChange chng;
1021 blob_append(p->pOut, "EDIT", 4);
1022 oneLineChange(pX, pY, &chng);
1023 for(i=x=0; i<chng.n; i++){
1024 blob_append_char(p->pOut, ' ');
1025 blob_append_tcl_literal(p->pOut, pX->z + x, chng.a[i].iStart1 - x);
1026 x = chng.a[i].iStart1;
1027 blob_append_char(p->pOut, ' ');
1028 blob_append_tcl_literal(p->pOut, pX->z + x, chng.a[i].iLen1);
1029 x += chng.a[i].iLen1;
1030 blob_append_char(p->pOut, ' ');
1031 blob_append_tcl_literal(p->pOut,
1032 pY->z + chng.a[i].iStart2, chng.a[i].iLen2);
1033 }
1034 if( x<pX->n ){
1035 blob_append_char(p->pOut, ' ');
1036 blob_append_tcl_literal(p->pOut, pX->z + x, pX->n - x);
1037 }
1038 blob_append_char(p->pOut, '\n');
1039 }
1040 static void dftclEnd(DiffBuilder *p){
1041 fossil_free(p);
1042 }
1043 static DiffBuilder *dftclNew(Blob *pOut){
1044 DiffBuilder *p = fossil_malloc(sizeof(*p));
1045 p->xSkip = dftclSkip;
1046 p->xCommon = dftclCommon;
1047 p->xInsert = dftclInsert;
1048 p->xDelete = dftclDelete;
1049 p->xReplace = dftclReplace;
1050 p->xEdit = dftclEdit;
1051 p->xEnd = dftclEnd;
1052 p->pOut = pOut;
1053 return p;
1054 }
1055
1056 /************************* DiffBuilderJson ********************************/
1057 /*
1058 ** This formatter generates a JSON array that describes the difference.
1059 **
1060 ** The Json array consists of integer opcodes with each opcode followed
1061 ** by zero or more arguments:
1062 **
1063 ** Syntax Mnemonic Description
1064 ** ----------- -------- --------------------------
1065 ** 0 END This is the end of the diff
1066 ** 1 INTEGER SKIP Skip N lines from both files
1067 ** 2 STRING COMMON The line show by STRING is in both files
1068 ** 3 STRING INSERT The line STRING is in only the right file
1069 ** 4 STRING DELETE The STRING line is in only the left file
1070 ** 5 SUBARRAY EDIT One line is different on left and right.
1071 **
1072 ** The SUBARRAY is an array of 3*N+1 strings with N>=0. The triples
1073 ** represent common-text, left-text, and right-text. The last string
1074 ** in SUBARRAY is the common-suffix. Any string can be empty if it does
1075 ** not apply.
1076 */
1077 static void dfjsonSkip(DiffBuilder *p, unsigned int n, int isFinal){
1078 blob_appendf(p->pOut, "1,%u,\n", n);
1079 }
1080 static void dfjsonCommon(DiffBuilder *p, const DLine *pLine){
1081 blob_append(p->pOut, "2,",2);
1082 blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1083 blob_append(p->pOut, ",\n",2);
1084 }
1085 static void dfjsonInsert(DiffBuilder *p, const DLine *pLine){
1086 blob_append(p->pOut, "3,",2);
1087 blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1088 blob_append(p->pOut, ",\n",2);
1089 }
1090 static void dfjsonDelete(DiffBuilder *p, const DLine *pLine){
1091 blob_append(p->pOut, "4,",2);
1092 blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1093 blob_append(p->pOut, ",\n",2);
1094 }
1095 static void dfjsonReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1096 blob_append(p->pOut, "5,[\"\",",-1);
1097 blob_append_json_literal(p->pOut, pX->z, (int)pX->n);
1098 blob_append(p->pOut, ",",1);
1099 blob_append_json_literal(p->pOut, pY->z, (int)pY->n);
1100 blob_append(p->pOut, ",\"\"],\n",-1);
1101 }
1102 static void dfjsonEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1103 int i, x;
1104 LineChange chng;
1105 blob_append(p->pOut, "5,[", 3);
1106 oneLineChange(pX, pY, &chng);
1107 for(i=x=0; i<chng.n; i++){
1108 blob_append_json_literal(p->pOut, pX->z + x, chng.a[i].iStart1 - x);
1109 x = chng.a[i].iStart1;
1110 blob_append_char(p->pOut, ',');
1111 blob_append_json_literal(p->pOut, pX->z + x, chng.a[i].iLen1);
1112 x += chng.a[i].iLen1;
1113 blob_append_char(p->pOut, ',');
1114 blob_append_json_literal(p->pOut,
1115 pY->z + chng.a[i].iStart2, chng.a[i].iLen2);
1116 }
1117 blob_append_char(p->pOut, ',');
1118 blob_append_json_literal(p->pOut, pX->z + x, pX->n - x);
1119 blob_append(p->pOut, "],\n",3);
1120 }
1121 static void dfjsonEnd(DiffBuilder *p){
1122 blob_append(p->pOut, "0]", 2);
1123 fossil_free(p);
1124 }
1125 static DiffBuilder *dfjsonNew(Blob *pOut){
1126 DiffBuilder *p = fossil_malloc(sizeof(*p));
1127 p->xSkip = dfjsonSkip;
1128 p->xCommon = dfjsonCommon;
1129 p->xInsert = dfjsonInsert;
1130 p->xDelete = dfjsonDelete;
1131 p->xReplace = dfjsonReplace;
1132 p->xEdit = dfjsonEdit;
1133 p->xEnd = dfjsonEnd;
1134 p->lnLeft = p->lnRight = 0;
1135 p->pOut = pOut;
1136 blob_append_char(pOut, '[');
1137 return p;
1138 }
1139
1140 /************************* DiffBuilderUnified********************************/
1141 /* This formatter generates a unified diff for HTML.
1142 **
1143 ** The result is a <table> with four columns. The four columns hold:
1144 **
1145 ** 1. The line numbers for the first file.
1146 ** 2. The line numbers for the second file.
1147 ** 3. The "diff mark": "+" or "-" or just a space
1148 ** 4. Text of the line
1149 **
1150 ** Inserted lines are marked with <ins> and deleted lines are marked
1151 ** with <del>. The whole line is marked this way, not just the part that
1152 ** changed. The part that change has an additional nested <ins> or <del>.
1153 ** The CSS needs to be set up such that a single <ins> or <del> gives a
1154 ** light background and a nested <ins> or <del> gives a darker background.
1155 ** Additional attributes (like bold font) might also be added to nested
1156 ** <ins> and <del> since those are the characters that have actually
1157 ** changed.
1158 **
1159 ** Accumulator strategy:
1160 **
1161 ** * Delete line numbers are output directly to p->pOut
1162 ** * Insert line numbers accumulate in p->aCol[0].
1163 ** * Separator marks accumulate in p->aCol[1].
1164 ** * Change text accumulates in p->aCol[2].
1165 ** * Pending insert line numbers go into p->aCol[3].
1166 ** * Pending insert text goes into p->aCol[4].
1167 **
1168 ** eState is 1 if text has an open <del>
1169 */
1170 static void dfunifiedFinishDelete(DiffBuilder *p){
1171 if( p->eState==0 ) return;
1172 blob_append(p->pOut, "</del>", 6);
1173 blob_append(&p->aCol[2], "</del>", 6);
1174 p->eState = 0;
1175 }
1176 static void dfunifiedFinishInsert(DiffBuilder *p){
1177 unsigned int i;
1178 if( p->nPending==0 ) return;
1179 dfunifiedFinishDelete(p);
1180
1181 /* Blank lines for delete line numbers for each inserted line */
1182 for(i=0; i<p->nPending; i++) blob_append_char(p->pOut, '\n');
1183
1184 /* Insert line numbers */
1185 blob_append(&p->aCol[0], "<ins>", 5);
1186 blob_append_xfer(&p->aCol[0], &p->aCol[3]);
1187 blob_append(&p->aCol[0], "</ins>", 6);
1188
1189 /* "+" marks for the separator on inserted lines */
1190 for(i=0; i<p->nPending; i++) blob_append(&p->aCol[1], "+\n", 2);
1191
1192 /* Text of the inserted lines */
1193 blob_append(&p->aCol[2], "<ins>", 5);
1194 blob_append_xfer(&p->aCol[2], &p->aCol[4]);
1195 blob_append(&p->aCol[2], "</ins>", 6);
1196
1197 p->nPending = 0;
1198 }
1199 static void dfunifiedFinishRow(DiffBuilder *p){
1200 dfunifiedFinishDelete(p);
1201 dfunifiedFinishInsert(p);
1202 if( blob_size(&p->aCol[0])==0 ) return;
1203 blob_append(p->pOut, "</pre></td><td class=\"diffln difflnr\"><pre>\n", -1);
1204 blob_append_xfer(p->pOut, &p->aCol[0]);
1205 blob_append(p->pOut, "</pre></td><td class=\"diffsep\"><pre>\n", -1);
1206 blob_append_xfer(p->pOut, &p->aCol[1]);
1207 blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtu\"><pre>\n",-1);
1208 blob_append_xfer(p->pOut, &p->aCol[2]);
1209 blob_append(p->pOut, "</pre></td></tr>\n", -1);
1210 }
1211 static void dfunifiedStartRow(DiffBuilder *p){
1212 if( blob_size(&p->aCol[0])>0 ) return;
1213 blob_appendf(p->pOut,"<tr id=\"chunk%d\">"
1214 "<td class=\"diffln difflnl\"><pre>\n", ++nChunk);
1215 p->eState = 0;
1216 p->nPending = 0;
1217 }
1218 static void dfunifiedSkip(DiffBuilder *p, unsigned int n, int isFinal){
1219 dfunifiedFinishRow(p);
1220 blob_append(p->pOut, "<tr><td class=\"diffln difflne\">"
1221 "&#xfe19;</td><td></td><td></td></tr>\n", -1);
1222 p->lnLeft += n;
1223 p->lnRight += n;
1224 }
1225 static void dfunifiedCommon(DiffBuilder *p, const DLine *pLine){
1226 dfunifiedStartRow(p);
1227 dfunifiedFinishDelete(p);
1228 dfunifiedFinishInsert(p);
1229 p->lnLeft++;
1230 p->lnRight++;
1231 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1232 blob_appendf(&p->aCol[0],"%d\n",p->lnRight);
1233 blob_append_char(&p->aCol[1], '\n');
1234 htmlize_to_blob(&p->aCol[2], pLine->z, (int)pLine->n);
1235 blob_append_char(&p->aCol[2], '\n');
1236 }
1237 static void dfunifiedInsert(DiffBuilder *p, const DLine *pLine){
1238 dfunifiedStartRow(p);
1239 p->lnRight++;
1240 blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1241 blob_append(&p->aCol[4], "<ins>", 5);
1242 htmlize_to_blob(&p->aCol[4], pLine->z, (int)pLine->n);
1243 blob_append(&p->aCol[4], "</ins>\n", 7);
1244 p->nPending++;
1245 }
1246 static void dfunifiedDelete(DiffBuilder *p, const DLine *pLine){
1247 dfunifiedStartRow(p);
1248 dfunifiedFinishInsert(p);
1249 if( p->eState==0 ){
1250 dfunifiedFinishInsert(p);
1251 blob_append(p->pOut, "<del>", 5);
1252 blob_append(&p->aCol[2], "<del>", 5);
1253 p->eState = 1;
1254 }
1255 p->lnLeft++;
1256 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1257 blob_append_char(&p->aCol[0],'\n');
1258 blob_append(&p->aCol[1],"-\n",2);
1259 blob_append(&p->aCol[2], "<del>", 5);
1260 htmlize_to_blob(&p->aCol[2], pLine->z, (int)pLine->n);
1261 blob_append(&p->aCol[2], "</del>\n", 7);
1262 }
1263 static void dfunifiedReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1264 dfunifiedStartRow(p);
1265 if( p->eState==0 ){
1266 dfunifiedFinishInsert(p);
1267 blob_append(p->pOut, "<del>", 5);
1268 blob_append(&p->aCol[2], "<del>", 5);
1269 p->eState = 1;
1270 }
1271 p->lnLeft++;
1272 p->lnRight++;
1273 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1274 blob_append_char(&p->aCol[0], '\n');
1275 blob_append(&p->aCol[1], "-\n", 2);
1276
1277 htmlize_to_blob(&p->aCol[2], pX->z, pX->n);
1278 blob_append_char(&p->aCol[2], '\n');
1279
1280 blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1281
1282 htmlize_to_blob(&p->aCol[4], pY->z, pY->n);
1283 blob_append_char(&p->aCol[4], '\n');
1284 p->nPending++;
1285 }
1286 static void dfunifiedEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1287 int i;
1288 int x;
1289 LineChange chng;
1290 oneLineChange(pX, pY, &chng);
1291 dfunifiedStartRow(p);
1292 if( p->eState==0 ){
1293 dfunifiedFinishInsert(p);
1294 blob_append(p->pOut, "<del>", 5);
1295 blob_append(&p->aCol[2], "<del>", 5);
1296 p->eState = 1;
1297 }
1298 p->lnLeft++;
1299 p->lnRight++;
1300 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1301 blob_append_char(&p->aCol[0], '\n');
1302 blob_append(&p->aCol[1], "-\n", 2);
1303
1304 for(i=x=0; i<chng.n; i++){
1305 int ofst = chng.a[i].iStart1;
1306 int len = chng.a[i].iLen1;
1307 if( len ){
1308 htmlize_to_blob(&p->aCol[2], pX->z+x, ofst - x);
1309 x = ofst;
1310 blob_append(&p->aCol[2], "<del>", 5);
1311 htmlize_to_blob(&p->aCol[2], pX->z+x, len);
1312 x += len;
1313 blob_append(&p->aCol[2], "</del>", 6);
1314 }
1315 }
1316 htmlize_to_blob(&p->aCol[2], pX->z+x, pX->n - x);
1317 blob_append_char(&p->aCol[2], '\n');
1318
1319 blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1320 for(i=x=0; i<chng.n; i++){
1321 int ofst = chng.a[i].iStart2;
1322 int len = chng.a[i].iLen2;
1323 if( len ){
1324 htmlize_to_blob(&p->aCol[4], pY->z+x, ofst - x);
1325 x = ofst;
1326 blob_append(&p->aCol[4], "<ins>", 5);
1327 htmlize_to_blob(&p->aCol[4], pY->z+x, len);
1328 x += len;
1329 blob_append(&p->aCol[4], "</ins>", 6);
1330 }
1331 }
1332 htmlize_to_blob(&p->aCol[4], pY->z+x, pY->n - x);
1333 blob_append_char(&p->aCol[4], '\n');
1334 p->nPending++;
1335 }
1336 static void dfunifiedEnd(DiffBuilder *p){
1337 dfunifiedFinishRow(p);
1338 blob_append(p->pOut, "</table>\n",-1);
1339 fossil_free(p);
1340 }
1341 static DiffBuilder *dfunifiedNew(Blob *pOut){
1342 DiffBuilder *p = fossil_malloc(sizeof(*p));
1343 p->xSkip = dfunifiedSkip;
1344 p->xCommon = dfunifiedCommon;
1345 p->xInsert = dfunifiedInsert;
1346 p->xDelete = dfunifiedDelete;
1347 p->xReplace = dfunifiedReplace;
1348 p->xEdit = dfunifiedEdit;
1349 p->xEnd = dfunifiedEnd;
1350 p->lnLeft = p->lnRight = 0;
1351 p->eState = 0;
1352 p->nPending = 0;
1353 p->pOut = pOut;
1354 blob_append(pOut, "<table class=\"diff udiff\">\n", -1);
1355 blob_init(&p->aCol[0], 0, 0);
1356 blob_init(&p->aCol[1], 0, 0);
1357 blob_init(&p->aCol[2], 0, 0);
1358 blob_init(&p->aCol[3], 0, 0);
1359 blob_init(&p->aCol[4], 0, 0);
1360 return p;
1361 }
1362
1363 /************************* DiffBuilderSplit ******************************/
1364 /* This formatter creates a side-by-side diff in HTML. The output is a
1365 ** <table> with 5 columns:
1366 **
1367 ** 1. Line numbers for the first file.
1368 ** 2. Text for the first file.
1369 ** 3. The difference mark. "<", ">", "|" or blank
1370 ** 4. Line numbers for the second file.
1371 ** 5. Text for the second file.
1372 **
1373 ** The <ins> and <del> strategy is the same as for unified diff above.
1374 ** In fact, the same CSS can be used for both.
1375 **
1376 ** Accumulator strategy:
1377 **
1378 ** * Left line numbers are output directly to p->pOut
1379 ** * Left text accumulates in p->aCol[0].
1380 ** * Edit marks accumulates in p->aCol[1].
1381 ** * Right line numbers accumulate in p->aCol[2].
1382 ** * Right text accumulates in p->aCol[3].
1383 **
1384 ** eState:
1385 ** 0 In common block
1386 ** 1 Have <del> on the left
1387 ** 2 Have <ins> on the right
1388 ** 3 Have <del> on left and <ins> on the right
1389 */
1390 static void dfsplitChangeState(DiffBuilder *p, int newState){
1391 if( p->eState == newState ) return;
1392 if( (p->eState&1)==0 && (newState & 1)!=0 ){
1393 blob_append(p->pOut, "<del>", 5);
1394 blob_append(&p->aCol[0], "<del>", 5);
1395 p->eState |= 1;
1396 }else if( (p->eState&1)!=0 && (newState & 1)==0 ){
1397 blob_append(p->pOut, "</del>", 6);
1398 blob_append(&p->aCol[0], "</del>", 6);
1399 p->eState &= ~1;
1400 }
1401 if( (p->eState&2)==0 && (newState & 2)!=0 ){
1402 blob_append(&p->aCol[2], "<ins>", 5);
1403 blob_append(&p->aCol[3], "<ins>", 5);
1404 p->eState |= 2;
1405 }else if( (p->eState&2)!=0 && (newState & 2)==0 ){
1406 blob_append(&p->aCol[2], "</ins>", 6);
1407 blob_append(&p->aCol[3], "</ins>", 6);
1408 p->eState &= ~2;
1409 }
1410 }
1411 static void dfsplitFinishRow(DiffBuilder *p){
1412 if( blob_size(&p->aCol[0])==0 ) return;
1413 dfsplitChangeState(p, 0);
1414 blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtl\"><pre>\n",-1);
1415 blob_append_xfer(p->pOut, &p->aCol[0]);
1416 blob_append(p->pOut, "</pre></td><td class=\"diffsep\"><pre>\n", -1);
1417 blob_append_xfer(p->pOut, &p->aCol[1]);
1418 blob_append(p->pOut, "</pre></td><td class=\"diffln difflnr\"><pre>\n",-1);
1419 blob_append_xfer(p->pOut, &p->aCol[2]);
1420 blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtr\"><pre>\n",-1);
1421 blob_append_xfer(p->pOut, &p->aCol[3]);
1422 blob_append(p->pOut, "</pre></td></tr>\n", -1);
1423 }
1424 static void dfsplitStartRow(DiffBuilder *p){
1425 if( blob_size(&p->aCol[0])>0 ) return;
1426 blob_appendf(p->pOut,"<tr id=\"chunk%d\">"
1427 "<td class=\"diffln difflnl\"><pre>\n", ++nChunk);
1428 p->eState = 0;
1429 }
1430 static void dfsplitSkip(DiffBuilder *p, unsigned int n, int isFinal){
1431 dfsplitFinishRow(p);
1432 blob_append(p->pOut,
1433 "<tr><td class=\"diffln difflnl difflne\">&#xfe19;</td>"
1434 "<td></td><td></td>"
1435 "<td class=\"diffln difflnr difflne\">&#xfe19;</td>"
1436 "<td/td></tr>\n", -1);
1437 p->lnLeft += n;
1438 p->lnRight += n;
1439 }
1440 static void dfsplitCommon(DiffBuilder *p, const DLine *pLine){
1441 dfsplitStartRow(p);
1442 dfsplitChangeState(p, 0);
1443 p->lnLeft++;
1444 p->lnRight++;
1445 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1446 htmlize_to_blob(&p->aCol[0], pLine->z, (int)pLine->n);
1447 blob_append_char(&p->aCol[0], '\n');
1448 blob_append_char(&p->aCol[1], '\n');
1449 blob_appendf(&p->aCol[2],"%d\n",p->lnRight);
1450 htmlize_to_blob(&p->aCol[3], pLine->z, (int)pLine->n);
1451 blob_append_char(&p->aCol[3], '\n');
1452 }
1453 static void dfsplitInsert(DiffBuilder *p, const DLine *pLine){
1454 dfsplitStartRow(p);
1455 dfsplitChangeState(p, 2);
1456 p->lnRight++;
1457 blob_append_char(p->pOut, '\n');
1458 blob_append_char(&p->aCol[0], '\n');
1459 blob_append(&p->aCol[1], "&gt;\n", -1);
1460 blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1461 blob_append(&p->aCol[3], "<ins>", 5);
1462 htmlize_to_blob(&p->aCol[3], pLine->z, (int)pLine->n);
1463 blob_append(&p->aCol[3], "</ins>\n", 7);
1464 }
1465 static void dfsplitDelete(DiffBuilder *p, const DLine *pLine){
1466 dfsplitStartRow(p);
1467 dfsplitChangeState(p, 1);
1468 p->lnLeft++;
1469 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1470 blob_append(&p->aCol[0], "<del>", 5);
1471 htmlize_to_blob(&p->aCol[0], pLine->z, (int)pLine->n);
1472 blob_append(&p->aCol[0], "</del>\n", 7);
1473 blob_append(&p->aCol[1], "&lt;\n", -1);
1474 blob_append_char(&p->aCol[2],'\n');
1475 blob_append_char(&p->aCol[3],'\n');
1476 }
1477 static void dfsplitReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1478 dfsplitStartRow(p);
1479 dfsplitChangeState(p, 3);
1480 p->lnLeft++;
1481 p->lnRight++;
1482 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1483 htmlize_to_blob(&p->aCol[0], pX->z, pX->n);
1484 blob_append_char(&p->aCol[0], '\n');
1485
1486 blob_append(&p->aCol[1], "|\n", 2);
1487
1488 blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1489
1490 htmlize_to_blob(&p->aCol[3], pY->z, pY->n);
1491 blob_append_char(&p->aCol[3], '\n');
1492 }
1493 static void dfsplitEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1494 int i;
1495 int x;
1496 LineChange chng;
1497 oneLineChange(pX, pY, &chng);
1498 dfsplitStartRow(p);
1499 dfsplitChangeState(p, 3);
1500 p->lnLeft++;
1501 p->lnRight++;
1502 blob_appendf(p->pOut,"%d\n", p->lnLeft);
1503 for(i=x=0; i<chng.n; i++){
1504 int ofst = chng.a[i].iStart1;
1505 int len = chng.a[i].iLen1;
1506 if( len ){
1507 htmlize_to_blob(&p->aCol[0], pX->z+x, ofst - x);
1508 x = ofst;
1509 if( chng.a[i].iLen2 ){
1510 blob_append(&p->aCol[0], "<del class='edit'>", -1);
1511 }else{
1512 blob_append(&p->aCol[0], "<del>", 5);
1513 }
1514 htmlize_to_blob(&p->aCol[0], pX->z+x, len);
1515 x += len;
1516 blob_append(&p->aCol[0], "</del>", 6);
1517 }
1518 }
1519 htmlize_to_blob(&p->aCol[0], pX->z+x, pX->n - x);
1520 blob_append_char(&p->aCol[0], '\n');
1521
1522 blob_append(&p->aCol[1], "|\n", 2);
1523
1524 blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1525 for(i=x=0; i<chng.n; i++){
1526 int ofst = chng.a[i].iStart2;
1527 int len = chng.a[i].iLen2;
1528 if( len ){
1529 htmlize_to_blob(&p->aCol[3], pY->z+x, ofst - x);
1530 x = ofst;
1531 if( chng.a[i].iLen1 ){
1532 blob_append(&p->aCol[3], "<ins class='edit'>", -1);
1533 }else{
1534 blob_append(&p->aCol[3], "<ins>", 5);
1535 }
1536 htmlize_to_blob(&p->aCol[3], pY->z+x, len);
1537 x += len;
1538 blob_append(&p->aCol[3], "</ins>", 6);
1539 }
1540 }
1541 htmlize_to_blob(&p->aCol[3], pY->z+x, pY->n - x);
1542 blob_append_char(&p->aCol[3], '\n');
1543 }
1544 static void dfsplitEnd(DiffBuilder *p){
1545 dfsplitFinishRow(p);
1546 blob_append(p->pOut, "</table>\n",-1);
1547 fossil_free(p);
1548 }
1549 static DiffBuilder *dfsplitNew(Blob *pOut){
1550 DiffBuilder *p = fossil_malloc(sizeof(*p));
1551 p->xSkip = dfsplitSkip;
1552 p->xCommon = dfsplitCommon;
1553 p->xInsert = dfsplitInsert;
1554 p->xDelete = dfsplitDelete;
1555 p->xReplace = dfsplitReplace;
1556 p->xEdit = dfsplitEdit;
1557 p->xEnd = dfsplitEnd;
1558 p->lnLeft = p->lnRight = 0;
1559 p->eState = 0;
1560 p->pOut = pOut;
1561 blob_append(pOut, "<table class=\"diff splitdiff\">\n", -1);
1562 blob_init(&p->aCol[0], 0, 0);
1563 blob_init(&p->aCol[1], 0, 0);
1564 blob_init(&p->aCol[2], 0, 0);
1565 blob_init(&p->aCol[3], 0, 0);
1566 blob_init(&p->aCol[4], 0, 0);
1567 return p;
1568 }
1569
1570 /************************* DiffBuilderSbs ******************************/
1571 /* This formatter creates a side-by-side diff in text.
1572 */
1573 static void dfsbsSkip(DiffBuilder *p, unsigned int n, int isFinal){
1574 if( (p->lnLeft || p->lnRight) && !isFinal ){
1575 blob_appendf(p->pOut, "%.*c\n", p->width*2 + 16, '.');
1576 }
1577 p->lnLeft += n;
1578 p->lnRight += n;
1579 }
1580
1581 /*
1582 ** Append at least iMin characters (not bytes) and at most iMax characters
1583 ** from pX onto the into of p.
1584 **
1585 ** This comment contains multibyte unicode characters (ü, Æ, ð) in order
1586 ** to test the ability of the diff code to handle such characters.
1587 */
1588 static void sbs_append_chars(Blob *p, int iMin, int iMax, const DLine *pX){
1589 int i;
1590 const char *z = pX->z;
1591 for(i=0; i<iMax && i<pX->n; i++){
1592 char c = z[i];
1593 blob_append_char(p, c);
1594 if( (c&0xc0)==0x80 ){ iMin++; iMax++; }
1595 }
1596 while( i<iMin ){
1597 blob_append_char(p, ' ');
1598 i++;
1599 }
1600 }
1601
1602 static void dfsbsCommon(DiffBuilder *p, const DLine *pLine){
1603 p->lnLeft++;
1604 p->lnRight++;
1605 blob_appendf(p->pOut,"%6u ", p->lnLeft);
1606 sbs_append_chars(p->pOut, p->width, p->width, pLine);
1607 blob_appendf(p->pOut," %6u ", p->lnRight);
1608 sbs_append_chars(p->pOut, 0, p->width, pLine);
1609 blob_append_char(p->pOut, '\n');
1610 }
1611 static void dfsbsInsert(DiffBuilder *p, const DLine *pLine){
1612 p->lnRight++;
1613 blob_appendf(p->pOut,"%6s %*s > %6u ",
1614 "", p->width, "", p->lnRight);
1615 sbs_append_chars(p->pOut, 0, p->width, pLine);
1616 blob_append_char(p->pOut, '\n');
1617 }
1618 static void dfsbsDelete(DiffBuilder *p, const DLine *pLine){
1619 p->lnLeft++;
1620 blob_appendf(p->pOut,"%6u ", p->lnLeft);
1621 sbs_append_chars(p->pOut, p->width, p->width, pLine);
1622 blob_append(p->pOut," <\n", 3);
1623 }
1624 static void dfsbsEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1625 p->lnLeft++;
1626 p->lnRight++;
1627 blob_appendf(p->pOut,"%6u ", p->lnLeft);
1628 sbs_append_chars(p->pOut, p->width, p->width, pX);
1629 blob_appendf(p->pOut, " | %6u ", p->lnRight);
1630 sbs_append_chars(p->pOut, 0, p->width, pY);
1631 blob_append_char(p->pOut, '\n');
1632 }
1633 static void dfsbsEnd(DiffBuilder *p){
1634 fossil_free(p);
1635 }
1636 static DiffBuilder *dfsbsNew(Blob *pOut, u64 diffFlags){
1637 DiffBuilder *p = fossil_malloc(sizeof(*p));
1638 p->xSkip = dfsbsSkip;
1639 p->xCommon = dfsbsCommon;
1640 p->xInsert = dfsbsInsert;
1641 p->xDelete = dfsbsDelete;
1642 p->xReplace = dfsbsEdit;
1643 p->xEdit = dfsbsEdit;
1644 p->xEnd = dfsbsEnd;
1645 p->lnLeft = p->lnRight = 0;
1646 p->width = diff_width(diffFlags);
1647 p->pOut = pOut;
1648 return p;
1649 }
1650 /****************************************************************************/
1651 /*
1652 ** Return the number between 0 and 100 that is smaller the closer pA and
1653 ** pB match. Return 0 for a perfect match. Return 100 if pA and pB are
1654 ** completely different.
1655 **
@@ -972,11 +1658,11 @@
1658 ** (1) Remove leading and trailing whitespace.
1659 ** (2) Truncate both strings to at most 250 characters
1660 ** (3) Find the length of the longest common subsequence
1661 ** (4) Longer common subsequences yield lower scores.
1662 */
1663 static int match_dline(const DLine *pA, const DLine *pB){
1664 const char *zA; /* Left string */
1665 const char *zB; /* right string */
1666 int nA; /* Bytes in zA[] */
1667 int nB; /* Bytes in zB[] */
1668 int avg; /* Average length of A and B */
@@ -1024,10 +1710,32 @@
1710 #endif
1711
1712 /* Return the result */
1713 return score;
1714 }
1715
1716 /*
1717 ** COMMAND: test-line-match
1718 ** Usage: %fossil test-line-match STRING1 STRING2
1719 **
1720 ** Return a score from 0 to 100 that is how similar STRING1 is to
1721 ** STRING2. Smaller numbers mean more similar. 0 is an exact match.
1722 **
1723 ** This command is used to test to match_dline() function in the
1724 ** internal Fossil diff logic.
1725 */
1726 void test_dline_match(void){
1727 DLine a, b;
1728 int x;
1729 if( g.argc!=4 ) usage("STRING1 STRING2");
1730 a.z = g.argv[2];
1731 a.n = (int)strlen(a.z);
1732 b.z = g.argv[3];
1733 b.n = (int)strlen(b.z);
1734 x = match_dline(&a, &b);
1735 fossil_print("%d\n", x);
1736 }
1737
1738 /*
1739 ** There is a change block in which nLeft lines of text on the left are
1740 ** converted into nRight lines of text on the right. This routine computes
1741 ** how the lines on the left line up with the lines on the right.
@@ -1039,61 +1747,113 @@
1747 ** 1. Delete the next line of pLeft.
1748 ** 2. Insert the next line of pRight.
1749 ** 3. The next line of pLeft changes into the next line of pRight.
1750 ** 4. Delete one line from pLeft and add one line to pRight.
1751 **
1752 ** The length of the returned array will be at most nLeft+nRight bytes.
1753 ** If the first bytes is 4, that means we could not compute reasonable
1754 ** alignment between the two blocks.
 
1755 **
1756 ** Algorithm: Wagner's minimum edit-distance algorithm, modified by
1757 ** adding a cost to each match based on how well the two rows match
1758 ** each other. Insertion and deletion costs are 50. Match costs
1759 ** are between 0 and 100 where 0 is a perfect match 100 is a complete
1760 ** mismatch.
1761 */
1762 static unsigned char *diffBlockAlignment(
1763 const DLine *aLeft, int nLeft, /* Text on the left */
1764 const DLine *aRight, int nRight, /* Text on the right */
1765 u64 diffFlags, /* Flags passed into the original diff */
1766 int *pNResult /* OUTPUT: Bytes of result */
1767 ){
1768 int i, j, k; /* Loop counters */
1769 int *a; /* One row of the Wagner matrix */
1770 int *pToFree; /* Space that needs to be freed */
1771 unsigned char *aM; /* Wagner result matrix */
1772 int nMatch, iMatch; /* Number of matching lines and match score */
1773 int mnLen; /* minInt(nLeft, nRight) */
1774 int mxLen; /* MAX(nLeft, nRight) */
1775 int aBuf[100]; /* Stack space for a[] if nRight not to big */
1776
1777 if( nLeft==0 ){
1778 aM = fossil_malloc( nRight + 2 );
1779 memset(aM, 2, nRight);
1780 *pNResult = nRight;
1781 return aM;
1782 }
1783 if( nRight==0 ){
1784 aM = fossil_malloc( nLeft + 2 );
1785 memset(aM, 1, nLeft);
1786 *pNResult = nLeft;
1787 return aM;
1788 }
1789
1790 /* For large alignments, use a divide and conquer algorithm that is
1791 ** O(NlogN). The result is not as precise, but this whole thing is an
1792 ** approximation anyhow, and the faster response time is an acceptable
1793 ** trade-off for reduced precision.
1794 */
1795 mnLen = nLeft<nRight ? nLeft : nRight;
1796 if( nLeft*nRight>1000 && (diffFlags & DIFF_SLOW_SBS)==0 ){
1797 const DLine *aSmall; /* The smaller of aLeft and aRight */
1798 const DLine *aBig; /* The larger of aLeft and aRight */
1799 int nSmall, nBig; /* Size of aSmall and aBig. nSmall<=nBig */
1800 int iDivSmall, iDivBig; /* Divider point for aSmall and aBig */
1801 int iDivLeft, iDivRight; /* Divider point for aLeft and aRight */
1802 unsigned char *a1, *a2; /* Results of the alignments on two halves */
1803 int n1, n2; /* Number of entries in a1 and a2 */
1804 int score, bestScore; /* Score and best score seen so far */
1805 if( nLeft>nRight ){
1806 aSmall = aRight;
1807 nSmall = nRight;
1808 aBig = aLeft;
1809 nBig = nLeft;
1810 }else{
1811 aSmall = aLeft;
1812 nSmall = nLeft;
1813 aBig = aRight;
1814 nBig = nRight;
1815 }
1816 iDivBig = nBig/2;
1817 iDivSmall = nSmall/2;
1818 bestScore = 10000;
1819 for(i=0; i<nSmall; i++){
1820 score = match_dline(aBig+iDivBig, aSmall+i) + abs(i-nSmall/2)*2;
1821 if( score<bestScore ){
1822 bestScore = score;
1823 iDivSmall = i;
1824 }
1825 }
1826 if( aSmall==aRight ){
1827 iDivRight = iDivSmall;
1828 iDivLeft = iDivBig;
1829 }else{
1830 iDivRight = iDivBig;
1831 iDivLeft = iDivSmall;
1832 }
1833 a1 = diffBlockAlignment(aLeft,iDivLeft,aRight,iDivRight,diffFlags,&n1);
1834 a2 = diffBlockAlignment(aLeft+iDivLeft, nLeft-iDivLeft,
1835 aRight+iDivRight, nRight-iDivRight,
1836 diffFlags, &n2);
1837 a1 = fossil_realloc(a1, n1+n2 );
1838 memcpy(a1+n1,a2,n2);
1839 fossil_free(a2);
1840 *pNResult = n1+n2;
1841 return a1;
1842 }
1843
1844 /* If we reach this point, we will be doing an O(N*N) Wagner minimum
1845 ** edit distance to compute the alignment.
1846 */
1847 if( nRight < count(aBuf)-1 ){
1848 pToFree = 0;
1849 a = aBuf;
1850 }else{
1851 a = pToFree = fossil_malloc( sizeof(a[0])*(nRight+1) );
1852 }
1853 aM = fossil_malloc( (nLeft+1)*(nRight+1) );
1854
1855
1856 /* Compute the best alignment */
1857 for(i=0; i<=nRight; i++){
1858 aM[i] = 2;
1859 a[i] = i*50;
@@ -1148,10 +1908,11 @@
1908 aM[k] = aM[j*(nRight+1)+i];
1909 }
1910 k++;
1911 i = (nRight+1)*(nLeft+1) - k;
1912 memmove(aM, &aM[k], i);
1913 *pNResult = i;
1914
1915 /* If:
1916 ** (1) the alignment is more than 25% longer than the longest side, and
1917 ** (2) the average match cost exceeds 15
1918 ** Then this is probably an alignment that will be difficult for humans
@@ -1161,13 +1922,13 @@
1922 ** The coefficients for conditions (1) and (2) above are determined by
1923 ** experimentation.
1924 */
1925 mxLen = nLeft>nRight ? nLeft : nRight;
1926 if( i*4>mxLen*5 && (nMatch==0 || iMatch/nMatch>15) ){
1927 memset(aM, 4, mnLen); *pNResult = mnLen;
1928 if( nLeft>mnLen ){ memset(aM+mnLen, 1, nLeft-mnLen); *pNResult = nLeft; }
1929 if( nRight>mnLen ){ memset(aM+mnLen, 2, nRight-mnLen); *pNResult = nRight; }
1930 }
1931
1932 /* Return the result */
1933 fossil_free(pToFree);
1934 return aM;
@@ -1177,70 +1938,47 @@
1938 ** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a
1939 ** pair of adjacent differences. Return true if the gap between these
1940 ** two differences is so small that they should be rendered as a single
1941 ** edit.
1942 */
1943 static int smallGap(const int *R){
1944 return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8;
1945 }
1946
1947 /*
1948 ** Format a diff using a DiffBuilder object
 
1949 */
1950 static void formatDiff(
1951 DContext *p, /* The computed diff */
1952 ReCompiled *pRe, /* Only show changes that match this regex */
1953 u64 diffFlags, /* Flags controlling the diff */
1954 DiffBuilder *pBuilder /* The formatter object */
1955 ){
1956 const DLine *A; /* Left side of the diff */
1957 const DLine *B; /* Right side of the diff */
1958 unsigned int a = 0; /* Index of next line in A[] */
1959 unsigned int b = 0; /* Index of next line in B[] */
1960 const int *R; /* Array of COPY/DELETE/INSERT triples */
1961 unsigned int r; /* Index into R[] */
1962 unsigned int nr; /* Number of COPY/DELETE/INSERT triples to process */
1963 unsigned int mxr; /* Maximum value for r */
1964 unsigned int na, nb; /* Number of lines shown from A and B */
1965 unsigned int i, j; /* Loop counters */
1966 unsigned int m, ma, mb;/* Number of lines to output */
1967 signed int skip = 0; /* Number of lines to skip */
1968 unsigned int nContext; /* Lines of context above and below each change */
1969
1970 nContext = diff_context_lines(diffFlags);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1971 A = p->aFrom;
1972 B = p->aTo;
1973 R = p->aEdit;
1974 mxr = p->nEdit;
1975 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
1976
1977 for(r=0; r<mxr; r += 3*nr){
1978 /* Figure out how many triples to show in a single block */
1979 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
 
1980
1981 /* If there is a regex, skip this block (generate no diff output)
1982 ** if the regex matches or does not match both insert and delete.
1983 ** Only display the block if one side matches but the other side does
1984 ** not.
@@ -1263,12 +2001,12 @@
2001 b = xb;
2002 continue;
2003 }
2004 }
2005
2006 /* Figure out how many lines of A and B are to be displayed
2007 ** for this change block.
2008 */
2009 if( R[r]>nContext ){
2010 na = nb = nContext;
2011 skip = R[r] - nContext;
2012 }else{
@@ -1289,127 +2027,91 @@
2027 for(i=1; i<nr; i++){
2028 na += R[r+i*3];
2029 nb += R[r+i*3];
2030 }
2031
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2032 /* Show the initial common area */
2033 a += skip;
2034 b += skip;
2035 m = R[r] - skip;
2036 if( r ) skip -= nContext;
2037 if( skip>0 ){
2038 pBuilder->xSkip(pBuilder, skip, 0);
2039 }
2040 for(j=0; j<m; j++){
2041 pBuilder->xCommon(pBuilder, &A[a+j]);
 
 
 
 
 
2042 }
2043 a += m;
2044 b += m;
2045
2046 /* Show the differences */
2047 for(i=0; i<nr; i++){
2048 int nAlign;
2049 unsigned char *alignment;
2050 ma = R[r+i*3+1]; /* Lines on left but not on right */
2051 mb = R[r+i*3+2]; /* Lines on right but not on left */
2052
2053 /* Try to find an alignment for the lines within this one block */
2054 alignment = diffBlockAlignment(&A[a], ma, &B[b], mb, diffFlags, &nAlign);
2055
2056 /* If we could not get a good alignment, try merging the current
2057 ** block with subsequent blocks, if the subsequent blocks are
2058 ** nearby */
2059 while( alignment[0]==4 && i<nr-1 && smallGap(&R[r+i*3]) ){
2060 i++;
2061 m = R[r+i*3];
2062 ma += R[r+i*3+1] + m;
2063 mb += R[r+i*3+2] + m;
2064 fossil_free(alignment);
2065 alignment = diffBlockAlignment(&A[a], ma, &B[b], mb, diffFlags,&nAlign);
2066 }
2067
2068 for(j=0; ma+mb>0; j++){
2069 assert( j<nAlign );
2070 switch( alignment[j] ){
2071 case 1: {
2072 /* Delete one line from the left */
2073 pBuilder->xDelete(pBuilder, &A[a]);
2074 ma--;
2075 a++;
2076 break;
2077 }
2078 case 2: {
2079 /* Insert one line on the right */
2080 pBuilder->xInsert(pBuilder, &B[b]);
2081 assert( mb>0 );
2082 mb--;
2083 b++;
2084 break;
2085 }
2086 case 3: {
2087 /* The left line is changed into the right line */
2088 pBuilder->xEdit(pBuilder, &A[a], &B[b]);
2089 assert( ma>0 && mb>0 );
2090 ma--;
2091 mb--;
2092 a++;
2093 b++;
2094 break;
2095 }
2096 case 4: {
2097 /* Delete from left then separately insert on the right */
2098 pBuilder->xReplace(pBuilder, &A[a], &B[b]);
2099 ma--;
2100 a++;
2101 mb--;
2102 b++;
2103 break;
2104 }
2105 }
2106 }
2107 assert( nAlign==j );
 
 
 
 
 
 
 
 
 
 
 
 
 
2108 fossil_free(alignment);
2109 if( i<nr-1 ){
2110 m = R[r+i*3+3];
2111 for(j=0; j<m; j++){
2112 pBuilder->xCommon(pBuilder, &A[a+j]);
 
 
 
 
 
2113 }
2114 b += m;
2115 a += m;
2116 }
2117 }
@@ -1416,29 +2118,20 @@
2118
2119 /* Show the final common area */
2120 assert( nr==i );
2121 m = R[r+nr*3];
2122 if( m>nContext ) m = nContext;
2123 for(j=0; j<m && j<nContext; j++){
2124 pBuilder->xCommon(pBuilder, &A[a+j]);
2125 }
2126 }
2127 if( R[r]>nContext ){
2128 pBuilder->xSkip(pBuilder, R[r] - nContext, 1);
2129 }
2130 pBuilder->xEnd(pBuilder);
2131 }
2132
 
 
 
 
 
 
 
 
 
2133
2134 /*
2135 ** Compute the optimal longest common subsequence (LCS) using an
2136 ** exhaustive search. This version of the LCS is only used for
2137 ** shorter input strings since runtime is O(N*N) where N is the
@@ -1509,11 +2202,11 @@
2202 int n; /* Loop limit */
2203 DLine *pA, *pB; /* Pointers to lines */
2204 int iSX, iSY, iEX, iEY; /* Current match */
2205 int skew = 0; /* How lopsided is the match */
2206 int dist = 0; /* Distance of match from center */
2207 int mid; /* Center of the chng */
2208 int iSXb, iSYb, iEXb, iEYb; /* Best match so far */
2209 int iSXp, iSYp, iEXp, iEYp; /* Previous match */
2210 sqlite3_int64 bestScore; /* Best score so far */
2211 sqlite3_int64 score; /* Score for current candidate LCS */
2212 int span; /* combined width of the input sequences */
@@ -1878,18 +2571,26 @@
2571 blob_append(pOut, msg, -1);
2572 }
2573 }
2574
2575 /*
2576 ** Generate a report of the differences between files pA_Blob and pB_Blob.
2577 **
2578 ** If pOut!=NULL then append text to pOut that will be the difference,
2579 ** formatted according to flags in diffFlags. The pOut Blob must have
2580 ** already been initialized.
2581 **
2582 ** If pOut==NULL then no formatting occurs. Instead, this routine
2583 ** returns a pointer to an array of integers. The integers come in
2584 ** triples. The elements of each triple are:
2585 **
2586 ** 1. The number of lines to copy
2587 ** 2. The number of lines to delete
2588 ** 3. The number of lines to insert
2589 **
2590 ** The return vector is terminated bin a triple of all zeros. The caller
2591 ** should free the returned vector using fossil_free().
2592 **
2593 ** This diff utility does not work on binary files. If a binary
2594 ** file is encountered, 0 is returned and pOut is written with
2595 ** text "cannot compute difference between binary files".
2596 */
@@ -1969,14 +2670,40 @@
2670 g.diffCnt[2] += nDel;
2671 if( nIns+nDel ){
2672 g.diffCnt[0]++;
2673 blob_appendf(pOut, "%10d %10d", nIns, nDel);
2674 }
2675 }else if( diffFlags & DIFF_RAW ){
2676 const int *R = c.aEdit;
2677 unsigned int r;
2678 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
2679 blob_appendf(pOut, " copy %6d delete %6d insert %6d\n",
2680 R[r], R[r+1], R[r+2]);
2681 }
2682 }else if( diffFlags & DIFF_JSON ){
2683 DiffBuilder *pBuilder = dfjsonNew(pOut);
2684 formatDiff(&c, pRe, diffFlags, pBuilder);
2685 blob_append_char(pOut, '\n');
2686 }else if( diffFlags & DIFF_TCL ){
2687 DiffBuilder *pBuilder = dftclNew(pOut);
2688 formatDiff(&c, pRe, diffFlags, pBuilder);
2689 }else if( diffFlags & DIFF_SIDEBYSIDE ){
2690 DiffBuilder *pBuilder;
2691 if( diffFlags & DIFF_HTML ){
2692 pBuilder = dfsplitNew(pOut);
2693 }else{
2694 pBuilder = dfsbsNew(pOut, diffFlags);
2695 }
2696 formatDiff(&c, pRe, diffFlags, pBuilder);
2697 }else if( diffFlags & DIFF_DEBUG ){
2698 DiffBuilder *pBuilder = dfdebugNew(pOut);
2699 formatDiff(&c, pRe, diffFlags, pBuilder);
2700 }else if( diffFlags & DIFF_HTML ){
2701 DiffBuilder *pBuilder = dfunifiedNew(pOut);
2702 formatDiff(&c, pRe, diffFlags, pBuilder);
2703 }else{
2704 contextDiff(&c, pOut, diffFlags);
2705 }
2706 fossil_free(c.aFrom);
2707 fossil_free(c.aTo);
2708 fossil_free(c.aEdit);
2709 return 0;
@@ -2049,48 +2776,40 @@
2776 }
2777 if( find_option("by",0,0)!=0 ){
2778 diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER
2779 |DIFF_SIDEBYSIDE;
2780 }
2781 if( find_option("json",0,0)!=0 ){
2782 diffFlags |= DIFF_JSON;
2783 }
2784 if( find_option("tcl",0,0)!=0 ){
2785 diffFlags |= DIFF_TCL;
2786 }
2787
2788 /* Undocumented and unsupported flags used for development
2789 ** debugging and analysis: */
2790 if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
2791 if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
2792 return diffFlags;
2793 }
2794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2795 /*
2796 ** COMMAND: test-diff
2797 ** COMMAND: xdiff
2798 **
2799 ** Usage: %fossil xdiff [options] FILE1 FILE2
2800 **
2801 ** This is the "external diff" feature. By "external" here we mean a diff
2802 ** applied to files that are not under version control. See the "diff"
2803 ** command for computing differences between files that are under control.
2804 **
2805 ** This command prints the differences between the two files FILE1 and FILE2.
2806 ** all of the usual diff command-line options apply. See the "diff" command
2807 ** for a full list of command-line options.
2808 **
2809 ** This command used to be called "test-diff". The older "test-diff" spelling
2810 ** still works, for compatibility.
2811 */
2812 void test_diff_cmd(void){
2813 Blob a, b, out;
2814 u64 diffFlag;
2815 const char *zRe; /* Regex filter for diff output */
2816
2817 DDED src/diff.js
+16
--- a/src/diff.js
+++ b/src/diff.js
@@ -0,0 +1,16 @@
1
+/* Refinements to the display of unified and side-by-side diffs.
2
+**
3
+** In all cases, the table columns tagged with "difftxt" are expanded,
4
+** where possible, to fill the width of the screen.
5
+**
6
+** For a side-by-side diff, if either column is two wide to fit on the
7
+** display, scrollbars are added. The scrollbars are linked, so that
8
+** both sides scroll together. Left and right arrows also scroll.
9
+*/
10
+(ow.addEventListener('load',function(){
11
+ var SCROLL_LEN = 25;
12
+ function initDiff(diff){
13
+ var txtCols = diff.querySelectorAll('td.difftxt');
14
+ var txtPres = diff.querySelectorAll('td.difftxt pre');
15
+ var width = 0;
16
+ if(txtPres.length>=2)Math.max(t setTimeout(checkWidth, 100)
--- a/src/diff.js
+++ b/src/diff.js
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/diff.js
+++ b/src/diff.js
@@ -0,0 +1,16 @@
1 /* Refinements to the display of unified and side-by-side diffs.
2 **
3 ** In all cases, the table columns tagged with "difftxt" are expanded,
4 ** where possible, to fill the width of the screen.
5 **
6 ** For a side-by-side diff, if either column is two wide to fit on the
7 ** display, scrollbars are added. The scrollbars are linked, so that
8 ** both sides scroll together. Left and right arrows also scroll.
9 */
10 (ow.addEventListener('load',function(){
11 var SCROLL_LEN = 25;
12 function initDiff(diff){
13 var txtCols = diff.querySelectorAll('td.difftxt');
14 var txtPres = diff.querySelectorAll('td.difftxt pre');
15 var width = 0;
16 if(txtPres.length>=2)Math.max(t setTimeout(checkWidth, 100)
+99 -61
--- src/diff.tcl
+++ src/diff.tcl
@@ -19,11 +19,11 @@
1919
MKR_COL_BG #444444
2020
MKR_COL_FG #dddddd
2121
CHNG_BG #d0d0ff
2222
ADD_BG #c0ffc0
2323
RM_BG #ffc0c0
24
- HR_FG #888888
24
+ HR_FG #444444
2525
HR_PAD_TOP 4
2626
HR_PAD_BTM 8
2727
FN_BG #444444
2828
FN_FG #ffffff
2929
FN_PAD 5
@@ -70,68 +70,104 @@
7070
close $in
7171
}
7272
set N [llength $difftxt]
7373
set ii 0
7474
set nDiffs 0
75
- array set widths {txt 0 ln 0 mkr 0}
76
- while {[set line [getLine $difftxt $N ii]] != -1} {
77
- set fn2 {}
78
- if {![regexp {^=+ (.*?) =+ versus =+ (.*?) =+$} $line all fn fn2]
79
- && ![regexp {^=+ (.*?) =+$} $line all fn]
80
- } {
81
- continue
82
- }
83
- set errMsg ""
84
- set line [getLine $difftxt $N ii]
85
- if {[string compare -length 6 $line "<table"]
86
- && ![regexp {<p[^>]*>(.+)} $line - errMsg]} {
87
- continue
88
- }
89
- incr nDiffs
90
- set idx [expr {$nDiffs > 1 ? [.txtA index end] : "1.0"}]
91
- .wfiles.lb insert end $fn
92
-
93
- foreach c [cols] {
94
- if {$nDiffs > 1} {
95
- $c insert end \n -
96
- }
97
- if {[colType $c] eq "txt"} {
98
- $c insert end $fn\n fn
99
- if {$fn2!=""} {set fn $fn2}
100
- } else {
101
- $c insert end \n fn
102
- }
103
- $c insert end \n -
104
-
105
- if {$errMsg ne ""} continue
106
- while {[getLine $difftxt $N ii] ne "<pre>"} continue
107
- set type [colType $c]
108
- set str {}
109
- while {[set line [getLine $difftxt $N ii]] ne "</pre>"} {
110
- set len [string length [dehtml $line]]
111
- if {$len > $widths($type)} {
112
- set widths($type) $len
113
- }
114
- append str $line\n
115
- }
116
-
117
- set re {<span class="diff([a-z]+)">([^<]*)</span>}
118
- # Use \r as separator since it can't appear in the diff output (it gets
119
- # converted to a space).
120
- set str [regsub -all $re $str "\r\\1\r\\2\r"]
121
- foreach {pre class mid} [split $str \r] {
122
- if {$class ne ""} {
123
- $c insert end [dehtml $pre] - [dehtml $mid] [list $class -]
124
- } else {
125
- $c insert end [dehtml $pre] -
126
- }
127
- }
128
- }
129
-
130
- if {$errMsg ne ""} {
131
- foreach c {.txtA .txtB} {$c insert end [string trim $errMsg] err}
132
- foreach c [cols] {$c insert end \n -}
75
+ set n1 0
76
+ set n2 0
77
+ array set widths {txt 0 ln 0 mkr 1}
78
+ while {[set line [getLine $difftxt $N ii]] != -1} {
79
+ switch -- [lindex $line 0] {
80
+ FILE {
81
+ incr nDiffs
82
+ foreach wx [list [string length $n1] [string length $n2]] {
83
+ if {$wx>$widths(ln)} {set widths(ln) $wx}
84
+ }
85
+ .lnA insert end \n fn \n -
86
+ .txtA insert end [lindex $line 1]\n fn \n -
87
+ .mkr insert end \n fn \n -
88
+ .lnB insert end \n fn \n -
89
+ .txtB insert end [lindex $line 2]\n fn \n -
90
+ .wfiles.lb insert end [lindex $line 2]
91
+ set n1 0
92
+ set n2 0
93
+ }
94
+ SKIP {
95
+ set n [lindex $line 1]
96
+ incr n1 $n
97
+ incr n2 $n
98
+ .lnA insert end ...\n hrln
99
+ .txtA insert end [string repeat . 30]\n hrtxt
100
+ .mkr insert end \n hrln
101
+ .lnB insert end ...\n hrln
102
+ .txtB insert end [string repeat . 30]\n hrtxt
103
+ }
104
+ COM {
105
+ set x [lindex $line 1]
106
+ incr n1
107
+ incr n2
108
+ .lnA insert end $n1\n -
109
+ .txtA insert end $x\n -
110
+ .mkr insert end \n -
111
+ .lnB insert end $n2\n -
112
+ .txtB insert end $x\n -
113
+ }
114
+ INS {
115
+ set x [lindex $line 1]
116
+ incr n2
117
+ .lnA insert end \n -
118
+ .txtA insert end \n -
119
+ .mkr insert end >\n -
120
+ .lnB insert end $n2\n -
121
+ .txtB insert end $x add \n -
122
+ }
123
+ DEL {
124
+ set x [lindex $line 1]
125
+ incr n1
126
+ .lnA insert end $n1\n -
127
+ .txtA insert end $x rm \n -
128
+ .mkr insert end <\n -
129
+ .lnB insert end \n -
130
+ .txtB insert end \n -
131
+ }
132
+ EDIT {
133
+ incr n1
134
+ incr n2
135
+ .lnA insert end $n1\n -
136
+ .lnB insert end $n2\n -
137
+ .mkr insert end |\n -
138
+ set nn [llength $line]
139
+ for {set i 1} {$i<$nn} {incr i 3} {
140
+ set x [lindex $line $i]
141
+ if {$x ne ""} {
142
+ .txtA insert end $x -
143
+ .txtB insert end $x -
144
+ }
145
+ if {$i+2<$nn} {
146
+ set x1 [lindex $line [expr {$i+1}]]
147
+ set x2 [lindex $line [expr {$i+2}]]
148
+ if {"$x1" eq ""} {
149
+ .txtB insert end $x2 add
150
+ } elseif {"$x2" eq ""} {
151
+ .txtA insert end $x1 rm
152
+ } else {
153
+ .txtA insert end $x1 chng
154
+ .txtB insert end $x2 chng
155
+ }
156
+ }
157
+ }
158
+ .txtA insert end \n -
159
+ .txtB insert end \n -
160
+ }
161
+ "" {
162
+ foreach wx [list [string length $n1] [string length $n2]] {
163
+ if {$wx>$widths(ln)} {set widths(ln) $wx}
164
+ }
165
+ }
166
+ default {
167
+ error "bad diff source line: $line"
168
+ }
133169
}
134170
}
135171
136172
foreach c [cols] {
137173
set type [colType $c]
@@ -330,12 +366,14 @@
330366
foreach c [cols] {
331367
set keyPrefix [string toupper [colType $c]]_COL_
332368
if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
333369
$c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
334370
-padx $CFG(PADX) -yscroll sync-y
335
- $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
336
- -foreground $CFG(HR_FG)
371
+ $c tag config hrln -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
372
+ -foreground $CFG(HR_FG) -justify right
373
+ $c tag config hrtxt -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
374
+ -foreground $CFG(HR_FG) -justify center
337375
$c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
338376
bindtags $c ". $c Text all"
339377
bind $c <1> {focus %W}
340378
}
341379
342380
--- src/diff.tcl
+++ src/diff.tcl
@@ -19,11 +19,11 @@
19 MKR_COL_BG #444444
20 MKR_COL_FG #dddddd
21 CHNG_BG #d0d0ff
22 ADD_BG #c0ffc0
23 RM_BG #ffc0c0
24 HR_FG #888888
25 HR_PAD_TOP 4
26 HR_PAD_BTM 8
27 FN_BG #444444
28 FN_FG #ffffff
29 FN_PAD 5
@@ -70,68 +70,104 @@
70 close $in
71 }
72 set N [llength $difftxt]
73 set ii 0
74 set nDiffs 0
75 array set widths {txt 0 ln 0 mkr 0}
76 while {[set line [getLine $difftxt $N ii]] != -1} {
77 set fn2 {}
78 if {![regexp {^=+ (.*?) =+ versus =+ (.*?) =+$} $line all fn fn2]
79 && ![regexp {^=+ (.*?) =+$} $line all fn]
80 } {
81 continue
82 }
83 set errMsg ""
84 set line [getLine $difftxt $N ii]
85 if {[string compare -length 6 $line "<table"]
86 && ![regexp {<p[^>]*>(.+)} $line - errMsg]} {
87 continue
88 }
89 incr nDiffs
90 set idx [expr {$nDiffs > 1 ? [.txtA index end] : "1.0"}]
91 .wfiles.lb insert end $fn
92
93 foreach c [cols] {
94 if {$nDiffs > 1} {
95 $c insert end \n -
96 }
97 if {[colType $c] eq "txt"} {
98 $c insert end $fn\n fn
99 if {$fn2!=""} {set fn $fn2}
100 } else {
101 $c insert end \n fn
102 }
103 $c insert end \n -
104
105 if {$errMsg ne ""} continue
106 while {[getLine $difftxt $N ii] ne "<pre>"} continue
107 set type [colType $c]
108 set str {}
109 while {[set line [getLine $difftxt $N ii]] ne "</pre>"} {
110 set len [string length [dehtml $line]]
111 if {$len > $widths($type)} {
112 set widths($type) $len
113 }
114 append str $line\n
115 }
116
117 set re {<span class="diff([a-z]+)">([^<]*)</span>}
118 # Use \r as separator since it can't appear in the diff output (it gets
119 # converted to a space).
120 set str [regsub -all $re $str "\r\\1\r\\2\r"]
121 foreach {pre class mid} [split $str \r] {
122 if {$class ne ""} {
123 $c insert end [dehtml $pre] - [dehtml $mid] [list $class -]
124 } else {
125 $c insert end [dehtml $pre] -
126 }
127 }
128 }
129
130 if {$errMsg ne ""} {
131 foreach c {.txtA .txtB} {$c insert end [string trim $errMsg] err}
132 foreach c [cols] {$c insert end \n -}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133 }
134 }
135
136 foreach c [cols] {
137 set type [colType $c]
@@ -330,12 +366,14 @@
330 foreach c [cols] {
331 set keyPrefix [string toupper [colType $c]]_COL_
332 if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
333 $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
334 -padx $CFG(PADX) -yscroll sync-y
335 $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
336 -foreground $CFG(HR_FG)
 
 
337 $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
338 bindtags $c ". $c Text all"
339 bind $c <1> {focus %W}
340 }
341
342
--- src/diff.tcl
+++ src/diff.tcl
@@ -19,11 +19,11 @@
19 MKR_COL_BG #444444
20 MKR_COL_FG #dddddd
21 CHNG_BG #d0d0ff
22 ADD_BG #c0ffc0
23 RM_BG #ffc0c0
24 HR_FG #444444
25 HR_PAD_TOP 4
26 HR_PAD_BTM 8
27 FN_BG #444444
28 FN_FG #ffffff
29 FN_PAD 5
@@ -70,68 +70,104 @@
70 close $in
71 }
72 set N [llength $difftxt]
73 set ii 0
74 set nDiffs 0
75 set n1 0
76 set n2 0
77 array set widths {txt 0 ln 0 mkr 1}
78 while {[set line [getLine $difftxt $N ii]] != -1} {
79 switch -- [lindex $line 0] {
80 FILE {
81 incr nDiffs
82 foreach wx [list [string length $n1] [string length $n2]] {
83 if {$wx>$widths(ln)} {set widths(ln) $wx}
84 }
85 .lnA insert end \n fn \n -
86 .txtA insert end [lindex $line 1]\n fn \n -
87 .mkr insert end \n fn \n -
88 .lnB insert end \n fn \n -
89 .txtB insert end [lindex $line 2]\n fn \n -
90 .wfiles.lb insert end [lindex $line 2]
91 set n1 0
92 set n2 0
93 }
94 SKIP {
95 set n [lindex $line 1]
96 incr n1 $n
97 incr n2 $n
98 .lnA insert end ...\n hrln
99 .txtA insert end [string repeat . 30]\n hrtxt
100 .mkr insert end \n hrln
101 .lnB insert end ...\n hrln
102 .txtB insert end [string repeat . 30]\n hrtxt
103 }
104 COM {
105 set x [lindex $line 1]
106 incr n1
107 incr n2
108 .lnA insert end $n1\n -
109 .txtA insert end $x\n -
110 .mkr insert end \n -
111 .lnB insert end $n2\n -
112 .txtB insert end $x\n -
113 }
114 INS {
115 set x [lindex $line 1]
116 incr n2
117 .lnA insert end \n -
118 .txtA insert end \n -
119 .mkr insert end >\n -
120 .lnB insert end $n2\n -
121 .txtB insert end $x add \n -
122 }
123 DEL {
124 set x [lindex $line 1]
125 incr n1
126 .lnA insert end $n1\n -
127 .txtA insert end $x rm \n -
128 .mkr insert end <\n -
129 .lnB insert end \n -
130 .txtB insert end \n -
131 }
132 EDIT {
133 incr n1
134 incr n2
135 .lnA insert end $n1\n -
136 .lnB insert end $n2\n -
137 .mkr insert end |\n -
138 set nn [llength $line]
139 for {set i 1} {$i<$nn} {incr i 3} {
140 set x [lindex $line $i]
141 if {$x ne ""} {
142 .txtA insert end $x -
143 .txtB insert end $x -
144 }
145 if {$i+2<$nn} {
146 set x1 [lindex $line [expr {$i+1}]]
147 set x2 [lindex $line [expr {$i+2}]]
148 if {"$x1" eq ""} {
149 .txtB insert end $x2 add
150 } elseif {"$x2" eq ""} {
151 .txtA insert end $x1 rm
152 } else {
153 .txtA insert end $x1 chng
154 .txtB insert end $x2 chng
155 }
156 }
157 }
158 .txtA insert end \n -
159 .txtB insert end \n -
160 }
161 "" {
162 foreach wx [list [string length $n1] [string length $n2]] {
163 if {$wx>$widths(ln)} {set widths(ln) $wx}
164 }
165 }
166 default {
167 error "bad diff source line: $line"
168 }
169 }
170 }
171
172 foreach c [cols] {
173 set type [colType $c]
@@ -330,12 +366,14 @@
366 foreach c [cols] {
367 set keyPrefix [string toupper [colType $c]]_COL_
368 if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
369 $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
370 -padx $CFG(PADX) -yscroll sync-y
371 $c tag config hrln -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
372 -foreground $CFG(HR_FG) -justify right
373 $c tag config hrtxt -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
374 -foreground $CFG(HR_FG) -justify center
375 $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
376 bindtags $c ". $c Text all"
377 bind $c <1> {focus %W}
378 }
379
380
+104 -66
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -114,11 +114,12 @@
114114
115115
/*
116116
** Print the "Index:" message that patches wants to see at the top of a diff.
117117
*/
118118
void diff_print_index(const char *zFile, u64 diffFlags, Blob *diffBlob){
119
- if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_WEBPAGE))==0 ){
119
+ if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_JSON|
120
+ DIFF_WEBPAGE|DIFF_TCL))==0 ){
120121
char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
121122
if( !diffBlob ){
122123
fossil_print("%s", z);
123124
}else{
124125
blob_appendf(diffBlob, "%s", z);
@@ -128,21 +129,47 @@
128129
}
129130
130131
/*
131132
** Print the +++/--- filename lines for a diff operation.
132133
*/
133
-void diff_print_filenames(const char *zLeft, const char *zRight,
134
- u64 diffFlags, Blob *diffBlob){
134
+void diff_print_filenames(
135
+ const char *zLeft,
136
+ const char *zRight,
137
+ u64 diffFlags,
138
+ Blob *diffBlob
139
+){
135140
char *z = 0;
136
- if( diffFlags & DIFF_BRIEF ){
141
+ if( diffFlags & (DIFF_BRIEF|DIFF_RAW|DIFF_JSON) ){
137142
/* no-op */
143
+ }else if( diffFlags & DIFF_DEBUG ){
144
+ fossil_print("FILE-LEFT %s\nFILE-RIGHT %s\n",
145
+ zLeft, zRight);
138146
}else if( diffFlags & DIFF_WEBPAGE ){
139147
if( fossil_strcmp(zLeft,zRight)==0 ){
140148
z = mprintf("<h1>%h</h1>\n", zLeft);
141149
}else{
142150
z = mprintf("<h1>%h &lrarr; %h</h1>\n", zLeft, zRight);
143151
}
152
+ }else if( diffFlags & DIFF_TCL ){
153
+ Blob *pOut;
154
+ Blob x;
155
+ if( diffBlob ){
156
+ pOut = diffBlob;
157
+ }else{
158
+ blob_init(&x, 0, 0);
159
+ pOut = &x;
160
+ }
161
+ blob_append(pOut, "FILE ", 5);
162
+ blob_append_tcl_literal(pOut, zLeft, (int)strlen(zLeft));
163
+ blob_append_char(pOut, ' ');
164
+ blob_append_tcl_literal(pOut, zRight, (int)strlen(zRight));
165
+ blob_append_char(pOut, '\n');
166
+ if( !diffBlob ){
167
+ fossil_print("%s", blob_str(pOut));
168
+ blob_reset(&x);
169
+ }
170
+ return;
144171
}else if( diffFlags & DIFF_SIDEBYSIDE ){
145172
int w = diff_width(diffFlags);
146173
int n1 = strlen(zLeft);
147174
int n2 = strlen(zRight);
148175
int x;
@@ -168,41 +195,10 @@
168195
blob_appendf(diffBlob, "%s", z);
169196
}
170197
fossil_free(z);
171198
}
172199
173
-/*
174
-** Extra CSS for side-by-side diffs
175
-*/
176
-static const char zSbsCss[] =
177
-@ table.sbsdiffcols {
178
-@ width: 90%;
179
-@ border-spacing: 0;
180
-@ font-size: small;
181
-@ }
182
-@ table.sbsdiffcols td {
183
-@ padding: 0;
184
-@ vertical-align: top;
185
-@ }
186
-@ table.sbsdiffcols pre {
187
-@ margin: 0;
188
-@ padding: 0;
189
-@ border: 0;
190
-@ }
191
-@ div.difflncol {
192
-@ padding-right: 1em;
193
-@ text-align: right;
194
-@ color: #a0a0a0;
195
-@ }
196
-@ div.difftxtcol {
197
-@ width: 10em;
198
-@ overflow-x: auto;
199
-@ }
200
-@ div.diffmkrcol {
201
-@ padding: 0 1em;
202
-@ }
203
-;
204200
205201
/*
206202
** Default header text for diff with --webpage
207203
*/
208204
static const char zWebpageHdr[] =
@@ -209,30 +205,77 @@
209205
@ <!DOCTYPE html>
210206
@ <html>
211207
@ <head>
212208
@ <meta charset="UTF-8">
213209
@ <style>
214
-@ %sspan.diffchng {
215
-@ background-color: #c0c0ff;
216
-@ }
217
-@ span.diffadd {
218
-@ background-color: #c0ffc0;
219
-@ }
220
-@ span.diffrm {
221
-@ background-color: #ffc8c8;
222
-@ }
223
-@ span.diffhr {
224
-@ display: inline-block;
225
-@ margin: .5em 0 1em;
226
-@ color: #0000ff;
227
-@ }
228
-@ span.diffln {
229
-@ color: #a0a0a0;
230
-@ }
231210
@ h1 {
232
-@ font-size: 150%%;
211
+@ font-size: 150%;
212
+@ }
213
+@
214
+@ table.diff {
215
+@ width: 98%;
216
+@ border-spacing: 0;
217
+@ border: 1px solid black;
218
+@ }
219
+@ table.diff td {
220
+@ vertical-align: top;
221
+@ }
222
+@ table.diff pre {
223
+@ margin: 0 0 0 0;
224
+@ }
225
+@ td.diffln {
226
+@ width: 1px;
227
+@ text-align: right;
228
+@ padding: 0 1em 0 0;
229
+@ }
230
+@ td.difflne {
231
+@ padding-bottom: 0.4em;
232
+@ }
233
+@ td.diffsep {
234
+@ width: 1px;
235
+@ padding: 0 0.3em 0 1em;
236
+@ }
237
+@ td.difftxt pre {
238
+@ overflow-x: auto;
239
+@ }
240
+@ td.diffln ins {
241
+@ background-color: #a0e4b2;
242
+@ text-decoration: none;
243
+@ }
244
+@ td.diffln del {
245
+@ background-color: #ffc0c0;
246
+@ text-decoration: none;
247
+@ }
248
+@ td.difftxt del {
249
+@ background-color: #ffe8e8;
250
+@ text-decoration: none;
251
+@ }
252
+@ td.difftxt del > del {
253
+@ background-color: #ffc0c0;
254
+@ text-decoration: none;
255
+@ font-weight: bold;
256
+@ }
257
+@ td.difftxt del > del.edit {
258
+@ background-color: #c0c0ff;
259
+@ text-decoration: none;
260
+@ font-weight: bold;
261
+@ }
262
+@ td.difftxt ins {
263
+@ background-color: #dafbe1;
264
+@ text-decoration: none;
265
+@ }
266
+@ td.difftxt ins > ins {
267
+@ background-color: #a0e4b2;
268
+@ text-decoration: none;
269
+@ font-weight: bold;
270
+@ }
271
+@ td.difftxt ins > ins.edit {
272
+@ background-color: #c0c0ff;
273
+@ text-decoration: none;
274
+@ font-weight: bold;
233275
@ }
276
+@
234277
@ </style>
235278
@ </head>
236279
@ <body>
237280
;
238281
const char zWebpageEnd[] =
@@ -294,17 +337,11 @@
294337
#else
295338
SetConsoleCtrlHandler(diff_console_ctrl_handler, TRUE);
296339
#endif
297340
}
298341
if( (diffFlags & DIFF_WEBPAGE)!=0 ){
299
- const char *zExtra;
300
- if( diffFlags & DIFF_SIDEBYSIDE ){
301
- zExtra = zSbsCss;
302
- }else{
303
- zExtra = "";
304
- }
305
- fossil_print(zWebpageHdr/*works-like:"%s"*/, zExtra);
342
+ fossil_print("%s",zWebpageHdr);
306343
fflush(stdout);
307344
}
308345
}
309346
310347
/* Do any final output required by a diff and complete the diff
@@ -318,11 +355,11 @@
318355
** of FOSSIL_BROWSER_DIFF_DELAY milliseconds, delete the temp file.
319356
*/
320357
void diff_end(u64 diffFlags, int nErr){
321358
if( (diffFlags & DIFF_WEBPAGE)!=0 ){
322359
if( diffFlags & DIFF_SIDEBYSIDE ){
323
- const unsigned char *zJs = builtin_file("sbsdiff.js", 0);
360
+ const unsigned char *zJs = builtin_file("diff.js", 0);
324361
fossil_print("<script>\n%s</script>\n", zJs);
325362
}
326363
fossil_print("%s", zWebpageEnd);
327364
}
328365
if( (diffFlags & DIFF_BROWSER)!=0 && nErr==0 ){
@@ -582,12 +619,12 @@
582619
Blob sql;
583620
Stmt q;
584621
int asNewFile; /* Treat non-existant files as empty files */
585622
int isNumStat; /* True for --numstat */
586623
587
- asNewFile = (diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT))!=0;
588
- isNumStat = (diffFlags & DIFF_NUMSTAT)!=0;
624
+ asNewFile = (diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT|DIFF_HTML))!=0;
625
+ isNumStat = (diffFlags & (DIFF_NUMSTAT|DIFF_TCL|DIFF_HTML))!=0;
589626
vid = db_lget_int("checkout", 0);
590627
vfile_check_signature(vid, CKSIG_ENOTFILE);
591628
blob_zero(&sql);
592629
db_begin_transaction();
593630
if( zFrom ){
@@ -819,11 +856,11 @@
819856
}else{
820857
cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
821858
}
822859
if( cmp<0 ){
823860
if( file_dir_match(pFileDir, pFromFile->zName) ){
824
- if( (diffFlags & DIFF_NUMSTAT)==0 ){
861
+ if( (diffFlags & (DIFF_NUMSTAT|DIFF_HTML))==0 ){
825862
fossil_print("DELETED %s\n", pFromFile->zName);
826863
}
827864
if( asNewFlag ){
828865
diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
829866
fIncludeBinary, diffFlags);
@@ -830,11 +867,11 @@
830867
}
831868
}
832869
pFromFile = manifest_file_next(pFrom,0);
833870
}else if( cmp>0 ){
834871
if( file_dir_match(pFileDir, pToFile->zName) ){
835
- if( (diffFlags & DIFF_NUMSTAT)==0 ){
872
+ if( (diffFlags & (DIFF_NUMSTAT|DIFF_HTML|DIFF_TCL|DIFF_JSON))==0 ){
836873
fossil_print("ADDED %s\n", pToFile->zName);
837874
}
838875
if( asNewFlag ){
839876
diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
840877
fIncludeBinary, diffFlags);
@@ -901,12 +938,13 @@
901938
Blob script;
902939
const char *zTempFile = 0;
903940
char *zCmd;
904941
const char *zTclsh;
905942
blob_zero(&script);
906
- blob_appendf(&script, "set fossilcmd {| \"%/\" %s --html -y -i -v",
943
+ blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -i -v",
907944
g.nameOfExe, zSubCmd);
945
+ find_option("tcl",0,0);
908946
find_option("html",0,0);
909947
find_option("side-by-side","y",0);
910948
find_option("internal","i",0);
911949
find_option("verbose","v",0);
912950
zTclsh = find_option("tclsh",0,1);
913951
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -114,11 +114,12 @@
114
115 /*
116 ** Print the "Index:" message that patches wants to see at the top of a diff.
117 */
118 void diff_print_index(const char *zFile, u64 diffFlags, Blob *diffBlob){
119 if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_WEBPAGE))==0 ){
 
120 char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
121 if( !diffBlob ){
122 fossil_print("%s", z);
123 }else{
124 blob_appendf(diffBlob, "%s", z);
@@ -128,21 +129,47 @@
128 }
129
130 /*
131 ** Print the +++/--- filename lines for a diff operation.
132 */
133 void diff_print_filenames(const char *zLeft, const char *zRight,
134 u64 diffFlags, Blob *diffBlob){
 
 
 
 
135 char *z = 0;
136 if( diffFlags & DIFF_BRIEF ){
137 /* no-op */
 
 
 
138 }else if( diffFlags & DIFF_WEBPAGE ){
139 if( fossil_strcmp(zLeft,zRight)==0 ){
140 z = mprintf("<h1>%h</h1>\n", zLeft);
141 }else{
142 z = mprintf("<h1>%h &lrarr; %h</h1>\n", zLeft, zRight);
143 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144 }else if( diffFlags & DIFF_SIDEBYSIDE ){
145 int w = diff_width(diffFlags);
146 int n1 = strlen(zLeft);
147 int n2 = strlen(zRight);
148 int x;
@@ -168,41 +195,10 @@
168 blob_appendf(diffBlob, "%s", z);
169 }
170 fossil_free(z);
171 }
172
173 /*
174 ** Extra CSS for side-by-side diffs
175 */
176 static const char zSbsCss[] =
177 @ table.sbsdiffcols {
178 @ width: 90%;
179 @ border-spacing: 0;
180 @ font-size: small;
181 @ }
182 @ table.sbsdiffcols td {
183 @ padding: 0;
184 @ vertical-align: top;
185 @ }
186 @ table.sbsdiffcols pre {
187 @ margin: 0;
188 @ padding: 0;
189 @ border: 0;
190 @ }
191 @ div.difflncol {
192 @ padding-right: 1em;
193 @ text-align: right;
194 @ color: #a0a0a0;
195 @ }
196 @ div.difftxtcol {
197 @ width: 10em;
198 @ overflow-x: auto;
199 @ }
200 @ div.diffmkrcol {
201 @ padding: 0 1em;
202 @ }
203 ;
204
205 /*
206 ** Default header text for diff with --webpage
207 */
208 static const char zWebpageHdr[] =
@@ -209,30 +205,77 @@
209 @ <!DOCTYPE html>
210 @ <html>
211 @ <head>
212 @ <meta charset="UTF-8">
213 @ <style>
214 @ %sspan.diffchng {
215 @ background-color: #c0c0ff;
216 @ }
217 @ span.diffadd {
218 @ background-color: #c0ffc0;
219 @ }
220 @ span.diffrm {
221 @ background-color: #ffc8c8;
222 @ }
223 @ span.diffhr {
224 @ display: inline-block;
225 @ margin: .5em 0 1em;
226 @ color: #0000ff;
227 @ }
228 @ span.diffln {
229 @ color: #a0a0a0;
230 @ }
231 @ h1 {
232 @ font-size: 150%%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233 @ }
 
234 @ </style>
235 @ </head>
236 @ <body>
237 ;
238 const char zWebpageEnd[] =
@@ -294,17 +337,11 @@
294 #else
295 SetConsoleCtrlHandler(diff_console_ctrl_handler, TRUE);
296 #endif
297 }
298 if( (diffFlags & DIFF_WEBPAGE)!=0 ){
299 const char *zExtra;
300 if( diffFlags & DIFF_SIDEBYSIDE ){
301 zExtra = zSbsCss;
302 }else{
303 zExtra = "";
304 }
305 fossil_print(zWebpageHdr/*works-like:"%s"*/, zExtra);
306 fflush(stdout);
307 }
308 }
309
310 /* Do any final output required by a diff and complete the diff
@@ -318,11 +355,11 @@
318 ** of FOSSIL_BROWSER_DIFF_DELAY milliseconds, delete the temp file.
319 */
320 void diff_end(u64 diffFlags, int nErr){
321 if( (diffFlags & DIFF_WEBPAGE)!=0 ){
322 if( diffFlags & DIFF_SIDEBYSIDE ){
323 const unsigned char *zJs = builtin_file("sbsdiff.js", 0);
324 fossil_print("<script>\n%s</script>\n", zJs);
325 }
326 fossil_print("%s", zWebpageEnd);
327 }
328 if( (diffFlags & DIFF_BROWSER)!=0 && nErr==0 ){
@@ -582,12 +619,12 @@
582 Blob sql;
583 Stmt q;
584 int asNewFile; /* Treat non-existant files as empty files */
585 int isNumStat; /* True for --numstat */
586
587 asNewFile = (diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT))!=0;
588 isNumStat = (diffFlags & DIFF_NUMSTAT)!=0;
589 vid = db_lget_int("checkout", 0);
590 vfile_check_signature(vid, CKSIG_ENOTFILE);
591 blob_zero(&sql);
592 db_begin_transaction();
593 if( zFrom ){
@@ -819,11 +856,11 @@
819 }else{
820 cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
821 }
822 if( cmp<0 ){
823 if( file_dir_match(pFileDir, pFromFile->zName) ){
824 if( (diffFlags & DIFF_NUMSTAT)==0 ){
825 fossil_print("DELETED %s\n", pFromFile->zName);
826 }
827 if( asNewFlag ){
828 diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
829 fIncludeBinary, diffFlags);
@@ -830,11 +867,11 @@
830 }
831 }
832 pFromFile = manifest_file_next(pFrom,0);
833 }else if( cmp>0 ){
834 if( file_dir_match(pFileDir, pToFile->zName) ){
835 if( (diffFlags & DIFF_NUMSTAT)==0 ){
836 fossil_print("ADDED %s\n", pToFile->zName);
837 }
838 if( asNewFlag ){
839 diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
840 fIncludeBinary, diffFlags);
@@ -901,12 +938,13 @@
901 Blob script;
902 const char *zTempFile = 0;
903 char *zCmd;
904 const char *zTclsh;
905 blob_zero(&script);
906 blob_appendf(&script, "set fossilcmd {| \"%/\" %s --html -y -i -v",
907 g.nameOfExe, zSubCmd);
 
908 find_option("html",0,0);
909 find_option("side-by-side","y",0);
910 find_option("internal","i",0);
911 find_option("verbose","v",0);
912 zTclsh = find_option("tclsh",0,1);
913
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -114,11 +114,12 @@
114
115 /*
116 ** Print the "Index:" message that patches wants to see at the top of a diff.
117 */
118 void diff_print_index(const char *zFile, u64 diffFlags, Blob *diffBlob){
119 if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_JSON|
120 DIFF_WEBPAGE|DIFF_TCL))==0 ){
121 char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
122 if( !diffBlob ){
123 fossil_print("%s", z);
124 }else{
125 blob_appendf(diffBlob, "%s", z);
@@ -128,21 +129,47 @@
129 }
130
131 /*
132 ** Print the +++/--- filename lines for a diff operation.
133 */
134 void diff_print_filenames(
135 const char *zLeft,
136 const char *zRight,
137 u64 diffFlags,
138 Blob *diffBlob
139 ){
140 char *z = 0;
141 if( diffFlags & (DIFF_BRIEF|DIFF_RAW|DIFF_JSON) ){
142 /* no-op */
143 }else if( diffFlags & DIFF_DEBUG ){
144 fossil_print("FILE-LEFT %s\nFILE-RIGHT %s\n",
145 zLeft, zRight);
146 }else if( diffFlags & DIFF_WEBPAGE ){
147 if( fossil_strcmp(zLeft,zRight)==0 ){
148 z = mprintf("<h1>%h</h1>\n", zLeft);
149 }else{
150 z = mprintf("<h1>%h &lrarr; %h</h1>\n", zLeft, zRight);
151 }
152 }else if( diffFlags & DIFF_TCL ){
153 Blob *pOut;
154 Blob x;
155 if( diffBlob ){
156 pOut = diffBlob;
157 }else{
158 blob_init(&x, 0, 0);
159 pOut = &x;
160 }
161 blob_append(pOut, "FILE ", 5);
162 blob_append_tcl_literal(pOut, zLeft, (int)strlen(zLeft));
163 blob_append_char(pOut, ' ');
164 blob_append_tcl_literal(pOut, zRight, (int)strlen(zRight));
165 blob_append_char(pOut, '\n');
166 if( !diffBlob ){
167 fossil_print("%s", blob_str(pOut));
168 blob_reset(&x);
169 }
170 return;
171 }else if( diffFlags & DIFF_SIDEBYSIDE ){
172 int w = diff_width(diffFlags);
173 int n1 = strlen(zLeft);
174 int n2 = strlen(zRight);
175 int x;
@@ -168,41 +195,10 @@
195 blob_appendf(diffBlob, "%s", z);
196 }
197 fossil_free(z);
198 }
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
201 /*
202 ** Default header text for diff with --webpage
203 */
204 static const char zWebpageHdr[] =
@@ -209,30 +205,77 @@
205 @ <!DOCTYPE html>
206 @ <html>
207 @ <head>
208 @ <meta charset="UTF-8">
209 @ <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210 @ h1 {
211 @ font-size: 150%;
212 @ }
213 @
214 @ table.diff {
215 @ width: 98%;
216 @ border-spacing: 0;
217 @ border: 1px solid black;
218 @ }
219 @ table.diff td {
220 @ vertical-align: top;
221 @ }
222 @ table.diff pre {
223 @ margin: 0 0 0 0;
224 @ }
225 @ td.diffln {
226 @ width: 1px;
227 @ text-align: right;
228 @ padding: 0 1em 0 0;
229 @ }
230 @ td.difflne {
231 @ padding-bottom: 0.4em;
232 @ }
233 @ td.diffsep {
234 @ width: 1px;
235 @ padding: 0 0.3em 0 1em;
236 @ }
237 @ td.difftxt pre {
238 @ overflow-x: auto;
239 @ }
240 @ td.diffln ins {
241 @ background-color: #a0e4b2;
242 @ text-decoration: none;
243 @ }
244 @ td.diffln del {
245 @ background-color: #ffc0c0;
246 @ text-decoration: none;
247 @ }
248 @ td.difftxt del {
249 @ background-color: #ffe8e8;
250 @ text-decoration: none;
251 @ }
252 @ td.difftxt del > del {
253 @ background-color: #ffc0c0;
254 @ text-decoration: none;
255 @ font-weight: bold;
256 @ }
257 @ td.difftxt del > del.edit {
258 @ background-color: #c0c0ff;
259 @ text-decoration: none;
260 @ font-weight: bold;
261 @ }
262 @ td.difftxt ins {
263 @ background-color: #dafbe1;
264 @ text-decoration: none;
265 @ }
266 @ td.difftxt ins > ins {
267 @ background-color: #a0e4b2;
268 @ text-decoration: none;
269 @ font-weight: bold;
270 @ }
271 @ td.difftxt ins > ins.edit {
272 @ background-color: #c0c0ff;
273 @ text-decoration: none;
274 @ font-weight: bold;
275 @ }
276 @
277 @ </style>
278 @ </head>
279 @ <body>
280 ;
281 const char zWebpageEnd[] =
@@ -294,17 +337,11 @@
337 #else
338 SetConsoleCtrlHandler(diff_console_ctrl_handler, TRUE);
339 #endif
340 }
341 if( (diffFlags & DIFF_WEBPAGE)!=0 ){
342 fossil_print("%s",zWebpageHdr);
 
 
 
 
 
 
343 fflush(stdout);
344 }
345 }
346
347 /* Do any final output required by a diff and complete the diff
@@ -318,11 +355,11 @@
355 ** of FOSSIL_BROWSER_DIFF_DELAY milliseconds, delete the temp file.
356 */
357 void diff_end(u64 diffFlags, int nErr){
358 if( (diffFlags & DIFF_WEBPAGE)!=0 ){
359 if( diffFlags & DIFF_SIDEBYSIDE ){
360 const unsigned char *zJs = builtin_file("diff.js", 0);
361 fossil_print("<script>\n%s</script>\n", zJs);
362 }
363 fossil_print("%s", zWebpageEnd);
364 }
365 if( (diffFlags & DIFF_BROWSER)!=0 && nErr==0 ){
@@ -582,12 +619,12 @@
619 Blob sql;
620 Stmt q;
621 int asNewFile; /* Treat non-existant files as empty files */
622 int isNumStat; /* True for --numstat */
623
624 asNewFile = (diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT|DIFF_HTML))!=0;
625 isNumStat = (diffFlags & (DIFF_NUMSTAT|DIFF_TCL|DIFF_HTML))!=0;
626 vid = db_lget_int("checkout", 0);
627 vfile_check_signature(vid, CKSIG_ENOTFILE);
628 blob_zero(&sql);
629 db_begin_transaction();
630 if( zFrom ){
@@ -819,11 +856,11 @@
856 }else{
857 cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
858 }
859 if( cmp<0 ){
860 if( file_dir_match(pFileDir, pFromFile->zName) ){
861 if( (diffFlags & (DIFF_NUMSTAT|DIFF_HTML))==0 ){
862 fossil_print("DELETED %s\n", pFromFile->zName);
863 }
864 if( asNewFlag ){
865 diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
866 fIncludeBinary, diffFlags);
@@ -830,11 +867,11 @@
867 }
868 }
869 pFromFile = manifest_file_next(pFrom,0);
870 }else if( cmp>0 ){
871 if( file_dir_match(pFileDir, pToFile->zName) ){
872 if( (diffFlags & (DIFF_NUMSTAT|DIFF_HTML|DIFF_TCL|DIFF_JSON))==0 ){
873 fossil_print("ADDED %s\n", pToFile->zName);
874 }
875 if( asNewFlag ){
876 diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
877 fIncludeBinary, diffFlags);
@@ -901,12 +938,13 @@
938 Blob script;
939 const char *zTempFile = 0;
940 char *zCmd;
941 const char *zTclsh;
942 blob_zero(&script);
943 blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -i -v",
944 g.nameOfExe, zSubCmd);
945 find_option("tcl",0,0);
946 find_option("html",0,0);
947 find_option("side-by-side","y",0);
948 find_option("internal","i",0);
949 find_option("verbose","v",0);
950 zTclsh = find_option("tclsh",0,1);
951
+1 -1
--- src/fileedit.c
+++ src/fileedit.c
@@ -1999,11 +1999,11 @@
19991999
** fossil.page.fileedit.js. Potential TODO: move this into the
20002000
** window.fossil bootstrapping so that we don't have to "fulfill"
20012001
** the JS multiple times.
20022002
*/
20032003
ajax_emit_js_preview_modes(1);
2004
- builtin_request_js("sbsdiff.js");
2004
+ builtin_request_js("diff.js");
20052005
builtin_request_js("fossil.page.fileedit.js");
20062006
builtin_fulfill_js_requests();
20072007
{
20082008
/* Dynamically populate the editor, display any error in the err
20092009
** blob, and/or switch to tab #0, where the file selector
20102010
--- src/fileedit.c
+++ src/fileedit.c
@@ -1999,11 +1999,11 @@
1999 ** fossil.page.fileedit.js. Potential TODO: move this into the
2000 ** window.fossil bootstrapping so that we don't have to "fulfill"
2001 ** the JS multiple times.
2002 */
2003 ajax_emit_js_preview_modes(1);
2004 builtin_request_js("sbsdiff.js");
2005 builtin_request_js("fossil.page.fileedit.js");
2006 builtin_fulfill_js_requests();
2007 {
2008 /* Dynamically populate the editor, display any error in the err
2009 ** blob, and/or switch to tab #0, where the file selector
2010
--- src/fileedit.c
+++ src/fileedit.c
@@ -1999,11 +1999,11 @@
1999 ** fossil.page.fileedit.js. Potential TODO: move this into the
2000 ** window.fossil bootstrapping so that we don't have to "fulfill"
2001 ** the JS multiple times.
2002 */
2003 ajax_emit_js_preview_modes(1);
2004 builtin_request_js("diff.js");
2005 builtin_request_js("fossil.page.fileedit.js");
2006 builtin_fulfill_js_requests();
2007 {
2008 /* Dynamically populate the editor, display any error in the err
2009 ** blob, and/or switch to tab #0, where the file selector
2010
+1 -1
--- src/info.c
+++ src/info.c
@@ -441,11 +441,11 @@
441441
/*
442442
** Generate javascript to enhance HTML diffs.
443443
*/
444444
void append_diff_javascript(int sideBySide){
445445
if( !sideBySide ) return;
446
- builtin_request_js("sbsdiff.js");
446
+ builtin_request_js("diff.js");
447447
}
448448
449449
/*
450450
** Construct an appropriate diffFlag for text_diff() based on query
451451
** parameters and the to boolean arguments.
452452
--- src/info.c
+++ src/info.c
@@ -441,11 +441,11 @@
441 /*
442 ** Generate javascript to enhance HTML diffs.
443 */
444 void append_diff_javascript(int sideBySide){
445 if( !sideBySide ) return;
446 builtin_request_js("sbsdiff.js");
447 }
448
449 /*
450 ** Construct an appropriate diffFlag for text_diff() based on query
451 ** parameters and the to boolean arguments.
452
--- src/info.c
+++ src/info.c
@@ -441,11 +441,11 @@
441 /*
442 ** Generate javascript to enhance HTML diffs.
443 */
444 void append_diff_javascript(int sideBySide){
445 if( !sideBySide ) return;
446 builtin_request_js("diff.js");
447 }
448
449 /*
450 ** Construct an appropriate diffFlag for text_diff() based on query
451 ** parameters and the to boolean arguments.
452
+1 -1
--- src/main.mk
+++ src/main.mk
@@ -218,10 +218,11 @@
218218
$(SRCDIR)/alerts/plunk.wav \
219219
$(SRCDIR)/chat.js \
220220
$(SRCDIR)/ci_edit.js \
221221
$(SRCDIR)/copybtn.js \
222222
$(SRCDIR)/default.css \
223
+ $(SRCDIR)/diff.js \
223224
$(SRCDIR)/diff.tcl \
224225
$(SRCDIR)/forum.js \
225226
$(SRCDIR)/fossil.bootstrap.js \
226227
$(SRCDIR)/fossil.confirmer.js \
227228
$(SRCDIR)/fossil.copybutton.js \
@@ -244,11 +245,10 @@
244245
$(SRCDIR)/hbmenu.js \
245246
$(SRCDIR)/href.js \
246247
$(SRCDIR)/login.js \
247248
$(SRCDIR)/markdown.md \
248249
$(SRCDIR)/menu.js \
249
- $(SRCDIR)/sbsdiff.js \
250250
$(SRCDIR)/scroll.js \
251251
$(SRCDIR)/skin.js \
252252
$(SRCDIR)/sorttable.js \
253253
$(SRCDIR)/sounds/0.wav \
254254
$(SRCDIR)/sounds/1.wav \
255255
256256
DELETED src/sbsdiff.js
--- src/main.mk
+++ src/main.mk
@@ -218,10 +218,11 @@
218 $(SRCDIR)/alerts/plunk.wav \
219 $(SRCDIR)/chat.js \
220 $(SRCDIR)/ci_edit.js \
221 $(SRCDIR)/copybtn.js \
222 $(SRCDIR)/default.css \
 
223 $(SRCDIR)/diff.tcl \
224 $(SRCDIR)/forum.js \
225 $(SRCDIR)/fossil.bootstrap.js \
226 $(SRCDIR)/fossil.confirmer.js \
227 $(SRCDIR)/fossil.copybutton.js \
@@ -244,11 +245,10 @@
244 $(SRCDIR)/hbmenu.js \
245 $(SRCDIR)/href.js \
246 $(SRCDIR)/login.js \
247 $(SRCDIR)/markdown.md \
248 $(SRCDIR)/menu.js \
249 $(SRCDIR)/sbsdiff.js \
250 $(SRCDIR)/scroll.js \
251 $(SRCDIR)/skin.js \
252 $(SRCDIR)/sorttable.js \
253 $(SRCDIR)/sounds/0.wav \
254 $(SRCDIR)/sounds/1.wav \
255
256 ELETED src/sbsdiff.js
--- src/main.mk
+++ src/main.mk
@@ -218,10 +218,11 @@
218 $(SRCDIR)/alerts/plunk.wav \
219 $(SRCDIR)/chat.js \
220 $(SRCDIR)/ci_edit.js \
221 $(SRCDIR)/copybtn.js \
222 $(SRCDIR)/default.css \
223 $(SRCDIR)/diff.js \
224 $(SRCDIR)/diff.tcl \
225 $(SRCDIR)/forum.js \
226 $(SRCDIR)/fossil.bootstrap.js \
227 $(SRCDIR)/fossil.confirmer.js \
228 $(SRCDIR)/fossil.copybutton.js \
@@ -244,11 +245,10 @@
245 $(SRCDIR)/hbmenu.js \
246 $(SRCDIR)/href.js \
247 $(SRCDIR)/login.js \
248 $(SRCDIR)/markdown.md \
249 $(SRCDIR)/menu.js \
 
250 $(SRCDIR)/scroll.js \
251 $(SRCDIR)/skin.js \
252 $(SRCDIR)/sorttable.js \
253 $(SRCDIR)/sounds/0.wav \
254 $(SRCDIR)/sounds/1.wav \
255
256 ELETED src/sbsdiff.js
D src/sbsdiff.js
-63
--- a/src/sbsdiff.js
+++ b/src/sbsdiff.js
@@ -1,63 +0,0 @@
1
-/* The javascript in this file was added by Joel Bruick on 2013-07-06,
2
-** originally as in-line javascript. It keeps the horizontal scrollbars
3
-** in sync on side-by-side diffs.
4
-*/
5
-(function(){
6
- var SCROLL_LEN = 25;
7
- function initSbsDiff(diff){
8
- var txtCols = diff.querySelectorAll('.difftxtcol');
9
- var txtPres = diff.querySelectorAll('.difftxtcol pre');
10
- var width = Math.max(txtPres[0].scrollWidth, txtPres[1].scrollWidth);
11
- var i;
12
- for(i=0; i<2; i++){
13
- txtPres[i].style.width = width + 'px';
14
- txtCols[i].onscroll = function(e){
15
- txtCols[0].scrollLeft = txtCols[1].scrollLeft = this.scrollLeft;
16
- };
17
- }
18
- diff.tabIndex = 0;
19
- diff.onkeydown = function(e){
20
- e = e || event;
21
- var len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
22
- if( !len ) return;
23
- txtCols[0].scrollLeft += len;
24
- return false;
25
- };
26
- }
27
- var i, diffs = document.querySelectorAll('.sbsdiffcols')
28
- /* Maintenance reminder: using forEach() here breaks
29
- MSIE<=11, and we need to keep those browsers working on
30
- the /info page. */;
31
- for(i=0; i<diffs.length; i++){
32
- initSbsDiff(diffs[i]);
33
- }
34
- if(window.fossil && fossil.page){
35
- /* Here we can use forEach() because the pages which use
36
- fossil.pages only work in HTML5-compliant browsers. */
37
- fossil.page.tweakSbsDiffs = function(){
38
- document.querySelectorAll('.sbsdiffcols').forEach(initSbsDiff);
39
- };
40
- }
41
- /* This part added by DRH on 2021-08-25 to adjust the width of the
42
- ** diff columns so that they fill the screen. */
43
- var lastWidth = 0;
44
- function checkWidth(){
45
- if( document.body.clientWidth!=lastWidth ){
46
- lastWidth = document.body.clientWidth;
47
- var w = lastWidth*0.5 - 100;
48
- var allCols = document.getElementsByClassName('difftxtcol');
49
- for(let i=0; i<allCols.length; i++){
50
- allCols[i].style.width = w + "px";
51
- allCols[i].style.maxWidth = w + "px";
52
- }
53
- var allDiffs = document.getElementsByClassName('sbsdiffcols');
54
- w = lastWidth;
55
- for(let i=0; i<allDiffs.length; i++){
56
- allDiffs[i].style.width = '98%'; // setting to w causes unsightly horiz. scrollbar
57
- allDiffs[i].style.maxWidth = w + "px";
58
- }
59
- }
60
- setTimeout(checkWidth, 100)
61
- }
62
- checkWidth();
63
-})();
--- a/src/sbsdiff.js
+++ b/src/sbsdiff.js
@@ -1,63 +0,0 @@
1 /* The javascript in this file was added by Joel Bruick on 2013-07-06,
2 ** originally as in-line javascript. It keeps the horizontal scrollbars
3 ** in sync on side-by-side diffs.
4 */
5 (function(){
6 var SCROLL_LEN = 25;
7 function initSbsDiff(diff){
8 var txtCols = diff.querySelectorAll('.difftxtcol');
9 var txtPres = diff.querySelectorAll('.difftxtcol pre');
10 var width = Math.max(txtPres[0].scrollWidth, txtPres[1].scrollWidth);
11 var i;
12 for(i=0; i<2; i++){
13 txtPres[i].style.width = width + 'px';
14 txtCols[i].onscroll = function(e){
15 txtCols[0].scrollLeft = txtCols[1].scrollLeft = this.scrollLeft;
16 };
17 }
18 diff.tabIndex = 0;
19 diff.onkeydown = function(e){
20 e = e || event;
21 var len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
22 if( !len ) return;
23 txtCols[0].scrollLeft += len;
24 return false;
25 };
26 }
27 var i, diffs = document.querySelectorAll('.sbsdiffcols')
28 /* Maintenance reminder: using forEach() here breaks
29 MSIE<=11, and we need to keep those browsers working on
30 the /info page. */;
31 for(i=0; i<diffs.length; i++){
32 initSbsDiff(diffs[i]);
33 }
34 if(window.fossil && fossil.page){
35 /* Here we can use forEach() because the pages which use
36 fossil.pages only work in HTML5-compliant browsers. */
37 fossil.page.tweakSbsDiffs = function(){
38 document.querySelectorAll('.sbsdiffcols').forEach(initSbsDiff);
39 };
40 }
41 /* This part added by DRH on 2021-08-25 to adjust the width of the
42 ** diff columns so that they fill the screen. */
43 var lastWidth = 0;
44 function checkWidth(){
45 if( document.body.clientWidth!=lastWidth ){
46 lastWidth = document.body.clientWidth;
47 var w = lastWidth*0.5 - 100;
48 var allCols = document.getElementsByClassName('difftxtcol');
49 for(let i=0; i<allCols.length; i++){
50 allCols[i].style.width = w + "px";
51 allCols[i].style.maxWidth = w + "px";
52 }
53 var allDiffs = document.getElementsByClassName('sbsdiffcols');
54 w = lastWidth;
55 for(let i=0; i<allDiffs.length; i++){
56 allDiffs[i].style.width = '98%'; // setting to w causes unsightly horiz. scrollbar
57 allDiffs[i].style.maxWidth = w + "px";
58 }
59 }
60 setTimeout(checkWidth, 100)
61 }
62 checkWidth();
63 })();
--- a/src/sbsdiff.js
+++ b/src/sbsdiff.js
@@ -1,63 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
+1 -1
--- src/wiki.c
+++ src/wiki.c
@@ -1454,11 +1454,11 @@
14541454
CX("</div>"/*#wikiedit-tab-save*/);
14551455
}
14561456
builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer",
14571457
"storage", "popupwidget", "copybutton",
14581458
"pikchr", NULL);
1459
- builtin_request_js("sbsdiff.js");
1459
+ builtin_request_js("diff.js");
14601460
builtin_request_js("fossil.page.wikiedit.js");
14611461
builtin_fulfill_js_requests();
14621462
/* Dynamically populate the editor... */
14631463
style_script_begin(__FILE__,__LINE__);
14641464
{
14651465
--- src/wiki.c
+++ src/wiki.c
@@ -1454,11 +1454,11 @@
1454 CX("</div>"/*#wikiedit-tab-save*/);
1455 }
1456 builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer",
1457 "storage", "popupwidget", "copybutton",
1458 "pikchr", NULL);
1459 builtin_request_js("sbsdiff.js");
1460 builtin_request_js("fossil.page.wikiedit.js");
1461 builtin_fulfill_js_requests();
1462 /* Dynamically populate the editor... */
1463 style_script_begin(__FILE__,__LINE__);
1464 {
1465
--- src/wiki.c
+++ src/wiki.c
@@ -1454,11 +1454,11 @@
1454 CX("</div>"/*#wikiedit-tab-save*/);
1455 }
1456 builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer",
1457 "storage", "popupwidget", "copybutton",
1458 "pikchr", NULL);
1459 builtin_request_js("diff.js");
1460 builtin_request_js("fossil.page.wikiedit.js");
1461 builtin_fulfill_js_requests();
1462 /* Dynamically populate the editor... */
1463 style_script_begin(__FILE__,__LINE__);
1464 {
1465
--- test/diff-test-1.wiki
+++ test/diff-test-1.wiki
@@ -20,11 +20,14 @@
2020
The edit of a line with multibyte characters is the first chunk.
2121
* <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"
2222
target="testwindow">Large diff of sqlite3.c</a>. This diff was very
2323
slow prior to the performance enhancement change [9e15437e97].
2424
* <a href="../../../info/bda00cbada#chunk49" target="testwindow">
25
- A difficult indentation change.</a>
25
+ A difficult indentation change.</a> UPDATE: Notice also the improved
26
+ multi-segment update marks on lines 122648 and 122763 on the new side.
27
+ * <a href="../../../fdiff?v1=bc8100c9ee01b8c2&v2=1d2acc1a2a65c2bf#chunk42"
28
+ target="testwindow">Inverse of the previous.</a>
2629
* <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"
2730
target="testwindow">Another tricky indentation.</a> Notice especially
2831
lines 59398 and 59407 on the left.
2932
* <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"
3033
target="testwindow">Inverse of the previous.</a>
3134
--- test/diff-test-1.wiki
+++ test/diff-test-1.wiki
@@ -20,11 +20,14 @@
20 The edit of a line with multibyte characters is the first chunk.
21 * <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"
22 target="testwindow">Large diff of sqlite3.c</a>. This diff was very
23 slow prior to the performance enhancement change [9e15437e97].
24 * <a href="../../../info/bda00cbada#chunk49" target="testwindow">
25 A difficult indentation change.</a>
 
 
 
26 * <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"
27 target="testwindow">Another tricky indentation.</a> Notice especially
28 lines 59398 and 59407 on the left.
29 * <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"
30 target="testwindow">Inverse of the previous.</a>
31
--- test/diff-test-1.wiki
+++ test/diff-test-1.wiki
@@ -20,11 +20,14 @@
20 The edit of a line with multibyte characters is the first chunk.
21 * <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"
22 target="testwindow">Large diff of sqlite3.c</a>. This diff was very
23 slow prior to the performance enhancement change [9e15437e97].
24 * <a href="../../../info/bda00cbada#chunk49" target="testwindow">
25 A difficult indentation change.</a> UPDATE: Notice also the improved
26 multi-segment update marks on lines 122648 and 122763 on the new side.
27 * <a href="../../../fdiff?v1=bc8100c9ee01b8c2&v2=1d2acc1a2a65c2bf#chunk42"
28 target="testwindow">Inverse of the previous.</a>
29 * <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"
30 target="testwindow">Another tricky indentation.</a> Notice especially
31 lines 59398 and 59407 on the left.
32 * <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"
33 target="testwindow">Inverse of the previous.</a>
34
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -627,10 +627,11 @@
627627
$(SRCDIR)/alerts/plunk.wav \
628628
$(SRCDIR)/chat.js \
629629
$(SRCDIR)/ci_edit.js \
630630
$(SRCDIR)/copybtn.js \
631631
$(SRCDIR)/default.css \
632
+ $(SRCDIR)/diff.js \
632633
$(SRCDIR)/diff.tcl \
633634
$(SRCDIR)/forum.js \
634635
$(SRCDIR)/fossil.bootstrap.js \
635636
$(SRCDIR)/fossil.confirmer.js \
636637
$(SRCDIR)/fossil.copybutton.js \
@@ -653,11 +654,10 @@
653654
$(SRCDIR)/hbmenu.js \
654655
$(SRCDIR)/href.js \
655656
$(SRCDIR)/login.js \
656657
$(SRCDIR)/markdown.md \
657658
$(SRCDIR)/menu.js \
658
- $(SRCDIR)/sbsdiff.js \
659659
$(SRCDIR)/scroll.js \
660660
$(SRCDIR)/skin.js \
661661
$(SRCDIR)/sorttable.js \
662662
$(SRCDIR)/sounds/0.wav \
663663
$(SRCDIR)/sounds/1.wav \
664664
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -627,10 +627,11 @@
627 $(SRCDIR)/alerts/plunk.wav \
628 $(SRCDIR)/chat.js \
629 $(SRCDIR)/ci_edit.js \
630 $(SRCDIR)/copybtn.js \
631 $(SRCDIR)/default.css \
 
632 $(SRCDIR)/diff.tcl \
633 $(SRCDIR)/forum.js \
634 $(SRCDIR)/fossil.bootstrap.js \
635 $(SRCDIR)/fossil.confirmer.js \
636 $(SRCDIR)/fossil.copybutton.js \
@@ -653,11 +654,10 @@
653 $(SRCDIR)/hbmenu.js \
654 $(SRCDIR)/href.js \
655 $(SRCDIR)/login.js \
656 $(SRCDIR)/markdown.md \
657 $(SRCDIR)/menu.js \
658 $(SRCDIR)/sbsdiff.js \
659 $(SRCDIR)/scroll.js \
660 $(SRCDIR)/skin.js \
661 $(SRCDIR)/sorttable.js \
662 $(SRCDIR)/sounds/0.wav \
663 $(SRCDIR)/sounds/1.wav \
664
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -627,10 +627,11 @@
627 $(SRCDIR)/alerts/plunk.wav \
628 $(SRCDIR)/chat.js \
629 $(SRCDIR)/ci_edit.js \
630 $(SRCDIR)/copybtn.js \
631 $(SRCDIR)/default.css \
632 $(SRCDIR)/diff.js \
633 $(SRCDIR)/diff.tcl \
634 $(SRCDIR)/forum.js \
635 $(SRCDIR)/fossil.bootstrap.js \
636 $(SRCDIR)/fossil.confirmer.js \
637 $(SRCDIR)/fossil.copybutton.js \
@@ -653,11 +654,10 @@
654 $(SRCDIR)/hbmenu.js \
655 $(SRCDIR)/href.js \
656 $(SRCDIR)/login.js \
657 $(SRCDIR)/markdown.md \
658 $(SRCDIR)/menu.js \
 
659 $(SRCDIR)/scroll.js \
660 $(SRCDIR)/skin.js \
661 $(SRCDIR)/sorttable.js \
662 $(SRCDIR)/sounds/0.wav \
663 $(SRCDIR)/sounds/1.wav \
664
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -569,10 +569,11 @@
569569
"$(SRCDIR)\alerts\plunk.wav" \
570570
"$(SRCDIR)\chat.js" \
571571
"$(SRCDIR)\ci_edit.js" \
572572
"$(SRCDIR)\copybtn.js" \
573573
"$(SRCDIR)\default.css" \
574
+ "$(SRCDIR)\diff.js" \
574575
"$(SRCDIR)\diff.tcl" \
575576
"$(SRCDIR)\forum.js" \
576577
"$(SRCDIR)\fossil.bootstrap.js" \
577578
"$(SRCDIR)\fossil.confirmer.js" \
578579
"$(SRCDIR)\fossil.copybutton.js" \
@@ -595,11 +596,10 @@
595596
"$(SRCDIR)\hbmenu.js" \
596597
"$(SRCDIR)\href.js" \
597598
"$(SRCDIR)\login.js" \
598599
"$(SRCDIR)\markdown.md" \
599600
"$(SRCDIR)\menu.js" \
600
- "$(SRCDIR)\sbsdiff.js" \
601601
"$(SRCDIR)\scroll.js" \
602602
"$(SRCDIR)\skin.js" \
603603
"$(SRCDIR)\sorttable.js" \
604604
"$(SRCDIR)\sounds\0.wav" \
605605
"$(SRCDIR)\sounds\1.wav" \
@@ -1177,10 +1177,11 @@
11771177
echo "$(SRCDIR)\alerts/plunk.wav" >> $@
11781178
echo "$(SRCDIR)\chat.js" >> $@
11791179
echo "$(SRCDIR)\ci_edit.js" >> $@
11801180
echo "$(SRCDIR)\copybtn.js" >> $@
11811181
echo "$(SRCDIR)\default.css" >> $@
1182
+ echo "$(SRCDIR)\diff.js" >> $@
11821183
echo "$(SRCDIR)\diff.tcl" >> $@
11831184
echo "$(SRCDIR)\forum.js" >> $@
11841185
echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
11851186
echo "$(SRCDIR)\fossil.confirmer.js" >> $@
11861187
echo "$(SRCDIR)\fossil.copybutton.js" >> $@
@@ -1203,11 +1204,10 @@
12031204
echo "$(SRCDIR)\hbmenu.js" >> $@
12041205
echo "$(SRCDIR)\href.js" >> $@
12051206
echo "$(SRCDIR)\login.js" >> $@
12061207
echo "$(SRCDIR)\markdown.md" >> $@
12071208
echo "$(SRCDIR)\menu.js" >> $@
1208
- echo "$(SRCDIR)\sbsdiff.js" >> $@
12091209
echo "$(SRCDIR)\scroll.js" >> $@
12101210
echo "$(SRCDIR)\skin.js" >> $@
12111211
echo "$(SRCDIR)\sorttable.js" >> $@
12121212
echo "$(SRCDIR)\sounds/0.wav" >> $@
12131213
echo "$(SRCDIR)\sounds/1.wav" >> $@
12141214
12151215
ADDED www/css/diff.md
12161216
ADDED www/css/index.md
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -569,10 +569,11 @@
569 "$(SRCDIR)\alerts\plunk.wav" \
570 "$(SRCDIR)\chat.js" \
571 "$(SRCDIR)\ci_edit.js" \
572 "$(SRCDIR)\copybtn.js" \
573 "$(SRCDIR)\default.css" \
 
574 "$(SRCDIR)\diff.tcl" \
575 "$(SRCDIR)\forum.js" \
576 "$(SRCDIR)\fossil.bootstrap.js" \
577 "$(SRCDIR)\fossil.confirmer.js" \
578 "$(SRCDIR)\fossil.copybutton.js" \
@@ -595,11 +596,10 @@
595 "$(SRCDIR)\hbmenu.js" \
596 "$(SRCDIR)\href.js" \
597 "$(SRCDIR)\login.js" \
598 "$(SRCDIR)\markdown.md" \
599 "$(SRCDIR)\menu.js" \
600 "$(SRCDIR)\sbsdiff.js" \
601 "$(SRCDIR)\scroll.js" \
602 "$(SRCDIR)\skin.js" \
603 "$(SRCDIR)\sorttable.js" \
604 "$(SRCDIR)\sounds\0.wav" \
605 "$(SRCDIR)\sounds\1.wav" \
@@ -1177,10 +1177,11 @@
1177 echo "$(SRCDIR)\alerts/plunk.wav" >> $@
1178 echo "$(SRCDIR)\chat.js" >> $@
1179 echo "$(SRCDIR)\ci_edit.js" >> $@
1180 echo "$(SRCDIR)\copybtn.js" >> $@
1181 echo "$(SRCDIR)\default.css" >> $@
 
1182 echo "$(SRCDIR)\diff.tcl" >> $@
1183 echo "$(SRCDIR)\forum.js" >> $@
1184 echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
1185 echo "$(SRCDIR)\fossil.confirmer.js" >> $@
1186 echo "$(SRCDIR)\fossil.copybutton.js" >> $@
@@ -1203,11 +1204,10 @@
1203 echo "$(SRCDIR)\hbmenu.js" >> $@
1204 echo "$(SRCDIR)\href.js" >> $@
1205 echo "$(SRCDIR)\login.js" >> $@
1206 echo "$(SRCDIR)\markdown.md" >> $@
1207 echo "$(SRCDIR)\menu.js" >> $@
1208 echo "$(SRCDIR)\sbsdiff.js" >> $@
1209 echo "$(SRCDIR)\scroll.js" >> $@
1210 echo "$(SRCDIR)\skin.js" >> $@
1211 echo "$(SRCDIR)\sorttable.js" >> $@
1212 echo "$(SRCDIR)\sounds/0.wav" >> $@
1213 echo "$(SRCDIR)\sounds/1.wav" >> $@
1214
1215 DDED www/css/diff.md
1216 DDED www/css/index.md
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -569,10 +569,11 @@
569 "$(SRCDIR)\alerts\plunk.wav" \
570 "$(SRCDIR)\chat.js" \
571 "$(SRCDIR)\ci_edit.js" \
572 "$(SRCDIR)\copybtn.js" \
573 "$(SRCDIR)\default.css" \
574 "$(SRCDIR)\diff.js" \
575 "$(SRCDIR)\diff.tcl" \
576 "$(SRCDIR)\forum.js" \
577 "$(SRCDIR)\fossil.bootstrap.js" \
578 "$(SRCDIR)\fossil.confirmer.js" \
579 "$(SRCDIR)\fossil.copybutton.js" \
@@ -595,11 +596,10 @@
596 "$(SRCDIR)\hbmenu.js" \
597 "$(SRCDIR)\href.js" \
598 "$(SRCDIR)\login.js" \
599 "$(SRCDIR)\markdown.md" \
600 "$(SRCDIR)\menu.js" \
 
601 "$(SRCDIR)\scroll.js" \
602 "$(SRCDIR)\skin.js" \
603 "$(SRCDIR)\sorttable.js" \
604 "$(SRCDIR)\sounds\0.wav" \
605 "$(SRCDIR)\sounds\1.wav" \
@@ -1177,10 +1177,11 @@
1177 echo "$(SRCDIR)\alerts/plunk.wav" >> $@
1178 echo "$(SRCDIR)\chat.js" >> $@
1179 echo "$(SRCDIR)\ci_edit.js" >> $@
1180 echo "$(SRCDIR)\copybtn.js" >> $@
1181 echo "$(SRCDIR)\default.css" >> $@
1182 echo "$(SRCDIR)\diff.js" >> $@
1183 echo "$(SRCDIR)\diff.tcl" >> $@
1184 echo "$(SRCDIR)\forum.js" >> $@
1185 echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
1186 echo "$(SRCDIR)\fossil.confirmer.js" >> $@
1187 echo "$(SRCDIR)\fossil.copybutton.js" >> $@
@@ -1203,11 +1204,10 @@
1204 echo "$(SRCDIR)\hbmenu.js" >> $@
1205 echo "$(SRCDIR)\href.js" >> $@
1206 echo "$(SRCDIR)\login.js" >> $@
1207 echo "$(SRCDIR)\markdown.md" >> $@
1208 echo "$(SRCDIR)\menu.js" >> $@
 
1209 echo "$(SRCDIR)\scroll.js" >> $@
1210 echo "$(SRCDIR)\skin.js" >> $@
1211 echo "$(SRCDIR)\sorttable.js" >> $@
1212 echo "$(SRCDIR)\sounds/0.wav" >> $@
1213 echo "$(SRCDIR)\sounds/1.wav" >> $@
1214
1215 DDED www/css/diff.md
1216 DDED www/css/index.md
--- a/www/css/diff.md
+++ b/www/css/diff.md
@@ -0,0 +1,112 @@
1
+Notes On Diff Formatting
2
+========================
3
+
4
+There are two main kinds of diff display for the web interface:
5
+unified and side-by-side. Both displays are implemented using
6
+a &lt;table&gt;. The unified diff is a 4-column table, and the
7
+side-by-side diff is a 5-column table. In a page like /info that
8
+might show multiple file diffs, each file diff is in a separate
9
+&lt;table&gt;. For side-by-side diffs, a small amount of Javascript
10
+code is used to resize the text columns so that they fill the screen
11
+width and to keep horizontal scrollbars in sync.
12
+
13
+For the unified diff, the basic structure
14
+is like this:
15
+
16
+> ~~~~
17
+<table class='diff udiff'>
18
+<tr>
19
+ <td class='diffln difflnl'><pre>
20
+ Line numbers for the left-hand file
21
+ </pre></td>
22
+ <td class='diffln difflnr'><pre>
23
+ Line numbers for the right-hand file
24
+ </pre></td>
25
+ <td class='diffsep'><pre>
26
+ Change marks. "+" or "=" or nothing
27
+ </pre></td>
28
+ <td class='difftxt difftxtu'><pre>
29
+ The text
30
+ </pre></td>
31
+</tr>
32
+</table>
33
+~~~~
34
+
35
+The structure for a side-by-side diff follows the
36
+same basic pattern, though with 5 columns instead of
37
+4, and slightly different class names:
38
+
39
+> ~~~~
40
+<table class='diff splitdiff'>
41
+<tr>
42
+ <td class='diffln difflnl'><pre>
43
+ Line numbers for the left-hand file
44
+ </pre></td>
45
+ <td class='difftxt difftxtl'><pre>
46
+ The text for the left side
47
+ </pre></td>
48
+ <td class='diffsep'><pre>
49
+ Change marks. "+" or "=" or nothing
50
+ </pre></td>
51
+ <td class='diffln difflnr'><pre>
52
+ Line numbers for the right-hand file
53
+ </pre></td>
54
+ <td class='difftxt difftxtr'><pre>
55
+ The text on the right-hand side
56
+ </pre></td>
57
+</tr>
58
+</table>
59
+~~~~
60
+
61
+The outer &lt;table&gt; always has class "diff". The "diffu" class
62
+is added for unified diffs and the "splitdiff" class is added for
63
+side-by-side diffs.
64
+
65
+All line-number columns have the "diffln" class. They also always
66
+have one of "difflnl" or "difflnr" depending on whether they hold
67
+line numbers for the left or right files, respectively.
68
+
69
+Text is always kept in a separate column so that it can be scraped
70
+and copied by the user. All text columns have the "difftxt" class.
71
+One additional class "difftxtu", "difftxtl", or "difftxtr" is added
72
+depending on if the text is for a unified diff, the left column of
73
+a side-by-side diff, or the right column of a side-by-side diff.
74
+
75
+The content of all columns is a single &lt;pre&gt; that contains the
76
+appropriate diff-text for that column. Scrolling is done on the
77
+&lt;pre&gt; element.
78
+
79
+Within text columns, highlighting is done with &lt;del&gt; and
80
+&lt;ins&gt; markup. All text on a line that contains an isert or
81
+delete is surrounded by &lt;ins&gt;...&lt;/ins&gt; or
82
+&lt;del&gt;..&lt;/del&gt;. Within that line, specific characters
83
+of text that specifically inserted deleted have an additional
84
+layer of &lt;ins&gt; or &lt;del&gt; markup. Thus CSS like the
85
+following is appropriate:
86
+
87
+> ~~~~
88
+td.difftxt ins {
89
+ background-color: #dafbe1; /* Light green for the whole line */
90
+ text-decoration: none;
91
+}
92
+td.difftxt ins > ins {
93
+ background-color: #a0e4b2; /* Dark green for specific characters that change */
94
+ text-decoration: none;
95
+}
96
+~~~~
97
+
98
+In a side-by-side diff, if an interior &lt;ins&gt; or &lt;del&gt; that mark
99
+specific characters that change correspond to a delete/insert on the other
100
+side, they they have the "edit" class tag. (ex: &lt;ins&nbsp;class='edit'&gt;
101
+or &lt;del&nbsp;class='edit'&gt;). Some skins choose to paint these "modified"
102
+regions blue:
103
+
104
+> ~~~~
105
+td.difftxt ins > ins.edit {
106
+ background-color: #c0c0ff; /* Blue for "modified" text region */
107
+ text-decoration: none;
108
+}
109
+~~~~
110
+
111
+Line number text also has &lt;ins&gt; and &lt;del&gt; tags for lines which
112
+are pure insert or pure delete. But the tags do not nest for line numbers.
--- a/www/css/diff.md
+++ b/www/css/diff.md
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/www/css/diff.md
+++ b/www/css/diff.md
@@ -0,0 +1,112 @@
1 Notes On Diff Formatting
2 ========================
3
4 There are two main kinds of diff display for the web interface:
5 unified and side-by-side. Both displays are implemented using
6 a &lt;table&gt;. The unified diff is a 4-column table, and the
7 side-by-side diff is a 5-column table. In a page like /info that
8 might show multiple file diffs, each file diff is in a separate
9 &lt;table&gt;. For side-by-side diffs, a small amount of Javascript
10 code is used to resize the text columns so that they fill the screen
11 width and to keep horizontal scrollbars in sync.
12
13 For the unified diff, the basic structure
14 is like this:
15
16 > ~~~~
17 <table class='diff udiff'>
18 <tr>
19 <td class='diffln difflnl'><pre>
20 Line numbers for the left-hand file
21 </pre></td>
22 <td class='diffln difflnr'><pre>
23 Line numbers for the right-hand file
24 </pre></td>
25 <td class='diffsep'><pre>
26 Change marks. "+" or "=" or nothing
27 </pre></td>
28 <td class='difftxt difftxtu'><pre>
29 The text
30 </pre></td>
31 </tr>
32 </table>
33 ~~~~
34
35 The structure for a side-by-side diff follows the
36 same basic pattern, though with 5 columns instead of
37 4, and slightly different class names:
38
39 > ~~~~
40 <table class='diff splitdiff'>
41 <tr>
42 <td class='diffln difflnl'><pre>
43 Line numbers for the left-hand file
44 </pre></td>
45 <td class='difftxt difftxtl'><pre>
46 The text for the left side
47 </pre></td>
48 <td class='diffsep'><pre>
49 Change marks. "+" or "=" or nothing
50 </pre></td>
51 <td class='diffln difflnr'><pre>
52 Line numbers for the right-hand file
53 </pre></td>
54 <td class='difftxt difftxtr'><pre>
55 The text on the right-hand side
56 </pre></td>
57 </tr>
58 </table>
59 ~~~~
60
61 The outer &lt;table&gt; always has class "diff". The "diffu" class
62 is added for unified diffs and the "splitdiff" class is added for
63 side-by-side diffs.
64
65 All line-number columns have the "diffln" class. They also always
66 have one of "difflnl" or "difflnr" depending on whether they hold
67 line numbers for the left or right files, respectively.
68
69 Text is always kept in a separate column so that it can be scraped
70 and copied by the user. All text columns have the "difftxt" class.
71 One additional class "difftxtu", "difftxtl", or "difftxtr" is added
72 depending on if the text is for a unified diff, the left column of
73 a side-by-side diff, or the right column of a side-by-side diff.
74
75 The content of all columns is a single &lt;pre&gt; that contains the
76 appropriate diff-text for that column. Scrolling is done on the
77 &lt;pre&gt; element.
78
79 Within text columns, highlighting is done with &lt;del&gt; and
80 &lt;ins&gt; markup. All text on a line that contains an isert or
81 delete is surrounded by &lt;ins&gt;...&lt;/ins&gt; or
82 &lt;del&gt;..&lt;/del&gt;. Within that line, specific characters
83 of text that specifically inserted deleted have an additional
84 layer of &lt;ins&gt; or &lt;del&gt; markup. Thus CSS like the
85 following is appropriate:
86
87 > ~~~~
88 td.difftxt ins {
89 background-color: #dafbe1; /* Light green for the whole line */
90 text-decoration: none;
91 }
92 td.difftxt ins > ins {
93 background-color: #a0e4b2; /* Dark green for specific characters that change */
94 text-decoration: none;
95 }
96 ~~~~
97
98 In a side-by-side diff, if an interior &lt;ins&gt; or &lt;del&gt; that mark
99 specific characters that change correspond to a delete/insert on the other
100 side, they they have the "edit" class tag. (ex: &lt;ins&nbsp;class='edit'&gt;
101 or &lt;del&nbsp;class='edit'&gt;). Some skins choose to paint these "modified"
102 regions blue:
103
104 > ~~~~
105 td.difftxt ins > ins.edit {
106 background-color: #c0c0ff; /* Blue for "modified" text region */
107 text-decoration: none;
108 }
109 ~~~~
110
111 Line number text also has &lt;ins&gt; and &lt;del&gt; tags for lines which
112 are pure insert or pure delete. But the tags do not nest for line numbers.
--- a/www/css/index.md
+++ b/www/css/index.md
@@ -0,0 +1,12 @@
1
+Cascading Style Sheet Notes
2
+===========================
3
+
4
+This is a collection of technical notes that document the design of
5
+the Document Object Model (DOM) and corresponding Cascading Style Sheet (CSS)
6
+attributes used for customing the look-and-feel of Fossil. These notes
7
+are of interest to people who want to customize the Fossil skin or
8
+enhance the internal display logic.
9
+
10
+This is a collection of documents that we hope will grow over time.
11
+
12
+ * [Diff styling](./diff.md)
--- a/www/css/index.md
+++ b/www/css/index.md
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
--- a/www/css/index.md
+++ b/www/css/index.md
@@ -0,0 +1,12 @@
1 Cascading Style Sheet Notes
2 ===========================
3
4 This is a collection of technical notes that document the design of
5 the Document Object Model (DOM) and corresponding Cascading Style Sheet (CSS)
6 attributes used for customing the look-and-feel of Fossil. These notes
7 are of interest to people who want to customize the Fossil skin or
8 enhance the internal display logic.
9
10 This is a collection of documents that we hope will grow over time.
11
12 * [Diff styling](./diff.md)

Keyboard Shortcuts

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