@@ -15,8 +15,8 @@ function createStringSequence(a: string): ISequence {
1515 } ;
1616}
1717
18- export function stringDiff ( original : string , modified : string ) : IDiffChange [ ] {
19- return new LcsDiff ( createStringSequence ( original ) , createStringSequence ( modified ) ) . ComputeDiff ( ) ;
18+ export function stringDiff ( original : string , modified : string , pretty : boolean ) : IDiffChange [ ] {
19+ return new LcsDiff ( createStringSequence ( original ) , createStringSequence ( modified ) ) . ComputeDiff ( pretty ) ;
2020}
2121
2222
@@ -286,18 +286,35 @@ export class LcsDiff {
286286 return this . m_originalIds [ originalIndex ] === this . m_modifiedIds [ newIndex ] ;
287287 }
288288
289- public ComputeDiff ( ) : IDiffChange [ ] {
290- return this . _ComputeDiff ( 0 , this . OriginalSequence . getLength ( ) - 1 , 0 , this . ModifiedSequence . getLength ( ) - 1 ) ;
289+ private OriginalElementsAreEqual ( index1 : number , index2 : number ) : boolean {
290+ return this . m_originalIds [ index1 ] === this . m_originalIds [ index2 ] ;
291+ }
292+
293+ private ModifiedElementsAreEqual ( index1 : number , index2 : number ) : boolean {
294+ return this . m_modifiedIds [ index1 ] === this . m_modifiedIds [ index2 ] ;
295+ }
296+
297+ public ComputeDiff ( pretty : boolean ) : IDiffChange [ ] {
298+ return this . _ComputeDiff ( 0 , this . OriginalSequence . getLength ( ) - 1 , 0 , this . ModifiedSequence . getLength ( ) - 1 , pretty ) ;
291299 }
292300
293301 /**
294302 * Computes the differences between the original and modified input
295303 * sequences on the bounded range.
296304 * @returns An array of the differences between the two input sequences.
297305 */
298- private _ComputeDiff ( originalStart : number , originalEnd : number , modifiedStart : number , modifiedEnd : number ) : DiffChange [ ] {
306+ private _ComputeDiff ( originalStart : number , originalEnd : number , modifiedStart : number , modifiedEnd : number , pretty : boolean ) : DiffChange [ ] {
299307 let quitEarlyArr = [ false ] ;
300- return this . ComputeDiffRecursive ( originalStart , originalEnd , modifiedStart , modifiedEnd , quitEarlyArr ) ;
308+ let changes = this . ComputeDiffRecursive ( originalStart , originalEnd , modifiedStart , modifiedEnd , quitEarlyArr ) ;
309+
310+ if ( pretty ) {
311+ // We have to clean up the computed diff to be more intuitive
312+ // but it turns out this cannot be done correctly until the entire set
313+ // of diffs have been computed
314+ return this . ShiftChanges ( changes ) ;
315+ }
316+
317+ return changes ;
301318 }
302319
303320 /**
@@ -769,6 +786,57 @@ export class LcsDiff {
769786 ) ;
770787 }
771788
789+ /**
790+ * Shifts the given changes to provide a more intuitive diff.
791+ * While the first element in a diff matches the first element after the diff,
792+ * we shift the diff down.
793+ *
794+ * @param changes The list of changes to shift
795+ * @returns The shifted changes
796+ */
797+ private ShiftChanges ( changes : DiffChange [ ] ) : DiffChange [ ] {
798+ let mergedDiffs : boolean ;
799+ do {
800+ mergedDiffs = false ;
801+
802+ // Shift all the changes first
803+ for ( let i = 0 ; i < changes . length ; i ++ ) {
804+ const change = changes [ i ] ;
805+ const originalStop = ( i < changes . length - 1 ) ? changes [ i + 1 ] . originalStart : this . OriginalSequence . getLength ( ) ;
806+ const modifiedStop = ( i < changes . length - 1 ) ? changes [ i + 1 ] . modifiedStart : this . ModifiedSequence . getLength ( ) ;
807+ const checkOriginal = change . originalLength > 0 ;
808+ const checkModified = change . modifiedLength > 0 ;
809+
810+ while ( change . originalStart + change . originalLength < originalStop &&
811+ change . modifiedStart + change . modifiedLength < modifiedStop &&
812+ ( ! checkOriginal || this . OriginalElementsAreEqual ( change . originalStart , change . originalStart + change . originalLength ) ) &&
813+ ( ! checkModified || this . ModifiedElementsAreEqual ( change . modifiedStart , change . modifiedStart + change . modifiedLength ) ) ) {
814+ change . originalStart ++ ;
815+ change . modifiedStart ++ ;
816+ }
817+ }
818+
819+ // Build up the new list (we have to build a new list because we
820+ // might have changes we can merge together now)
821+ let result = new Array < DiffChange > ( ) ;
822+ let mergedChangeArr : DiffChange [ ] = [ null ] ;
823+ for ( let i = 0 ; i < changes . length ; i ++ ) {
824+ if ( i < changes . length - 1 && this . ChangesOverlap ( changes [ i ] , changes [ i + 1 ] , mergedChangeArr ) ) {
825+ mergedDiffs = true ;
826+ result . push ( mergedChangeArr [ 0 ] ) ;
827+ i ++ ;
828+ }
829+ else {
830+ result . push ( changes [ i ] ) ;
831+ }
832+ }
833+
834+ changes = result ;
835+ } while ( mergedDiffs ) ;
836+
837+ return changes ;
838+ }
839+
772840 /**
773841 * Concatenates the two input DiffChange lists and returns the resulting
774842 * list.
@@ -847,7 +915,6 @@ export class LcsDiff {
847915 * @param numDiagonals The total number of diagonals.
848916 * @returns The clipped diagonal index.
849917 */
850-
851918 private ClipDiagonalBound ( diagonal : number , numDifferences : number , diagonalBaseIndex : number , numDiagonals : number ) : number {
852919 if ( diagonal >= 0 && diagonal < numDiagonals ) {
853920 // Nothing to clip, its in range
@@ -868,5 +935,4 @@ export class LcsDiff {
868935 return ( diffEven === upperBoundEven ) ? numDiagonals - 1 : numDiagonals - 2 ;
869936 }
870937 }
871-
872- }
938+ }
0 commit comments