8
0
Просмотр исходного кода

Other: Make Position immutable.

Maciej Gołaszewski 8 лет назад
Родитель
Сommit
0f3acc2742

+ 13 - 15
packages/ckeditor5-engine/src/model/delta/basic-transformations.js

@@ -73,8 +73,7 @@ addTransformationCase( AttributeDelta, SplitDelta, ( a, b, context ) => {
 			const additionalAttributeDelta = new AttributeDelta();
 
 			const rangeStart = splitPosition.getShiftedBy( 1 );
-			const rangeEnd = Position.createFromPosition( rangeStart );
-			rangeEnd.path.push( 0 );
+			const rangeEnd = rangeStart.getMovedToChild();
 
 			const oldValue = b._cloneOperation.nodes.getNode( 0 ).getAttribute( operation.key );
 
@@ -236,7 +235,7 @@ addTransformationCase( SplitDelta, SplitDelta, ( a, b, context ) => {
 				a._cloneOperation instanceof ReinsertOperation && b._cloneOperation instanceof ReinsertOperation &&
 				a._cloneOperation.sourcePosition.offset > b._cloneOperation.sourcePosition.offset
 			) {
-				a._cloneOperation.sourcePosition.offset--;
+				a._cloneOperation.sourcePosition = a._cloneOperation.sourcePosition.getShiftedBy( -1 );
 			}
 
 			// `a` splits closer or at same offset.
@@ -317,29 +316,24 @@ addTransformationCase( SplitDelta, WrapDelta, ( a, b, context ) => {
 		// Wrapping element is the element inserted by WrapDelta (re)insert operation.
 		// It is inserted after the wrapped range, but the wrapped range will be moved inside it.
 		// Having this in mind, it is correct to use wrapped range start position as the position before wrapping element.
-		const splitNodePos = Position.createFromPosition( b.range.start );
+
 		// Now, `splitNodePos` points before wrapping element.
 		// To get a position before last children of that element, we expand position's `path` member by proper offset.
-		splitNodePos.path.push( b.howMany - 1 );
+		const splitNodePos = b.range.start.getMovedToChild( b.howMany - 1 );
 
 		// SplitDelta insert operation position should be right after the node we split.
-		const insertPos = splitNodePos.getShiftedBy( 1 );
-		delta._cloneOperation.position = insertPos;
+		delta._cloneOperation.position = splitNodePos.getShiftedBy( 1 );
 
 		// 2. Fix move operation source position.
 		// Nodes moved by SplitDelta will be moved from new position, modified by WrapDelta.
 		// To obtain that new position, `splitNodePos` will be used, as this is the node we are extracting children from.
-		const sourcePos = Position.createFromPosition( splitNodePos );
 		// Nothing changed inside split node so it is correct to use the original split position offset.
-		sourcePos.path.push( a.position.offset );
-		delta._moveOperation.sourcePosition = sourcePos;
+		delta._moveOperation.sourcePosition = splitNodePos.getMovedToChild( a.position.offset );
 
 		// 3. Fix move operation target position.
 		// SplitDelta move operation target position should be inside the node inserted by operation above.
 		// Since the node is empty, we will insert at offset 0.
-		const targetPos = Position.createFromPosition( insertPos );
-		targetPos.path.push( 0 );
-		delta._moveOperation.targetPosition = targetPos;
+		delta._moveOperation.targetPosition = splitNodePos.getShiftedBy( 1 ).getMovedToChild();
 
 		return [ delta ];
 	}
@@ -434,13 +428,17 @@ addTransformationCase( WrapDelta, SplitDelta, ( a, b, context ) => {
 		const delta = a.clone();
 
 		// Move wrapping element insert position one node further so it is after the split node insertion.
-		delta._insertOperation.position.offset++;
+		delta._insertOperation.position = delta._insertOperation.position.getShiftedBy( 1 );
 
 		// Include the split node copy.
 		delta._moveOperation.howMany++;
 
 		// Change the path to wrapping element in move operation.
-		delta._moveOperation.targetPosition.path[ delta._moveOperation.targetPosition.path.length - 2 ]++;
+		const index = delta._moveOperation.targetPosition.path.length - 2;
+
+		const path = delta._moveOperation.targetPosition.path.slice();
+		path[ index ] += 1;
+		delta._moveOperation.targetPosition = delta._moveOperation.targetPosition.getMovedToPath( path );
 
 		return [ delta ];
 	}

+ 3 - 3
packages/ckeditor5-engine/src/model/liveposition.js

@@ -7,7 +7,7 @@
  * @module engine/model/liveposition
  */
 
-import Position from './position';
+import Position, { setPath, setRoot } from './position';
 import Range from './range';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
@@ -194,8 +194,8 @@ function transform( type, range, position ) {
 	if ( !this.isEqual( transformed ) ) {
 		const oldPosition = Position.createFromPosition( this );
 
-		this.path = transformed.path;
-		this.root = transformed.root;
+		setPath( this, transformed.path );
+		setRoot( this, transformed.root );
 
 		this.fire( 'change', oldPosition );
 	}

+ 175 - 79
packages/ckeditor5-engine/src/model/position.js

@@ -13,6 +13,9 @@ import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Text from './text';
 
+const _path = Symbol( 'path' );
+const _root = Symbol( 'root' );
+
 /**
  * Represents a position in the model tree.
  *
@@ -66,64 +69,66 @@ export default class Position {
 
 		// Normalize the root and path (if element was passed).
 		path = root.getPath().concat( path );
-		root = root.root;
-
-		/**
-		 * Root of the position path.
-		 *
-		 * @readonly
-		 * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
-		 * module:engine/model/position~Position#root
-		 */
-		this.root = root;
-
-		/**
-		 * Position of the node in the tree. **Path contains offsets, not indexes.**
-		 *
-		 * Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
-		 * {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
-		 * {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
-		 * down to the position offset in it's parent.
-		 *
-		 *		 ROOT
-		 *		  |- P            before: [ 0 ]         after: [ 1 ]
-		 *		  |- UL           before: [ 1 ]         after: [ 2 ]
-		 *		     |- LI        before: [ 1, 0 ]      after: [ 1, 1 ]
-		 *		     |  |- foo    before: [ 1, 0, 0 ]   after: [ 1, 0, 3 ]
-		 *		     |- LI        before: [ 1, 1 ]      after: [ 1, 2 ]
-		 *		        |- bar    before: [ 1, 1, 0 ]   after: [ 1, 1, 3 ]
-		 *
-		 * `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
-		 * greater than `1` you can place position offset between their start and end:
-		 *
-		 *		 ROOT
-		 *		  |- P
-		 *		  |- UL
-		 *		     |- LI
-		 *		     |  |- f^o|o  ^ has path: [ 1, 0, 1 ]   | has path: [ 1, 0, 2 ]
-		 *		     |- LI
-		 *		        |- b^a|r  ^ has path: [ 1, 1, 1 ]   | has path: [ 1, 1, 2 ]
-		 *
-		 * @member {Array.<Number>} module:engine/model/position~Position#path
-		 */
-		this.path = path;
+
+		// Make path immutable
+		Object.freeze( path );
+
+		setRoot( this, root.root );
+		setPath( this, path );
 	}
 
 	/**
-	 * Offset at which this position is located in its {@link module:engine/model/position~Position#parent parent}. It is equal
-	 * to the last item in position {@link module:engine/model/position~Position#path path}.
+	 * Position of the node in the tree. **Path contains offsets, not indexes.**
+	 *
+	 * Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
+	 * {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
+	 * {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
+	 * down to the position offset in it's parent.
+	 *
+	 *		 ROOT
+	 *		  |- P            before: [ 0 ]         after: [ 1 ]
+	 *		  |- UL           before: [ 1 ]         after: [ 2 ]
+	 *		     |- LI        before: [ 1, 0 ]      after: [ 1, 1 ]
+	 *		     |  |- foo    before: [ 1, 0, 0 ]   after: [ 1, 0, 3 ]
+	 *		     |- LI        before: [ 1, 1 ]      after: [ 1, 2 ]
+	 *		        |- bar    before: [ 1, 1, 0 ]   after: [ 1, 1, 3 ]
+	 *
+	 * `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
+	 * greater than `1` you can place position offset between their start and end:
+	 *
+	 *		 ROOT
+	 *		  |- P
+	 *		  |- UL
+	 *		     |- LI
+	 *		     |  |- f^o|o  ^ has path: [ 1, 0, 1 ]   | has path: [ 1, 0, 2 ]
+	 *		     |- LI
+	 *		        |- b^a|r  ^ has path: [ 1, 1, 1 ]   | has path: [ 1, 1, 2 ]
+	 *
+	 * @member {Array.<Number>} module:engine/model/position~Position#path
+	 */
+	get path() {
+		return this[ _path ];
+	}
+
+	/**
+	 * Root of the position path.
 	 *
-	 * @type {Number}
+	 * @readonly
+	 * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
+	 * module:engine/model/position~Position#root
 	 */
-	get offset() {
-		return last( this.path );
+	get root() {
+		return this[ _root ];
 	}
 
 	/**
-	 * @param {Number} newOffset
+	 * Offset at which this position is located in its {@link module:engine/model/position~Position#parent parent}. It is equal
+	 * to the last item in position {@link module:engine/model/position~Position#path path}.
+	 *
+	 * @type {Number}
 	 */
-	set offset( newOffset ) {
-		this.path[ this.path.length - 1 ] = newOffset;
+	get offset() {
+		return getOffset( this.path );
 	}
 
 	/**
@@ -340,6 +345,31 @@ export default class Position {
 		return i === 0 ? null : ancestorsA[ i - 1 ];
 	}
 
+	/**
+	 * Returns a new instance of `Position`, that has same {@link #root root} but it's path is set to `path` value.
+	 *
+	 * @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
+	 * @returns {module:engine/model/position~Position} Moved position.
+	 */
+	getMovedToPath( path ) {
+		return new Position( this.root, path );
+	}
+
+	/**
+	 * Returns a new instance of `Position`, that has same {@link #parent parent} but it's offset
+	 * is set to `offset` value.
+	 *
+	 * @param {Number} offset Position offset. See {@link module:engine/model/position~Position#offset}.
+	 * @returns {module:engine/model/position~Position} Moved position.
+	 */
+	getShiftedTo( offset ) {
+		const path = this.path.slice();
+
+		setOffset( path, offset );
+
+		return this.getMovedToPath( path );
+	}
+
 	/**
 	 * Returns a new instance of `Position`, that has same {@link #parent parent} but it's offset
 	 * is shifted by `shift` value (can be a negative value).
@@ -348,12 +378,31 @@ export default class Position {
 	 * @returns {module:engine/model/position~Position} Shifted position.
 	 */
 	getShiftedBy( shift ) {
-		const shifted = Position.createFromPosition( this );
+		const newOffset = this.offset + shift;
+
+		return this.getShiftedTo( newOffset < 0 ? 0 : newOffset );
+	}
+
+	/**
+	 * Returns a new instance of `Position`, that has same {@link #root root} but it's moved in path by one level up.
+	 *
+	 * @returns {module:engine/model/position~Position} Moved position.
+	 */
+	getMovedToParent() {
+		return this.getMovedToPath( this.getParentPath() );
+	}
+
+	/**
+	 * Returns a new instance of `Position`, that has same {@link #root root} but it's moved in path by one level down.
+	 *
+	 * @returns {module:engine/model/position~Position} Moved position.
+	 */
+	getMovedToChild( offsetInChild = 0 ) {
+		const path = this.path.slice();
 
-		const offset = shifted.offset + shift;
-		shifted.offset = offset < 0 ? 0 : offset;
+		path.push( offsetInChild );
 
-		return shifted;
+		return this.getMovedToPath( path );
 	}
 
 	/**
@@ -433,13 +482,13 @@ export default class Position {
 				return true;
 
 			case 'before':
-				left = Position.createFromPosition( this );
-				right = Position.createFromPosition( otherPosition );
+				left = this; // eslint-disable-line consistent-this
+				right = otherPosition;
 				break;
 
 			case 'after':
-				left = Position.createFromPosition( otherPosition );
-				right = Position.createFromPosition( this );
+				left = otherPosition;
+				right = this; // eslint-disable-line consistent-this
 				break;
 
 			default:
@@ -459,19 +508,30 @@ export default class Position {
 					return false;
 				}
 
-				left.path = left.path.slice( 0, -1 );
+				left = left.getMovedToParent().getShiftedBy( 1 );
 				leftParent = leftParent.parent;
-				left.offset++;
 			} else {
 				if ( right.offset !== 0 ) {
 					return false;
 				}
 
-				right.path = right.path.slice( 0, -1 );
+				right = right.getMovedToParent();
 			}
 		}
 	}
 
+	/**
+	 * Converts `Position` to plain object and returns it.
+	 *
+	 * @returns {Object} `Position` converted to plain object.
+	 */
+	toJSON() {
+		return {
+			root: this.root.toJSON(),
+			path: this.path
+		};
+	}
+
 	/**
 	 * Returns a copy of this position that is updated by removing `howMany` nodes starting from `deletePosition`.
 	 * It may happen that this position is in a removed node. If that is the case, `null` is returned instead.
@@ -482,14 +542,14 @@ export default class Position {
 	 * @returns {module:engine/model/position~Position|null} Transformed position or `null`.
 	 */
 	_getTransformedByDeletion( deletePosition, howMany ) {
-		const transformed = Position.createFromPosition( this );
-
 		// This position can't be affected if deletion was in a different root.
 		if ( this.root != deletePosition.root ) {
-			return transformed;
+			return this;
 		}
 
-		if ( compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == 'same' ) {
+		const comparisonResult = compareArrays( deletePosition.getParentPath(), this.getParentPath() );
+
+		if ( comparisonResult == 'same' ) {
 			// If nodes are removed from the node that is pointed by this position...
 			if ( deletePosition.offset < this.offset ) {
 				// And are removed from before an offset of that position...
@@ -497,11 +557,10 @@ export default class Position {
 					// Position is in removed range, it's no longer in the tree.
 					return null;
 				} else {
-					// Decrement the offset accordingly.
-					transformed.offset -= howMany;
+					return this.getShiftedBy( -howMany );
 				}
 			}
-		} else if ( compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == 'prefix' ) {
+		} else if ( comparisonResult == 'prefix' ) {
 			// If nodes are removed from a node that is on a path to this position...
 			const i = deletePosition.path.length - 1;
 
@@ -513,12 +572,14 @@ export default class Position {
 					return null;
 				} else {
 					// Otherwise, decrement index on that path.
-					transformed.path[ i ] -= howMany;
+					const path = this.path.slice();
+					path[ i ] -= howMany;
+					return this.getMovedToPath( path );
 				}
 			}
 		}
 
-		return transformed;
+		return this;
 	}
 
 	/**
@@ -533,11 +594,9 @@ export default class Position {
 	 * @returns {module:engine/model/position~Position} Transformed position.
 	 */
 	_getTransformedByInsertion( insertPosition, howMany, insertBefore ) {
-		const transformed = Position.createFromPosition( this );
-
 		// This position can't be affected if insertion was in a different root.
 		if ( this.root != insertPosition.root ) {
-			return transformed;
+			return this;
 		}
 
 		if ( compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == 'same' ) {
@@ -545,7 +604,7 @@ export default class Position {
 			if ( insertPosition.offset < this.offset || ( insertPosition.offset == this.offset && insertBefore ) ) {
 				// And are inserted before an offset of that position...
 				// "Push" this positions offset.
-				transformed.offset += howMany;
+				return this.getShiftedBy( howMany );
 			}
 		} else if ( compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == 'prefix' ) {
 			// If nodes are inserted in a node that is on a path to this position...
@@ -554,11 +613,13 @@ export default class Position {
 			if ( insertPosition.offset <= this.path[ i ] ) {
 				// And are inserted before next node of that path...
 				// "Push" the index on that path.
-				transformed.path[ i ] += howMany;
+				const path = this.path.slice();
+				path[ i ] += howMany;
+				return this.getMovedToPath( path );
 			}
 		}
 
-		return transformed;
+		return this;
 	}
 
 	/**
@@ -626,18 +687,18 @@ export default class Position {
 		const i = source.path.length - 1;
 
 		// The first part of a path to combined position is a path to the place where nodes were moved.
-		const combined = Position.createFromPosition( target );
+		let combinedPath = target.path.slice();
 
 		// Then we have to update the rest of the path.
 
 		// Fix the offset because this position might be after `from` position and we have to reflect that.
-		combined.offset = combined.offset + this.path[ i ] - source.offset;
+		setOffset( combinedPath, getOffset( combinedPath ) + this.path[ i ] - source.offset );
 
 		// Then, add the rest of the path.
 		// If this position is at the same level as `from` position nothing will get added.
-		combined.path = combined.path.concat( this.path.slice( i + 1 ) );
+		combinedPath = combinedPath.concat( this.path.slice( i + 1 ) );
 
-		return combined;
+		return new Position( target.root, combinedPath );
 	}
 
 	/**
@@ -781,6 +842,41 @@ export default class Position {
 	}
 }
 
+/**
+ * Method used to expose root setter to child classes.
+ * @protected
+ * @param {module:engine/model/position~Position} position Position of which root should be modified.
+ * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
+ */
+export function setRoot( position, root ) {
+	position[ _root ] = root;
+}
+
+/**
+ * Method used to expose path setter to child classes.
+ * @protected
+ * @param {module:engine/model/position~Position} position Position of which path should be modified.
+ * @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
+ */
+export function setPath( position, path ) {
+	position[ _path ] = path;
+}
+
+// Helper for setting offset on give path array.
+// @private
+// @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
+function getOffset( path ) {
+	return last( path ) || 0;
+}
+
+// Helper for setting offset on give path array.
+// @private
+// @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
+// @param {Number} newOffset Offset to set.
+function setOffset( path, newOffset ) {
+	path[ path.length - 1 ] = newOffset;
+}
+
 /**
  * A flag indicating whether this position is `'before'` or `'after'` or `'same'` as given position.
  * If positions are in different roots `'different'` flag is returned.

+ 9 - 11
packages/ckeditor5-engine/src/model/range.js

@@ -30,7 +30,7 @@ export default class Range {
 		 * @readonly
 		 * @member {module:engine/model/position~Position}
 		 */
-		this.start = Position.createFromPosition( start );
+		this.start = start;
 
 		/**
 		 * End position.
@@ -38,7 +38,7 @@ export default class Range {
 		 * @readonly
 		 * @member {module:engine/model/position~Position}
 		 */
-		this.end = end ? Position.createFromPosition( end ) : Position.createFromPosition( start );
+		this.end = end ? end : start;
 	}
 
 	/**
@@ -280,7 +280,7 @@ export default class Range {
 		const ranges = [];
 		const diffAt = this.start.getCommonPath( this.end ).length;
 
-		const pos = Position.createFromPosition( this.start );
+		let pos = this.start;
 		let posParent = pos.parent;
 
 		// Go up.
@@ -291,8 +291,8 @@ export default class Range {
 				ranges.push( new Range( pos, pos.getShiftedBy( howMany ) ) );
 			}
 
-			pos.path = pos.path.slice( 0, -1 );
-			pos.offset++;
+			pos = pos.getMovedToParent().getShiftedBy( 1 );
+
 			posParent = posParent.parent;
 		}
 
@@ -305,8 +305,7 @@ export default class Range {
 				ranges.push( new Range( pos, pos.getShiftedBy( howMany ) ) );
 			}
 
-			pos.offset = offset;
-			pos.path.push( 0 );
+			pos = pos.getShiftedTo( offset ).getMovedToChild();
 		}
 
 		return ranges;
@@ -755,9 +754,8 @@ export default class Range {
 	 */
 	static createCollapsedAt( itemOrPosition, offset ) {
 		const start = Position.createAt( itemOrPosition, offset );
-		const end = Position.createFromPosition( start );
 
-		return new Range( start, end );
+		return new Range( start, start );
 	}
 
 	/**
@@ -810,7 +808,7 @@ export default class Range {
 		// Since ranges are sorted, start with the range with index that is closest to reference range index.
 		for ( let i = refIndex - 1; i >= 0; i++ ) {
 			if ( ranges[ i ].end.isEqual( result.start ) ) {
-				result.start = Position.createFromPosition( ranges[ i ].start );
+				result.start = ranges[ i ].start;
 			} else {
 				// If ranges are not starting/ending at the same position there is no point in looking further.
 				break;
@@ -821,7 +819,7 @@ export default class Range {
 		// Since ranges are sorted, start with the range with index that is closest to reference range index.
 		for ( let i = refIndex + 1; i < ranges.length; i++ ) {
 			if ( ranges[ i ].start.isEqual( result.end ) ) {
-				result.end = Position.createFromPosition( ranges[ i ].end );
+				result.end = ranges[ i ].end;
 			} else {
 				// If ranges are not starting/ending at the same position there is no point in looking further.
 				break;

+ 25 - 19
packages/ckeditor5-engine/src/model/treewalker.js

@@ -204,33 +204,35 @@ export default class TreeWalker {
 	 */
 	_next() {
 		const previousPosition = this.position;
-		const position = Position.createFromPosition( this.position );
+
+		let position = this.position;
 		const parent = this._visitedParent;
 
 		// We are at the end of the root.
-		if ( parent.parent === null && position.offset === parent.maxOffset ) {
+		if ( parent.parent === null && this.position.offset === parent.maxOffset ) {
 			return { done: true };
 		}
 
 		// We reached the walker boundary.
-		if ( parent === this._boundaryEndParent && position.offset == this.boundaries.end.offset ) {
+		if ( parent === this._boundaryEndParent && this.position.offset == this.boundaries.end.offset ) {
 			return { done: true };
 		}
 
-		const node = position.textNode ? position.textNode : position.nodeAfter;
+		const node = this.position.textNode ? this.position.textNode : this.position.nodeAfter;
 
 		if ( node instanceof Element ) {
 			if ( !this.shallow ) {
 				// Manual operations on path internals for optimization purposes. Here and in the rest of the method.
-				position.path.push( 0 );
+				position = position.getMovedToChild();
+
 				this._visitedParent = node;
 			} else {
-				position.offset++;
+				position = position.getShiftedBy( 1 );
 			}
 
 			this.position = position;
 
-			return formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
+			return formatReturnValue( 'elementStart', node, previousPosition, this.position, 1 );
 		} else if ( node instanceof Text ) {
 			let charactersCount;
 
@@ -249,14 +251,15 @@ export default class TreeWalker {
 			const offsetInTextNode = position.offset - node.startOffset;
 			const item = new TextProxy( node, offsetInTextNode, charactersCount );
 
-			position.offset += charactersCount;
+			position = position.getShiftedBy( charactersCount );
+
 			this.position = position;
 
-			return formatReturnValue( 'text', item, previousPosition, position, charactersCount );
+			return formatReturnValue( 'text', item, previousPosition, this.position, charactersCount );
 		} else {
 			// `node` is not set, we reached the end of current `parent`.
-			position.path.pop();
-			position.offset++;
+			position = position.getMovedToParent().getShiftedBy( 1 );
+
 			this.position = position;
 			this._visitedParent = parent.parent;
 
@@ -278,27 +281,28 @@ export default class TreeWalker {
 	 */
 	_previous() {
 		const previousPosition = this.position;
-		const position = Position.createFromPosition( this.position );
+		let position = this.position;
 		const parent = this._visitedParent;
 
 		// We are at the beginning of the root.
-		if ( parent.parent === null && position.offset === 0 ) {
+		if ( parent.parent === null && this.position.offset === 0 ) {
 			return { done: true };
 		}
 
 		// We reached the walker boundary.
-		if ( parent == this._boundaryStartParent && position.offset == this.boundaries.start.offset ) {
+		if ( parent == this._boundaryStartParent && this.position.offset == this.boundaries.start.offset ) {
 			return { done: true };
 		}
 
 		// Get node just before current position
-		const node = position.textNode ? position.textNode : position.nodeBefore;
+		const node = this.position.textNode ? this.position.textNode : this.position.nodeBefore;
 
 		if ( node instanceof Element ) {
-			position.offset--;
+			position = position.getShiftedBy( -1 );
 
 			if ( !this.shallow ) {
-				position.path.push( node.maxOffset );
+				position = position.getMovedToChild( node.maxOffset );
+
 				this.position = position;
 				this._visitedParent = node;
 
@@ -330,13 +334,15 @@ export default class TreeWalker {
 			const offsetInTextNode = position.offset - node.startOffset;
 			const item = new TextProxy( node, offsetInTextNode - charactersCount, charactersCount );
 
-			position.offset -= charactersCount;
+			position = position.getShiftedBy( -charactersCount );
+
 			this.position = position;
 
 			return formatReturnValue( 'text', item, previousPosition, position, charactersCount );
 		} else {
 			// `node` is not set, we reached the beginning of current `parent`.
-			position.path.pop();
+			position = position.getMovedToParent();
+
 			this.position = position;
 			this._visitedParent = parent.parent;
 

+ 8 - 16
packages/ckeditor5-engine/tests/model/delta/transform/_utils/utils.js

@@ -68,12 +68,9 @@ export function getMarkerDelta( name, oldRange, newRange, version ) {
 export function getMergeDelta( position, howManyInPrev, howManyInNext, version ) {
 	const delta = new MergeDelta();
 
-	const sourcePosition = Position.createFromPosition( position );
-	sourcePosition.path.push( 0 );
+	const sourcePosition = position.getMovedToChild();
 
-	const targetPosition = Position.createFromPosition( position );
-	targetPosition.offset--;
-	targetPosition.path.push( howManyInPrev );
+	const targetPosition = position.getShiftedBy( -1 ).getMovedToChild( howManyInPrev );
 
 	const move = new MoveOperation( sourcePosition, howManyInNext, targetPosition, version );
 	move.isSticky = true;
@@ -129,12 +126,9 @@ export function getRenameDelta( position, oldName, newName, baseVersion ) {
 export function getSplitDelta( position, nodeCopy, howManyMove, version ) {
 	const delta = new SplitDelta();
 
-	const insertPosition = Position.createFromPosition( position );
-	insertPosition.path = insertPosition.getParentPath();
-	insertPosition.offset++;
+	const insertPosition = position.getMovedToParent().getShiftedBy( 1 );
 
-	const targetPosition = Position.createFromPosition( insertPosition );
-	targetPosition.path.push( 0 );
+	const targetPosition = insertPosition.getMovedToChild();
 
 	delta.addOperation( new InsertOperation( insertPosition, [ nodeCopy ], version ) );
 
@@ -153,8 +147,8 @@ export function getWrapDelta( range, element, version ) {
 
 	const insert = new InsertOperation( range.end, element, version );
 
-	const targetPosition = Position.createFromPosition( range.end );
-	targetPosition.path.push( 0 );
+	const targetPosition = range.end.getMovedToChild();
+
 	const move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, version + 1 );
 
 	delta.addOperation( insert );
@@ -168,14 +162,12 @@ export function getWrapDelta( range, element, version ) {
 export function getUnwrapDelta( positionBefore, howManyChildren, version ) {
 	const delta = new UnwrapDelta();
 
-	const sourcePosition = Position.createFromPosition( positionBefore );
-	sourcePosition.path.push( 0 );
+	const sourcePosition = positionBefore.getMovedToChild();
 
 	const move = new MoveOperation( sourcePosition, howManyChildren, positionBefore, version );
 	move.isSticky = true;
 
-	const removePosition = Position.createFromPosition( positionBefore );
-	removePosition.offset += howManyChildren;
+	const removePosition = positionBefore.getShiftedBy( howManyChildren );
 
 	const gy = sourcePosition.root.document.graveyard;
 	const gyPos = Position.createAt( gy, 0 );

+ 2 - 2
packages/ckeditor5-engine/tests/model/delta/transform/movedelta.js

@@ -135,8 +135,8 @@ describe( 'transform', () => {
 			} );
 
 			it( 'move range in merged node #2', () => {
-				moveDelta._moveOperation.sourcePosition.path = [ 3, 3, 1 ];
-				moveDelta._moveOperation.targetPosition.path = [ 3, 3, 4 ];
+				moveDelta._moveOperation.sourcePosition = new Position( root, [ 3, 3, 1 ] );
+				moveDelta._moveOperation.targetPosition = new Position( root, [ 3, 3, 4 ] );
 
 				const mergePosition = new Position( root, [ 3, 3 ] );
 				const mergeDelta = getMergeDelta( mergePosition, 1, 4, baseVersion );

+ 5 - 8
packages/ckeditor5-engine/tests/model/delta/transform/splitdelta.js

@@ -6,6 +6,7 @@
 import transformations from '../../../../src/model/delta/basic-transformations'; // eslint-disable-line no-unused-vars
 
 import deltaTransform from '../../../../src/model/delta/transform';
+
 const transform = deltaTransform.transform;
 
 import Element from '../../../../src/model/element';
@@ -967,10 +968,8 @@ describe( 'transform', () => {
 				baseVersion = removeDelta.operations.length;
 
 				const newInsertPosition = removeOperation.targetPosition.getShiftedBy( 2 );
-				const newMoveSourcePosition = removeOperation.targetPosition.getShiftedBy( 1 );
-				newMoveSourcePosition.path.push( 2 );
-				const newMoveTargetPosition = Position.createAt( newInsertPosition );
-				newMoveTargetPosition.path.push( 0 );
+				const newMoveSourcePosition = removeOperation.targetPosition.getShiftedBy( 1 ).getMovedToChild( 2 );
+				const newMoveTargetPosition = newInsertPosition.getMovedToChild( );
 
 				expectDelta( transformed[ 0 ], {
 					type: SplitDelta,
@@ -1003,10 +1002,8 @@ describe( 'transform', () => {
 				baseVersion = removeDelta.operations.length;
 
 				const newInsertPosition = removeOperation.targetPosition.getShiftedBy( 2 );
-				const newMoveSourcePosition = removeOperation.targetPosition.getShiftedBy( 1 );
-				newMoveSourcePosition.path.push( 3 );
-				const newMoveTargetPosition = Position.createAt( newInsertPosition );
-				newMoveTargetPosition.path.push( 0 );
+				const newMoveSourcePosition = removeOperation.targetPosition.getShiftedBy( 1 ).getMovedToChild( 3 );
+				const newMoveTargetPosition = newInsertPosition.getMovedToChild( );
 
 				expectDelta( transformed[ 0 ], {
 					type: SplitDelta,

+ 5 - 5
packages/ckeditor5-engine/tests/model/liverange.js

@@ -208,7 +208,7 @@ describe( 'LiveRange', () => {
 			} );
 
 			it( 'is at the live range start position and live range is collapsed', () => {
-				live.end.path = [ 0, 1, 4 ];
+				live.end = new Position( live.end.root, [ 0, 1, 4 ] );
 
 				const insertRange = new Range( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 1, 8 ] ) );
 
@@ -372,7 +372,7 @@ describe( 'LiveRange', () => {
 			} );
 
 			it( 'is equal to live range', () => {
-				live.end.path = [ 0, 1, 7 ];
+				live.end = new Position( live.end.root, [ 0, 1, 7 ] );
 
 				const moveSource = new Position( root, [ 0, 1, 4 ] );
 				const moveRange = new Range( new Position( root, [ 0, 3, 0 ] ), new Position( root, [ 0, 3, 3 ] ) );
@@ -389,7 +389,7 @@ describe( 'LiveRange', () => {
 			} );
 
 			it( 'contains live range', () => {
-				live.end.path = [ 0, 1, 7 ];
+				live.end = new Position( live.end.root, [ 0, 1, 7 ] );
 
 				const moveSource = new Position( root, [ 0, 1, 3 ] );
 				const moveRange = new Range( new Position( root, [ 0, 3, 0 ] ), new Position( root, [ 0, 3, 9 ] ) );
@@ -406,7 +406,7 @@ describe( 'LiveRange', () => {
 			} );
 
 			it( 'is intersecting with live range and points to live range', () => {
-				live.end.path = [ 0, 1, 12 ];
+				live.end = new Position( live.end.root, [ 0, 1, 12 ] );
 
 				const moveSource = new Position( root, [ 0, 1, 2 ] );
 				const moveRange = new Range( new Position( root, [ 0, 1, 7 ] ), new Position( root, [ 0, 1, 10 ] ) );
@@ -656,7 +656,7 @@ describe( 'LiveRange', () => {
 			} );
 
 			it( 'from the range to the range', () => {
-				live.end.path = [ 0, 1, 12 ];
+				live.end = new Position( live.end.root, [ 0, 1, 12 ] );
 
 				const moveSource = new Position( root, [ 0, 1, 6 ] );
 				const moveRange = new Range( new Position( root, [ 0, 1, 8 ] ), new Position( root, [ 0, 1, 10 ] ) );

Разница между файлами не показана из-за своего большого размера
+ 152 - 146
packages/ckeditor5-engine/tests/model/operation/transform.js


+ 0 - 24
packages/ckeditor5-engine/tests/model/position.js

@@ -291,14 +291,6 @@ describe( 'Position', () => {
 		expect( new Position( root, [ 1, 0, 3 ] ) ).to.have.property( 'index' ).that.equals( 1 );
 	} );
 
-	it( 'should be able to set offset', () => {
-		const position = new Position( root, [ 1, 0, 2 ] );
-		position.offset = 4;
-
-		expect( position.offset ).to.equal( 4 );
-		expect( position.path ).to.deep.equal( [ 1, 0, 4 ] );
-	} );
-
 	it( 'should have nodeBefore if it is not inside a text node', () => {
 		expect( new Position( root, [ 0 ] ).nodeBefore ).to.be.null;
 		expect( new Position( root, [ 1 ] ).nodeBefore ).to.equal( p );
@@ -605,14 +597,6 @@ describe( 'Position', () => {
 	} );
 
 	describe( '_getTransformedByInsertion()', () => {
-		it( 'should return a new Position instance', () => {
-			const position = new Position( root, [ 0 ] );
-			const transformed = position._getTransformedByInsertion( new Position( root, [ 2 ] ), 4, false );
-
-			expect( transformed ).not.to.equal( position );
-			expect( transformed ).to.be.instanceof( Position );
-		} );
-
 		it( 'should increment offset if insertion is in the same parent and closer offset', () => {
 			const position = new Position( root, [ 1, 2, 3 ] );
 			const transformed = position._getTransformedByInsertion( new Position( root, [ 1, 2, 2 ] ), 2, false );
@@ -665,14 +649,6 @@ describe( 'Position', () => {
 	} );
 
 	describe( '_getTransformedByDeletion()', () => {
-		it( 'should return a new Position instance', () => {
-			const position = new Position( root, [ 0 ] );
-			const transformed = position._getTransformedByDeletion( new Position( root, [ 2 ] ), 4 );
-
-			expect( transformed ).not.to.equal( position );
-			expect( transformed ).to.be.instanceof( Position );
-		} );
-
 		it( 'should return null if original position is inside one of removed nodes', () => {
 			const position = new Position( root, [ 1, 2 ] );
 			const transformed = position._getTransformedByDeletion( new Position( root, [ 0 ] ), 2 );

+ 2 - 1
packages/ckeditor5-engine/tests/model/range.js

@@ -855,7 +855,8 @@ describe( 'Range', () => {
 			} );
 
 			it( 'move inside the range', () => {
-				range.end.offset = 6;
+				range.end = range.end.getShiftedTo( 6 );
+
 				const start = new Position( root, [ 3 ] );
 				const target = new Position( root, [ 5 ] );
 				const delta = getMoveDelta( start, 1, target, 1 );

Некоторые файлы не были показаны из-за большого количества измененных файлов