Browse Source

Split view selection into Selection and DocumentSelection.

Szymon Kupś 7 years ago
parent
commit
a82cae2688

+ 22 - 372
packages/ckeditor5-engine/src/view/documentselection.js

@@ -7,15 +7,9 @@
  * @module engine/view/documentselection
  */
 
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import Range from './range';
-import Position from './position';
+import Selection from './selection';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
-import Node from './node';
-import Element from './element';
-import count from '@ckeditor/ckeditor5-utils/src/count';
-import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
 
 /**
  * Class representing selection in tree view.
@@ -93,39 +87,10 @@ export default class DocumentSelection {
 	 * @param {String} [options.label] Label for the fake selection.
 	 */
 	constructor( selectable = null, placeOrOffset, options ) {
-		/**
-		 * Stores all ranges that are selected.
-		 *
-		 * @protected
-		 * @member {Array.<module:engine/view/range~Range>}
-		 */
-		this._ranges = [];
+		this._selection = new Selection( selectable, placeOrOffset, options );
 
-		/**
-		 * Specifies whether the last added range was added as a backward or forward range.
-		 *
-		 * @protected
-		 * @member {Boolean}
-		 */
-		this._lastRangeBackward = false;
-
-		/**
-		 * Specifies whether selection instance is fake.
-		 *
-		 * @private
-		 * @member {Boolean}
-		 */
-		this._isFake = false;
-
-		/**
-		 * Fake selection's label.
-		 *
-		 * @private
-		 * @member {String}
-		 */
-		this._fakeSelectionLabel = '';
-
-		this._setTo( selectable, placeOrOffset, options );
+		// Delegate change event to be fired on DocumentSelection instance.
+		this._selection.delegate( 'change' ).to( this );
 	}
 
 	/**
@@ -135,7 +100,7 @@ export default class DocumentSelection {
 	 * @returns {Boolean}
 	 */
 	get isFake() {
-		return this._isFake;
+		return this._selection.isFake;
 	}
 
 	/**
@@ -145,7 +110,7 @@ export default class DocumentSelection {
 	 * @returns {String}
 	 */
 	get fakeSelectionLabel() {
-		return this._fakeSelectionLabel;
+		return this._selection.fakeSelectionLabel;
 	}
 
 	/**
@@ -158,13 +123,7 @@ export default class DocumentSelection {
 	 * @type {module:engine/view/position~Position}
 	 */
 	get anchor() {
-		if ( !this._ranges.length ) {
-			return null;
-		}
-		const range = this._ranges[ this._ranges.length - 1 ];
-		const anchor = this._lastRangeBackward ? range.end : range.start;
-
-		return Position.createFromPosition( anchor );
+		return this._selection.anchor;
 	}
 
 	/**
@@ -174,13 +133,7 @@ export default class DocumentSelection {
 	 * @type {module:engine/view/position~Position}
 	 */
 	get focus() {
-		if ( !this._ranges.length ) {
-			return null;
-		}
-		const range = this._ranges[ this._ranges.length - 1 ];
-		const focus = this._lastRangeBackward ? range.start : range.end;
-
-		return Position.createFromPosition( focus );
+		return this._selection.focus;
 	}
 
 	/**
@@ -190,7 +143,7 @@ export default class DocumentSelection {
 	 * @type {Boolean}
 	 */
 	get isCollapsed() {
-		return this.rangeCount === 1 && this._ranges[ 0 ].isCollapsed;
+		return this._selection.isCollapsed;
 	}
 
 	/**
@@ -199,7 +152,7 @@ export default class DocumentSelection {
 	 * @type {Number}
 	 */
 	get rangeCount() {
-		return this._ranges.length;
+		return this._selection.rangeCount;
 	}
 
 	/**
@@ -208,7 +161,7 @@ export default class DocumentSelection {
 	 * @type {Boolean}
 	 */
 	get isBackward() {
-		return !this.isCollapsed && this._lastRangeBackward;
+		return this._selection.isBackward;
 	}
 
 	/**
@@ -218,11 +171,7 @@ export default class DocumentSelection {
 	 * @type {module:engine/view/editableelement~EditableElement|null}
 	 */
 	get editableElement() {
-		if ( this.anchor ) {
-			return this.anchor.editableElement;
-		}
-
-		return null;
+		return this._selection.editableElement;
 	}
 
 	/**
@@ -231,9 +180,7 @@ export default class DocumentSelection {
 	 * @returns {Iterable.<module:engine/view/range~Range>}
 	 */
 	* getRanges() {
-		for ( const range of this._ranges ) {
-			yield Range.createFromRange( range );
-		}
+		yield* this._selection.getRanges();
 	}
 
 	/**
@@ -245,15 +192,7 @@ export default class DocumentSelection {
 	 * @returns {module:engine/view/range~Range|null}
 	 */
 	getFirstRange() {
-		let first = null;
-
-		for ( const range of this._ranges ) {
-			if ( !first || range.start.isBefore( first.start ) ) {
-				first = range;
-			}
-		}
-
-		return first ? Range.createFromRange( first ) : null;
+		return this._selection.getFirstRange();
 	}
 
 	/**
@@ -264,15 +203,7 @@ export default class DocumentSelection {
 	 * @returns {module:engine/view/range~Range|null}
 	 */
 	getLastRange() {
-		let last = null;
-
-		for ( const range of this._ranges ) {
-			if ( !last || range.end.isAfter( last.end ) ) {
-				last = range;
-			}
-		}
-
-		return last ? Range.createFromRange( last ) : null;
+		return this._selection.getLastRange();
 	}
 
 	/**
@@ -283,9 +214,7 @@ export default class DocumentSelection {
 	 * @returns {module:engine/view/position~Position|null}
 	 */
 	getFirstPosition() {
-		const firstRange = this.getFirstRange();
-
-		return firstRange ? Position.createFromPosition( firstRange.start ) : null;
+		return this._selection.getFirstPosition();
 	}
 
 	/**
@@ -296,104 +225,7 @@ export default class DocumentSelection {
 	 * @returns {module:engine/view/position~Position|null}
 	 */
 	getLastPosition() {
-		const lastRange = this.getLastRange();
-
-		return lastRange ? Position.createFromPosition( lastRange.end ) : null;
-	}
-
-	/**
-	 * Checks whether, this selection is equal to given selection. Selections are equal if they have same directions,
-	 * same number of ranges and all ranges from one selection equal to a range from other selection.
-	 *
-	 * @param {module:engine/view/documentselection~DocumentSelection} otherSelection Selection to compare with.
-	 * @returns {Boolean} `true` if selections are equal, `false` otherwise.
-	 */
-	isEqual( otherSelection ) {
-		if ( this.isFake != otherSelection.isFake ) {
-			return false;
-		}
-
-		if ( this.isFake && this.fakeSelectionLabel != otherSelection.fakeSelectionLabel ) {
-			return false;
-		}
-
-		if ( this.rangeCount != otherSelection.rangeCount ) {
-			return false;
-		} else if ( this.rangeCount === 0 ) {
-			return true;
-		}
-
-		if ( !this.anchor.isEqual( otherSelection.anchor ) || !this.focus.isEqual( otherSelection.focus ) ) {
-			return false;
-		}
-
-		for ( const thisRange of this._ranges ) {
-			let found = false;
-
-			for ( const otherRange of otherSelection._ranges ) {
-				if ( thisRange.isEqual( otherRange ) ) {
-					found = true;
-					break;
-				}
-			}
-
-			if ( !found ) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * Checks whether this selection is similar to given selection. Selections are similar if they have same directions, same
-	 * number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
-	 * equal to any trimmed range from other selection.
-	 *
-	 * @param {module:engine/view/documentselection~DocumentSelection} otherSelection Selection to compare with.
-	 * @returns {Boolean} `true` if selections are similar, `false` otherwise.
-	 */
-	isSimilar( otherSelection ) {
-		if ( this.isBackward != otherSelection.isBackward ) {
-			return false;
-		}
-
-		const numOfRangesA = count( this.getRanges() );
-		const numOfRangesB = count( otherSelection.getRanges() );
-
-		// If selections have different number of ranges, they cannot be similar.
-		if ( numOfRangesA != numOfRangesB ) {
-			return false;
-		}
-
-		// If both selections have no ranges, they are similar.
-		if ( numOfRangesA == 0 ) {
-			return true;
-		}
-
-		// Check if each range in one selection has a similar range in other selection.
-		for ( let rangeA of this.getRanges() ) {
-			rangeA = rangeA.getTrimmed();
-
-			let found = false;
-
-			for ( let rangeB of otherSelection.getRanges() ) {
-				rangeB = rangeB.getTrimmed();
-
-				if ( rangeA.start.isEqual( rangeB.start ) && rangeA.end.isEqual( rangeB.end ) ) {
-					found = true;
-					break;
-				}
-			}
-
-			// For `rangeA`, neither range in `otherSelection` was similar. So selections are not similar.
-			if ( !found ) {
-				return false;
-			}
-		}
-
-		// There were no ranges that weren't matched. Selections are similar.
-		return true;
+		return this._selection.getLastPosition();
 	}
 
 	/**
@@ -404,15 +236,7 @@ export default class DocumentSelection {
 	 * @returns {module:engine/view/element~Element|null}
 	 */
 	getSelectedElement() {
-		if ( this.rangeCount !== 1 ) {
-			return null;
-		}
-
-		const range = this.getFirstRange();
-		const nodeAfterStart = range.start.nodeAfter;
-		const nodeBeforeEnd = range.end.nodeBefore;
-
-		return ( nodeAfterStart instanceof Element && nodeAfterStart == nodeBeforeEnd ) ? nodeAfterStart : null;
+		return this._selection.getSelectedElement();
 	}
 
 	/**
@@ -478,81 +302,7 @@ export default class DocumentSelection {
 	 * @param {String} [options.label] Label for the fake selection.
 	 */
 	_setTo( selectable, placeOrOffset, options ) {
-		if ( selectable === null ) {
-			this._setRanges( [] );
-			this._setFakeOptions( placeOrOffset );
-		} else if ( selectable instanceof DocumentSelection ) {
-			this._setRanges( selectable.getRanges(), selectable.isBackward );
-			this._setFakeOptions( { fake: selectable.isFake, label: selectable.fakeSelectionLabel } );
-		} else if ( selectable instanceof Range ) {
-			this._setRanges( [ selectable ], placeOrOffset && placeOrOffset.backward );
-			this._setFakeOptions( placeOrOffset );
-		} else if ( selectable instanceof Position ) {
-			this._setRanges( [ new Range( selectable ) ] );
-			this._setFakeOptions( placeOrOffset );
-		} else if ( selectable instanceof Node ) {
-			const backward = !!options && !!options.backward;
-			let range;
-
-			if ( placeOrOffset === undefined ) {
-				/**
-				 * selection.setTo requires the second parameter when the first parameter is a node.
-				 *
-				 * @error view-selection-setTo-required-second-parameter
-				 */
-				throw new CKEditorError(
-					'view-selection-setTo-required-second-parameter: ' +
-					'selection.setTo requires the second parameter when the first parameter is a node.'
-				);
-			} else if ( placeOrOffset == 'in' ) {
-				range = Range.createIn( selectable );
-			} else if ( placeOrOffset == 'on' ) {
-				range = Range.createOn( selectable );
-			} else {
-				range = Range.createCollapsedAt( selectable, placeOrOffset );
-			}
-
-			this._setRanges( [ range ], backward );
-			this._setFakeOptions( options );
-		} else if ( isIterable( selectable ) ) {
-			// We assume that the selectable is an iterable of ranges.
-			// Array.from() is used to prevent setting ranges to the old iterable
-			this._setRanges( selectable, placeOrOffset && placeOrOffset.backward );
-			this._setFakeOptions( placeOrOffset );
-		} else {
-			/**
-			 * Cannot set selection to given place.
-			 *
-			 * @error view-selection-setTo-not-selectable
-			 */
-			throw new CKEditorError( 'view-selection-setTo-not-selectable: Cannot set selection to given place.' );
-		}
-
-		this.fire( 'change' );
-	}
-
-	/**
-	 * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
-	 * is treated like the last added range and is used to set {@link #anchor anchor} and {@link #focus focus}.
-	 * Accepts a flag describing in which way the selection is made.
-	 *
-	 * @private
-	 * @param {Iterable.<module:engine/view/range~Range>} newRanges Iterable object of ranges to set.
-	 * @param {Boolean} [isLastBackward=false] Flag describing if last added range was selected forward - from start to end
-	 * (`false`) or backward - from end to start (`true`). Defaults to `false`.
-	 */
-	_setRanges( newRanges, isLastBackward = false ) {
-		// New ranges should be copied to prevent removing them by setting them to `[]` first.
-		// Only applies to situations when selection is set to the same selection or same selection's ranges.
-		newRanges = Array.from( newRanges );
-
-		this._ranges = [];
-
-		for ( const range of newRanges ) {
-			this._addRange( range );
-		}
-
-		this._lastRangeBackward = !!isLastBackward;
+		this._selection.setTo( selectable, placeOrOffset, options );
 	}
 
 	/**
@@ -567,114 +317,14 @@ export default class DocumentSelection {
 	 * first parameter is a {@link module:engine/view/item~Item view item}.
 	 */
 	_setFocus( itemOrPosition, offset ) {
-		if ( this.anchor === null ) {
-			/**
-			 * Cannot set selection focus if there are no ranges in selection.
-			 *
-			 * @error view-selection-setFocus-no-ranges
-			 */
-			throw new CKEditorError(
-				'view-selection-setFocus-no-ranges: Cannot set selection focus if there are no ranges in selection.'
-			);
-		}
-
-		const newFocus = Position.createAt( itemOrPosition, offset );
-
-		if ( newFocus.compareWith( this.focus ) == 'same' ) {
-			return;
-		}
-
-		const anchor = this.anchor;
-
-		this._ranges.pop();
-
-		if ( newFocus.compareWith( anchor ) == 'before' ) {
-			this._addRange( new Range( newFocus, anchor ), true );
-		} else {
-			this._addRange( new Range( anchor, newFocus ) );
-		}
-
-		this.fire( 'change' );
+		this._selection.setFocus( itemOrPosition, offset );
 	}
 
 	/**
-	 * Sets this selection instance to be marked as `fake`. A fake selection does not render as browser native selection
-	 * over selected elements and is hidden to the user. This way, no native selection UI artifacts are displayed to
-	 * the user and selection over elements can be represented in other way, for example by applying proper CSS class.
-	 *
-	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM (and be
-	 * properly handled by screen readers).
+	 * Fired whenever selection ranges are changed through {@link ~DocumentSelection Selection API}.
 	 *
-	 * @private
-	 * @param {Object} [options] Options.
-	 * @param {Boolean} [options.fake] If set to true selection will be marked as `fake`.
-	 * @param {String} [options.label=''] Fake selection label.
+	 * @event change
 	 */
-	_setFakeOptions( options = {} ) {
-		this._isFake = !!options.fake;
-		this._fakeSelectionLabel = options.fake ? options.label || '' : '';
-	}
-
-	/**
-	 * Adds a range to the selection. Added range is copied. This means that passed range is not saved in the
-	 * selection instance and you can safely operate on it.
-	 *
-	 * Accepts a flag describing in which way the selection is made - passed range might be selected from
-	 * {@link module:engine/view/range~Range#start start} to {@link module:engine/view/range~Range#end end}
-	 * or from {@link module:engine/view/range~Range#end end} to {@link module:engine/view/range~Range#start start}.
-	 * The flag is used to set {@link #anchor anchor} and {@link #focus focus} properties.
-	 *
-	 * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
-	 * with ranges already stored in Selection instance.
-	 *
-	 * @private
-	 * @fires change
-	 * @param {module:engine/view/range~Range} range
-	 * @param {Boolean} [isBackward]
-	 */
-	_addRange( range, isBackward = false ) {
-		if ( !( range instanceof Range ) ) {
-			throw new CKEditorError( 'view-selection-invalid-range: Invalid Range.' );
-		}
-
-		this._pushRange( range );
-		this._lastRangeBackward = !!isBackward;
-	}
-
-	/**
-	 * Adds range to selection - creates copy of given range so it can be safely used and modified.
-	 *
-	 * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
-	 * with ranges already stored in selection instance.
-	 *
-	 * @private
-	 * @param {module:engine/view/range~Range} range
-	 */
-	_pushRange( range ) {
-		for ( const storedRange of this._ranges ) {
-			if ( range.isIntersecting( storedRange ) ) {
-				/**
-				 * Trying to add a range that intersects with another range from selection.
-				 *
-				 * @error view-selection-range-intersects
-				 * @param {module:engine/view/range~Range} addedRange Range that was added to the selection.
-				 * @param {module:engine/view/range~Range} intersectingRange Range from selection that intersects with `addedRange`.
-				 */
-				throw new CKEditorError(
-					'view-selection-range-intersects: Trying to add a range that intersects with another range from selection.',
-					{ addedRange: range, intersectingRange: storedRange }
-				);
-			}
-		}
-
-		this._ranges.push( Range.createFromRange( range ) );
-	}
 }
 
 mix( DocumentSelection, EmitterMixin );
-
-/**
- * Fired whenever selection ranges are changed through {@link ~DocumentSelection Selection API}.
- *
- * @event change
- */

+ 681 - 0
packages/ckeditor5-engine/src/view/selection.js

@@ -0,0 +1,681 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module engine/view/selection
+ */
+
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import Range from './range';
+import Position from './position';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import Node from './node';
+import Element from './element';
+import count from '@ckeditor/ckeditor5-utils/src/count';
+import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
+import DocumentSelection from './documentselection';
+
+/**
+ * Class representing selection in tree view.
+ *
+ * Selection can consist of {@link module:engine/view/range~Range ranges} that can be set using
+ * {@link module:engine/view/documentselection~DocumentSelection#_setTo} method.
+ * That method create copies of provided ranges and store those copies internally. Further modifications to passed
+ * ranges will not change selection's state.
+ * Selection's ranges can be obtained via {@link module:engine/view/documentselection~DocumentSelection#getRanges getRanges},
+ * {@link module:engine/view/documentselection~DocumentSelection#getFirstRange getFirstRange}
+ * and {@link module:engine/view/documentselection~DocumentSelection#getLastRange getLastRange}
+ * methods, which return copies of ranges stored inside selection. Modifications made on these copies will not change
+ * selection's state. Similar situation occurs when getting {@link module:engine/view/documentselection~DocumentSelection#anchor anchor},
+ * {@link module:engine/view/documentselection~DocumentSelection#focus focus},
+ * {@link module:engine/view/documentselection~DocumentSelection#getFirstPosition first} and
+ * {@link module:engine/view/documentselection~DocumentSelection#getLastPosition last} positions - all will return
+ * copies of requested positions.
+ */
+export default class Selection {
+	/**
+	 * Creates new selection instance.
+	 *
+	 * 		// Creates empty selection without ranges.
+	 *		const selection = new Selection();
+	 *
+	 *		// Creates selection at the given range.
+	 *		const range = new Range( start, end );
+	 *		const selection = new Selection( range );
+	 *
+	 *		// Creates selection at the given ranges
+	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
+	 *		const selection = new Selection( ranges );
+	 *
+	 *		// Creates selection from the other selection.
+	 *		const otherSelection = new Selection();
+	 *		const selection = new Selection( otherSelection );
+	 *
+	 * 		// Creates selection at the given position.
+	 *		const position = new Position( root, path );
+	 *		const selection = new Selection( position );
+	 *
+	 *		// Creates collapsed selection at the position of given item and offset.
+	 *		const paragraph = writer.createElement( 'paragraph' );
+	 *		const selection = new Selection( paragraph, offset );
+	 *
+	 *		// Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
+	 *		// first child of that element and ends after the last child of that element.
+	 *		const selection = new Selection( paragraph, 'in' );
+	 *
+	 *		// Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
+	 *		// just after the item.
+	 *		const selection = new Selection( paragraph, 'on' );
+	 *
+	 * `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+	 *
+	 *		// Creates backward selection.
+	 *		const selection = new Selection( range, { backward: true } );
+	 *
+	 * Fake selection does not render as browser native selection over selected elements and is hidden to the user.
+	 * This way, no native selection UI artifacts are displayed to the user and selection over elements can be
+	 * represented in other way, for example by applying proper CSS class.
+	 *
+	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
+	 * (and be  properly handled by screen readers).
+	 *
+	 *		// Creates fake selection with label.
+	 *		const selection = new Selection( range, { fake: true, label: 'foo' } );
+	 *
+	 * @param {module:engine/view/documentselection~DocumentSelection|module:engine/view/position~Position|
+	 * Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|module:engine/view/item~Item|null} [selectable=null]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
+	 * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
+	 * @param {String} [options.label] Label for the fake selection.
+	 */
+	constructor( selectable = null, placeOrOffset, options ) {
+		/**
+		 * Stores all ranges that are selected.
+		 *
+		 * @protected
+		 * @member {Array.<module:engine/view/range~Range>}
+		 */
+		this._ranges = [];
+
+		/**
+		 * Specifies whether the last added range was added as a backward or forward range.
+		 *
+		 * @protected
+		 * @member {Boolean}
+		 */
+		this._lastRangeBackward = false;
+
+		/**
+		 * Specifies whether selection instance is fake.
+		 *
+		 * @private
+		 * @member {Boolean}
+		 */
+		this._isFake = false;
+
+		/**
+		 * Fake selection's label.
+		 *
+		 * @private
+		 * @member {String}
+		 */
+		this._fakeSelectionLabel = '';
+
+		this.setTo( selectable, placeOrOffset, options );
+	}
+
+	/**
+	 * Returns true if selection instance is marked as `fake`.
+	 *
+	 * @see #_setTo
+	 * @returns {Boolean}
+	 */
+	get isFake() {
+		return this._isFake;
+	}
+
+	/**
+	 * Returns fake selection label.
+	 *
+	 * @see #_setTo
+	 * @returns {String}
+	 */
+	get fakeSelectionLabel() {
+		return this._fakeSelectionLabel;
+	}
+
+	/**
+	 * Selection anchor. Anchor may be described as a position where the selection starts. Together with
+	 * {@link #focus focus} they define the direction of selection, which is important
+	 * when expanding/shrinking selection. Anchor is always the start or end of the most recent added range.
+	 * It may be a bit unintuitive when there are multiple ranges in selection.
+	 *
+	 * @see #focus
+	 * @type {module:engine/view/position~Position}
+	 */
+	get anchor() {
+		if ( !this._ranges.length ) {
+			return null;
+		}
+		const range = this._ranges[ this._ranges.length - 1 ];
+		const anchor = this._lastRangeBackward ? range.end : range.start;
+
+		return Position.createFromPosition( anchor );
+	}
+
+	/**
+	 * Selection focus. Focus is a position where the selection ends.
+	 *
+	 * @see #anchor
+	 * @type {module:engine/view/position~Position}
+	 */
+	get focus() {
+		if ( !this._ranges.length ) {
+			return null;
+		}
+		const range = this._ranges[ this._ranges.length - 1 ];
+		const focus = this._lastRangeBackward ? range.start : range.end;
+
+		return Position.createFromPosition( focus );
+	}
+
+	/**
+	 * Returns whether the selection is collapsed. Selection is collapsed when there is exactly one range which is
+	 * collapsed.
+	 *
+	 * @type {Boolean}
+	 */
+	get isCollapsed() {
+		return this.rangeCount === 1 && this._ranges[ 0 ].isCollapsed;
+	}
+
+	/**
+	 * Returns number of ranges in selection.
+	 *
+	 * @type {Number}
+	 */
+	get rangeCount() {
+		return this._ranges.length;
+	}
+
+	/**
+	 * Specifies whether the {@link #focus} precedes {@link #anchor}.
+	 *
+	 * @type {Boolean}
+	 */
+	get isBackward() {
+		return !this.isCollapsed && this._lastRangeBackward;
+	}
+
+	/**
+	 * {@link module:engine/view/editableelement~EditableElement EditableElement} instance that contains this selection, or `null`
+	 * if the selection is not inside an editable element.
+	 *
+	 * @type {module:engine/view/editableelement~EditableElement|null}
+	 */
+	get editableElement() {
+		if ( this.anchor ) {
+			return this.anchor.editableElement;
+		}
+
+		return null;
+	}
+
+	/**
+	 * Returns an iterable that contains copies of all ranges added to the selection.
+	 *
+	 * @returns {Iterable.<module:engine/view/range~Range>}
+	 */
+	* getRanges() {
+		for ( const range of this._ranges ) {
+			yield Range.createFromRange( range );
+		}
+	}
+
+	/**
+	 * Returns copy of the first range in the selection. First range is the one which
+	 * {@link module:engine/view/range~Range#start start} position {@link module:engine/view/position~Position#isBefore is before} start
+	 * position of all other ranges (not to confuse with the first range added to the selection).
+	 * Returns `null` if no ranges are added to selection.
+	 *
+	 * @returns {module:engine/view/range~Range|null}
+	 */
+	getFirstRange() {
+		let first = null;
+
+		for ( const range of this._ranges ) {
+			if ( !first || range.start.isBefore( first.start ) ) {
+				first = range;
+			}
+		}
+
+		return first ? Range.createFromRange( first ) : null;
+	}
+
+	/**
+	 * Returns copy of the last range in the selection. Last range is the one which {@link module:engine/view/range~Range#end end}
+	 * position {@link module:engine/view/position~Position#isAfter is after} end position of all other ranges (not to confuse
+	 * with the last range added to the selection). Returns `null` if no ranges are added to selection.
+	 *
+	 * @returns {module:engine/view/range~Range|null}
+	 */
+	getLastRange() {
+		let last = null;
+
+		for ( const range of this._ranges ) {
+			if ( !last || range.end.isAfter( last.end ) ) {
+				last = range;
+			}
+		}
+
+		return last ? Range.createFromRange( last ) : null;
+	}
+
+	/**
+	 * Returns copy of the first position in the selection. First position is the position that
+	 * {@link module:engine/view/position~Position#isBefore is before} any other position in the selection ranges.
+	 * Returns `null` if no ranges are added to selection.
+	 *
+	 * @returns {module:engine/view/position~Position|null}
+	 */
+	getFirstPosition() {
+		const firstRange = this.getFirstRange();
+
+		return firstRange ? Position.createFromPosition( firstRange.start ) : null;
+	}
+
+	/**
+	 * Returns copy of the last position in the selection. Last position is the position that
+	 * {@link module:engine/view/position~Position#isAfter is after} any other position in the selection ranges.
+	 * Returns `null` if no ranges are added to selection.
+	 *
+	 * @returns {module:engine/view/position~Position|null}
+	 */
+	getLastPosition() {
+		const lastRange = this.getLastRange();
+
+		return lastRange ? Position.createFromPosition( lastRange.end ) : null;
+	}
+
+	/**
+	 * Checks whether, this selection is equal to given selection. Selections are equal if they have same directions,
+	 * same number of ranges and all ranges from one selection equal to a range from other selection.
+	 *
+	 * @param {module:engine/view/documentselection~DocumentSelection} otherSelection Selection to compare with.
+	 * @returns {Boolean} `true` if selections are equal, `false` otherwise.
+	 */
+	isEqual( otherSelection ) {
+		if ( this.isFake != otherSelection.isFake ) {
+			return false;
+		}
+
+		if ( this.isFake && this.fakeSelectionLabel != otherSelection.fakeSelectionLabel ) {
+			return false;
+		}
+
+		if ( this.rangeCount != otherSelection.rangeCount ) {
+			return false;
+		} else if ( this.rangeCount === 0 ) {
+			return true;
+		}
+
+		if ( !this.anchor.isEqual( otherSelection.anchor ) || !this.focus.isEqual( otherSelection.focus ) ) {
+			return false;
+		}
+
+		for ( const thisRange of this._ranges ) {
+			let found = false;
+
+			for ( const otherRange of otherSelection._ranges ) {
+				if ( thisRange.isEqual( otherRange ) ) {
+					found = true;
+					break;
+				}
+			}
+
+			if ( !found ) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Checks whether this selection is similar to given selection. Selections are similar if they have same directions, same
+	 * number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
+	 * equal to any trimmed range from other selection.
+	 *
+	 * @param {module:engine/view/documentselection~DocumentSelection} otherSelection Selection to compare with.
+	 * @returns {Boolean} `true` if selections are similar, `false` otherwise.
+	 */
+	isSimilar( otherSelection ) {
+		if ( this.isBackward != otherSelection.isBackward ) {
+			return false;
+		}
+
+		const numOfRangesA = count( this.getRanges() );
+		const numOfRangesB = count( otherSelection.getRanges() );
+
+		// If selections have different number of ranges, they cannot be similar.
+		if ( numOfRangesA != numOfRangesB ) {
+			return false;
+		}
+
+		// If both selections have no ranges, they are similar.
+		if ( numOfRangesA == 0 ) {
+			return true;
+		}
+
+		// Check if each range in one selection has a similar range in other selection.
+		for ( let rangeA of this.getRanges() ) {
+			rangeA = rangeA.getTrimmed();
+
+			let found = false;
+
+			for ( let rangeB of otherSelection.getRanges() ) {
+				rangeB = rangeB.getTrimmed();
+
+				if ( rangeA.start.isEqual( rangeB.start ) && rangeA.end.isEqual( rangeB.end ) ) {
+					found = true;
+					break;
+				}
+			}
+
+			// For `rangeA`, neither range in `otherSelection` was similar. So selections are not similar.
+			if ( !found ) {
+				return false;
+			}
+		}
+
+		// There were no ranges that weren't matched. Selections are similar.
+		return true;
+	}
+
+	/**
+	 * Returns the selected element. {@link module:engine/view/element~Element Element} is considered as selected if there is only
+	 * one range in the selection, and that range contains exactly one element.
+	 * Returns `null` if there is no selected element.
+	 *
+	 * @returns {module:engine/view/element~Element|null}
+	 */
+	getSelectedElement() {
+		if ( this.rangeCount !== 1 ) {
+			return null;
+		}
+
+		const range = this.getFirstRange();
+		const nodeAfterStart = range.start.nodeAfter;
+		const nodeBeforeEnd = range.end.nodeBefore;
+
+		return ( nodeAfterStart instanceof Element && nodeAfterStart == nodeBeforeEnd ) ? nodeAfterStart : null;
+	}
+
+	/**
+	 * Sets this selection's ranges and direction to the specified location based on the given
+	 * {@link module:engine/view/documentselection~DocumentSelection selection}, {@link module:engine/view/position~Position position},
+	 * {@link module:engine/view/item~Item item}, {@link module:engine/view/range~Range range},
+	 * an iterable of {@link module:engine/view/range~Range ranges} or null.
+	 *
+	 *		// Sets selection to the given range.
+	 *		const range = new Range( start, end );
+	 *		selection._setTo( range );
+	 *
+	 *		// Sets selection to given ranges.
+	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
+	 *		selection._setTo( range );
+	 *
+	 *		// Sets selection to the other selection.
+	 *		const otherSelection = new Selection();
+	 *		selection._setTo( otherSelection );
+	 *
+	 * 		// Sets collapsed selection at the given position.
+	 *		const position = new Position( root, path );
+	 *		selection._setTo( position );
+	 *
+	 * 		// Sets collapsed selection at the position of given item and offset.
+	 *		selection._setTo( paragraph, offset );
+	 *
+	 * Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
+	 * that element and ends after the last child of that element.
+	 *
+	 *		selection._setTo( paragraph, 'in' );
+	 *
+	 * Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends just after the item.
+	 *
+	 *		selection._setTo( paragraph, 'on' );
+	 *
+	 * 		// Clears selection. Removes all ranges.
+	 *		selection._setTo( null );
+	 *
+	 * `Selection#_setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+	 *
+	 *		// Sets selection as backward.
+	 *		selection._setTo( range, { backward: true } );
+	 *
+	 * Fake selection does not render as browser native selection over selected elements and is hidden to the user.
+	 * This way, no native selection UI artifacts are displayed to the user and selection over elements can be
+	 * represented in other way, for example by applying proper CSS class.
+	 *
+	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
+	 * (and be  properly handled by screen readers).
+	 *
+	 *		// Creates fake selection with label.
+	 *		selection._setTo( range, { fake: true, label: 'foo' } );
+	 *
+	 * @protected
+	 * @fires change
+	 * @param {module:engine/view/documentselection~DocumentSelection|module:engine/view/position~Position|
+	 * Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|module:engine/view/item~Item|null} selectable
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
+	 * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
+	 * @param {String} [options.label] Label for the fake selection.
+	 */
+	setTo( selectable, placeOrOffset, options ) {
+		if ( selectable === null ) {
+			this._setRanges( [] );
+			this._setFakeOptions( placeOrOffset );
+		} else if ( selectable instanceof Selection || selectable instanceof DocumentSelection ) {
+			this._setRanges( selectable.getRanges(), selectable.isBackward );
+			this._setFakeOptions( { fake: selectable.isFake, label: selectable.fakeSelectionLabel } );
+		} else if ( selectable instanceof Range ) {
+			this._setRanges( [ selectable ], placeOrOffset && placeOrOffset.backward );
+			this._setFakeOptions( placeOrOffset );
+		} else if ( selectable instanceof Position ) {
+			this._setRanges( [ new Range( selectable ) ] );
+			this._setFakeOptions( placeOrOffset );
+		} else if ( selectable instanceof Node ) {
+			const backward = !!options && !!options.backward;
+			let range;
+
+			if ( placeOrOffset === undefined ) {
+				/**
+				 * selection.setTo requires the second parameter when the first parameter is a node.
+				 *
+				 * @error view-selection-setTo-required-second-parameter
+				 */
+				throw new CKEditorError(
+					'view-selection-setTo-required-second-parameter: ' +
+					'selection.setTo requires the second parameter when the first parameter is a node.'
+				);
+			} else if ( placeOrOffset == 'in' ) {
+				range = Range.createIn( selectable );
+			} else if ( placeOrOffset == 'on' ) {
+				range = Range.createOn( selectable );
+			} else {
+				range = Range.createCollapsedAt( selectable, placeOrOffset );
+			}
+
+			this._setRanges( [ range ], backward );
+			this._setFakeOptions( options );
+		} else if ( isIterable( selectable ) ) {
+			// We assume that the selectable is an iterable of ranges.
+			// Array.from() is used to prevent setting ranges to the old iterable
+			this._setRanges( selectable, placeOrOffset && placeOrOffset.backward );
+			this._setFakeOptions( placeOrOffset );
+		} else {
+			/**
+			 * Cannot set selection to given place.
+			 *
+			 * @error view-selection-setTo-not-selectable
+			 */
+			throw new CKEditorError( 'view-selection-setTo-not-selectable: Cannot set selection to given place.' );
+		}
+
+		this.fire( 'change' );
+	}
+
+	/**
+	 * Moves {@link #focus} to the specified location.
+	 *
+	 * The location can be specified in the same form as {@link module:engine/view/position~Position.createAt} parameters.
+	 *
+	 * @protected
+	 * @fires change
+	 * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
+	 * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when
+	 * first parameter is a {@link module:engine/view/item~Item view item}.
+	 */
+	setFocus( itemOrPosition, offset ) {
+		if ( this.anchor === null ) {
+			/**
+			 * Cannot set selection focus if there are no ranges in selection.
+			 *
+			 * @error view-selection-setFocus-no-ranges
+			 */
+			throw new CKEditorError(
+				'view-selection-setFocus-no-ranges: Cannot set selection focus if there are no ranges in selection.'
+			);
+		}
+
+		const newFocus = Position.createAt( itemOrPosition, offset );
+
+		if ( newFocus.compareWith( this.focus ) == 'same' ) {
+			return;
+		}
+
+		const anchor = this.anchor;
+
+		this._ranges.pop();
+
+		if ( newFocus.compareWith( anchor ) == 'before' ) {
+			this._addRange( new Range( newFocus, anchor ), true );
+		} else {
+			this._addRange( new Range( anchor, newFocus ) );
+		}
+
+		this.fire( 'change' );
+	}
+
+	/**
+	 * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
+	 * is treated like the last added range and is used to set {@link #anchor anchor} and {@link #focus focus}.
+	 * Accepts a flag describing in which way the selection is made.
+	 *
+	 * @private
+	 * @param {Iterable.<module:engine/view/range~Range>} newRanges Iterable object of ranges to set.
+	 * @param {Boolean} [isLastBackward=false] Flag describing if last added range was selected forward - from start to end
+	 * (`false`) or backward - from end to start (`true`). Defaults to `false`.
+	 */
+	_setRanges( newRanges, isLastBackward = false ) {
+		// New ranges should be copied to prevent removing them by setting them to `[]` first.
+		// Only applies to situations when selection is set to the same selection or same selection's ranges.
+		newRanges = Array.from( newRanges );
+
+		this._ranges = [];
+
+		for ( const range of newRanges ) {
+			this._addRange( range );
+		}
+
+		this._lastRangeBackward = !!isLastBackward;
+	}
+
+	/**
+	 * Sets this selection instance to be marked as `fake`. A fake selection does not render as browser native selection
+	 * over selected elements and is hidden to the user. This way, no native selection UI artifacts are displayed to
+	 * the user and selection over elements can be represented in other way, for example by applying proper CSS class.
+	 *
+	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM (and be
+	 * properly handled by screen readers).
+	 *
+	 * @private
+	 * @param {Object} [options] Options.
+	 * @param {Boolean} [options.fake] If set to true selection will be marked as `fake`.
+	 * @param {String} [options.label=''] Fake selection label.
+	 */
+	_setFakeOptions( options = {} ) {
+		this._isFake = !!options.fake;
+		this._fakeSelectionLabel = options.fake ? options.label || '' : '';
+	}
+
+	/**
+	 * Adds a range to the selection. Added range is copied. This means that passed range is not saved in the
+	 * selection instance and you can safely operate on it.
+	 *
+	 * Accepts a flag describing in which way the selection is made - passed range might be selected from
+	 * {@link module:engine/view/range~Range#start start} to {@link module:engine/view/range~Range#end end}
+	 * or from {@link module:engine/view/range~Range#end end} to {@link module:engine/view/range~Range#start start}.
+	 * The flag is used to set {@link #anchor anchor} and {@link #focus focus} properties.
+	 *
+	 * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
+	 * with ranges already stored in Selection instance.
+	 *
+	 * @private
+	 * @fires change
+	 * @param {module:engine/view/range~Range} range
+	 * @param {Boolean} [isBackward]
+	 */
+	_addRange( range, isBackward = false ) {
+		if ( !( range instanceof Range ) ) {
+			throw new CKEditorError( 'view-selection-invalid-range: Invalid Range.' );
+		}
+
+		this._pushRange( range );
+		this._lastRangeBackward = !!isBackward;
+	}
+
+	/**
+	 * Adds range to selection - creates copy of given range so it can be safely used and modified.
+	 *
+	 * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
+	 * with ranges already stored in selection instance.
+	 *
+	 * @private
+	 * @param {module:engine/view/range~Range} range
+	 */
+	_pushRange( range ) {
+		for ( const storedRange of this._ranges ) {
+			if ( range.isIntersecting( storedRange ) ) {
+				/**
+				 * Trying to add a range that intersects with another range from selection.
+				 *
+				 * @error view-selection-range-intersects
+				 * @param {module:engine/view/range~Range} addedRange Range that was added to the selection.
+				 * @param {module:engine/view/range~Range} intersectingRange Range from selection that intersects with `addedRange`.
+				 */
+				throw new CKEditorError(
+					'view-selection-range-intersects: Trying to add a range that intersects with another range from selection.',
+					{ addedRange: range, intersectingRange: storedRange }
+				);
+			}
+		}
+
+		this._ranges.push( Range.createFromRange( range ) );
+	}
+
+	/**
+	 * Fired whenever selection ranges are changed through {@link ~Selection Selection API}.
+	 *
+	 * @event change
+	 */
+}
+
+mix( Selection, EmitterMixin );

+ 133 - 133
packages/ckeditor5-engine/tests/view/documentselection.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md.
  */
 
-import DocumentSelection from '../../src/view/documentselection';
+import Selection from '../../src/view/selection';
 import Range from '../../src/view/range';
 import Document from '../../src/view/document';
 import Element from '../../src/view/element';
@@ -21,7 +21,7 @@ describe( 'Selection', () => {
 		const text = new Text( 'xxxxxxxxxxxxxxxxxxxx' );
 		el = new Element( 'p', null, text );
 
-		selection = new DocumentSelection();
+		selection = new Selection();
 
 		range1 = Range.createFromParentsAndOffsets( text, 5, text, 10 );
 		range2 = Range.createFromParentsAndOffsets( text, 1, text, 2 );
@@ -30,27 +30,27 @@ describe( 'Selection', () => {
 
 	describe( 'constructor()', () => {
 		it( 'should be able to create an empty selection', () => {
-			const selection = new DocumentSelection();
+			const selection = new Selection();
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [] );
 		} );
 
 		it( 'should be able to create a selection from the given ranges', () => {
 			const ranges = [ range1, range2, range3 ];
-			const selection = new DocumentSelection( ranges );
+			const selection = new Selection( ranges );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( ranges );
 		} );
 
 		it( 'should be able to create a selection from the given ranges and isLastBackward flag', () => {
 			const ranges = [ range1, range2, range3 ];
-			const selection = new DocumentSelection( ranges, { backward: true } );
+			const selection = new Selection( ranges, { backward: true } );
 
 			expect( selection.isBackward ).to.be.true;
 		} );
 
 		it( 'should be able to create a selection from the given range and isLastBackward flag', () => {
-			const selection = new DocumentSelection( range1, { backward: true } );
+			const selection = new Selection( range1, { backward: true } );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
 			expect( selection.isBackward ).to.be.true;
@@ -58,7 +58,7 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a selection from the given iterable of ranges and isLastBackward flag', () => {
 			const ranges = new Set( [ range1, range2, range3 ] );
-			const selection = new DocumentSelection( ranges, { backward: false } );
+			const selection = new Selection( ranges, { backward: false } );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2, range3 ] );
 			expect( selection.isBackward ).to.be.false;
@@ -66,7 +66,7 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a collapsed selection at the given position', () => {
 			const position = range1.start;
-			const selection = new DocumentSelection( position );
+			const selection = new Selection( position );
 
 			expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
 			expect( selection.getFirstRange().start ).to.deep.equal( position );
@@ -76,7 +76,7 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a collapsed selection at the given position', () => {
 			const position = range1.start;
-			const selection = new DocumentSelection( position );
+			const selection = new Selection( position );
 
 			expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
 			expect( selection.getFirstRange().start ).to.deep.equal( position );
@@ -85,16 +85,16 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should be able to create a selection from the other selection', () => {
-			const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
-			const selection = new DocumentSelection( otherSelection );
+			const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
+			const selection = new Selection( otherSelection );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
 			expect( selection.isBackward ).to.be.true;
 		} );
 
 		it( 'should be able to create a fake selection from the other fake selection', () => {
-			const otherSelection = new DocumentSelection( [ range2, range3 ], { fake: true, label: 'foo bar baz' } );
-			const selection = new DocumentSelection( otherSelection );
+			const otherSelection = new Selection( [ range2, range3 ], { fake: true, label: 'foo bar baz' } );
+			const selection = new Selection( otherSelection );
 
 			expect( selection.isFake ).to.be.true;
 			expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
@@ -103,7 +103,7 @@ describe( 'Selection', () => {
 		it( 'should throw an error when range is invalid', () => {
 			expect( () => {
 				// eslint-disable-next-line no-new
-				new DocumentSelection( [ { invalid: 'range' } ] );
+				new Selection( [ { invalid: 'range' } ] );
 			} ).to.throw( CKEditorError, 'view-selection-invalid-range: Invalid Range.' );
 		} );
 
@@ -113,14 +113,14 @@ describe( 'Selection', () => {
 
 			expect( () => {
 				// eslint-disable-next-line no-new
-				new DocumentSelection( [ range1, range2 ] );
+				new Selection( [ range1, range2 ] );
 			} ).to.throw( CKEditorError, 'view-selection-range-intersects' );
 		} );
 
 		it( 'should throw an error when trying to set to not selectable', () => {
 			expect( () => {
 				// eslint-disable-next-line no-new
-				new DocumentSelection( {} );
+				new Selection( {} );
 			} ).to.throw( /view-selection-setTo-not-selectable/ );
 		} );
 	} );
@@ -131,7 +131,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return start of single range in selection', () => {
-			selection._setTo( range1 );
+			selection.setTo( range1 );
 			const anchor = selection.anchor;
 
 			expect( anchor.isEqual( range1.start ) ).to.be.true;
@@ -139,7 +139,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return end of single range in selection when added as backward', () => {
-			selection._setTo( range1, { backward: true } );
+			selection.setTo( range1, { backward: true } );
 			const anchor = selection.anchor;
 
 			expect( anchor.isEqual( range1.end ) ).to.be.true;
@@ -147,7 +147,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should get anchor from last inserted range', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
 			expect( selection.anchor.isEqual( range2.start ) ).to.be.true;
 		} );
@@ -159,14 +159,14 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return end of single range in selection', () => {
-			selection._setTo( range1 );
+			selection.setTo( range1 );
 			const focus = selection.focus;
 
 			expect( focus.isEqual( range1.end ) ).to.be.true;
 		} );
 
 		it( 'should return start of single range in selection when added as backward', () => {
-			selection._setTo( range1, { backward: true } );
+			selection.setTo( range1, { backward: true } );
 			const focus = selection.focus;
 
 			expect( focus.isEqual( range1.start ) ).to.be.true;
@@ -174,16 +174,16 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should get focus from last inserted range', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
 			expect( selection.focus.isEqual( range2.end ) ).to.be.true;
 		} );
 	} );
 
-	describe( '_setFocus()', () => {
+	describe( 'setFocus()', () => {
 		it( 'keeps all existing ranges when no modifications needed', () => {
-			selection._setTo( range1 );
-			selection._setFocus( selection.focus );
+			selection.setTo( range1 );
+			selection.setFocus( selection.focus );
 
 			expect( count( selection.getRanges() ) ).to.equal( 1 );
 		} );
@@ -192,7 +192,7 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 'end' );
 
 			expect( () => {
-				selection._setFocus( endPos );
+				selection.setFocus( endPos );
 			} ).to.throw( CKEditorError, /view-selection-setFocus-no-ranges/ );
 		} );
 
@@ -200,9 +200,9 @@ describe( 'Selection', () => {
 			const startPos = Position.createAt( el, 1 );
 			const endPos = Position.createAt( el, 2 );
 
-			selection._setTo( startPos );
+			selection.setTo( startPos );
 
-			selection._setFocus( endPos );
+			selection.setFocus( endPos );
 
 			expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
 			expect( selection.focus.compareWith( endPos ) ).to.equal( 'same' );
@@ -212,9 +212,9 @@ describe( 'Selection', () => {
 			const startPos = Position.createAt( el, 1 );
 			const endPos = Position.createAt( el, 0 );
 
-			selection._setTo( startPos );
+			selection.setTo( startPos );
 
-			selection._setFocus( endPos );
+			selection.setFocus( endPos );
 
 			expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
 			expect( selection.focus.compareWith( endPos ) ).to.equal( 'same' );
@@ -226,9 +226,9 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 2 );
 			const newEndPos = Position.createAt( el, 3 );
 
-			selection._setTo( new Range( startPos, endPos ) );
+			selection.setTo( new Range( startPos, endPos ) );
 
-			selection._setFocus( newEndPos );
+			selection.setFocus( newEndPos );
 
 			expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
 			expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -239,9 +239,9 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 2 );
 			const newEndPos = Position.createAt( el, 0 );
 
-			selection._setTo( new Range( startPos, endPos ) );
+			selection.setTo( new Range( startPos, endPos ) );
 
-			selection._setFocus( newEndPos );
+			selection.setFocus( newEndPos );
 
 			expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
 			expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -253,9 +253,9 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 2 );
 			const newEndPos = Position.createAt( el, 3 );
 
-			selection._setTo( new Range( startPos, endPos ), { backward: true } );
+			selection.setTo( new Range( startPos, endPos ), { backward: true } );
 
-			selection._setFocus( newEndPos );
+			selection.setFocus( newEndPos );
 
 			expect( selection.anchor.compareWith( endPos ) ).to.equal( 'same' );
 			expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -267,9 +267,9 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 2 );
 			const newEndPos = Position.createAt( el, 0 );
 
-			selection._setTo( new Range( startPos, endPos ), { backward: true } );
+			selection.setTo( new Range( startPos, endPos ), { backward: true } );
 
-			selection._setFocus( newEndPos );
+			selection.setFocus( newEndPos );
 
 			expect( selection.anchor.compareWith( endPos ) ).to.equal( 'same' );
 			expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -285,12 +285,12 @@ describe( 'Selection', () => {
 
 			const newEndPos = Position.createAt( el, 0 );
 
-			selection._setTo( [
+			selection.setTo( [
 				new Range( startPos1, endPos1 ),
 				new Range( startPos2, endPos2 )
 			] );
 
-			selection._setFocus( newEndPos );
+			selection.setFocus( newEndPos );
 
 			const ranges = Array.from( selection.getRanges() );
 
@@ -307,9 +307,9 @@ describe( 'Selection', () => {
 			const startPos = Position.createAt( el, 1 );
 			const endPos = Position.createAt( el, 2 );
 
-			selection._setTo( new Range( startPos, endPos ) );
+			selection.setTo( new Range( startPos, endPos ) );
 
-			selection._setFocus( startPos );
+			selection.setFocus( startPos );
 
 			expect( selection.focus.compareWith( startPos ) ).to.equal( 'same' );
 			expect( selection.isCollapsed ).to.be.true;
@@ -322,8 +322,8 @@ describe( 'Selection', () => {
 
 			const spy = sinon.stub( Position, 'createAt' ).returns( newEndPos );
 
-			selection._setTo( new Range( startPos, endPos ) );
-			selection._setFocus( el, 'end' );
+			selection.setTo( new Range( startPos, endPos ) );
+			selection.setFocus( el, 'end' );
 
 			expect( spy.calledOnce ).to.be.true;
 			expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -335,7 +335,7 @@ describe( 'Selection', () => {
 	describe( 'isCollapsed', () => {
 		it( 'should return true when there is single collapsed range', () => {
 			const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
-			selection._setTo( range );
+			selection.setTo( range );
 
 			expect( selection.isCollapsed ).to.be.true;
 		} );
@@ -343,14 +343,14 @@ describe( 'Selection', () => {
 		it( 'should return false when there are multiple ranges', () => {
 			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 5 );
 			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 15 );
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
 			expect( selection.isCollapsed ).to.be.false;
 		} );
 
 		it( 'should return false when there is not collapsed range', () => {
 			const range = Range.createFromParentsAndOffsets( el, 15, el, 16 );
-			selection._setTo( range );
+			selection.setTo( range );
 
 			expect( selection.isCollapsed ).to.be.false;
 		} );
@@ -360,11 +360,11 @@ describe( 'Selection', () => {
 		it( 'should return proper range count', () => {
 			expect( selection.rangeCount ).to.equal( 0 );
 
-			selection._setTo( range1 );
+			selection.setTo( range1 );
 
 			expect( selection.rangeCount ).to.equal( 1 );
 
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
 			expect( selection.rangeCount ).to.equal( 2 );
 		} );
@@ -375,17 +375,17 @@ describe( 'Selection', () => {
 			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
 			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 16 );
 
-			selection._setTo( range1, { backward: true } );
+			selection.setTo( range1, { backward: true } );
 			expect( selection ).to.have.property( 'isBackward', true );
 
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 			expect( selection ).to.have.property( 'isBackward', false );
 		} );
 
 		it( 'is false when last range is collapsed', () => {
 			const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
 
-			selection._setTo( range, { backward: true } );
+			selection.setTo( range, { backward: true } );
 
 			expect( selection.isBackward ).to.be.false;
 		} );
@@ -393,7 +393,7 @@ describe( 'Selection', () => {
 
 	describe( 'getRanges', () => {
 		it( 'should return iterator with copies of all ranges', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
 			const iterable = selection.getRanges();
 			const ranges = Array.from( iterable );
@@ -408,7 +408,7 @@ describe( 'Selection', () => {
 
 	describe( 'getFirstRange', () => {
 		it( 'should return copy of range with first position', () => {
-			selection._setTo( [ range1, range2, range3 ] );
+			selection.setTo( [ range1, range2, range3 ] );
 
 			const range = selection.getFirstRange();
 
@@ -423,7 +423,7 @@ describe( 'Selection', () => {
 
 	describe( 'getLastRange', () => {
 		it( 'should return copy of range with last position', () => {
-			selection._setTo( [ range1, range2, range3 ] );
+			selection.setTo( [ range1, range2, range3 ] );
 
 			const range = selection.getLastRange();
 
@@ -438,7 +438,7 @@ describe( 'Selection', () => {
 
 	describe( 'getFirstPosition', () => {
 		it( 'should return copy of first position', () => {
-			selection._setTo( [ range1, range2, range3 ] );
+			selection.setTo( [ range1, range2, range3 ] );
 
 			const position = selection.getFirstPosition();
 
@@ -453,7 +453,7 @@ describe( 'Selection', () => {
 
 	describe( 'getLastPosition', () => {
 		it( 'should return copy of range with last position', () => {
-			selection._setTo( [ range1, range2, range3 ] );
+			selection.setTo( [ range1, range2, range3 ] );
 
 			const position = selection.getLastPosition();
 
@@ -468,68 +468,68 @@ describe( 'Selection', () => {
 
 	describe( 'isEqual', () => {
 		it( 'should return true if selections equal', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
-			const otherSelection = new DocumentSelection();
-			otherSelection._setTo( [ range1, range2 ] );
+			const otherSelection = new Selection();
+			otherSelection.setTo( [ range1, range2 ] );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
 
 		it( 'should return true if backward selections equal', () => {
-			selection._setTo( range1, { backward: true } );
+			selection.setTo( range1, { backward: true } );
 
-			const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
 
 		it( 'should return false if ranges count does not equal', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
-			const otherSelection = new DocumentSelection( [ range1 ] );
+			const otherSelection = new Selection( [ range1 ] );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return false if ranges (other than the last added one) do not equal', () => {
-			selection._setTo( [ range1, range3 ] );
+			selection.setTo( [ range1, range3 ] );
 
-			const otherSelection = new DocumentSelection( [ range2, range3 ] );
+			const otherSelection = new Selection( [ range2, range3 ] );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return false if directions do not equal', () => {
-			selection._setTo( range1 );
+			selection.setTo( range1 );
 
-			const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return false if one selection is fake', () => {
-			const otherSelection = new DocumentSelection( null, { fake: true } );
+			const otherSelection = new Selection( null, { fake: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return true if both selection are fake', () => {
-			const otherSelection = new DocumentSelection( range1, { fake: true } );
-			selection._setTo( range1, { fake: true } );
+			const otherSelection = new Selection( range1, { fake: true } );
+			selection.setTo( range1, { fake: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
 
 		it( 'should return false if both selection are fake but have different label', () => {
-			const otherSelection = new DocumentSelection( [ range1 ], { fake: true, label: 'foo bar baz' } );
-			selection._setTo( range1, { fake: true, label: 'foo' } );
+			const otherSelection = new Selection( [ range1 ], { fake: true, label: 'foo bar baz' } );
+			selection.setTo( range1, { fake: true, label: 'foo' } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return true if both selections are empty', () => {
-			const otherSelection = new DocumentSelection();
+			const otherSelection = new Selection();
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
@@ -537,39 +537,39 @@ describe( 'Selection', () => {
 
 	describe( 'isSimilar', () => {
 		it( 'should return true if selections equal', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
-			const otherSelection = new DocumentSelection( [ range1, range2 ] );
+			const otherSelection = new Selection( [ range1, range2 ] );
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.true;
 		} );
 
 		it( 'should return false if ranges count does not equal', () => {
-			selection._setTo( [ range1, range2 ] );
+			selection.setTo( [ range1, range2 ] );
 
-			const otherSelection = new DocumentSelection( [ range1 ] );
+			const otherSelection = new Selection( [ range1 ] );
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return false if trimmed ranges (other than the last added one) are not equal', () => {
-			selection._setTo( [ range1, range3 ] );
+			selection.setTo( [ range1, range3 ] );
 
-			const otherSelection = new DocumentSelection( [ range2, range3 ] );
+			const otherSelection = new Selection( [ range2, range3 ] );
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return false if directions are not equal', () => {
-			selection._setTo( range1 );
+			selection.setTo( range1 );
 
-			const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return true if both selections are empty', () => {
-			const otherSelection = new DocumentSelection();
+			const otherSelection = new Selection();
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.true;
 		} );
@@ -591,9 +591,9 @@ describe( 'Selection', () => {
 			const rangeA2 = Range.createFromParentsAndOffsets( p2, 0, p2, 1 );
 			const rangeB2 = Range.createFromParentsAndOffsets( span2, 0, span2, 1 );
 
-			selection._setTo( [ rangeA1, rangeA2 ] );
+			selection.setTo( [ rangeA1, rangeA2 ] );
 
-			const otherSelection = new DocumentSelection( [ rangeB2, rangeB1 ] );
+			const otherSelection = new Selection( [ rangeB2, rangeB1 ] );
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.true;
 			expect( otherSelection.isSimilar( selection ) ).to.be.true;
@@ -603,14 +603,14 @@ describe( 'Selection', () => {
 		} );
 	} );
 
-	describe( '_setTo()', () => {
+	describe( 'setTo()', () => {
 		describe( 'simple scenarios', () => {
 			it( 'should set selection ranges from the given selection', () => {
-				selection._setTo( range1 );
+				selection.setTo( range1 );
 
-				const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
+				const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
 
-				selection._setTo( otherSelection );
+				selection.setTo( otherSelection );
 
 				expect( selection.rangeCount ).to.equal( 2 );
 				expect( selection._ranges[ 0 ].isEqual( range2 ) ).to.be.true;
@@ -622,21 +622,21 @@ describe( 'Selection', () => {
 			} );
 
 			it( 'should set selection on the given Range', () => {
-				selection._setTo( range1 );
+				selection.setTo( range1 );
 
 				expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
 				expect( selection.isBackward ).to.be.false;
 			} );
 
 			it( 'should set selection on the given iterable of Ranges', () => {
-				selection._setTo( new Set( [ range1, range2 ] ) );
+				selection.setTo( new Set( [ range1, range2 ] ) );
 
 				expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2 ] );
 				expect( selection.isBackward ).to.be.false;
 			} );
 
 			it( 'should set collapsed selection on the given Position', () => {
-				selection._setTo( range1.start );
+				selection.setTo( range1.start );
 
 				expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
 				expect( Array.from( selection.getRanges() )[ 0 ].start ).to.deep.equal( range1.start );
@@ -651,46 +651,46 @@ describe( 'Selection', () => {
 					done();
 				} );
 
-				const otherSelection = new DocumentSelection( [ range1 ] );
+				const otherSelection = new Selection( [ range1 ] );
 
-				selection._setTo( otherSelection );
+				selection.setTo( otherSelection );
 			} );
 
 			it( 'should set fake state and label', () => {
 				const label = 'foo bar baz';
-				const otherSelection = new DocumentSelection( null, { fake: true, label } );
-				selection._setTo( otherSelection );
+				const otherSelection = new Selection( null, { fake: true, label } );
+				selection.setTo( otherSelection );
 
 				expect( selection.isFake ).to.be.true;
 				expect( selection.fakeSelectionLabel ).to.equal( label );
 			} );
 
 			it( 'should throw an error when trying to set to not selectable', () => {
-				const otherSelection = new DocumentSelection();
+				const otherSelection = new Selection();
 
 				expect( () => {
-					otherSelection._setTo( {} );
+					otherSelection.setTo( {} );
 				} ).to.throw( /view-selection-setTo-not-selectable/ );
 			} );
 
 			it( 'should throw an error when trying to set to not selectable #2', () => {
-				const otherSelection = new DocumentSelection();
+				const otherSelection = new Selection();
 
 				expect( () => {
-					otherSelection._setTo();
+					otherSelection.setTo();
 				} ).to.throw( /view-selection-setTo-not-selectable/ );
 			} );
 		} );
 
 		describe( 'setting collapsed selection', () => {
 			beforeEach( () => {
-				selection._setTo( [ range1, range2 ] );
+				selection.setTo( [ range1, range2 ] );
 			} );
 
 			it( 'should collapse selection at position', () => {
 				const position = new Position( el, 4 );
 
-				selection._setTo( position );
+				selection.setTo( position );
 				const range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( el );
@@ -702,14 +702,14 @@ describe( 'Selection', () => {
 				const foo = new Text( 'foo' );
 				const p = new Element( 'p', null, foo );
 
-				selection._setTo( foo, 0 );
+				selection.setTo( foo, 0 );
 				let range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( foo );
 				expect( range.start.offset ).to.equal( 0 );
 				expect( range.start.isEqual( range.end ) ).to.be.true;
 
-				selection._setTo( p, 1 );
+				selection.setTo( p, 1 );
 				range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( p );
@@ -721,7 +721,7 @@ describe( 'Selection', () => {
 				const foo = new Text( 'foo' );
 
 				expect( () => {
-					selection._setTo( foo );
+					selection.setTo( foo );
 				} ).to.throw( CKEditorError, /view-selection-setTo-required-second-parameter/ );
 			} );
 
@@ -729,21 +729,21 @@ describe( 'Selection', () => {
 				const foo = new Text( 'foo' );
 				const p = new Element( 'p', null, foo );
 
-				selection._setTo( foo, 'end' );
+				selection.setTo( foo, 'end' );
 				let range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( foo );
 				expect( range.start.offset ).to.equal( 3 );
 				expect( range.start.isEqual( range.end ) ).to.be.true;
 
-				selection._setTo( foo, 'before' );
+				selection.setTo( foo, 'before' );
 				range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( p );
 				expect( range.start.offset ).to.equal( 0 );
 				expect( range.start.isEqual( range.end ) ).to.be.true;
 
-				selection._setTo( foo, 'after' );
+				selection.setTo( foo, 'after' );
 				range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( p );
@@ -754,7 +754,7 @@ describe( 'Selection', () => {
 
 		describe( 'setting collapsed selection at start', () => {
 			it( 'should collapse to start position and fire change event', done => {
-				selection._setTo( [ range1, range2, range3 ] );
+				selection.setTo( [ range1, range2, range3 ] );
 				selection.once( 'change', () => {
 					expect( selection.rangeCount ).to.equal( 1 );
 					expect( selection.isCollapsed ).to.be.true;
@@ -762,13 +762,13 @@ describe( 'Selection', () => {
 					done();
 				} );
 
-				selection._setTo( selection.getFirstPosition() );
+				selection.setTo( selection.getFirstPosition() );
 			} );
 		} );
 
 		describe( 'setting collapsed selection to end', () => {
 			it( 'should collapse to end position and fire change event', done => {
-				selection._setTo( [ range1, range2, range3 ] );
+				selection.setTo( [ range1, range2, range3 ] );
 				selection.once( 'change', () => {
 					expect( selection.rangeCount ).to.equal( 1 );
 					expect( selection.isCollapsed ).to.be.true;
@@ -776,48 +776,48 @@ describe( 'Selection', () => {
 					done();
 				} );
 
-				selection._setTo( selection.getLastPosition() );
+				selection.setTo( selection.getLastPosition() );
 			} );
 		} );
 
 		describe( 'removing all ranges', () => {
 			it( 'should remove all ranges and fire change event', done => {
-				selection._setTo( [ range1, range2 ] );
+				selection.setTo( [ range1, range2 ] );
 
 				selection.once( 'change', () => {
 					expect( selection.rangeCount ).to.equal( 0 );
 					done();
 				} );
 
-				selection._setTo( null );
+				selection.setTo( null );
 			} );
 		} );
 
 		describe( 'setting fake selection', () => {
 			it( 'should allow to set selection to fake', () => {
-				selection._setTo( range1, { fake: true } );
+				selection.setTo( range1, { fake: true } );
 
 				expect( selection.isFake ).to.be.true;
 			} );
 
 			it( 'should allow to set fake selection label', () => {
 				const label = 'foo bar baz';
-				selection._setTo( range1, { fake: true, label } );
+				selection.setTo( range1, { fake: true, label } );
 
 				expect( selection.fakeSelectionLabel ).to.equal( label );
 			} );
 
 			it( 'should not set label when set to false', () => {
 				const label = 'foo bar baz';
-				selection._setTo( range1, { fake: false, label } );
+				selection.setTo( range1, { fake: false, label } );
 
 				expect( selection.fakeSelectionLabel ).to.equal( '' );
 			} );
 
 			it( 'should reset label when set to false', () => {
 				const label = 'foo bar baz';
-				selection._setTo( range1, { fake: true, label } );
-				selection._setTo( range1 );
+				selection.setTo( range1, { fake: true, label } );
+				selection.setTo( range1 );
 
 				expect( selection.fakeSelectionLabel ).to.equal( '' );
 			} );
@@ -830,11 +830,11 @@ describe( 'Selection', () => {
 					done();
 				} );
 
-				selection._setTo( range1, { fake: true, label: 'foo bar baz' } );
+				selection.setTo( range1, { fake: true, label: 'foo bar baz' } );
 			} );
 
 			it( 'should be possible to create an empty fake selection', () => {
-				selection._setTo( null, { fake: true, label: 'foo bar baz' } );
+				selection.setTo( null, { fake: true, label: 'foo bar baz' } );
 
 				expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
 				expect( selection.isFake ).to.be.true;
@@ -843,8 +843,8 @@ describe( 'Selection', () => {
 
 		describe( 'setting selection to itself', () => {
 			it( 'should correctly set ranges when setting to the same selection', () => {
-				selection._setTo( [ range1, range2 ] );
-				selection._setTo( selection );
+				selection.setTo( [ range1, range2 ] );
+				selection.setTo( selection );
 
 				const ranges = Array.from( selection.getRanges() );
 				expect( ranges.length ).to.equal( 2 );
@@ -854,8 +854,8 @@ describe( 'Selection', () => {
 			} );
 
 			it( 'should correctly set ranges when setting to the same selection\'s ranges', () => {
-				selection._setTo( [ range1, range2 ] );
-				selection._setTo( selection.getRanges() );
+				selection.setTo( [ range1, range2 ] );
+				selection.setTo( selection.getRanges() );
 
 				const ranges = Array.from( selection.getRanges() );
 				expect( ranges.length ).to.equal( 2 );
@@ -868,7 +868,7 @@ describe( 'Selection', () => {
 		describe( 'throwing errors', () => {
 			it( 'should throw an error when range is invalid', () => {
 				expect( () => {
-					selection._setTo( [ { invalid: 'range' } ] );
+					selection.setTo( [ { invalid: 'range' } ] );
 				} ).to.throw( CKEditorError, 'view-selection-invalid-range: Invalid Range.' );
 			} );
 
@@ -877,7 +877,7 @@ describe( 'Selection', () => {
 				const range2 = Range.createFromParentsAndOffsets( text, 7, text, 15 );
 
 				expect( () => {
-					selection._setTo( [ range1, range2 ] );
+					selection.setTo( [ range1, range2 ] );
 				} ).to.throw( CKEditorError, 'view-selection-range-intersects' );
 			} );
 		} );
@@ -888,7 +888,7 @@ describe( 'Selection', () => {
 			const textNode3 = new Text( 'baz' );
 			const element = new Element( 'p', null, [ textNode1, textNode2, textNode3 ] );
 
-			selection._setTo( textNode2, 'on' );
+			selection.setTo( textNode2, 'on' );
 
 			const ranges = Array.from( selection.getRanges() );
 			expect( ranges.length ).to.equal( 1 );
@@ -901,7 +901,7 @@ describe( 'Selection', () => {
 		it( 'should allow setting selection inside an element', () => {
 			const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
 
-			selection._setTo( element, 'in' );
+			selection.setTo( element, 'in' );
 
 			const ranges = Array.from( selection.getRanges() );
 			expect( ranges.length ).to.equal( 1 );
@@ -914,7 +914,7 @@ describe( 'Selection', () => {
 		it( 'should allow setting backward selection inside an element', () => {
 			const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
 
-			selection._setTo( element, 'in', { backward: true } );
+			selection.setTo( element, 'in', { backward: true } );
 
 			const ranges = Array.from( selection.getRanges() );
 			expect( ranges.length ).to.equal( 1 );
@@ -932,19 +932,19 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return null if selection is placed in container that is not EditableElement', () => {
-			selection._setTo( range1 );
+			selection.setTo( range1 );
 
 			expect( selection.editableElement ).to.be.null;
 		} );
 
 		it( 'should return EditableElement when selection is placed inside', () => {
 			const viewDocument = new Document();
-			const selection = viewDocument.selection;
+			selection.setTo( viewDocument.selection );
 			const root = createViewRoot( viewDocument, 'div', 'main' );
 			const element = new Element( 'p' );
 			root._appendChildren( element );
 
-			selection._setTo( Range.createFromParentsAndOffsets( element, 0, element, 0 ) );
+			selection.setTo( Range.createFromParentsAndOffsets( element, 0, element, 0 ) );
 
 			expect( selection.editableElement ).to.equal( root );
 		} );