Przeglądaj źródła

WIP - DocumentSelection. part 2.

Maciej Bukowski 8 lat temu
rodzic
commit
677bfb770e

+ 4 - 4
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -114,8 +114,8 @@ export function setData( model, data, options = {} ) {
 		writer.insert( modelDocumentFragment, modelRoot );
 
 		// Clean up previous document selection.
-		model.document.selection.clearAttributes();
-		model.document.selection.removeAllRanges();
+		model.document.selection._clearAttributes();
+		model.document.selection._removeAllRanges();
 
 		// Update document selection if specified.
 		if ( selection ) {
@@ -128,10 +128,10 @@ export function setData( model, data, options = {} ) {
 				ranges.push( new ModelRange( start, end ) );
 			}
 
-			model.document.selection.setRanges( ranges, selection.isBackward );
+			model.document.selection._setTo( ranges, selection.isBackward );
 
 			if ( options.selectionAttributes ) {
-				model.document.selection.setAttributesTo( selection.getAttributes() );
+				model.document.selection._setAttributesTo( selection.getAttributes() );
 			}
 		}
 	} );

+ 98 - 86
packages/ckeditor5-engine/src/model/documentselection.js

@@ -7,14 +7,15 @@
  * @module engine/model/documentselection
  */
 
-import Position from './position';
-import Text from './text';
-import TextProxy from './textproxy';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
 
+import Position from './position';
 import Selection from './selection';
+import Text from './text';
+import TextProxy from './textproxy';
 
 const storePrefix = 'selection:';
 
@@ -63,7 +64,7 @@ export default class DocumentSelection {
 		 * Document which owns this selection.
 		 *
 		 * @protected
-		 * @member {module:engine/model/model~Model}
+		 * @type {module:engine/model/model~Model}
 		 */
 		this._model = doc.model;
 
@@ -71,7 +72,7 @@ export default class DocumentSelection {
 		 * Document which owns this selection.
 		 *
 		 * @protected
-		 * @member {module:engine/model/document~Document}
+		 * @type {module:engine/model/document~Document}
 		 */
 		this._document = doc;
 
@@ -83,7 +84,7 @@ export default class DocumentSelection {
 		 * attributes API are set with `'normal'` priority.
 		 *
 		 * @private
-		 * @member {Map} module:engine/model/documentselection~DocumentSelection#_attributePriority
+		 * @type {Map.<String,String>}
 		 */
 		this._attributePriority = new Map();
 
@@ -179,14 +180,16 @@ export default class DocumentSelection {
 	 */
 	destroy() {
 		for ( const range of this._selection._ranges ) {
-			range.detach();
+			if ( range.detach ) {
+				// TODO
+				range.detach();
+			}
 		}
 
 		this.stopListening();
 	}
 
 	/**
-	 * @inheritDoc
 	 */
 	* getRanges() {
 		if ( this._selection.rangeCount > 0 ) {
@@ -197,14 +200,18 @@ export default class DocumentSelection {
 	}
 
 	/**
-	 * @inheritDoc
+	 */
+	getFirstPosition() {
+		return this.getFirstRange().start;
+	}
+
+	/**
 	 */
 	getFirstRange() {
 		return this._selection.getFirstRange() || this._document._getDefaultRange();
 	}
 
 	/**
-	 * @inheritDoc
 	 */
 	getLastRange() {
 		return this._selection.getLastRange() || this._document._getDefaultRange();
@@ -223,8 +230,8 @@ export default class DocumentSelection {
 	 * @protected
 	 * @param {*} selectable
 	 */
-	_setTo( selectable ) {
-		this._selection._setTo( selectable );
+	_setTo( selectable, isBackward ) {
+		this._selection.setTo( selectable, isBackward );
 		this._refreshAttributes();
 	}
 
@@ -276,35 +283,36 @@ export default class DocumentSelection {
 		return this._selection.getAttributes();
 	}
 
-	getAttribute() {
-		return this._selection.getAttribute();
+	getAttribute( key ) {
+		return this._selection.getAttribute( key );
 	}
 
-	// /**
-	//  * @inheritDoc
-	//  */
-	// setAttributesTo( attrs ) {
-	// 	attrs = toMap( attrs );
+	/**
+	 * @inheritDoc
+	 */
+	// TODO: Remove: for ( attr ) removeAttribute; setAttribute( newAttrs );
+	_setAttributesTo( attrs ) {
+		attrs = toMap( attrs );
 
-	// 	if ( this.isCollapsed && this.anchor.parent.isEmpty ) {
-	// 		this._setStoredAttributesTo( attrs );
-	// 	}
+		if ( this.isCollapsed && this.anchor.parent.isEmpty ) {
+			this._setStoredAttributesTo( attrs );
+		}
 
-	// 	const changed = this._setAttributesTo( attrs );
+		const changed = this.__setAttributesTo( attrs );
 
-	// 	if ( changed.size > 0 ) {
-	// 		// Fire event with exact data (fire only if anything changed).
-	// 		const attributeKeys = Array.from( changed );
-	// 		this.fire( 'change:attribute', { attributeKeys, directChange: true } );
-	// 	}
-	// }
+		if ( changed.size > 0 ) {
+			// Fire event with exact data (fire only if anything changed).
+			const attributeKeys = Array.from( changed );
+			this.fire( 'change:attribute', { attributeKeys, directChange: true } );
+		}
+	}
 
-	// /**
-	//  * @inheritDoc
-	//  */
-	// clearAttributes() {
-	// 	this.setAttributesTo( [] );
-	// }
+	/**
+	 * @inheritDoc
+	 */
+	_clearAttributes() {
+		this._setAttributesTo( [] );
+	}
 
 	/**
 	 * Removes all attributes from the selection and sets attributes according to the surrounding nodes.
@@ -379,56 +387,60 @@ export default class DocumentSelection {
 	// 	return liveRange;
 	// }
 
-	// /**
-	//  * Updates this selection attributes according to its ranges and the {@link module:engine/model/document~Document model document}.
-	//  *
-	//  * @protected
-	//  * @param {Boolean} clearAll
-	//  * @fires change:attribute
-	//  */
-	// _updateAttributes( clearAll ) {
-	// 	const newAttributes = toMap( this._getSurroundingAttributes() );
-	// 	const oldAttributes = toMap( this.getAttributes() );
-
-	// 	if ( clearAll ) {
-	// 		// If `clearAll` remove all attributes and reset priorities.
-	// 		this._attributePriority = new Map();
-	// 		this._attrs = new Map();
-	// 	} else {
-	// 		// If not, remove only attributes added with `low` priority.
-	// 		for ( const [ key, priority ] of this._attributePriority ) {
-	// 			if ( priority == 'low' ) {
-	// 				this._attrs.delete( key );
-	// 				this._attributePriority.delete( key );
-	// 			}
-	// 		}
-	// 	}
+	/**
+	 * Updates this selection attributes according to its ranges and the {@link module:engine/model/document~Document model document}.
+	 *
+	 * @private
+	 * @param {Boolean} clearAll
+	 * @fires change:attribute
+	 */
+	_updateAttributes( clearAll ) {
+		const newAttributes = toMap( this._getSurroundingAttributes() );
+		const oldAttributes = toMap( this.getAttributes() );
+
+		if ( clearAll ) {
+			// If `clearAll` remove all attributes and reset priorities.
+			this._attributePriority.clear();
+			this._selection._attrs.clear();
+		} else {
+			// If not, remove only attributes added with `low` priority.
+			for ( const [ key, priority ] of this._attributePriority ) {
+				if ( priority == 'low' ) {
+					this._selection._attrs.delete( key );
+					this._attributePriority.delete( key );
+				}
+			}
+		}
 
-	// 	this._setAttributesTo( newAttributes, false );
+		this.__setAttributesTo( newAttributes, false );
 
-	// 	// Let's evaluate which attributes really changed.
-	// 	const changed = [];
+		// Let's evaluate which attributes really changed.
+		const changed = [];
 
-	// 	// First, loop through all attributes that are set on selection right now.
-	// 	// Check which of them are different than old attributes.
-	// 	for ( const [ newKey, newValue ] of this.getAttributes() ) {
-	// 		if ( !oldAttributes.has( newKey ) || oldAttributes.get( newKey ) !== newValue ) {
-	// 			changed.push( newKey );
-	// 		}
-	// 	}
+		// First, loop through all attributes that are set on selection right now.
+		// Check which of them are different than old attributes.
+		for ( const [ newKey, newValue ] of this.getAttributes() ) {
+			if ( !oldAttributes.has( newKey ) || oldAttributes.get( newKey ) !== newValue ) {
+				changed.push( newKey );
+			}
+		}
 
-	// 	// Then, check which of old attributes got removed.
-	// 	for ( const [ oldKey ] of oldAttributes ) {
-	// 		if ( !this.hasAttribute( oldKey ) ) {
-	// 			changed.push( oldKey );
-	// 		}
-	// 	}
+		// Then, check which of old attributes got removed.
+		for ( const [ oldKey ] of oldAttributes ) {
+			if ( !this.hasAttribute( oldKey ) ) {
+				changed.push( oldKey );
+			}
+		}
 
-	// 	// Fire event with exact data (fire only if anything changed).
-	// 	if ( changed.length > 0 ) {
-	// 		this.fire( 'change:attribute', { attributeKeys: changed, directChange: false } );
-	// 	}
-	// }
+		// Fire event with exact data (fire only if anything changed).
+		if ( changed.length > 0 ) {
+			this.fire( 'change:attribute', { attributeKeys: changed, directChange: false } );
+		}
+	}
+
+	hasAttribute( key ) {
+		return this._selection.hasAttribute( key );
+	}
 
 	/**
 	 * Generates and returns an attribute key for selection attributes store, basing on original attribute key.
@@ -471,14 +483,14 @@ export default class DocumentSelection {
 			return false;
 		}
 
-		const oldValue = this.selection.getAttribute( key );
+		const oldValue = this._selection.getAttribute( key );
 
 		// Don't do anything if value has not changed.
 		if ( oldValue === value ) {
 			return false;
 		}
 
-		this._attrs.set( key, value );
+		this._selection._attrs.set( key, value );
 
 		// Update priorities map.
 		this._attributePriority.set( key, priority );
@@ -510,7 +522,7 @@ export default class DocumentSelection {
 			return false;
 		}
 
-		this._attrs.delete( key );
+		this._selection._attrs.delete( key );
 
 		// Update priorities map.
 		this._attributePriority.set( key, priority );
@@ -528,7 +540,7 @@ export default class DocumentSelection {
 	 * is caused by `Batch` API.
 	 * @returns {Set.<String>} Changed attribute keys.
 	 */
-	_setAttributesTo( attrs, directChange = true ) {
+	__setAttributesTo( attrs, directChange = true ) {
 		const changed = new Set();
 
 		for ( const [ oldKey, oldValue ] of this.getAttributes() ) {
@@ -562,7 +574,7 @@ export default class DocumentSelection {
 	 * @returns {Iterable.<*>}
 	 */
 	* _getStoredAttributes() {
-		const selectionParent = this.getFirstPosition().parent;
+		const selectionParent = this.getFirstRange().start.parent;
 
 		if ( this.isCollapsed && selectionParent.isEmpty ) {
 			for ( const key of selectionParent.getAttributeKeys() ) {
@@ -637,7 +649,7 @@ export default class DocumentSelection {
 	 * @returns {Iterable.<*>} Collection of attributes.
 	 */
 	_getSurroundingAttributes() {
-		const position = this.getFirstPosition();
+		const position = this.getFirstRange().start;
 		const schema = this._model.schema;
 
 		let attrs = null;

+ 45 - 0
packages/ckeditor5-engine/src/model/selection.js

@@ -293,6 +293,31 @@ export default class Selection {
 		}
 	}
 
+	/**
+	 * Sets this selection's ranges and direction to the specified location based on the given
+	 * {@link module:engine/model/selection~Selection selection}, {@link module:engine/model/position~Position position},
+	 * {@link module:engine/model/range~Range range} or an iterable of {@link module:engine/model/range~Range ranges}.
+	 *
+	 * @protected
+	 * @param {module:engine/model/selection~Selection|module:engine/model/position~Position|
+	 * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} selectable
+	 */
+	setTo( selectable ) {
+		if ( !selectable ) {
+			this.removeAllRanges();
+		} else if ( selectable instanceof Selection ) {
+			this.setRanges( selectable.getRanges(), selectable.isBackward );
+		} else if ( selectable instanceof Range ) {
+			this.setRanges( [ selectable ] );
+		} else if ( isIterable( selectable ) ) {
+			// We assume that the selectable is an iterable of ranges.
+			this.setRanges( selectable );
+		} else {
+			// We assume that the selectable is a position.
+			this.setRanges( [ new Range( selectable ) ] );
+		}
+	}
+
 	/**
 	 * 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 module:engine/model/selection~Selection#anchor} and
@@ -334,6 +359,7 @@ export default class Selection {
 		this.fire( 'change:range', { directChange: true } );
 	}
 
+<<<<<<< HEAD
 	/**
 	 * Sets this selection's ranges and direction to the specified location based on the given
 	 * {@link module:engine/model/selection~Selection selection}, {@link module:engine/model/position~Position position},
@@ -373,6 +399,25 @@ export default class Selection {
 	setOn( item ) {
 		this.setRanges( [ Range.createOn( item ) ] );
 	}
+=======
+	// /**
+	//  * Sets this selection in the provided element.
+	//  *
+	//  * @param {module:engine/model/element~Element} element
+	//  */
+	// setIn( element ) {
+	// 	this.setRanges( [ Range.createIn( element ) ] );
+	// }
+
+	// /**
+	//  * Sets this selection on the provided item.
+	//  *
+	//  * @param {module:engine/model/item~Item} item
+	//  */
+	// setOn( item ) {
+	// 	this.setRanges( [ Range.createOn( item ) ] );
+	// }
+>>>>>>> WIP - DocumentSelection. part 2.
 
 	/**
 	 * Sets collapsed selection at the specified location.

+ 78 - 61
packages/ckeditor5-engine/tests/model/documentselection.js

@@ -203,9 +203,9 @@ describe( 'DocumentSelection', () => {
 
 	describe( 'addRange()', () => {
 		it( 'should convert added Range to LiveRange', () => {
-			selection.addRange( range );
+			selection._setTo( range );
 
-			expect( selection._ranges[ 0 ] ).to.be.instanceof( LiveRange );
+			expect( selection.getFirstRange() ).to.be.instanceof( LiveRange );
 		} );
 
 		it( 'should throw an error when range is invalid', () => {
@@ -237,7 +237,7 @@ describe( 'DocumentSelection', () => {
 			selection._setTo( [ range, liveRange ] );
 
 			const spy = testUtils.sinon.spy( LiveRange.prototype, 'detach' );
-			selection.setCollapsedAt( root );
+			selection._setTo( root );
 
 			expect( spy.calledTwice ).to.be.true;
 		} );
@@ -301,7 +301,7 @@ describe( 'DocumentSelection', () => {
 			sinon.spy( ranges[ 0 ], 'detach' );
 			sinon.spy( ranges[ 1 ], 'detach' );
 
-			selection.removeAllRanges();
+			selection._removeAllRanges();
 		} );
 
 		afterEach( () => {
@@ -323,7 +323,7 @@ describe( 'DocumentSelection', () => {
 		it( 'should refresh attributes', () => {
 			const spy = sinon.spy( selection, '_updateAttributes' );
 
-			selection.removeAllRanges();
+			selection._removeAllRanges();
 
 			expect( spy.called ).to.be.true;
 		} );
@@ -344,7 +344,7 @@ describe( 'DocumentSelection', () => {
 			sinon.spy( oldRanges[ 0 ], 'detach' );
 			sinon.spy( oldRanges[ 1 ], 'detach' );
 
-			selection.setTo( [] );
+			selection._setTo( [] );
 
 			expect( oldRanges[ 0 ].detach.called ).to.be.true;
 			expect( oldRanges[ 1 ].detach.called ).to.be.true;
@@ -394,7 +394,7 @@ describe( 'DocumentSelection', () => {
 
 	describe( 'createFromSelection()', () => {
 		it( 'should throw', () => {
-			selection.addRange( range, true );
+			selection._setTo( range, true );
 
 			expect( () => {
 				DocumentSelection.createFromSelection( selection );
@@ -425,7 +425,7 @@ describe( 'DocumentSelection', () => {
 				new Text( 'xyz' )
 			] );
 
-			selection.addRange( new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 1, 4 ] ) ) );
+			selection._setTo( new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 1, 4 ] ) ) );
 
 			spyRange = sinon.spy();
 			selection.on( 'change:range', spyRange );
@@ -620,7 +620,7 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'should not overwrite previously set attributes', () => {
-				selection.setAttribute( 'foo', 'xyz' );
+				selection._setAttribute( 'foo', 'xyz' );
 
 				const spyAttribute = sinon.spy();
 				selection.on( 'change:attribute', spyAttribute );
@@ -640,8 +640,8 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'should not overwrite previously removed attributes', () => {
-				selection.setAttribute( 'foo', 'xyz' );
-				selection.removeAttribute( 'foo' );
+				selection._setAttribute( 'foo', 'xyz' );
+				selection._removeAttribute( 'foo' );
 
 				const spyAttribute = sinon.spy();
 				selection.on( 'change:attribute', spyAttribute );
@@ -663,7 +663,7 @@ describe( 'DocumentSelection', () => {
 
 		describe( 'RemoveOperation', () => {
 			it( 'fix selection range if it ends up in graveyard #1', () => {
-				selection.setCollapsedAt( new Position( root, [ 1, 3 ] ) );
+				selection._setTo( new Position( root, [ 1, 3 ] ) );
 
 				model.applyOperation( wrapInDelta(
 					new RemoveOperation(
@@ -678,7 +678,7 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'fix selection range if it ends up in graveyard #2', () => {
-				selection.setRanges( [ new Range( new Position( root, [ 1, 2 ] ), new Position( root, [ 1, 4 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 1, 2 ] ), new Position( root, [ 1, 4 ] ) ) ] );
 
 				model.applyOperation( wrapInDelta(
 					new RemoveOperation(
@@ -693,7 +693,7 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'fix selection range if it ends up in graveyard #3', () => {
-				selection.setRanges( [ new Range( new Position( root, [ 1, 1 ] ), new Position( root, [ 1, 2 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 1, 1 ] ), new Position( root, [ 1, 2 ] ) ) ] );
 
 				model.applyOperation( wrapInDelta(
 					new RemoveOperation(
@@ -754,10 +754,10 @@ describe( 'DocumentSelection', () => {
 			expect( root.childCount ).to.equal( 2 );
 		} );
 
-		describe( 'setAttribute()', () => {
+		describe( '_setAttribute()', () => {
 			it( 'should store attribute if the selection is in empty node', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
 
 				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
 
@@ -765,9 +765,9 @@ describe( 'DocumentSelection', () => {
 			} );
 		} );
 
-		describe( 'setAttributesTo()', () => {
+		describe( '_setAttributesTo()', () => {
 			it( 'should fire change:attribute event with correct parameters', done => {
-				selection.setAttributesTo( { foo: 'bar', abc: 'def' } );
+				selection._setAttributesTo( { foo: 'bar', abc: 'def' } );
 
 				selection.on( 'change:attribute', ( evt, data ) => {
 					expect( data.directChange ).to.be.true;
@@ -776,24 +776,24 @@ describe( 'DocumentSelection', () => {
 					done();
 				} );
 
-				selection.setAttributesTo( { foo: 'bar', xxx: 'yyy' } );
+				selection._setAttributesTo( { foo: 'bar', xxx: 'yyy' } );
 			} );
 
 			it( 'should not fire change:attribute event if same attributes are set', () => {
-				selection.setAttributesTo( { foo: 'bar', abc: 'def' } );
+				selection._setAttributesTo( { foo: 'bar', abc: 'def' } );
 
 				const spy = sinon.spy();
 				selection.on( 'change:attribute', spy );
 
-				selection.setAttributesTo( { foo: 'bar', abc: 'def' } );
+				selection._setAttributesTo( { foo: 'bar', abc: 'def' } );
 
 				expect( spy.called ).to.be.false;
 			} );
 
 			it( 'should remove all stored attributes and store the given ones if the selection is in empty node', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'abc', 'xyz' );
-				selection.setAttributesTo( { foo: 'bar' } );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'abc', 'xyz' );
+				selection._setAttributesTo( { foo: 'bar' } );
 
 				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
 				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
@@ -805,9 +805,9 @@ describe( 'DocumentSelection', () => {
 
 		describe( 'removeAttribute()', () => {
 			it( 'should remove attribute set on the text fragment', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.removeAttribute( 'foo' );
+				selection._setTo( [ rangeInFullP ] );
+				selection._setAttribute( 'foo', 'bar' );
+				selection._removeAttribute( 'foo' );
 
 				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
 
@@ -815,9 +815,9 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'should remove stored attribute if the selection is in empty node', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.removeAttribute( 'foo' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
+				selection._removeAttribute( 'foo' );
 
 				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
 
@@ -827,11 +827,11 @@ describe( 'DocumentSelection', () => {
 
 		describe( 'clearAttributes()', () => {
 			it( 'should remove all stored attributes if the selection is in empty node', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.setAttribute( 'abc', 'xyz' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
+				selection._setAttribute( 'abc', 'xyz' );
 
-				selection.clearAttributes();
+				selection._clearAttributes();
 
 				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
 				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
@@ -866,50 +866,50 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'if selection is a range, should find first character in it and copy it\'s attributes', () => {
-				selection.setRanges( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 5 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 5 ] ) ) ] );
 
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'b', true ] ] );
 
 				// Step into elements when looking for first character:
-				selection.setRanges( [ new Range( new Position( root, [ 5 ] ), new Position( root, [ 7 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 5 ] ), new Position( root, [ 7 ] ) ) ] );
 
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ] ] );
 			} );
 
 			it( 'if selection is collapsed it should seek a character to copy that character\'s attributes', () => {
 				// Take styles from character before selection.
-				selection.setRanges( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 2 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 2 ] ) ) ] );
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'a', true ] ] );
 
 				// If there are none,
 				// Take styles from character after selection.
-				selection.setRanges( [ new Range( new Position( root, [ 3 ] ), new Position( root, [ 3 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 3 ] ), new Position( root, [ 3 ] ) ) ] );
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'b', true ] ] );
 
 				// If there are none,
 				// Look from the selection position to the beginning of node looking for character to take attributes from.
-				selection.setRanges( [ new Range( new Position( root, [ 6 ] ), new Position( root, [ 6 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 6 ] ), new Position( root, [ 6 ] ) ) ] );
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'c', true ] ] );
 
 				// If there are none,
 				// Look from the selection position to the end of node looking for character to take attributes from.
-				selection.setRanges( [ new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) ) ] );
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'a', true ] ] );
 
 				// If there are no characters to copy attributes from, use stored attributes.
-				selection.setRanges( [ new Range( new Position( root, [ 0, 0 ] ), new Position( root, [ 0, 0 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 0, 0 ] ), new Position( root, [ 0, 0 ] ) ) ] );
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [] );
 			} );
 
 			it( 'should overwrite any previously set attributes', () => {
-				selection.setCollapsedAt( new Position( root, [ 5, 0 ] ) );
+				selection._setTo( new Position( root, [ 5, 0 ] ) );
 
-				selection.setAttribute( 'x', true );
-				selection.setAttribute( 'y', true );
+				selection._setAttribute( 'x', true );
+				selection._setAttribute( 'y', true );
 
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ], [ 'x', true ], [ 'y', true ] ] );
 
-				selection.setCollapsedAt( new Position( root, [ 1 ] ) );
+				selection._setTo( new Position( root, [ 1 ] ) );
 
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'a', true ] ] );
 			} );
@@ -918,20 +918,20 @@ describe( 'DocumentSelection', () => {
 				const spy = sinon.spy();
 				selection.on( 'change:attribute', spy );
 
-				selection.setRanges( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 5 ] ) ) ] );
+				selection._setTo( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 5 ] ) ) ] );
 
 				expect( spy.calledOnce ).to.be.true;
 			} );
 
 			it( 'should not fire change:attribute event if attributes did not change', () => {
-				selection.setCollapsedAt( new Position( root, [ 5, 0 ] ) );
+				selection._setTo( new Position( root, [ 5, 0 ] ) );
 
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ] ] );
 
 				const spy = sinon.spy();
 				selection.on( 'change:attribute', spy );
 
-				selection.setCollapsedAt( new Position( root, [ 5, 1 ] ) );
+				selection._setTo( new Position( root, [ 5, 1 ] ) );
 
 				expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ] ] );
 				expect( spy.called ).to.be.false;
@@ -1003,8 +1003,8 @@ describe( 'DocumentSelection', () => {
 					batchTypes.push( batch.type );
 				} );
 
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
 
 				expect( batchTypes ).to.deep.equal( [ 'default' ] );
 				expect( emptyP.getAttribute( fooStoreAttrKey ) ).to.equal( 'bar' );
@@ -1014,9 +1014,9 @@ describe( 'DocumentSelection', () => {
 				// Dedupe batches by using a map (multiple change events will be fired).
 				const batchTypes = new Map();
 
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.setAttribute( 'abc', 'bar' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
+				selection._setAttribute( 'abc', 'bar' );
 
 				model.on( 'applyOperation', ( event, args ) => {
 					const operation = args[ 0 ];
@@ -1036,8 +1036,8 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'are removed when any content is moved into', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
 
 				model.change( writer => {
 					writer.move( Range.createOn( fullP.getChild( 0 ) ), rangeInEmptyP.start );
@@ -1065,7 +1065,7 @@ describe( 'DocumentSelection', () => {
 			it( 'are removed even when there is no selection in it', () => {
 				emptyP.setAttribute( fooStoreAttrKey, 'bar' );
 
-				selection.setRanges( [ rangeInFullP ] );
+				selection._setTo( [ rangeInFullP ] );
 
 				model.change( writer => {
 					writer.insertText( 'x', rangeInEmptyP.start );
@@ -1094,8 +1094,8 @@ describe( 'DocumentSelection', () => {
 			} );
 
 			it( 'uses model change to clear attributes', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
 
 				model.change( writer => {
 					writer.insertText( 'x', rangeInEmptyP.start );
@@ -1127,11 +1127,28 @@ describe( 'DocumentSelection', () => {
 				expect( emptyP.parent ).to.equal( root ); // Just to be sure we're checking the right element.
 			} );
 
+<<<<<<< HEAD
+=======
+			it( 'are not removed on transparent batches', () => {
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
+
+				model.enqueueChange( 'transparent', writer => {
+					sinon.spy( model, 'enqueueChange' );
+
+					writer.insertText( 'x', rangeInEmptyP.start );
+
+					expect( model.enqueueChange.called ).to.be.false;
+					expect( emptyP.getAttribute( fooStoreAttrKey ) ).to.equal( 'bar' );
+				} );
+			} );
+
+>>>>>>> WIP - DocumentSelection. part 2.
 			// Rename and some other deltas don't specify range in doc#change event.
 			// So let's see if there's no crash or something.
 			it( 'are not removed on rename', () => {
-				selection.setRanges( [ rangeInEmptyP ] );
-				selection.setAttribute( 'foo', 'bar' );
+				selection._setTo( [ rangeInEmptyP ] );
+				selection._setAttribute( 'foo', 'bar' );
 
 				sinon.spy( model, 'enqueueChange' );