浏览代码

Added: model.Range#getTransformedByDeltas.
Fixed: model.LiveRange#createFromRanges.

Szymon Cofalik 9 年之前
父节点
当前提交
b237f351f1
共有 2 个文件被更改,包括 88 次插入1 次删除
  1. 46 1
      packages/ckeditor5-engine/src/model/range.js
  2. 42 0
      packages/ckeditor5-engine/tests/model/range.js

+ 46 - 1
packages/ckeditor5-engine/src/model/range.js

@@ -372,6 +372,10 @@ export default class Range {
 	/**
 	 * Returns a range that is a result of transforming this range by given `delta`.
 	 *
+	 * **Note:** transformation may break one range into multiple ranges (e.g. when a part of the range is
+	 * moved to a different part of document tree). For this reason, an array is returned by this method and it
+	 * may contain one or more `Range` instances.
+	 *
 	 * @param {module:engine/model/delta~Delta} delta Delta to transform range by.
 	 * @returns {Array.<module:engine/model/range~Range>} Range which is the result of transformation.
 	 */
@@ -402,6 +406,47 @@ export default class Range {
 	}
 
 	/**
+	 * Returns a range that is a result of transforming this range by multiple `deltas`.
+	 *
+	 * **Note:** transformation may break one range into multiple ranges (e.g. when a part of the range is
+	 * moved to a different part of document tree). For this reason, an array is returned by this method and it
+	 * may contain one or more `Range` instances.
+	 *
+	 * @param {Iterable.<module:engine/model/delta~Delta>} deltas Deltas to transform the range by.
+	 * @returns {Array.<module:engine/model/range~Range>} Range which is the result of transformation.
+	 */
+	getTransformedByDeltas( deltas ) {
+		let ranges = [ Range.createFromRange( this ) ];
+
+		for ( let delta of deltas ) {
+			for ( let i = 0; i < ranges.length; i++ ) {
+				let result = ranges[ i ].getTransformedByDelta( delta );
+
+				ranges.splice( i, 1, ...result );
+				i += result.length - 1;
+			}
+		}
+
+		// It may happen that a range is split into two, and then the part of second "piece" is moved into first
+		// "piece". In this case we will have incorrect third rage, which should not be included in the result --
+		// because it is already included in first "piece". In this loop we are looking for all such ranges that
+		// are inside other ranges and we simply remove them.
+		for ( let i = 0; i < ranges.length; i++ ) {
+			const range = ranges[ i ];
+
+			for ( let j = i + 1; j < ranges.length; j++ ) {
+				const next = ranges[ j ];
+
+				if ( range.containsRange( next ) || next.containsRange( range ) || range.isEqual( next ) ) {
+					ranges.splice( j, 1 );
+				}
+			}
+		}
+
+		return ranges;
+	}
+
+	/**
 	 * Returns a range that is a result of transforming this range by a change in the model document.
 	 *
 	 * @protected
@@ -627,7 +672,7 @@ export default class Range {
 			 */
 			throw new CKEditorError( 'range-create-from-ranges-empty-array: At least one range has to be passed.' );
 		} else if ( ranges.length == 1 ) {
-			return Range.createFromRange( ranges[ 0 ] );
+			return this.createFromRange( ranges[ 0 ] );
 		}
 
 		// 1. Set the first range in `ranges` array as a reference range.

+ 42 - 0
packages/ckeditor5-engine/tests/model/range.js

@@ -843,6 +843,48 @@ describe( 'Range', () => {
 		} );
 	} );
 
+	describe( 'getTransformedByDeltas', () => {
+		beforeEach( () => {
+			root.appendChildren( new Text( 'foobar' ) );
+			range = Range.createFromParentsAndOffsets( root, 2, root, 5 );
+		} );
+
+		function expectRange( range, startOffset, endOffset ) {
+			expect( range.start.offset ).to.equal( startOffset );
+			expect( range.end.offset ).to.equal( endOffset );
+		}
+
+		it( 'should return a range transformed by multiple deltas', () => {
+			const transformed = range.getTransformedByDeltas( [
+				getInsertDelta( new Position( root, [ 1 ] ), new Text( 'abc' ), 1 ), // Range becomes 5..8.
+				getInsertDelta( new Position( root, [ 6 ] ), new Text( 'xx' ), 2 ) // Range becomes 5..10.
+			] );
+
+			expectRange( transformed[ 0 ], 5, 10 );
+		} );
+
+		it( 'should correctly handle breaking transformed range and all range "pieces"', () => {
+			const transformed = range.getTransformedByDeltas( [
+				getInsertDelta( new Position( root, [ 3 ] ), new Text( 'abc' ), 1 ), // Range becomes 2..8.
+				getMoveDelta( new Position( root, [ 4 ] ), 3, new Position( root, [ 9 ] ), 2 ), // Range becomes 2..5 and 6..9.
+				getInsertDelta( new Position( root, [ 0 ] ), new Text( 'x' ), 3 ), // Range becomes 3..6 and 7..10.
+				getMoveDelta( new Position( root, [ 9 ] ), 1, new Position( root, [ 4 ] ), 4 ), // Range becomes 3..7 and 8..10.
+				getMoveDelta( new Position( root, [ 6 ] ), 1, new Position( root, [ 1 ] ), 5 ) // Range becomes 1..2, 4..7 and 8..10.
+			] );
+
+			expect( transformed.length ).to.equal( 3 );
+			expectRange( transformed[ 0 ], 4, 7 );
+			expectRange( transformed[ 1 ], 1, 2 );
+			expectRange( transformed[ 2 ], 8, 10 );
+		} );
+
+		it( 'should return range equal to original range for empty delta set', () => {
+			const transformed = range.getTransformedByDeltas( [] );
+
+			expectRange( transformed[ 0 ], 2, 5 );
+		} );
+	} );
+
 	describe( 'getMinimalFlatRanges', () => {
 		beforeEach( () => {
 			prepareRichRoot( root );