Ver código fonte

Changed: Refactored `fastDiff`.

Szymon Cofalik 7 anos atrás
pai
commit
37efb0be99

+ 38 - 57
packages/ckeditor5-utils/src/fastdiff.js

@@ -57,70 +57,51 @@ export default function fastDiff( oldText, newText ) {
 // @param {String} oldText
 // @param {String} oldText
 // @param {String} newText
 // @param {String} newText
 // @returns {Object}
 // @returns {Object}
-// @returns {Number} return.firstIndex Position of the first change in both strings (always the same for both).
-// @returns {Number} result.lastIndexOld Position of the last change in `oldText` string.
-// @returns {Number} result.lastIndexNew Position of the last change in `newText` string.
+// @returns {Number} return.firstIndex Index of the first change in both strings (always the same for both).
+// @returns {Number} result.lastIndexOld Index of the last common character in `oldText` string looking from back.
+// @returns {Number} result.lastIndexNew Index of the last common character in `newText` string looking from back.
 function findChangeBoundaryIndexes( oldText, newText ) {
 function findChangeBoundaryIndexes( oldText, newText ) {
-	const oldTextLength = oldText.length;
-	const newTextLength = newText.length;
+	// Find the first difference between texts.
+	const firstIndex = findFirstDifferenceIndex( oldText, newText );
+
+	// Remove the common part of texts and reverse them to make it simpler to find the last difference between texts.
+	const oldTextReversed = cutAndReverse( oldText, firstIndex );
+	const newTextReversed = cutAndReverse( newText, firstIndex );
+
+	// Find the first difference between reversed texts.
+	// It should be treated as "how many characters from the end the last difference occurred".
+	//
+	// For example:
+	//
+	// 			initial	->	after cut	-> reversed:
+	// oldText:	'321ba'	->	'21ba'		-> 'ab12'
+	// newText:	'31xba'	->	'1xba'		-> 'abx1'
+	// lastIndex:						->    2
+	//
+	// So the last change occurred two characters from the end of the texts.
+	const lastIndex = findFirstDifferenceIndex( oldTextReversed, newTextReversed );
+
+	// Use `lastIndex` to calculate proper offset, starting from the beginning (`lastIndex` kind of starts from the end).
+	const lastIndexOld = oldText.length - lastIndex;
+	const lastIndexNew = newText.length - lastIndex;
 
 
-	// Find first change.
-	// Iterate over both strings starting from the beginning to find position of the first different character.
-	// If not found, it means first change is at the end of the string.
-	let firstIndex = oldTextLength;
+	return { firstIndex, lastIndexOld, lastIndexNew };
+}
 
 
-	for ( let i = 0; i < oldTextLength; i++ ) {
+// Returns a first index on which `oldText` and `newText` differ.
+function findFirstDifferenceIndex( oldText, newText ) {
+	for ( let i = 0; i < Math.max( oldText.length, newText.length ); i++ ) {
 		if ( oldText[ i ] !== newText[ i ] ) {
 		if ( oldText[ i ] !== newText[ i ] ) {
-			firstIndex = i;
-			break;
-		}
-	}
-
-	// Find last change.
-	// Iterate over both strings starting from the end to find position of the first different character.
-	// We remove identical part from both strings and reverse them so it is easier to iterate. The identical part
-	// needs to be removed to properly handle cases like:
-	//		oldText = '123';
-	//		newText = '123123;
-	// After removing identical part we have:
-	//		oldText = '';
-	//		newText = '123';
-	//		// { firstIndex: 3, lastIndexOld: 3, lastIndexNew: 6 }
-	const oldTextReversed = oldText.substring( firstIndex ).split( '' ).reverse().join( '' );
-	const newTextReversed = newText.substring( firstIndex ).split( '' ).reverse().join( '' );
-
-	let lastIndexOld;
-	let lastIndexNew;
-
-	for ( let i = 0; i < Math.max( oldTextReversed.length, newTextReversed.length ); i++ ) {
-		// Initial text -> after removing identical part -> reversed:
-		// oldText: '321ba' -> '21ba' -> 'ab12'
-		// newText: '31ba'  -> '1ba'  -> 'ab1'
-		// { firstIndex: 1, lastIndexOld: 2, lastIndexNew: 1 }
-		if ( i >= newTextReversed.length ) {
-			lastIndexOld = oldTextLength - i;
-			lastIndexNew = firstIndex;
-			break;
-		}
-
-		// Initial text -> after removing identical part -> reversed:
-		// oldText: '31ba'  -> '1ba'  -> 'ab1'
-		// newText: '321ba' -> '21ba' -> 'ab12'
-		// { firstIndex: 1, lastIndexOld: 1, lastIndexNew: 2 }
-		if ( i >= oldTextReversed.length ) {
-			lastIndexOld = firstIndex;
-			lastIndexNew = newTextLength - i;
-			break;
-		}
-
-		if ( oldTextReversed[ i ] !== newTextReversed[ i ] ) {
-			lastIndexOld = oldTextLength - i;
-			lastIndexNew = newTextLength - i;
-			break;
+			return i;
 		}
 		}
 	}
 	}
+	// No "backup" return cause we assume that `oldText` and `newText` differ. This means that they either have a
+	// difference or they have a different lengths. This means that the `if` condition will always be met eventually.
+}
 
 
-	return { firstIndex, lastIndexOld, lastIndexNew };
+// Removes `cutHowMany` first characters from the given `text` string and then reverses it and returns it.
+function cutAndReverse( text, cutHowMany ) {
+	return text.substring( cutHowMany ).split( '' ).reverse().join( '' );
 }
 }
 
 
 // Generates changes array based on change indexes from `findChangeBoundaryIndexes` function. This function will
 // Generates changes array based on change indexes from `findChangeBoundaryIndexes` function. This function will

+ 14 - 0
packages/ckeditor5-utils/tests/fastdiff.js

@@ -161,6 +161,20 @@ describe( 'fastDiff', () => {
 			], false );
 			], false );
 		} );
 		} );
 
 
+		it( 'should diff insertion of duplicated content', () => {
+			expectDiff( '1234', '123123', [
+				{ index: 3, type: 'insert', values: [ '1', '2', '3' ] },
+				{ index: 6, type: 'delete', howMany: 1 }
+			], false );
+		} );
+
+		it( 'should diff insertion of duplicated content', () => {
+			expectDiff( '1234', '13424', [
+				{ index: 1, type: 'insert', values: [ '3', '4', '2' ] },
+				{ index: 4, type: 'delete', howMany: 2 }
+			], false );
+		} );
+
 		it( 'should diff replacement in the middle', () => {
 		it( 'should diff replacement in the middle', () => {
 			expectDiff( '12345', '12ab5', [
 			expectDiff( '12345', '12ab5', [
 				{ index: 2, type: 'insert', values: [ 'a', 'b' ] },
 				{ index: 2, type: 'insert', values: [ 'a', 'b' ] },