summaryrefslogtreecommitdiffabout
authorRagnar Ouchterlony <ragnar@lysator.liu.se>2009-10-25 17:13:22 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-11-07 14:37:11 (UTC)
commit735e15e38a484bf0daa98776fa7cde270a271cda (patch) (unidiff)
tree588545cead471e8997adc1ae5abca164a4873481
parent4a198e4b8ee62a9a8b5156a46bfce46dc7223fe9 (diff)
downloadcgit-735e15e38a484bf0daa98776fa7cde270a271cda.zip
cgit-735e15e38a484bf0daa98776fa7cde270a271cda.tar.gz
cgit-735e15e38a484bf0daa98776fa7cde270a271cda.tar.bz2
In side-by-side diff, add support for marking individual characters.
Refuses to do so if the left hand side of the diff has different amount of differing lines to the right hand side to avoid confusion. Note that I use the naive dynamic programming approach for calculating the longest common subsequence. We could probably be more efficient by using a better algorithm. The LCS calculating function is O(n*m) and uses up n*m amount of memory too (so if we we compare two strings of length 100, I use an array of 10000 for calculating the LCS). Might want to not calculate LCS if the length of the line is too large. Signed-off-by: Ragnar Ouchterlony <ragnar@lysator.liu.se>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.css10
-rw-r--r--ui-ssdiff.c145
2 files changed, 130 insertions, 25 deletions
diff --git a/cgit.css b/cgit.css
index 3f37165..9e6d2a4 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,692 +1,702 @@
1body, table, form { 1body, table, form {
2 padding: 0em; 2 padding: 0em;
3 margin: 0em; 3 margin: 0em;
4} 4}
5 5
6body { 6body {
7 font-family: sans-serif; 7 font-family: sans-serif;
8 font-size: 10pt; 8 font-size: 10pt;
9 color: #333; 9 color: #333;
10 background: white; 10 background: white;
11 padding: 4px; 11 padding: 4px;
12} 12}
13 13
14a { 14a {
15 color: blue; 15 color: blue;
16 text-decoration: none; 16 text-decoration: none;
17} 17}
18 18
19a:hover { 19a:hover {
20 text-decoration: underline; 20 text-decoration: underline;
21} 21}
22 22
23table { 23table {
24 border-collapse: collapse; 24 border-collapse: collapse;
25} 25}
26 26
27table#header { 27table#header {
28 width: 100%; 28 width: 100%;
29 margin-bottom: 1em; 29 margin-bottom: 1em;
30} 30}
31 31
32table#header td.logo { 32table#header td.logo {
33 width: 96px; 33 width: 96px;
34} 34}
35 35
36table#header td.main { 36table#header td.main {
37 font-size: 250%; 37 font-size: 250%;
38 padding-left: 10px; 38 padding-left: 10px;
39 white-space: nowrap; 39 white-space: nowrap;
40} 40}
41 41
42table#header td.main a { 42table#header td.main a {
43 color: #000; 43 color: #000;
44} 44}
45 45
46table#header td.form { 46table#header td.form {
47 text-align: right; 47 text-align: right;
48 vertical-align: bottom; 48 vertical-align: bottom;
49 padding-right: 1em; 49 padding-right: 1em;
50 padding-bottom: 2px; 50 padding-bottom: 2px;
51 white-space: nowrap; 51 white-space: nowrap;
52} 52}
53 53
54table#header td.form form, 54table#header td.form form,
55table#header td.form input, 55table#header td.form input,
56table#header td.form select { 56table#header td.form select {
57 font-size: 90%; 57 font-size: 90%;
58} 58}
59 59
60table#header td.sub { 60table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 /* border-bottom: solid 2px #ccc; */
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
74table.tabs td { 74table.tabs td {
75 padding: 0px 1em; 75 padding: 0px 1em;
76 vertical-align: bottom; 76 vertical-align: bottom;
77} 77}
78 78
79table.tabs td a { 79table.tabs td a {
80 padding: 2px 0.75em; 80 padding: 2px 0.75em;
81 color: #777; 81 color: #777;
82 font-size: 110%; 82 font-size: 110%;
83} 83}
84 84
85table.tabs td a.active { 85table.tabs td a.active {
86 color: #000; 86 color: #000;
87 background-color: #ccc; 87 background-color: #ccc;
88} 88}
89 89
90table.tabs td.form { 90table.tabs td.form {
91 text-align: right; 91 text-align: right;
92} 92}
93 93
94table.tabs td.form form { 94table.tabs td.form form {
95 padding-bottom: 2px; 95 padding-bottom: 2px;
96 font-size: 90%; 96 font-size: 90%;
97 white-space: nowrap; 97 white-space: nowrap;
98} 98}
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.content { 105div.content {
106 margin: 0px; 106 margin: 0px;
107 padding: 2em; 107 padding: 2em;
108 border-top: solid 3px #ccc; 108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 109 border-bottom: solid 3px #ccc;
110} 110}
111 111
112 112
113table.list { 113table.list {
114 width: 100%; 114 width: 100%;
115 border: none; 115 border: none;
116 border-collapse: collapse; 116 border-collapse: collapse;
117} 117}
118 118
119table.list tr { 119table.list tr {
120 background: white; 120 background: white;
121} 121}
122 122
123table.list tr.logheader { 123table.list tr.logheader {
124 background: #eee; 124 background: #eee;
125} 125}
126 126
127table.list tr:hover { 127table.list tr:hover {
128 background: #eee; 128 background: #eee;
129} 129}
130 130
131table.list tr.nohover:hover { 131table.list tr.nohover:hover {
132 background: white; 132 background: white;
133} 133}
134 134
135table.list th { 135table.list th {
136 font-weight: bold; 136 font-weight: bold;
137 /* color: #888; 137 /* color: #888;
138 border-top: dashed 1px #888; 138 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 139 border-bottom: dashed 1px #888;
140 */ 140 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 141 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 142 vertical-align: baseline;
143} 143}
144 144
145table.list td { 145table.list td {
146 border: none; 146 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 147 padding: 0.1em 0.5em 0.1em 0.5em;
148} 148}
149 149
150table.list td.logsubject { 150table.list td.logsubject {
151 font-family: monospace; 151 font-family: monospace;
152 font-weight: bold; 152 font-weight: bold;
153} 153}
154 154
155table.list td.logmsg { 155table.list td.logmsg {
156 font-family: monospace; 156 font-family: monospace;
157 white-space: pre; 157 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 158 padding: 1em 0.5em 2em 0.5em;
159} 159}
160 160
161table.list td a { 161table.list td a {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a:hover { 165table.list td a:hover {
166 color: #00f; 166 color: #00f;
167} 167}
168 168
169img { 169img {
170 border: none; 170 border: none;
171} 171}
172 172
173input#switch-btn { 173input#switch-btn {
174 margin: 2px 0px 0px 0px; 174 margin: 2px 0px 0px 0px;
175} 175}
176 176
177td#sidebar input.txt { 177td#sidebar input.txt {
178 width: 100%; 178 width: 100%;
179 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
180} 180}
181 181
182table#grid { 182table#grid {
183 margin: 0px; 183 margin: 0px;
184} 184}
185 185
186td#content { 186td#content {
187 vertical-align: top; 187 vertical-align: top;
188 padding: 1em 2em 1em 1em; 188 padding: 1em 2em 1em 1em;
189 border: none; 189 border: none;
190} 190}
191 191
192div#summary { 192div#summary {
193 vertical-align: top; 193 vertical-align: top;
194 margin-bottom: 1em; 194 margin-bottom: 1em;
195} 195}
196 196
197table#downloads { 197table#downloads {
198 float: right; 198 float: right;
199 border-collapse: collapse; 199 border-collapse: collapse;
200 border: solid 1px #777; 200 border: solid 1px #777;
201 margin-left: 0.5em; 201 margin-left: 0.5em;
202 margin-bottom: 0.5em; 202 margin-bottom: 0.5em;
203} 203}
204 204
205table#downloads th { 205table#downloads th {
206 background-color: #ccc; 206 background-color: #ccc;
207} 207}
208 208
209div#blob { 209div#blob {
210 border: solid 1px black; 210 border: solid 1px black;
211} 211}
212 212
213div.error { 213div.error {
214 color: red; 214 color: red;
215 font-weight: bold; 215 font-weight: bold;
216 margin: 1em 2em; 216 margin: 1em 2em;
217} 217}
218 218
219a.ls-blob, a.ls-dir, a.ls-mod { 219a.ls-blob, a.ls-dir, a.ls-mod {
220 font-family: monospace; 220 font-family: monospace;
221} 221}
222 222
223td.ls-size { 223td.ls-size {
224 text-align: right; 224 text-align: right;
225 font-family: monospace; 225 font-family: monospace;
226 width: 10em; 226 width: 10em;
227} 227}
228 228
229td.ls-mode { 229td.ls-mode {
230 font-family: monospace; 230 font-family: monospace;
231 width: 10em; 231 width: 10em;
232} 232}
233 233
234table.blob { 234table.blob {
235 margin-top: 0.5em; 235 margin-top: 0.5em;
236 border-top: solid 1px black; 236 border-top: solid 1px black;
237} 237}
238 238
239table.blob td.lines { 239table.blob td.lines {
240 margin: 0; padding: 0 0 0 0.5em; 240 margin: 0; padding: 0 0 0 0.5em;
241 vertical-align: top; 241 vertical-align: top;
242 color: black; 242 color: black;
243} 243}
244 244
245table.blob td.linenumbers { 245table.blob td.linenumbers {
246 margin: 0; padding: 0 0.5em 0 0.5em; 246 margin: 0; padding: 0 0.5em 0 0.5em;
247 vertical-align: top; 247 vertical-align: top;
248 text-align: right; 248 text-align: right;
249 border-right: 1px solid gray; 249 border-right: 1px solid gray;
250} 250}
251 251
252table.blob pre { 252table.blob pre {
253 padding: 0; margin: 0; 253 padding: 0; margin: 0;
254} 254}
255 255
256table.blob a.no { 256table.blob a.no {
257 color: gray; 257 color: gray;
258 text-align: right; 258 text-align: right;
259 text-decoration: none; 259 text-decoration: none;
260} 260}
261 261
262table.blob a.no a:hover { 262table.blob a.no a:hover {
263 color: black; 263 color: black;
264} 264}
265 265
266table.bin-blob { 266table.bin-blob {
267 margin-top: 0.5em; 267 margin-top: 0.5em;
268 border: solid 1px black; 268 border: solid 1px black;
269} 269}
270 270
271table.bin-blob th { 271table.bin-blob th {
272 font-family: monospace; 272 font-family: monospace;
273 white-space: pre; 273 white-space: pre;
274 border: solid 1px #777; 274 border: solid 1px #777;
275 padding: 0.5em 1em; 275 padding: 0.5em 1em;
276} 276}
277 277
278table.bin-blob td { 278table.bin-blob td {
279 font-family: monospace; 279 font-family: monospace;
280 white-space: pre; 280 white-space: pre;
281 border-left: solid 1px #777; 281 border-left: solid 1px #777;
282 padding: 0em 1em; 282 padding: 0em 1em;
283} 283}
284 284
285table.nowrap td { 285table.nowrap td {
286 white-space: nowrap; 286 white-space: nowrap;
287} 287}
288 288
289table.commit-info { 289table.commit-info {
290 border-collapse: collapse; 290 border-collapse: collapse;
291 margin-top: 1.5em; 291 margin-top: 1.5em;
292} 292}
293 293
294table.commit-info th { 294table.commit-info th {
295 text-align: left; 295 text-align: left;
296 font-weight: normal; 296 font-weight: normal;
297 padding: 0.1em 1em 0.1em 0.1em; 297 padding: 0.1em 1em 0.1em 0.1em;
298 vertical-align: top; 298 vertical-align: top;
299} 299}
300 300
301table.commit-info td { 301table.commit-info td {
302 font-weight: normal; 302 font-weight: normal;
303 padding: 0.1em 1em 0.1em 0.1em; 303 padding: 0.1em 1em 0.1em 0.1em;
304} 304}
305 305
306div.commit-subject { 306div.commit-subject {
307 font-weight: bold; 307 font-weight: bold;
308 font-size: 125%; 308 font-size: 125%;
309 margin: 1.5em 0em 0.5em 0em; 309 margin: 1.5em 0em 0.5em 0em;
310 padding: 0em; 310 padding: 0em;
311} 311}
312 312
313div.commit-msg { 313div.commit-msg {
314 white-space: pre; 314 white-space: pre;
315 font-family: monospace; 315 font-family: monospace;
316} 316}
317 317
318div.diffstat-header { 318div.diffstat-header {
319 font-weight: bold; 319 font-weight: bold;
320 padding-top: 1.5em; 320 padding-top: 1.5em;
321} 321}
322 322
323table.diffstat { 323table.diffstat {
324 border-collapse: collapse; 324 border-collapse: collapse;
325 border: solid 1px #aaa; 325 border: solid 1px #aaa;
326 background-color: #eee; 326 background-color: #eee;
327} 327}
328 328
329table.diffstat th { 329table.diffstat th {
330 font-weight: normal; 330 font-weight: normal;
331 text-align: left; 331 text-align: left;
332 text-decoration: underline; 332 text-decoration: underline;
333 padding: 0.1em 1em 0.1em 0.1em; 333 padding: 0.1em 1em 0.1em 0.1em;
334 font-size: 100%; 334 font-size: 100%;
335} 335}
336 336
337table.diffstat td { 337table.diffstat td {
338 padding: 0.2em 0.2em 0.1em 0.1em; 338 padding: 0.2em 0.2em 0.1em 0.1em;
339 font-size: 100%; 339 font-size: 100%;
340 border: none; 340 border: none;
341} 341}
342 342
343table.diffstat td.mode { 343table.diffstat td.mode {
344 white-space: nowrap; 344 white-space: nowrap;
345} 345}
346 346
347table.diffstat td span.modechange { 347table.diffstat td span.modechange {
348 padding-left: 1em; 348 padding-left: 1em;
349 color: red; 349 color: red;
350} 350}
351 351
352table.diffstat td.add a { 352table.diffstat td.add a {
353 color: green; 353 color: green;
354} 354}
355 355
356table.diffstat td.del a { 356table.diffstat td.del a {
357 color: red; 357 color: red;
358} 358}
359 359
360table.diffstat td.upd a { 360table.diffstat td.upd a {
361 color: blue; 361 color: blue;
362} 362}
363 363
364table.diffstat td.graph { 364table.diffstat td.graph {
365 width: 500px; 365 width: 500px;
366 vertical-align: middle; 366 vertical-align: middle;
367} 367}
368 368
369table.diffstat td.graph table { 369table.diffstat td.graph table {
370 border: none; 370 border: none;
371} 371}
372 372
373table.diffstat td.graph td { 373table.diffstat td.graph td {
374 padding: 0px; 374 padding: 0px;
375 border: 0px; 375 border: 0px;
376 height: 7pt; 376 height: 7pt;
377} 377}
378 378
379table.diffstat td.graph td.add { 379table.diffstat td.graph td.add {
380 background-color: #5c5; 380 background-color: #5c5;
381} 381}
382 382
383table.diffstat td.graph td.rem { 383table.diffstat td.graph td.rem {
384 background-color: #c55; 384 background-color: #c55;
385} 385}
386 386
387div.diffstat-summary { 387div.diffstat-summary {
388 color: #888; 388 color: #888;
389 padding-top: 0.5em; 389 padding-top: 0.5em;
390} 390}
391 391
392table.diff { 392table.diff {
393 width: 100%; 393 width: 100%;
394} 394}
395 395
396table.diff td { 396table.diff td {
397 font-family: monospace; 397 font-family: monospace;
398 white-space: pre; 398 white-space: pre;
399} 399}
400 400
401table.diff td div.head { 401table.diff td div.head {
402 font-weight: bold; 402 font-weight: bold;
403 margin-top: 1em; 403 margin-top: 1em;
404 color: black; 404 color: black;
405} 405}
406 406
407table.diff td div.hunk { 407table.diff td div.hunk {
408 color: #009; 408 color: #009;
409} 409}
410 410
411table.diff td div.add { 411table.diff td div.add {
412 color: green; 412 color: green;
413} 413}
414 414
415table.diff td div.del { 415table.diff td div.del {
416 color: red; 416 color: red;
417} 417}
418 418
419.sha1 { 419.sha1 {
420 font-family: monospace; 420 font-family: monospace;
421 font-size: 90%; 421 font-size: 90%;
422} 422}
423 423
424.left { 424.left {
425 text-align: left; 425 text-align: left;
426} 426}
427 427
428.right { 428.right {
429 text-align: right; 429 text-align: right;
430} 430}
431 431
432table.list td.reposection { 432table.list td.reposection {
433 font-style: italic; 433 font-style: italic;
434 color: #888; 434 color: #888;
435} 435}
436 436
437a.button { 437a.button {
438 font-size: 80%; 438 font-size: 80%;
439 padding: 0em 0.5em; 439 padding: 0em 0.5em;
440} 440}
441 441
442a.primary { 442a.primary {
443 font-size: 100%; 443 font-size: 100%;
444} 444}
445 445
446a.secondary { 446a.secondary {
447 font-size: 90%; 447 font-size: 90%;
448} 448}
449 449
450td.toplevel-repo { 450td.toplevel-repo {
451 451
452} 452}
453 453
454table.list td.sublevel-repo { 454table.list td.sublevel-repo {
455 padding-left: 1.5em; 455 padding-left: 1.5em;
456} 456}
457 457
458div.pager { 458div.pager {
459 text-align: center; 459 text-align: center;
460 margin: 1em 0em 0em 0em; 460 margin: 1em 0em 0em 0em;
461} 461}
462 462
463div.pager a { 463div.pager a {
464 color: #777; 464 color: #777;
465 margin: 0em 0.5em; 465 margin: 0em 0.5em;
466} 466}
467 467
468span.age-mins { 468span.age-mins {
469 font-weight: bold; 469 font-weight: bold;
470 color: #080; 470 color: #080;
471} 471}
472 472
473span.age-hours { 473span.age-hours {
474 color: #080; 474 color: #080;
475} 475}
476 476
477span.age-days { 477span.age-days {
478 color: #040; 478 color: #040;
479} 479}
480 480
481span.age-weeks { 481span.age-weeks {
482 color: #444; 482 color: #444;
483} 483}
484 484
485span.age-months { 485span.age-months {
486 color: #888; 486 color: #888;
487} 487}
488 488
489span.age-years { 489span.age-years {
490 color: #bbb; 490 color: #bbb;
491} 491}
492div.footer { 492div.footer {
493 margin-top: 0.5em; 493 margin-top: 0.5em;
494 text-align: center; 494 text-align: center;
495 font-size: 80%; 495 font-size: 80%;
496 color: #ccc; 496 color: #ccc;
497} 497}
498a.branch-deco { 498a.branch-deco {
499 margin: 0px 0.5em; 499 margin: 0px 0.5em;
500 padding: 0px 0.25em; 500 padding: 0px 0.25em;
501 background-color: #88ff88; 501 background-color: #88ff88;
502 border: solid 1px #007700; 502 border: solid 1px #007700;
503} 503}
504a.tag-deco { 504a.tag-deco {
505 margin: 0px 0.5em; 505 margin: 0px 0.5em;
506 padding: 0px 0.25em; 506 padding: 0px 0.25em;
507 background-color: #ffff88; 507 background-color: #ffff88;
508 border: solid 1px #777700; 508 border: solid 1px #777700;
509} 509}
510a.remote-deco { 510a.remote-deco {
511 margin: 0px 0.5em; 511 margin: 0px 0.5em;
512 padding: 0px 0.25em; 512 padding: 0px 0.25em;
513 background-color: #ccccff; 513 background-color: #ccccff;
514 border: solid 1px #000077; 514 border: solid 1px #000077;
515} 515}
516a.deco { 516a.deco {
517 margin: 0px 0.5em; 517 margin: 0px 0.5em;
518 padding: 0px 0.25em; 518 padding: 0px 0.25em;
519 background-color: #ff8888; 519 background-color: #ff8888;
520 border: solid 1px #770000; 520 border: solid 1px #770000;
521} 521}
522 522
523div.commit-subject a { 523div.commit-subject a {
524 margin-left: 1em; 524 margin-left: 1em;
525 font-size: 75%; 525 font-size: 75%;
526} 526}
527 527
528table.stats { 528table.stats {
529 border: solid 1px black; 529 border: solid 1px black;
530 border-collapse: collapse; 530 border-collapse: collapse;
531} 531}
532 532
533table.stats th { 533table.stats th {
534 text-align: left; 534 text-align: left;
535 padding: 1px 0.5em; 535 padding: 1px 0.5em;
536 background-color: #eee; 536 background-color: #eee;
537 border: solid 1px black; 537 border: solid 1px black;
538} 538}
539 539
540table.stats td { 540table.stats td {
541 text-align: right; 541 text-align: right;
542 padding: 1px 0.5em; 542 padding: 1px 0.5em;
543 border: solid 1px black; 543 border: solid 1px black;
544} 544}
545 545
546table.stats td.total { 546table.stats td.total {
547 font-weight: bold; 547 font-weight: bold;
548 text-align: left; 548 text-align: left;
549} 549}
550 550
551table.stats td.sum { 551table.stats td.sum {
552 color: #c00; 552 color: #c00;
553 font-weight: bold; 553 font-weight: bold;
554 /*background-color: #eee; */ 554 /*background-color: #eee; */
555} 555}
556 556
557table.stats td.left { 557table.stats td.left {
558 text-align: left; 558 text-align: left;
559} 559}
560 560
561table.vgraph { 561table.vgraph {
562 border-collapse: separate; 562 border-collapse: separate;
563 border: solid 1px black; 563 border: solid 1px black;
564 height: 200px; 564 height: 200px;
565} 565}
566 566
567table.vgraph th { 567table.vgraph th {
568 background-color: #eee; 568 background-color: #eee;
569 font-weight: bold; 569 font-weight: bold;
570 border: solid 1px white; 570 border: solid 1px white;
571 padding: 1px 0.5em; 571 padding: 1px 0.5em;
572} 572}
573 573
574table.vgraph td { 574table.vgraph td {
575 vertical-align: bottom; 575 vertical-align: bottom;
576 padding: 0px 10px; 576 padding: 0px 10px;
577} 577}
578 578
579table.vgraph div.bar { 579table.vgraph div.bar {
580 background-color: #eee; 580 background-color: #eee;
581} 581}
582 582
583table.hgraph { 583table.hgraph {
584 border: solid 1px black; 584 border: solid 1px black;
585 width: 800px; 585 width: 800px;
586} 586}
587 587
588table.hgraph th { 588table.hgraph th {
589 background-color: #eee; 589 background-color: #eee;
590 font-weight: bold; 590 font-weight: bold;
591 border: solid 1px black; 591 border: solid 1px black;
592 padding: 1px 0.5em; 592 padding: 1px 0.5em;
593} 593}
594 594
595table.hgraph td { 595table.hgraph td {
596 vertical-align: center; 596 vertical-align: center;
597 padding: 2px 2px; 597 padding: 2px 2px;
598} 598}
599 599
600table.hgraph div.bar { 600table.hgraph div.bar {
601 background-color: #eee; 601 background-color: #eee;
602 height: 1em; 602 height: 1em;
603} 603}
604 604
605table.ssdiff { 605table.ssdiff {
606 width: 100%; 606 width: 100%;
607} 607}
608 608
609table.ssdiff td { 609table.ssdiff td {
610 font-size: 75%; 610 font-size: 75%;
611 font-family: monospace; 611 font-family: monospace;
612 white-space: pre; 612 white-space: pre;
613 padding: 1px 4px 1px 4px; 613 padding: 1px 4px 1px 4px;
614 border-left: solid 1px #aaa; 614 border-left: solid 1px #aaa;
615 border-right: solid 1px #aaa; 615 border-right: solid 1px #aaa;
616} 616}
617 617
618table.ssdiff td.add { 618table.ssdiff td.add {
619 color: black; 619 color: black;
620 background: #cfc; 620 background: #cfc;
621 min-width: 50%; 621 min-width: 50%;
622} 622}
623 623
624table.ssdiff td.add_dark { 624table.ssdiff td.add_dark {
625 color: black; 625 color: black;
626 background: #aca; 626 background: #aca;
627 min-width: 50%; 627 min-width: 50%;
628} 628}
629 629
630table.ssdiff span.add {
631 background: #cfc;
632 font-weight: bold;
633}
634
630table.ssdiff td.del { 635table.ssdiff td.del {
631 color: black; 636 color: black;
632 background: #fcc; 637 background: #fcc;
633 min-width: 50%; 638 min-width: 50%;
634} 639}
635 640
636table.ssdiff td.del_dark { 641table.ssdiff td.del_dark {
637 color: black; 642 color: black;
638 background: #caa; 643 background: #caa;
639 min-width: 50%; 644 min-width: 50%;
640} 645}
641 646
647table.ssdiff span.del {
648 background: #fcc;
649 font-weight: bold;
650}
651
642table.ssdiff td.changed { 652table.ssdiff td.changed {
643 color: black; 653 color: black;
644 background: #ffc; 654 background: #ffc;
645 min-width: 50%; 655 min-width: 50%;
646} 656}
647 657
648table.ssdiff td.changed_dark { 658table.ssdiff td.changed_dark {
649 color: black; 659 color: black;
650 background: #cca; 660 background: #cca;
651 min-width: 50%; 661 min-width: 50%;
652} 662}
653 663
654table.ssdiff td.lineno { 664table.ssdiff td.lineno {
655 color: black; 665 color: black;
656 background: #eee; 666 background: #eee;
657 text-align: right; 667 text-align: right;
658 width: 3em; 668 width: 3em;
659 min-width: 3em; 669 min-width: 3em;
660} 670}
661 671
662table.ssdiff td.hunk { 672table.ssdiff td.hunk {
663 color: #black; 673 color: #black;
664 background: #ccf; 674 background: #ccf;
665 border-top: solid 1px #aaa; 675 border-top: solid 1px #aaa;
666 border-bottom: solid 1px #aaa; 676 border-bottom: solid 1px #aaa;
667} 677}
668 678
669table.ssdiff td.head { 679table.ssdiff td.head {
670 border-top: solid 1px #aaa; 680 border-top: solid 1px #aaa;
671 border-bottom: solid 1px #aaa; 681 border-bottom: solid 1px #aaa;
672} 682}
673 683
674table.ssdiff td.head div.head { 684table.ssdiff td.head div.head {
675 font-weight: bold; 685 font-weight: bold;
676 color: black; 686 color: black;
677} 687}
678 688
679table.ssdiff td.foot { 689table.ssdiff td.foot {
680 border-top: solid 1px #aaa; 690 border-top: solid 1px #aaa;
681 border-left: none; 691 border-left: none;
682 border-right: none; 692 border-right: none;
683 border-bottom: none; 693 border-bottom: none;
684} 694}
685 695
686table.ssdiff td.space { 696table.ssdiff td.space {
687 border: none; 697 border: none;
688} 698}
689 699
690table.ssdiff td.space div { 700table.ssdiff td.space div {
691 min-height: 3em; 701 min-height: 3em;
692} \ No newline at end of file 702} \ No newline at end of file
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
index 5673642..408e620 100644
--- a/ui-ssdiff.c
+++ b/ui-ssdiff.c
@@ -1,274 +1,369 @@
1#include "cgit.h" 1#include "cgit.h"
2#include "html.h" 2#include "html.h"
3#include "ui-shared.h" 3#include "ui-shared.h"
4 4
5extern int use_ssdiff; 5extern int use_ssdiff;
6 6
7static int current_old_line, current_new_line; 7static int current_old_line, current_new_line;
8 8
9struct deferred_lines { 9struct deferred_lines {
10 int line_no; 10 int line_no;
11 char *line; 11 char *line;
12 struct deferred_lines *next; 12 struct deferred_lines *next;
13}; 13};
14 14
15static struct deferred_lines *deferred_old, *deferred_old_last; 15static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last; 16static struct deferred_lines *deferred_new, *deferred_new_last;
17 17
18static char *longest_common_subsequence(char *A, char *B)
19{
20 int i, j, ri;
21 int m = strlen(A);
22 int n = strlen(B);
23 int L[m + 1][n + 1];
24 int tmp1, tmp2;
25 int lcs_length;
26 char *result;
27
28 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0;
32 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1];
34 } else {
35 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 }
39 }
40 }
41
42 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45
46 ri = 0;
47 i = 0;
48 j = 0;
49 while (i < m && j < n) {
50 if (A[i] == B[j]) {
51 result[ri] = A[i];
52 ri += 1;
53 i += 1;
54 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1;
57 } else {
58 j += 1;
59 }
60 }
61 return result;
62}
63
18static int line_from_hunk(char *line, char type) 64static int line_from_hunk(char *line, char type)
19{ 65{
20 char *buf1, *buf2; 66 char *buf1, *buf2;
21 int len; 67 int len;
22 68
23 buf1 = strchr(line, type); 69 buf1 = strchr(line, type);
24 if (buf1 == NULL) 70 if (buf1 == NULL)
25 return 0; 71 return 0;
26 buf1 += 1; 72 buf1 += 1;
27 buf2 = strchr(buf1, ','); 73 buf2 = strchr(buf1, ',');
28 if (buf2 == NULL) 74 if (buf2 == NULL)
29 return 0; 75 return 0;
30 len = buf2 - buf1; 76 len = buf2 - buf1;
31 buf2 = xmalloc(len + 1); 77 buf2 = xmalloc(len + 1);
32 strncpy(buf2, buf1, len); 78 strncpy(buf2, buf1, len);
33 buf2[len] = '\0'; 79 buf2[len] = '\0';
34 int res = atoi(buf2); 80 int res = atoi(buf2);
35 free(buf2); 81 free(buf2);
36 return res; 82 return res;
37} 83}
38 84
39static char *replace_tabs(char *line) 85static char *replace_tabs(char *line)
40{ 86{
41 char *prev_buf = line; 87 char *prev_buf = line;
42 char *cur_buf; 88 char *cur_buf;
43 int linelen = strlen(line); 89 int linelen = strlen(line);
44 int n_tabs = 0; 90 int n_tabs = 0;
45 int i; 91 int i;
46 char *result; 92 char *result;
47 char *spaces = " "; 93 char *spaces = " ";
48 94
49 if (linelen == 0) { 95 if (linelen == 0) {
50 result = xmalloc(1); 96 result = xmalloc(1);
51 result[0] = '\0'; 97 result[0] = '\0';
52 return result; 98 return result;
53 } 99 }
54 100
55 for (i = 0; i < linelen; i++) 101 for (i = 0; i < linelen; i++)
56 if (line[i] == '\t') 102 if (line[i] == '\t')
57 n_tabs += 1; 103 n_tabs += 1;
58 result = xmalloc(linelen + n_tabs * 8 + 1); 104 result = xmalloc(linelen + n_tabs * 8 + 1);
59 result[0] = '\0'; 105 result[0] = '\0';
60 106
61 while (1) { 107 while (1) {
62 cur_buf = strchr(prev_buf, '\t'); 108 cur_buf = strchr(prev_buf, '\t');
63 if (!cur_buf) { 109 if (!cur_buf) {
64 strcat(result, prev_buf); 110 strcat(result, prev_buf);
65 break; 111 break;
66 } else { 112 } else {
67 strcat(result, " "); 113 strcat(result, " ");
68 strncat(result, spaces, 8 - (strlen(result) % 8)); 114 strncat(result, spaces, 8 - (strlen(result) % 8));
69 strncat(result, prev_buf, cur_buf - prev_buf); 115 strncat(result, prev_buf, cur_buf - prev_buf);
70 } 116 }
71 prev_buf = cur_buf + 1; 117 prev_buf = cur_buf + 1;
72 } 118 }
73 return result; 119 return result;
74} 120}
75 121
122static int calc_deferred_lines(struct deferred_lines *start)
123{
124 struct deferred_lines *item = start;
125 int result = 0;
126 while (item) {
127 result += 1;
128 item = item->next;
129 }
130 return result;
131}
132
76static void deferred_old_add(char *line, int line_no) 133static void deferred_old_add(char *line, int line_no)
77{ 134{
78 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); 135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
79 item->line = xstrdup(line); 136 item->line = xstrdup(line);
80 item->line_no = line_no; 137 item->line_no = line_no;
81 item->next = NULL; 138 item->next = NULL;
82 if (deferred_old) { 139 if (deferred_old) {
83 deferred_old_last->next = item; 140 deferred_old_last->next = item;
84 deferred_old_last = item; 141 deferred_old_last = item;
85 } else { 142 } else {
86 deferred_old = deferred_old_last = item; 143 deferred_old = deferred_old_last = item;
87 } 144 }
88} 145}
89 146
90static void deferred_new_add(char *line, int line_no) 147static void deferred_new_add(char *line, int line_no)
91{ 148{
92 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); 149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
93 item->line = xstrdup(line); 150 item->line = xstrdup(line);
94 item->line_no = line_no; 151 item->line_no = line_no;
95 item->next = NULL; 152 item->next = NULL;
96 if (deferred_new) { 153 if (deferred_new) {
97 deferred_new_last->next = item; 154 deferred_new_last->next = item;
98 deferred_new_last = item; 155 deferred_new_last = item;
99 } else { 156 } else {
100 deferred_new = deferred_new_last = item; 157 deferred_new = deferred_new_last = item;
101 } 158 }
102} 159}
103 160
104static void print_ssdiff_line(char *class, int old_line_no, char *old_line, 161static void print_part_with_lcs(char *class, char *line, char *lcs)
105 int new_line_no, char *new_line) 162{
163 int line_len = strlen(line);
164 int i, j;
165 char c[2] = " ";
166 int same = 1;
167
168 j = 0;
169 for (i = 0; i < line_len; i++) {
170 c[0] = line[i];
171 if (same) {
172 if (line[i] == lcs[j])
173 j += 1;
174 else {
175 same = 0;
176 htmlf("<span class='%s'>", class);
177 }
178 } else if (line[i] == lcs[j]) {
179 same = 1;
180 htmlf("</span>");
181 j += 1;
182 }
183 html_txt(c);
184 }
185}
186
187static void print_ssdiff_line(char *class,
188 int old_line_no,
189 char *old_line,
190 int new_line_no,
191 char *new_line, int individual_chars)
106{ 192{
193 char *lcs = NULL;
194 if (old_line)
195 old_line = replace_tabs(old_line + 1);
196 if (new_line)
197 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line);
107 html("<tr>"); 200 html("<tr>");
108 if (old_line_no > 0) 201 if (old_line_no > 0)
109 htmlf("<td class='lineno'>%d</td><td class='%s'>", 202 htmlf("<td class='lineno'>%d</td><td class='%s'>",
110 old_line_no, class); 203 old_line_no, class);
111 else if (old_line) 204 else if (old_line)
112 htmlf("<td class='lineno'></td><td class='%s'>", class); 205 htmlf("<td class='lineno'></td><td class='%s'>", class);
113 else 206 else
114 htmlf("<td class='lineno'></td><td class='%s_dark'>", class); 207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
115
116 if (old_line) { 208 if (old_line) {
117 old_line = replace_tabs(old_line + 1); 209 if (lcs)
118 html_txt(old_line); 210 print_part_with_lcs("del", old_line, lcs);
119 free(old_line); 211 else
212 html_txt(old_line);
120 } 213 }
121 214
122 html("</td>"); 215 html("</td>");
123
124 if (new_line_no > 0) 216 if (new_line_no > 0)
125 htmlf("<td class='lineno'>%d</td><td class='%s'>", 217 htmlf("<td class='lineno'>%d</td><td class='%s'>",
126 new_line_no, class); 218 new_line_no, class);
127 else if (new_line) 219 else if (new_line)
128 htmlf("<td class='lineno'></td><td class='%s'>", class); 220 htmlf("<td class='lineno'></td><td class='%s'>", class);
129 else 221 else
130 htmlf("<td class='lineno'></td><td class='%s_dark'>", class); 222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
131
132 if (new_line) { 223 if (new_line) {
133 new_line = replace_tabs(new_line + 1); 224 if (lcs)
134 html_txt(new_line); 225 print_part_with_lcs("add", new_line, lcs);
135 free(new_line); 226 else
227 html_txt(new_line);
136 } 228 }
137 229
138 html("</td></tr>"); 230 html("</td></tr>");
231 if (lcs)
232 free(lcs);
233 if (new_line)
234 free(new_line);
235 if (old_line)
236 free(old_line);
139} 237}
140 238
141static void print_deferred_old_lines() 239static void print_deferred_old_lines()
142{ 240{
143 struct deferred_lines *iter_old, *tmp; 241 struct deferred_lines *iter_old, *tmp;
144
145 iter_old = deferred_old; 242 iter_old = deferred_old;
146 while (iter_old) { 243 while (iter_old) {
147 print_ssdiff_line("del", iter_old->line_no, 244 print_ssdiff_line("del", iter_old->line_no,
148 iter_old->line, -1, NULL); 245 iter_old->line, -1, NULL, 0);
149 tmp = iter_old->next; 246 tmp = iter_old->next;
150 free(iter_old); 247 free(iter_old);
151 iter_old = tmp; 248 iter_old = tmp;
152 } 249 }
153} 250}
154 251
155static void print_deferred_new_lines() 252static void print_deferred_new_lines()
156{ 253{
157 struct deferred_lines *iter_new, *tmp; 254 struct deferred_lines *iter_new, *tmp;
158
159 iter_new = deferred_new; 255 iter_new = deferred_new;
160 while (iter_new) { 256 while (iter_new) {
161 print_ssdiff_line("add", -1, NULL, iter_new->line_no, 257 print_ssdiff_line("add", -1, NULL,
162 iter_new->line); 258 iter_new->line_no, iter_new->line, 0);
163 tmp = iter_new->next; 259 tmp = iter_new->next;
164 free(iter_new); 260 free(iter_new);
165 iter_new = tmp; 261 iter_new = tmp;
166 } 262 }
167} 263}
168 264
169static void print_deferred_changed_lines() 265static void print_deferred_changed_lines()
170{ 266{
171 struct deferred_lines *iter_old, *iter_new, *tmp; 267 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
172 271
173 iter_old = deferred_old; 272 iter_old = deferred_old;
174 iter_new = deferred_new; 273 iter_new = deferred_new;
175 while (iter_old || iter_new) { 274 while (iter_old || iter_new) {
176 if (iter_old && iter_new) 275 if (iter_old && iter_new)
177 print_ssdiff_line("changed", iter_old->line_no, 276 print_ssdiff_line("changed", iter_old->line_no,
178 iter_old->line, 277 iter_old->line,
179 iter_new->line_no, iter_new->line); 278 iter_new->line_no, iter_new->line,
279 individual_chars);
180 else if (iter_old) 280 else if (iter_old)
181 print_ssdiff_line("changed", iter_old->line_no, 281 print_ssdiff_line("changed", iter_old->line_no,
182 iter_old->line, -1, NULL); 282 iter_old->line, -1, NULL, 0);
183 else if (iter_new) 283 else if (iter_new)
184 print_ssdiff_line("changed", -1, NULL, 284 print_ssdiff_line("changed", -1, NULL,
185 iter_new->line_no, iter_new->line); 285 iter_new->line_no, iter_new->line, 0);
186
187 if (iter_old) { 286 if (iter_old) {
188 tmp = iter_old->next; 287 tmp = iter_old->next;
189 free(iter_old); 288 free(iter_old);
190 iter_old = tmp; 289 iter_old = tmp;
191 } 290 }
192 291
193 if (iter_new) { 292 if (iter_new) {
194 tmp = iter_new->next; 293 tmp = iter_new->next;
195 free(iter_new); 294 free(iter_new);
196 iter_new = tmp; 295 iter_new = tmp;
197 } 296 }
198 } 297 }
199} 298}
200 299
201void cgit_ssdiff_print_deferred_lines() 300void cgit_ssdiff_print_deferred_lines()
202{ 301{
203 if (!deferred_old && !deferred_new) 302 if (!deferred_old && !deferred_new)
204 return; 303 return;
205
206 if (deferred_old && !deferred_new) 304 if (deferred_old && !deferred_new)
207 print_deferred_old_lines(); 305 print_deferred_old_lines();
208 else if (!deferred_old && deferred_new) 306 else if (!deferred_old && deferred_new)
209 print_deferred_new_lines(); 307 print_deferred_new_lines();
210 else 308 else
211 print_deferred_changed_lines(); 309 print_deferred_changed_lines();
212
213 deferred_old = deferred_old_last = NULL; 310 deferred_old = deferred_old_last = NULL;
214 deferred_new = deferred_new_last = NULL; 311 deferred_new = deferred_new_last = NULL;
215} 312}
216 313
217/* 314/*
218 * print a single line returned from xdiff 315 * print a single line returned from xdiff
219 */ 316 */
220void cgit_ssdiff_line_cb(char *line, int len) 317void cgit_ssdiff_line_cb(char *line, int len)
221{ 318{
222 char c = line[len - 1]; 319 char c = line[len - 1];
223
224 line[len - 1] = '\0'; 320 line[len - 1] = '\0';
225
226 if (line[0] == '@') { 321 if (line[0] == '@') {
227 current_old_line = line_from_hunk(line, '-'); 322 current_old_line = line_from_hunk(line, '-');
228 current_new_line = line_from_hunk(line, '+'); 323 current_new_line = line_from_hunk(line, '+');
229 } 324 }
230 325
231 if (line[0] == ' ') { 326 if (line[0] == ' ') {
232 if (deferred_old || deferred_new) 327 if (deferred_old || deferred_new)
233 cgit_ssdiff_print_deferred_lines(); 328 cgit_ssdiff_print_deferred_lines();
234 print_ssdiff_line("ctx", current_old_line, line, 329 print_ssdiff_line("ctx", current_old_line, line,
235 current_new_line, line); 330 current_new_line, line, 0);
236 current_old_line += 1; 331 current_old_line += 1;
237 current_new_line += 1; 332 current_new_line += 1;
238 } else if (line[0] == '+') { 333 } else if (line[0] == '+') {
239 deferred_new_add(line, current_new_line); 334 deferred_new_add(line, current_new_line);
240 current_new_line += 1; 335 current_new_line += 1;
241 } else if (line[0] == '-') { 336 } else if (line[0] == '-') {
242 deferred_old_add(line, current_old_line); 337 deferred_old_add(line, current_old_line);
243 current_old_line += 1; 338 current_old_line += 1;
244 } else if (line[0] == '@') { 339 } else if (line[0] == '@') {
245 html("<tr><td colspan='4' class='hunk'>"); 340 html("<tr><td colspan='4' class='hunk'>");
246 html_txt(line); 341 html_txt(line);
247 html("</td></tr>"); 342 html("</td></tr>");
248 } else { 343 } else {
249 html("<tr><td colspan='4' class='ctx'>"); 344 html("<tr><td colspan='4' class='ctx'>");
250 html_txt(line); 345 html_txt(line);
251 html("</td></tr>"); 346 html("</td></tr>");
252 } 347 }
253 line[len - 1] = c; 348 line[len - 1] = c;
254} 349}
255 350
256void cgit_ssdiff_header_begin() 351void cgit_ssdiff_header_begin()
257{ 352{
258 current_old_line = -1; 353 current_old_line = -1;
259 current_new_line = -1; 354 current_new_line = -1;
260 html("<tr><td class='space' colspan='4'><div></div></td></tr>"); 355 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
261 html("<tr><td class='head' colspan='4'>"); 356 html("<tr><td class='head' colspan='4'>");
262} 357}
263 358
264void cgit_ssdiff_header_end() 359void cgit_ssdiff_header_end()
265{ 360{
266 html("</td><tr>"); 361 html("</td><tr>");
267} 362}
268 363
269void cgit_ssdiff_footer() 364void cgit_ssdiff_footer()
270{ 365{
271 if (deferred_old || deferred_new) 366 if (deferred_old || deferred_new)
272 cgit_ssdiff_print_deferred_lines(); 367 cgit_ssdiff_print_deferred_lines();
273 html("<tr><td class='foot' colspan='4'></td></tr>"); 368 html("<tr><td class='foot' colspan='4'></td></tr>");
274} 369}