Fossil SCM
Further diff enhancements: Allow up to two diff-marks per line on a side-by-side diff.
Commit
937514b96854b8ba378850ebab10cd893fed5d75
Parent
9c28bca430e4eae…
1 file changed
+157
-11
+157
-11
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -363,10 +363,13 @@ | ||
| 363 | 363 | int width; /* Maximum width of a column in the output */ |
| 364 | 364 | unsigned char escHtml; /* True to escape html characters */ |
| 365 | 365 | int iStart; /* Write zStart prior to character iStart */ |
| 366 | 366 | const char *zStart; /* A <span> tag */ |
| 367 | 367 | int iEnd; /* Write </span> prior to character iEnd */ |
| 368 | + int iStart2; /* Write zStart2 prior to character iStart2 */ | |
| 369 | + const char *zStart2; /* A <span> tag */ | |
| 370 | + int iEnd2; /* Write </span> prior to character iEnd2 */ | |
| 368 | 371 | }; |
| 369 | 372 | |
| 370 | 373 | /* |
| 371 | 374 | ** Flags for sbsWriteText() |
| 372 | 375 | */ |
| @@ -381,10 +384,11 @@ | ||
| 381 | 384 | static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){ |
| 382 | 385 | int n = pLine->h & LENGTH_MASK; |
| 383 | 386 | int i; /* Number of input characters consumed */ |
| 384 | 387 | int j; /* Number of output characters generated */ |
| 385 | 388 | int k; /* Cursor position */ |
| 389 | + int needEndSpan = 0; | |
| 386 | 390 | const char *zIn = pLine->z; |
| 387 | 391 | char *z = &p->zLine[p->n]; |
| 388 | 392 | int w = p->width; |
| 389 | 393 | for(i=j=k=0; k<w && i<n; i++, k++){ |
| 390 | 394 | char c = zIn[i]; |
| @@ -391,13 +395,24 @@ | ||
| 391 | 395 | if( p->escHtml ){ |
| 392 | 396 | if( i==p->iStart ){ |
| 393 | 397 | int x = strlen(p->zStart); |
| 394 | 398 | memcpy(z+j, p->zStart, x); |
| 395 | 399 | j += x; |
| 400 | + needEndSpan = 1; | |
| 401 | + if( p->iStart2 ){ | |
| 402 | + p->iStart = p->iStart2; | |
| 403 | + p->zStart = p->zStart2; | |
| 404 | + p->iStart2 = 0; | |
| 405 | + } | |
| 396 | 406 | }else if( i==p->iEnd ){ |
| 397 | 407 | memcpy(z+j, "</span>", 7); |
| 398 | 408 | j += 7; |
| 409 | + needEndSpan = 0; | |
| 410 | + if( p->iEnd2 ){ | |
| 411 | + p->iEnd = p->iEnd2; | |
| 412 | + p->iEnd2 = 0; | |
| 413 | + } | |
| 399 | 414 | } |
| 400 | 415 | } |
| 401 | 416 | if( c=='\t' ){ |
| 402 | 417 | z[j++] = ' '; |
| 403 | 418 | while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; } |
| @@ -414,11 +429,11 @@ | ||
| 414 | 429 | j += 4; |
| 415 | 430 | }else{ |
| 416 | 431 | z[j++] = c; |
| 417 | 432 | } |
| 418 | 433 | } |
| 419 | - if( p->escHtml && i<=p->iEnd ){ | |
| 434 | + if( needEndSpan ){ | |
| 420 | 435 | memcpy(&z[j], "</span>", 7); |
| 421 | 436 | j += 7; |
| 422 | 437 | } |
| 423 | 438 | if( (flags & SBS_PAD)!=0 ){ |
| 424 | 439 | while( k<w ){ k++; z[j++] = ' '; } |
| @@ -459,10 +474,77 @@ | ||
| 459 | 474 | sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1); |
| 460 | 475 | p->n += 6; |
| 461 | 476 | sbsWriteHtml(p, "</span>"); |
| 462 | 477 | p->zLine[p->n++] = ' '; |
| 463 | 478 | } |
| 479 | + | |
| 480 | +/* | |
| 481 | +** The two text segments zLeft and zRight are known to be different on | |
| 482 | +** both ends, but they might have a common segment in the middle. If | |
| 483 | +** they do not have a common segment, return 0. If they do have a large | |
| 484 | +** common segment, return 1 and before doing so set: | |
| 485 | +** | |
| 486 | +** aLCS[0] = start of the common segment in zLeft | |
| 487 | +** aLCS[1] = end of the common segment in zLeft | |
| 488 | +** aLCS[2] = start of the common segment in zLeft | |
| 489 | +** aLCS[3] = end of the common segment in zLeft | |
| 490 | +** | |
| 491 | +** This computation is for display purposes only and does not have to be | |
| 492 | +** optimal or exact. | |
| 493 | +*/ | |
| 494 | +static int textLCS( | |
| 495 | + const char *zLeft, int nA, /* String on the left */ | |
| 496 | + const char *zRight, int nB, /* String on the right */ | |
| 497 | + int *aLCS /* Identify bounds of LCS here */ | |
| 498 | +){ | |
| 499 | + const unsigned char *zA = (const unsigned char*)zLeft; /* left string */ | |
| 500 | + const unsigned char *zB = (const unsigned char*)zRight; /* right string */ | |
| 501 | + int nt; /* Number of target points */ | |
| 502 | + int ti[3]; /* Index for start of each 4-byte target */ | |
| 503 | + unsigned int target[3]; /* 4-byte alignment targets */ | |
| 504 | + unsigned int probe; /* probe to compare against target */ | |
| 505 | + int iAS, iAE, iBS, iBE; /* Range of common segment */ | |
| 506 | + int i, j; /* Loop counters */ | |
| 507 | + int rc = 0; /* Result code. 1 for success */ | |
| 508 | + | |
| 509 | + if( nA<6 || nB<6 ) return 0; | |
| 510 | + memset(aLCS, 0, sizeof(int)*4); | |
| 511 | + ti[0] = i = nB/2-2; | |
| 512 | + target[0] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; | |
| 513 | + probe = 0; | |
| 514 | + if( nB<16 ){ | |
| 515 | + nt = 1; | |
| 516 | + }else{ | |
| 517 | + ti[1] = i = nB/4-2; | |
| 518 | + target[1] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; | |
| 519 | + ti[2] = i = (nB*3)/4-2; | |
| 520 | + target[2] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; | |
| 521 | + nt = 3; | |
| 522 | + } | |
| 523 | + probe = (zA[0]<<16) | (zA[1]<<8) | zA[2]; | |
| 524 | + for(i=3; i<nA; i++){ | |
| 525 | + probe = (probe<<8) | zA[i]; | |
| 526 | + for(j=0; j<nt; j++){ | |
| 527 | + if( probe==target[j] ){ | |
| 528 | + iAS = i-3; | |
| 529 | + iAE = i+1; | |
| 530 | + iBS = ti[j]; | |
| 531 | + iBE = ti[j]+4; | |
| 532 | + while( iAE<nA && iBE<nB && zA[iAE]==zB[iBE] ){ iAE++; iBE++; } | |
| 533 | + while( iAS>0 && iBS>0 && zA[iAS-1]==zB[iBS-1] ){ iAS--; iBS--; } | |
| 534 | + if( iAE-iAS > aLCS[1] - aLCS[0] ){ | |
| 535 | + aLCS[0] = iAS; | |
| 536 | + aLCS[1] = iAE; | |
| 537 | + aLCS[2] = iBS; | |
| 538 | + aLCS[3] = iBE; | |
| 539 | + rc = 1; | |
| 540 | + } | |
| 541 | + } | |
| 542 | + } | |
| 543 | + } | |
| 544 | + return rc; | |
| 545 | +} | |
| 464 | 546 | |
| 465 | 547 | /* |
| 466 | 548 | ** Write out lines that have been edited. Adjust the highlight to cover |
| 467 | 549 | ** only those parts of the line that actually changed. |
| 468 | 550 | */ |
| @@ -477,10 +559,16 @@ | ||
| 477 | 559 | int nRight; /* Length of right line in bytes */ |
| 478 | 560 | int nPrefix; /* Length of common prefix */ |
| 479 | 561 | int nSuffix; /* Length of common suffix */ |
| 480 | 562 | const char *zLeft; /* Text of the left line */ |
| 481 | 563 | const char *zRight; /* Text of the right line */ |
| 564 | + int nLeftDiff; /* nLeft - nPrefix - nSuffix */ | |
| 565 | + int nRightDiff; /* nRight - nPrefix - nSuffix */ | |
| 566 | + int aLCS[4]; /* Bounds of common middle segment */ | |
| 567 | + static const char zClassRm[] = "<span class=\"diffrm\">"; | |
| 568 | + static const char zClassAdd[] = "<span class=\"diffadd\">"; | |
| 569 | + static const char zClassChng[] = "<span class=\"diffchng\">"; | |
| 482 | 570 | |
| 483 | 571 | nLeft = pLeft->h & LENGTH_MASK; |
| 484 | 572 | zLeft = pLeft->z; |
| 485 | 573 | nRight = pRight->h & LENGTH_MASK; |
| 486 | 574 | zRight = pRight->z; |
| @@ -497,44 +585,102 @@ | ||
| 497 | 585 | } |
| 498 | 586 | if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0; |
| 499 | 587 | } |
| 500 | 588 | if( nPrefix+nSuffix > nLeft ) nSuffix = nLeft - nPrefix; |
| 501 | 589 | if( nPrefix+nSuffix > nRight ) nSuffix = nRight - nPrefix; |
| 590 | + | |
| 591 | + /* A single chunk of text inserted on the right */ | |
| 502 | 592 | if( nPrefix+nSuffix==nLeft ){ |
| 503 | - /* Text inserted on the right */ | |
| 504 | 593 | sbsWriteLineno(p, lnLeft); |
| 594 | + p->iStart2 = p->iEnd2 = 0; | |
| 505 | 595 | p->iStart = p->iEnd = -1; |
| 506 | 596 | sbsWriteText(p, pLeft, SBS_PAD); |
| 507 | 597 | sbsWrite(p, " | ", 3); |
| 508 | 598 | sbsWriteLineno(p, lnRight); |
| 509 | 599 | p->iStart = nPrefix; |
| 510 | 600 | p->iEnd = nRight - nSuffix; |
| 511 | - p->zStart = "<span class=\"diffadd\">"; | |
| 601 | + p->zStart = zClassAdd; | |
| 512 | 602 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 513 | - }else if( nPrefix+nSuffix==nRight ){ | |
| 603 | + return; | |
| 604 | + } | |
| 605 | + | |
| 606 | + /* A single chunk of text deleted from the left */ | |
| 607 | + if( nPrefix+nSuffix==nRight ){ | |
| 514 | 608 | /* Text deleted from the left */ |
| 515 | 609 | sbsWriteLineno(p, lnLeft); |
| 610 | + p->iStart2 = p->iEnd2 = 0; | |
| 516 | 611 | p->iStart = nPrefix; |
| 517 | 612 | p->iEnd = nLeft - nSuffix; |
| 518 | - p->zStart = "<span class=\"diffrm\">"; | |
| 613 | + p->zStart = zClassRm; | |
| 519 | 614 | sbsWriteText(p, pLeft, SBS_PAD); |
| 520 | 615 | sbsWrite(p, " | ", 3); |
| 521 | 616 | sbsWriteLineno(p, lnRight); |
| 522 | 617 | p->iStart = p->iEnd = -1; |
| 523 | 618 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 524 | - }else{ | |
| 525 | - /* Text modified between left and right */ | |
| 619 | + return; | |
| 620 | + } | |
| 621 | + | |
| 622 | + /* At this point we know that there is a chunk of text that has | |
| 623 | + ** changed between the left and the right. Check to see if there | |
| 624 | + ** is a large unchanged section in the middle of that changed block. | |
| 625 | + */ | |
| 626 | + nLeftDiff = nLeft - nSuffix - nPrefix; | |
| 627 | + nRightDiff = nRight - nSuffix - nPrefix; | |
| 628 | + if( p->escHtml | |
| 629 | + && nLeftDiff >= 6 | |
| 630 | + && nRightDiff >= 6 | |
| 631 | + && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS) | |
| 632 | + ){ | |
| 526 | 633 | sbsWriteLineno(p, lnLeft); |
| 527 | 634 | p->iStart = nPrefix; |
| 528 | - p->iEnd = nLeft - nSuffix; | |
| 529 | - p->zStart = "<span class=\"diffchng\">"; | |
| 635 | + p->iEnd = nPrefix + aLCS[0]; | |
| 636 | + p->zStart = aLCS[2]==0 ? zClassRm : zClassChng; | |
| 637 | + p->iStart2 = nPrefix + aLCS[1]; | |
| 638 | + p->iEnd2 = nLeft - nSuffix; | |
| 639 | + p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng; | |
| 640 | + if( p->iStart2==p->iEnd2 ) p->iStart2 = p->iEnd2 = 0; | |
| 641 | + if( p->iStart==p->iEnd ){ | |
| 642 | + p->iStart = p->iStart2; | |
| 643 | + p->iEnd = p->iEnd2; | |
| 644 | + p->zStart = p->zStart2; | |
| 645 | + p->iStart2 = 0; | |
| 646 | + p->iEnd2 = 0; | |
| 647 | + } | |
| 648 | + if( p->iStart==p->iEnd ) p->iStart = p->iEnd = -1; | |
| 530 | 649 | sbsWriteText(p, pLeft, SBS_PAD); |
| 531 | 650 | sbsWrite(p, " | ", 3); |
| 532 | 651 | sbsWriteLineno(p, lnRight); |
| 533 | - p->iEnd = nRight - nSuffix; | |
| 652 | + p->iStart = nPrefix; | |
| 653 | + p->iEnd = nPrefix + aLCS[2]; | |
| 654 | + p->zStart = aLCS[0]==0 ? zClassAdd : zClassChng; | |
| 655 | + p->iStart2 = nPrefix + aLCS[3]; | |
| 656 | + p->iEnd2 = nRight - nSuffix; | |
| 657 | + p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng; | |
| 658 | + if( p->iStart2==p->iEnd2 ) p->iStart2 = p->iEnd2 = 0; | |
| 659 | + if( p->iStart==p->iEnd ){ | |
| 660 | + p->iStart = p->iStart2; | |
| 661 | + p->iEnd = p->iEnd2; | |
| 662 | + p->zStart = p->zStart2; | |
| 663 | + p->iStart2 = 0; | |
| 664 | + p->iEnd2 = 0; | |
| 665 | + } | |
| 666 | + if( p->iStart==p->iEnd ) p->iStart = p->iEnd = -1; | |
| 534 | 667 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 668 | + return; | |
| 535 | 669 | } |
| 670 | + | |
| 671 | + /* If all else fails, show a single big change between left and right */ | |
| 672 | + sbsWriteLineno(p, lnLeft); | |
| 673 | + p->iStart2 = p->iEnd2 = 0; | |
| 674 | + p->iStart = nPrefix; | |
| 675 | + p->iEnd = nLeft - nSuffix; | |
| 676 | + p->zStart = zClassChng; | |
| 677 | + sbsWriteText(p, pLeft, SBS_PAD); | |
| 678 | + sbsWrite(p, " | ", 3); | |
| 679 | + sbsWriteLineno(p, lnRight); | |
| 680 | + p->iEnd = nRight - nSuffix; | |
| 681 | + sbsWriteText(p, pRight, SBS_NEWLINE); | |
| 536 | 682 | } |
| 537 | 683 | |
| 538 | 684 | /* |
| 539 | 685 | ** Minimum of two values |
| 540 | 686 | */ |
| @@ -734,11 +880,11 @@ | ||
| 734 | 880 | int m, ma, mb;/* Number of lines to output */ |
| 735 | 881 | int skip; /* Number of lines to skip */ |
| 736 | 882 | int nChunk = 0; /* Number of chunks of diff output seen so far */ |
| 737 | 883 | SbsLine s; /* Output line buffer */ |
| 738 | 884 | |
| 739 | - s.zLine = fossil_malloc( 10*width + 100 ); | |
| 885 | + s.zLine = fossil_malloc( 10*width + 200 ); | |
| 740 | 886 | if( s.zLine==0 ) return; |
| 741 | 887 | s.width = width; |
| 742 | 888 | s.escHtml = escHtml; |
| 743 | 889 | s.iStart = -1; |
| 744 | 890 | s.iEnd = -1; |
| 745 | 891 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -363,10 +363,13 @@ | |
| 363 | int width; /* Maximum width of a column in the output */ |
| 364 | unsigned char escHtml; /* True to escape html characters */ |
| 365 | int iStart; /* Write zStart prior to character iStart */ |
| 366 | const char *zStart; /* A <span> tag */ |
| 367 | int iEnd; /* Write </span> prior to character iEnd */ |
| 368 | }; |
| 369 | |
| 370 | /* |
| 371 | ** Flags for sbsWriteText() |
| 372 | */ |
| @@ -381,10 +384,11 @@ | |
| 381 | static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){ |
| 382 | int n = pLine->h & LENGTH_MASK; |
| 383 | int i; /* Number of input characters consumed */ |
| 384 | int j; /* Number of output characters generated */ |
| 385 | int k; /* Cursor position */ |
| 386 | const char *zIn = pLine->z; |
| 387 | char *z = &p->zLine[p->n]; |
| 388 | int w = p->width; |
| 389 | for(i=j=k=0; k<w && i<n; i++, k++){ |
| 390 | char c = zIn[i]; |
| @@ -391,13 +395,24 @@ | |
| 391 | if( p->escHtml ){ |
| 392 | if( i==p->iStart ){ |
| 393 | int x = strlen(p->zStart); |
| 394 | memcpy(z+j, p->zStart, x); |
| 395 | j += x; |
| 396 | }else if( i==p->iEnd ){ |
| 397 | memcpy(z+j, "</span>", 7); |
| 398 | j += 7; |
| 399 | } |
| 400 | } |
| 401 | if( c=='\t' ){ |
| 402 | z[j++] = ' '; |
| 403 | while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; } |
| @@ -414,11 +429,11 @@ | |
| 414 | j += 4; |
| 415 | }else{ |
| 416 | z[j++] = c; |
| 417 | } |
| 418 | } |
| 419 | if( p->escHtml && i<=p->iEnd ){ |
| 420 | memcpy(&z[j], "</span>", 7); |
| 421 | j += 7; |
| 422 | } |
| 423 | if( (flags & SBS_PAD)!=0 ){ |
| 424 | while( k<w ){ k++; z[j++] = ' '; } |
| @@ -459,10 +474,77 @@ | |
| 459 | sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1); |
| 460 | p->n += 6; |
| 461 | sbsWriteHtml(p, "</span>"); |
| 462 | p->zLine[p->n++] = ' '; |
| 463 | } |
| 464 | |
| 465 | /* |
| 466 | ** Write out lines that have been edited. Adjust the highlight to cover |
| 467 | ** only those parts of the line that actually changed. |
| 468 | */ |
| @@ -477,10 +559,16 @@ | |
| 477 | int nRight; /* Length of right line in bytes */ |
| 478 | int nPrefix; /* Length of common prefix */ |
| 479 | int nSuffix; /* Length of common suffix */ |
| 480 | const char *zLeft; /* Text of the left line */ |
| 481 | const char *zRight; /* Text of the right line */ |
| 482 | |
| 483 | nLeft = pLeft->h & LENGTH_MASK; |
| 484 | zLeft = pLeft->z; |
| 485 | nRight = pRight->h & LENGTH_MASK; |
| 486 | zRight = pRight->z; |
| @@ -497,44 +585,102 @@ | |
| 497 | } |
| 498 | if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0; |
| 499 | } |
| 500 | if( nPrefix+nSuffix > nLeft ) nSuffix = nLeft - nPrefix; |
| 501 | if( nPrefix+nSuffix > nRight ) nSuffix = nRight - nPrefix; |
| 502 | if( nPrefix+nSuffix==nLeft ){ |
| 503 | /* Text inserted on the right */ |
| 504 | sbsWriteLineno(p, lnLeft); |
| 505 | p->iStart = p->iEnd = -1; |
| 506 | sbsWriteText(p, pLeft, SBS_PAD); |
| 507 | sbsWrite(p, " | ", 3); |
| 508 | sbsWriteLineno(p, lnRight); |
| 509 | p->iStart = nPrefix; |
| 510 | p->iEnd = nRight - nSuffix; |
| 511 | p->zStart = "<span class=\"diffadd\">"; |
| 512 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 513 | }else if( nPrefix+nSuffix==nRight ){ |
| 514 | /* Text deleted from the left */ |
| 515 | sbsWriteLineno(p, lnLeft); |
| 516 | p->iStart = nPrefix; |
| 517 | p->iEnd = nLeft - nSuffix; |
| 518 | p->zStart = "<span class=\"diffrm\">"; |
| 519 | sbsWriteText(p, pLeft, SBS_PAD); |
| 520 | sbsWrite(p, " | ", 3); |
| 521 | sbsWriteLineno(p, lnRight); |
| 522 | p->iStart = p->iEnd = -1; |
| 523 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 524 | }else{ |
| 525 | /* Text modified between left and right */ |
| 526 | sbsWriteLineno(p, lnLeft); |
| 527 | p->iStart = nPrefix; |
| 528 | p->iEnd = nLeft - nSuffix; |
| 529 | p->zStart = "<span class=\"diffchng\">"; |
| 530 | sbsWriteText(p, pLeft, SBS_PAD); |
| 531 | sbsWrite(p, " | ", 3); |
| 532 | sbsWriteLineno(p, lnRight); |
| 533 | p->iEnd = nRight - nSuffix; |
| 534 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 535 | } |
| 536 | } |
| 537 | |
| 538 | /* |
| 539 | ** Minimum of two values |
| 540 | */ |
| @@ -734,11 +880,11 @@ | |
| 734 | int m, ma, mb;/* Number of lines to output */ |
| 735 | int skip; /* Number of lines to skip */ |
| 736 | int nChunk = 0; /* Number of chunks of diff output seen so far */ |
| 737 | SbsLine s; /* Output line buffer */ |
| 738 | |
| 739 | s.zLine = fossil_malloc( 10*width + 100 ); |
| 740 | if( s.zLine==0 ) return; |
| 741 | s.width = width; |
| 742 | s.escHtml = escHtml; |
| 743 | s.iStart = -1; |
| 744 | s.iEnd = -1; |
| 745 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -363,10 +363,13 @@ | |
| 363 | int width; /* Maximum width of a column in the output */ |
| 364 | unsigned char escHtml; /* True to escape html characters */ |
| 365 | int iStart; /* Write zStart prior to character iStart */ |
| 366 | const char *zStart; /* A <span> tag */ |
| 367 | int iEnd; /* Write </span> prior to character iEnd */ |
| 368 | int iStart2; /* Write zStart2 prior to character iStart2 */ |
| 369 | const char *zStart2; /* A <span> tag */ |
| 370 | int iEnd2; /* Write </span> prior to character iEnd2 */ |
| 371 | }; |
| 372 | |
| 373 | /* |
| 374 | ** Flags for sbsWriteText() |
| 375 | */ |
| @@ -381,10 +384,11 @@ | |
| 384 | static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){ |
| 385 | int n = pLine->h & LENGTH_MASK; |
| 386 | int i; /* Number of input characters consumed */ |
| 387 | int j; /* Number of output characters generated */ |
| 388 | int k; /* Cursor position */ |
| 389 | int needEndSpan = 0; |
| 390 | const char *zIn = pLine->z; |
| 391 | char *z = &p->zLine[p->n]; |
| 392 | int w = p->width; |
| 393 | for(i=j=k=0; k<w && i<n; i++, k++){ |
| 394 | char c = zIn[i]; |
| @@ -391,13 +395,24 @@ | |
| 395 | if( p->escHtml ){ |
| 396 | if( i==p->iStart ){ |
| 397 | int x = strlen(p->zStart); |
| 398 | memcpy(z+j, p->zStart, x); |
| 399 | j += x; |
| 400 | needEndSpan = 1; |
| 401 | if( p->iStart2 ){ |
| 402 | p->iStart = p->iStart2; |
| 403 | p->zStart = p->zStart2; |
| 404 | p->iStart2 = 0; |
| 405 | } |
| 406 | }else if( i==p->iEnd ){ |
| 407 | memcpy(z+j, "</span>", 7); |
| 408 | j += 7; |
| 409 | needEndSpan = 0; |
| 410 | if( p->iEnd2 ){ |
| 411 | p->iEnd = p->iEnd2; |
| 412 | p->iEnd2 = 0; |
| 413 | } |
| 414 | } |
| 415 | } |
| 416 | if( c=='\t' ){ |
| 417 | z[j++] = ' '; |
| 418 | while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; } |
| @@ -414,11 +429,11 @@ | |
| 429 | j += 4; |
| 430 | }else{ |
| 431 | z[j++] = c; |
| 432 | } |
| 433 | } |
| 434 | if( needEndSpan ){ |
| 435 | memcpy(&z[j], "</span>", 7); |
| 436 | j += 7; |
| 437 | } |
| 438 | if( (flags & SBS_PAD)!=0 ){ |
| 439 | while( k<w ){ k++; z[j++] = ' '; } |
| @@ -459,10 +474,77 @@ | |
| 474 | sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1); |
| 475 | p->n += 6; |
| 476 | sbsWriteHtml(p, "</span>"); |
| 477 | p->zLine[p->n++] = ' '; |
| 478 | } |
| 479 | |
| 480 | /* |
| 481 | ** The two text segments zLeft and zRight are known to be different on |
| 482 | ** both ends, but they might have a common segment in the middle. If |
| 483 | ** they do not have a common segment, return 0. If they do have a large |
| 484 | ** common segment, return 1 and before doing so set: |
| 485 | ** |
| 486 | ** aLCS[0] = start of the common segment in zLeft |
| 487 | ** aLCS[1] = end of the common segment in zLeft |
| 488 | ** aLCS[2] = start of the common segment in zLeft |
| 489 | ** aLCS[3] = end of the common segment in zLeft |
| 490 | ** |
| 491 | ** This computation is for display purposes only and does not have to be |
| 492 | ** optimal or exact. |
| 493 | */ |
| 494 | static int textLCS( |
| 495 | const char *zLeft, int nA, /* String on the left */ |
| 496 | const char *zRight, int nB, /* String on the right */ |
| 497 | int *aLCS /* Identify bounds of LCS here */ |
| 498 | ){ |
| 499 | const unsigned char *zA = (const unsigned char*)zLeft; /* left string */ |
| 500 | const unsigned char *zB = (const unsigned char*)zRight; /* right string */ |
| 501 | int nt; /* Number of target points */ |
| 502 | int ti[3]; /* Index for start of each 4-byte target */ |
| 503 | unsigned int target[3]; /* 4-byte alignment targets */ |
| 504 | unsigned int probe; /* probe to compare against target */ |
| 505 | int iAS, iAE, iBS, iBE; /* Range of common segment */ |
| 506 | int i, j; /* Loop counters */ |
| 507 | int rc = 0; /* Result code. 1 for success */ |
| 508 | |
| 509 | if( nA<6 || nB<6 ) return 0; |
| 510 | memset(aLCS, 0, sizeof(int)*4); |
| 511 | ti[0] = i = nB/2-2; |
| 512 | target[0] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; |
| 513 | probe = 0; |
| 514 | if( nB<16 ){ |
| 515 | nt = 1; |
| 516 | }else{ |
| 517 | ti[1] = i = nB/4-2; |
| 518 | target[1] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; |
| 519 | ti[2] = i = (nB*3)/4-2; |
| 520 | target[2] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; |
| 521 | nt = 3; |
| 522 | } |
| 523 | probe = (zA[0]<<16) | (zA[1]<<8) | zA[2]; |
| 524 | for(i=3; i<nA; i++){ |
| 525 | probe = (probe<<8) | zA[i]; |
| 526 | for(j=0; j<nt; j++){ |
| 527 | if( probe==target[j] ){ |
| 528 | iAS = i-3; |
| 529 | iAE = i+1; |
| 530 | iBS = ti[j]; |
| 531 | iBE = ti[j]+4; |
| 532 | while( iAE<nA && iBE<nB && zA[iAE]==zB[iBE] ){ iAE++; iBE++; } |
| 533 | while( iAS>0 && iBS>0 && zA[iAS-1]==zB[iBS-1] ){ iAS--; iBS--; } |
| 534 | if( iAE-iAS > aLCS[1] - aLCS[0] ){ |
| 535 | aLCS[0] = iAS; |
| 536 | aLCS[1] = iAE; |
| 537 | aLCS[2] = iBS; |
| 538 | aLCS[3] = iBE; |
| 539 | rc = 1; |
| 540 | } |
| 541 | } |
| 542 | } |
| 543 | } |
| 544 | return rc; |
| 545 | } |
| 546 | |
| 547 | /* |
| 548 | ** Write out lines that have been edited. Adjust the highlight to cover |
| 549 | ** only those parts of the line that actually changed. |
| 550 | */ |
| @@ -477,10 +559,16 @@ | |
| 559 | int nRight; /* Length of right line in bytes */ |
| 560 | int nPrefix; /* Length of common prefix */ |
| 561 | int nSuffix; /* Length of common suffix */ |
| 562 | const char *zLeft; /* Text of the left line */ |
| 563 | const char *zRight; /* Text of the right line */ |
| 564 | int nLeftDiff; /* nLeft - nPrefix - nSuffix */ |
| 565 | int nRightDiff; /* nRight - nPrefix - nSuffix */ |
| 566 | int aLCS[4]; /* Bounds of common middle segment */ |
| 567 | static const char zClassRm[] = "<span class=\"diffrm\">"; |
| 568 | static const char zClassAdd[] = "<span class=\"diffadd\">"; |
| 569 | static const char zClassChng[] = "<span class=\"diffchng\">"; |
| 570 | |
| 571 | nLeft = pLeft->h & LENGTH_MASK; |
| 572 | zLeft = pLeft->z; |
| 573 | nRight = pRight->h & LENGTH_MASK; |
| 574 | zRight = pRight->z; |
| @@ -497,44 +585,102 @@ | |
| 585 | } |
| 586 | if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0; |
| 587 | } |
| 588 | if( nPrefix+nSuffix > nLeft ) nSuffix = nLeft - nPrefix; |
| 589 | if( nPrefix+nSuffix > nRight ) nSuffix = nRight - nPrefix; |
| 590 | |
| 591 | /* A single chunk of text inserted on the right */ |
| 592 | if( nPrefix+nSuffix==nLeft ){ |
| 593 | sbsWriteLineno(p, lnLeft); |
| 594 | p->iStart2 = p->iEnd2 = 0; |
| 595 | p->iStart = p->iEnd = -1; |
| 596 | sbsWriteText(p, pLeft, SBS_PAD); |
| 597 | sbsWrite(p, " | ", 3); |
| 598 | sbsWriteLineno(p, lnRight); |
| 599 | p->iStart = nPrefix; |
| 600 | p->iEnd = nRight - nSuffix; |
| 601 | p->zStart = zClassAdd; |
| 602 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 603 | return; |
| 604 | } |
| 605 | |
| 606 | /* A single chunk of text deleted from the left */ |
| 607 | if( nPrefix+nSuffix==nRight ){ |
| 608 | /* Text deleted from the left */ |
| 609 | sbsWriteLineno(p, lnLeft); |
| 610 | p->iStart2 = p->iEnd2 = 0; |
| 611 | p->iStart = nPrefix; |
| 612 | p->iEnd = nLeft - nSuffix; |
| 613 | p->zStart = zClassRm; |
| 614 | sbsWriteText(p, pLeft, SBS_PAD); |
| 615 | sbsWrite(p, " | ", 3); |
| 616 | sbsWriteLineno(p, lnRight); |
| 617 | p->iStart = p->iEnd = -1; |
| 618 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 619 | return; |
| 620 | } |
| 621 | |
| 622 | /* At this point we know that there is a chunk of text that has |
| 623 | ** changed between the left and the right. Check to see if there |
| 624 | ** is a large unchanged section in the middle of that changed block. |
| 625 | */ |
| 626 | nLeftDiff = nLeft - nSuffix - nPrefix; |
| 627 | nRightDiff = nRight - nSuffix - nPrefix; |
| 628 | if( p->escHtml |
| 629 | && nLeftDiff >= 6 |
| 630 | && nRightDiff >= 6 |
| 631 | && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS) |
| 632 | ){ |
| 633 | sbsWriteLineno(p, lnLeft); |
| 634 | p->iStart = nPrefix; |
| 635 | p->iEnd = nPrefix + aLCS[0]; |
| 636 | p->zStart = aLCS[2]==0 ? zClassRm : zClassChng; |
| 637 | p->iStart2 = nPrefix + aLCS[1]; |
| 638 | p->iEnd2 = nLeft - nSuffix; |
| 639 | p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng; |
| 640 | if( p->iStart2==p->iEnd2 ) p->iStart2 = p->iEnd2 = 0; |
| 641 | if( p->iStart==p->iEnd ){ |
| 642 | p->iStart = p->iStart2; |
| 643 | p->iEnd = p->iEnd2; |
| 644 | p->zStart = p->zStart2; |
| 645 | p->iStart2 = 0; |
| 646 | p->iEnd2 = 0; |
| 647 | } |
| 648 | if( p->iStart==p->iEnd ) p->iStart = p->iEnd = -1; |
| 649 | sbsWriteText(p, pLeft, SBS_PAD); |
| 650 | sbsWrite(p, " | ", 3); |
| 651 | sbsWriteLineno(p, lnRight); |
| 652 | p->iStart = nPrefix; |
| 653 | p->iEnd = nPrefix + aLCS[2]; |
| 654 | p->zStart = aLCS[0]==0 ? zClassAdd : zClassChng; |
| 655 | p->iStart2 = nPrefix + aLCS[3]; |
| 656 | p->iEnd2 = nRight - nSuffix; |
| 657 | p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng; |
| 658 | if( p->iStart2==p->iEnd2 ) p->iStart2 = p->iEnd2 = 0; |
| 659 | if( p->iStart==p->iEnd ){ |
| 660 | p->iStart = p->iStart2; |
| 661 | p->iEnd = p->iEnd2; |
| 662 | p->zStart = p->zStart2; |
| 663 | p->iStart2 = 0; |
| 664 | p->iEnd2 = 0; |
| 665 | } |
| 666 | if( p->iStart==p->iEnd ) p->iStart = p->iEnd = -1; |
| 667 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 668 | return; |
| 669 | } |
| 670 | |
| 671 | /* If all else fails, show a single big change between left and right */ |
| 672 | sbsWriteLineno(p, lnLeft); |
| 673 | p->iStart2 = p->iEnd2 = 0; |
| 674 | p->iStart = nPrefix; |
| 675 | p->iEnd = nLeft - nSuffix; |
| 676 | p->zStart = zClassChng; |
| 677 | sbsWriteText(p, pLeft, SBS_PAD); |
| 678 | sbsWrite(p, " | ", 3); |
| 679 | sbsWriteLineno(p, lnRight); |
| 680 | p->iEnd = nRight - nSuffix; |
| 681 | sbsWriteText(p, pRight, SBS_NEWLINE); |
| 682 | } |
| 683 | |
| 684 | /* |
| 685 | ** Minimum of two values |
| 686 | */ |
| @@ -734,11 +880,11 @@ | |
| 880 | int m, ma, mb;/* Number of lines to output */ |
| 881 | int skip; /* Number of lines to skip */ |
| 882 | int nChunk = 0; /* Number of chunks of diff output seen so far */ |
| 883 | SbsLine s; /* Output line buffer */ |
| 884 | |
| 885 | s.zLine = fossil_malloc( 10*width + 200 ); |
| 886 | if( s.zLine==0 ) return; |
| 887 | s.width = width; |
| 888 | s.escHtml = escHtml; |
| 889 | s.iStart = -1; |
| 890 | s.iEnd = -1; |
| 891 |