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

Merge pull request #1388 from ckeditor/t/1299

Feature: Introduce `writer#updateMarker()` method. Closes #1299.

BREAKING CHANGE: The `writer#setMarker()` method is used only to create a new marker and it does not accept a `marker` instance as a parameter. To update existing marker use `writer#updateMarker()` method.
BREAKING CHANGE: The `options.usingOperation` option in `writer#setMarker()` is now a required one.
BREAKING CHANGE: The `range` parameter was removed. Use `options.range` instead.
Piotr Jasiun 7 лет назад
Родитель
Сommit
4f8a49b480

+ 2 - 2
packages/ckeditor5-engine/src/model/delta/markerdelta.js

@@ -11,8 +11,8 @@ import Delta from './delta';
 import DeltaFactory from './deltafactory';
 
 /**
- * To provide specific OT behavior and better collisions solving, the {@link module:engine/model/writer~Writer#setMarker Batch#setMarker}
- * and {@link module:engine/model/writer~Writer#removeMarker Batch#removeMarker} methods use the `MarkerDelta` class which inherits
+ * To provide specific OT behavior and better collisions solving, the {@link module:engine/model/writer~Writer#addMarker Writer#addMarker}
+ * and {@link module:engine/model/writer~Writer#removeMarker Writer#removeMarker} methods use the `MarkerDelta` class which inherits
  * from the `Delta` class and may overwrite some methods.
  *
  * @extends module:engine/model/delta/delta~Delta

+ 4 - 4
packages/ckeditor5-engine/src/model/markercollection.js

@@ -20,7 +20,7 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
  * {@link module:engine/model/markercollection~MarkerCollection#event:update} event.
  *
  * To create, change or remove makers use {@link module:engine/model/writer~Writer model writers'} methods:
- * {@link module:engine/model/writer~Writer#setMarker} or {@link module:engine/model/writer~Writer#removeMarker}. Since
+ * {@link module:engine/model/writer~Writer#addMarker} or {@link module:engine/model/writer~Writer#removeMarker}. Since
  * the writer is the only proper way to change the data model it is not possible to change markers directly using this
  * collection. All markers created by the writer will be automatically added to this collection.
  *
@@ -275,11 +275,11 @@ mix( MarkerCollection, EmitterMixin );
  * Therefore, they are handled in the undo stack and synchronized between clients if the collaboration plugin is enabled.
  * This type of markers is useful for solutions like spell checking or comments.
  *
- * Both type of them should be added / updated by {@link module:engine/model/writer~Writer#setMarker}
+ * Both type of them should be added / updated by {@link module:engine/model/writer~Writer#addMarker}
  * and removed by {@link module:engine/model/writer~Writer#removeMarker} methods.
  *
  *		model.change( ( writer ) => {
- * 			const marker = writer.setMarker( name, range, { usingOperation: true } );
+ * 			const marker = writer.addMarker( name, { range, usingOperation: true } );
  *
  * 			// ...
  *
@@ -343,7 +343,7 @@ class Marker {
 	/**
 	 * Returns value of flag indicates if the marker is managed using operations or not.
 	 * See {@link ~Marker marker class description} to learn more about marker types.
-	 * See {@link module:engine/model/writer~Writer#setMarker}.
+	 * See {@link module:engine/model/writer~Writer#addMarker}.
 	 *
 	 * @returns {Boolean}
 	 */

+ 123 - 68
packages/ckeditor5-engine/src/model/writer.js

@@ -40,7 +40,6 @@ import DocumentSelection from './documentselection';
 import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
 
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import uid from '@ckeditor/ckeditor5-utils/src/uid';
 
 /**
  * The model can only be modified by using the writer. It should be used whenever you want to create a node, modify
@@ -206,7 +205,7 @@ export default class Writer {
 					markerRange.end._getCombined( rangeRootPosition, position )
 				);
 
-				this.setMarker( markerName, range, { usingOperation: true } );
+				this.addMarker( markerName, { range, usingOperation: true } );
 			}
 		}
 	}
@@ -772,6 +771,78 @@ export default class Writer {
 	}
 
 	/**
+	 * Adds a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks
+	 * changes in the document and updates its range automatically, when model tree changes.
+	 *
+	 * As the first parameter you can set marker name.
+	 *
+	 * The required `options.usingOperation` parameter lets you decide if the marker should be managed by operations or not. See
+	 * {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
+	 * markers managed by operations and not-managed by operations.
+	 *
+	 * Create marker directly base on marker's name:
+	 *
+	 * 		addMarker( markerName, { range, usingOperation: false } );
+	 *
+	 * Create marker using operation:
+	 *
+	 * 		addMarker( markerName, { range, usingOperation: true } );
+	 *
+	 * Note: For efficiency reasons, it's best to create and keep as little markers as possible.
+	 *
+	 * @see module:engine/model/markercollection~Marker
+	 * @param {String} name Name of a marker to create - must be unique.
+	 * @param {Object} options
+	 * @param {Boolean} options.usingOperation Flag indicated whether the marker should be added by MarkerOperation.
+	 * See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
+	 * @param {module:engine/model/range~Range} options.range Marker range.
+	 * @returns {module:engine/model/markercollection~Marker} Marker that was set.
+	 */
+	addMarker( name, options ) {
+		this._assertWriterUsedCorrectly();
+
+		if ( !options || typeof options.usingOperation != 'boolean' ) {
+			/**
+			 * The options.usingOperations parameter is required when adding a new marker.
+			 *
+			 * @error writer-addMarker-no-usingOperations
+			 */
+			throw new CKEditorError(
+				'writer-addMarker-no-usingOperations: The options.usingOperations parameter is required when adding a new marker.'
+			);
+		}
+
+		const usingOperation = options.usingOperation;
+		const range = options.range;
+
+		if ( this.model.markers.has( name ) ) {
+			/**
+			 * Marker with provided name already exists.
+			 *
+			 * @error writer-addMarker-marker-exists
+			 */
+			throw new CKEditorError( 'writer-addMarker-marker-exists: Marker with provided name already exists.' );
+		}
+
+		if ( !range ) {
+			/**
+			 * Range parameter is required when adding a new marker.
+			 *
+			 * @error writer-addMarker-no-range
+			 */
+			throw new CKEditorError( 'writer-addMarker-no-range: Range parameter is required when adding a new marker.' );
+		}
+
+		if ( !usingOperation ) {
+			return this.model.markers._set( name, range, usingOperation );
+		}
+
+		applyMarkerOperation( this, name, null, range );
+
+		return this.model.markers.get( name );
+	}
+
+	/**
 	 * Adds or updates a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks
 	 * changes in the document and updates its range automatically, when model tree changes. Still, it is possible to change the
 	 * marker's range directly using this method.
@@ -779,101 +850,85 @@ export default class Writer {
 	 * As the first parameter you can set marker name or instance. If none of them is provided, new marker, with a unique
 	 * name is created and returned.
 	 *
-	 * The `options.usingOperation` parameter lets you decide if the marker should be managed by operations or not. See
+	 * The `options.usingOperation` parameter lets you change if the marker should be managed by operations or not. See
 	 * {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
 	 * markers managed by operations and not-managed by operations. It is possible to change this option for an existing marker.
 	 * This is useful when a marker have been created earlier and then later, it needs to be added to the document history.
 	 *
-	 * Create/update marker directly base on marker's name:
+	 * Update marker directly base on marker's name:
 	 *
-	 * 		setMarker( markerName, range );
+	 * 		updateMarker( markerName, { range } );
 	 *
 	 * Update marker using operation:
 	 *
-	 * 		setMarker( marker, range, { usingOperation: true } );
-	 * 		setMarker( markerName, range, { usingOperation: true } );
-	 *
-	 * Create marker with a unique id using operation:
-	 *
-	 * 		setMarker( range, { usingOperation: true } );
-	 *
-	 * Create marker directly without using operations:
-	 *
-	 * 		setMarker( range )
+	 * 		updateMarker( marker, { range, usingOperation: true } );
+	 * 		updateMarker( markerName, { range, usingOperation: true } );
 	 *
 	 * Change marker's option (start using operations to manage it):
 	 *
-	 * 		setMarker( marker, { usingOperation: true } );
-	 *
-	 * Note: For efficiency reasons, it's best to create and keep as little markers as possible.
+	 * 		updateMarker( marker, { usingOperation: true } );
 	 *
 	 * @see module:engine/model/markercollection~Marker
-	 * @param {module:engine/model/markercollection~Marker|String} [markerOrName]
-	 * Name of a marker to create or update, or `Marker` instance to update, or range for the marker with a unique name.
-	 * @param {module:engine/model/range~Range} [range] Marker range.
-	 * @param {Object} [options]
-	 * @param {Boolean} [options.usingOperation=false] Flag indicated whether the marker should be added by MarkerOperation.
+	 * @param {String} markerOrName Name of a marker to update, or a marker instance.
+	 * @param {Object} options
+	 * @param {module:engine/model/range~Range} [options.range] Marker range to update.
+	 * @param {Boolean} [options.usingOperation] Flag indicated whether the marker should be added by MarkerOperation.
 	 * See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
-	 * @returns {module:engine/model/markercollection~Marker} Marker that was set.
 	 */
-	setMarker( markerOrNameOrRange, rangeOrOptions, options ) {
+	updateMarker( markerOrName, options ) {
 		this._assertWriterUsedCorrectly();
 
-		let markerName, newRange, usingOperation;
+		const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
 
-		if ( markerOrNameOrRange instanceof Range ) {
-			markerName = uid();
-			newRange = markerOrNameOrRange;
-			usingOperation = !!rangeOrOptions && !!rangeOrOptions.usingOperation;
-		} else {
-			markerName = typeof markerOrNameOrRange === 'string' ? markerOrNameOrRange : markerOrNameOrRange.name;
+		const currentMarker = this.model.markers.get( markerName );
 
-			if ( rangeOrOptions instanceof Range ) {
-				newRange = rangeOrOptions;
-				usingOperation = !!options && !!options.usingOperation;
-			} else {
-				newRange = null;
-				usingOperation = !!rangeOrOptions && !!rangeOrOptions.usingOperation;
-			}
+		if ( !currentMarker ) {
+			/**
+			 * Marker with provided name does not exists.
+			 *
+			 * @error writer-updateMarker-marker-not-exists
+			 */
+			throw new CKEditorError( 'writer-updateMarker-marker-not-exists: Marker with provided name does not exists.' );
 		}
 
-		const currentMarker = this.model.markers.get( markerName );
+		const newRange = options && options.range;
+		const hasUsingOperationDefined = !!options && typeof options.usingOperation == 'boolean';
 
-		if ( !usingOperation ) {
-			if ( !newRange ) {
-				/**
-			 	 * Range parameter is required when adding a new marker.
-				 *
-				 * @error writer-setMarker-no-range
-				 */
-				throw new CKEditorError( 'writer-setMarker-no-range: Range parameter is required when adding a new marker.' );
-			}
+		if ( !hasUsingOperationDefined && !newRange ) {
+			/**
+			 * One of options is required - provide range or usingOperations.
+			 *
+			 * @error writer-updateMarker-wrong-options
+			 */
+			throw new CKEditorError( 'writer-updateMarker-wrong-options: One of options is required - provide range or usingOperations.' );
+		}
 
-			// If marker changes to marker that do not use operations then we need to create additional operation
-			// that removes that marker first.
-			if ( currentMarker && currentMarker.managedUsingOperations && !usingOperation ) {
-				applyMarkerOperation( this, markerName, currentMarker.getRange(), null );
-			}
+		if ( hasUsingOperationDefined && options.usingOperation !== currentMarker.managedUsingOperations ) {
+			// The marker type is changed so it's necessary to create proper operations.
+			if ( options.usingOperation ) {
+				// If marker changes to a managed one treat this as synchronizing existing marker.
+				// If marker changes to a managed one treat this as synchronizing existing marker.
+				// Create `MarkerOperation` with `oldRange` set to `null`, so reverse operation will remove the marker.
+				applyMarkerOperation( this, markerName, null, newRange ? newRange : currentMarker.getRange() );
+			} else {
+				// If marker changes to a marker that do not use operations then we need to create additional operation
+				// that removes that marker first.
+				const currentRange = currentMarker.getRange();
+				applyMarkerOperation( this, markerName, currentRange, null );
 
-			return this.model.markers._set( markerName, newRange, usingOperation );
-		}
+				// Although not managed the marker itself should stay in model and its range should be preserver or changed to passed range.
+				this.model.markers._set( markerName, newRange ? newRange : currentRange );
+			}
 
-		if ( !newRange && !currentMarker ) {
-			throw new CKEditorError( 'writer-setMarker-no-range: Range parameter is required when adding a new marker.' );
+			return;
 		}
 
-		const currentRange = currentMarker ? currentMarker.getRange() : null;
-
-		if ( !newRange ) {
-			// If `newRange` is not given, treat this as synchronizing existing marker.
-			// Create `MarkerOperation` with `oldRange` set to `null`, so reverse operation will remove the marker.
-			applyMarkerOperation( this, markerName, null, currentRange );
+		// Marker's type doesn't change so update it accordingly.
+		if ( currentMarker.managedUsingOperations ) {
+			applyMarkerOperation( this, markerName, currentMarker.getRange(), newRange );
 		} else {
-			// Just change marker range.
-			applyMarkerOperation( this, markerName, currentRange, newRange );
+			this.model.markers._set( markerName, newRange );
 		}
-
-		return this.model.markers.get( markerName );
 	}
 
 	/**

+ 7 - 3
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -406,7 +406,8 @@ describe( 'DataController', () => {
 
 			model.change( writer => {
 				writer.insert( modelElement, modelRoot, 0 );
-				writer.setMarker( 'marker:a', Range.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 1 ), { usingOperation: true } );
+				const range = Range.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 1 );
+				writer.addMarker( 'marker:a', { range, usingOperation: true } );
 			} );
 
 			const viewDocumentFragment = data.toView( modelElement );
@@ -428,8 +429,11 @@ describe( 'DataController', () => {
 			model.change( writer => {
 				writer.insert( modelElement, modelRoot, 0 );
 
-				writer.setMarker( 'marker:a', Range.createFromParentsAndOffsets( modelP1, 1, modelP1, 3 ), { usingOperation: true } );
-				writer.setMarker( 'marker:b', Range.createFromParentsAndOffsets( modelP2, 0, modelP2, 2 ), { usingOperation: true } );
+				const rangeA = Range.createFromParentsAndOffsets( modelP1, 1, modelP1, 3 );
+				const rangeB = Range.createFromParentsAndOffsets( modelP2, 0, modelP2, 2 );
+
+				writer.addMarker( 'marker:a', { range: rangeA, usingOperation: true } );
+				writer.addMarker( 'marker:b', { range: rangeB, usingOperation: true } );
 			} );
 
 			const viewDocumentFragment = data.toView( modelP1 );

+ 8 - 8
packages/ckeditor5-engine/tests/controller/editingcontroller.js

@@ -235,7 +235,7 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			expect( getViewData( editing.view, { withoutSelection: true } ) )
@@ -246,7 +246,7 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			model.change( writer => {
@@ -261,13 +261,13 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			const range2 = new ModelRange( new ModelPosition( modelRoot, [ 0, 0 ] ), new ModelPosition( modelRoot, [ 0, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range2 );
+				writer.updateMarker( 'marker', { range: range2 } );
 			} );
 
 			expect( getViewData( editing.view, { withoutSelection: true } ) )
@@ -278,7 +278,7 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 				writer.insertText( 'xyz', new ModelPosition( modelRoot, [ 1, 0 ] ) );
 			} );
 
@@ -290,7 +290,7 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			model.change( writer => {
@@ -308,7 +308,7 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			model.change( writer => {
@@ -326,7 +326,7 @@ describe( 'EditingController', () => {
 			const range = new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 0, 3 ] ) );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', range );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			model.change( writer => {

+ 39 - 28
packages/ckeditor5-engine/tests/conversion/downcast-converters.js

@@ -449,7 +449,9 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 ) );
+
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 );
+				writer.addMarker( 'search', { range, usingOperation: false } );
 			} );
 
 			expectResult( 'f<marker-search></marker-search>o<marker-search></marker-search>o' );
@@ -463,7 +465,8 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 ) );
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 );
+				writer.addMarker( 'search', { range, usingOperation: false } );
 			} );
 
 			expectResult( 'f<search></search>o<search></search>o' );
@@ -484,7 +487,8 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 ) );
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 );
+				writer.addMarker( 'search', { range, usingOperation: false } );
 			} );
 
 			expectResult( 'f<span data-marker="search"></span>o<span data-marker="search"></span>o' );
@@ -502,7 +506,8 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'search', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 ) );
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 );
+				writer.addMarker( 'search', { range, usingOperation: false } );
 			} );
 
 			expectResult( 'f<span data-marker="search" data-start="true"></span>o<span data-marker="search" data-start="false"></span>o' );
@@ -517,7 +522,8 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'comment', ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 ) );
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 );
+				writer.addMarker( 'comment', { range, usingOperation: false } );
 			} );
 
 			expectResult( '<span class="comment">foo</span>' );
@@ -531,7 +537,8 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'comment', ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 ) );
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 );
+				writer.addMarker( 'comment', { range, usingOperation: false } );
 			} );
 
 			expectResult( '<span class="new-comment">foo</span>' );
@@ -553,7 +560,8 @@ describe( 'downcast-helpers', () => {
 
 			model.change( writer => {
 				writer.insertText( 'foo', modelRoot, 0 );
-				writer.setMarker( 'comment:abc', ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 ) );
+				const range = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 );
+				writer.addMarker( 'comment:abc', { range, usingOperation: false } );
 			} );
 
 			expectResult( '<span class="comment comment-abc">foo</span>' );
@@ -924,7 +932,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeUIElement( creator ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foo<span class="marker"></span>bar</p></div>' );
@@ -948,7 +956,7 @@ describe( 'downcast-converters', () => {
 				}, { priority: 'high' } );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
@@ -960,7 +968,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeUIElement( () => null ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
@@ -985,7 +993,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeUIElement( creator ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) )
@@ -1005,7 +1013,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeUIElement( creator ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) )
@@ -1031,7 +1039,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeUIElement( creator ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1056,7 +1064,7 @@ describe( 'downcast-converters', () => {
 				}, { priority: 'high' } );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', range );
+					writer.addMarker( 'marker', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
@@ -1268,7 +1276,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeHighlight( highlightDescriptor ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', markerRange );
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1301,7 +1309,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeHighlight( newDescriptor ), { priority: 'high' } );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', markerRange );
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1328,7 +1336,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeHighlight( () => null ), { priority: 'high' } );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', markerRange );
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foo</p><p>bar</p></div>' );
@@ -1349,8 +1357,8 @@ describe( 'downcast-converters', () => {
 
 				markerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 0 );
 
-				model.change( () => {
-					model.markers._set( 'marker', markerRange );
+				model.change( writer => {
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foo</p><p>bar</p></div>' );
@@ -1379,7 +1387,8 @@ describe( 'downcast-converters', () => {
 				const p2 = modelRoot.getChild( 1 );
 
 				model.change( writer => {
-					writer.setMarker( 'markerFoo', ModelRange.createFromParentsAndOffsets( p1, 0, p1, 3 ) );
+					const range = ModelRange.createFromParentsAndOffsets( p1, 0, p1, 3 );
+					writer.addMarker( 'markerFoo', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1392,7 +1401,8 @@ describe( 'downcast-converters', () => {
 				);
 
 				model.change( writer => {
-					writer.setMarker( 'markerBar', ModelRange.createFromParentsAndOffsets( p1, 1, p2, 2 ) );
+					const range = ModelRange.createFromParentsAndOffsets( p1, 1, p2, 2 );
+					writer.addMarker( 'markerBar', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1411,7 +1421,8 @@ describe( 'downcast-converters', () => {
 				);
 
 				model.change( writer => {
-					writer.setMarker( 'markerXyz', ModelRange.createFromParentsAndOffsets( p1, 2, p2, 3 ) );
+					const range = ModelRange.createFromParentsAndOffsets( p1, 2, p2, 3 );
+					writer.addMarker( 'markerXyz', { range, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1485,13 +1496,13 @@ describe( 'downcast-converters', () => {
 				const markerRange = ModelRange.createFromParentsAndOffsets( p1, 3, p2, 0 );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', markerRange );
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foo</p><p>bar</p></div>' );
 
 				model.change( writer => {
-					writer.removeMarker( 'marker', markerRange );
+					writer.removeMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><p>foo</p><p>bar</p></div>' );
@@ -1539,7 +1550,7 @@ describe( 'downcast-converters', () => {
 
 			it( 'should use addHighlight and removeHighlight on elements and not convert children nodes', () => {
 				model.change( writer => {
-					writer.setMarker( 'marker', markerRange );
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1565,7 +1576,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker', removeHighlight( newDescriptor ), { priority: 'high' } );
 
 				model.change( writer => {
-					writer.setMarker( 'marker', markerRange );
+					writer.addMarker( 'marker', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal(
@@ -1600,7 +1611,7 @@ describe( 'downcast-converters', () => {
 				} );
 
 				model.change( writer => {
-					writer.setMarker( 'marker2', markerRange );
+					writer.addMarker( 'marker2', { range: markerRange, usingOperation: false } );
 				} );
 			} );
 
@@ -1610,7 +1621,7 @@ describe( 'downcast-converters', () => {
 				dispatcher.on( 'removeMarker:marker2', removeHighlight( () => null ) );
 
 				model.change( writer => {
-					writer.setMarker( 'marker2', markerRange );
+					writer.addMarker( 'marker2', { range: markerRange, usingOperation: false } );
 				} );
 
 				expect( viewToString( viewRoot ) ).to.equal( '<div><div>foo</div></div>' );

+ 8 - 4
packages/ckeditor5-engine/tests/conversion/downcast-selection-converters.js

@@ -197,7 +197,8 @@ describe( 'downcast-selection-converters', () => {
 				setModelData( model, 'fo<$text bold="true">ob</$text>ar' );
 
 				model.change( writer => {
-					marker = writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 ) );
+					const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 );
+					marker = writer.addMarker( 'marker', { range, usingOperation: false } );
 					writer.setSelection( new ModelRange( ModelPosition.createAt( modelRoot, 3 ) ) );
 				} );
 
@@ -221,7 +222,8 @@ describe( 'downcast-selection-converters', () => {
 				setModelData( model, 'fo<$text bold="true">ob</$text>ar' );
 
 				model.change( writer => {
-					marker = writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 ) );
+					const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 );
+					marker = writer.addMarker( 'marker', { range, usingOperation: false } );
 					writer.setSelection( new ModelRange( ModelPosition.createAt( modelRoot, 3 ) ) );
 					writer.removeSelectionAttribute( 'bold' );
 				} );
@@ -249,7 +251,8 @@ describe( 'downcast-selection-converters', () => {
 				setModelData( model, 'foobar' );
 
 				model.change( writer => {
-					marker = writer.setMarker( 'marker2', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 ) );
+					const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 );
+					marker = writer.addMarker( 'marker2', { range, usingOperation: false } );
 					writer.setSelection( new ModelRange( ModelPosition.createAt( modelRoot, 3 ) ) );
 				} );
 
@@ -274,7 +277,8 @@ describe( 'downcast-selection-converters', () => {
 				setModelData( model, 'foobar' );
 
 				model.change( writer => {
-					marker = writer.setMarker( 'marker3', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 ) );
+					const range = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 );
+					marker = writer.addMarker( 'marker3', { range, usingOperation: false } );
 					writer.setSelection( new ModelRange( ModelPosition.createAt( modelRoot, 3 ) ) );
 				} );
 

+ 10 - 5
packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js

@@ -347,7 +347,8 @@ describe( 'DowncastDispatcher', () => {
 				writer.setSelection(
 					new ModelRange( new ModelPosition( root, [ 1 ] ), new ModelPosition( root, [ 1 ] ) )
 				);
-				writer.setMarker( 'name', ModelRange.createFromParentsAndOffsets( root, 0, root, 2 ) );
+				const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 2 );
+				writer.addMarker( 'name', { range, usingOperation: false } );
 			} );
 
 			sinon.spy( dispatcher, 'fire' );
@@ -360,7 +361,8 @@ describe( 'DowncastDispatcher', () => {
 
 		it( 'should not fire events for markers for non-collapsed selection', () => {
 			model.change( writer => {
-				writer.setMarker( 'name', ModelRange.createFromParentsAndOffsets( root, 0, root, 2 ) );
+				const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 2 );
+				writer.addMarker( 'name', { range, usingOperation: false } );
 			} );
 
 			sinon.spy( dispatcher, 'fire' );
@@ -400,7 +402,8 @@ describe( 'DowncastDispatcher', () => {
 			};
 
 			model.change( writer => {
-				writer.setMarker( 'name', ModelRange.createFromParentsAndOffsets( root, 0, root, 1 ) );
+				const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 1 );
+				writer.addMarker( 'name', { range, usingOperation: false } );
 				writer.setSelection( ModelRange.createFromParentsAndOffsets( caption, 1, caption, 1 ) );
 			} );
 			sinon.spy( dispatcher, 'fire' );
@@ -417,8 +420,10 @@ describe( 'DowncastDispatcher', () => {
 				writer.setSelection(
 					new ModelRange( new ModelPosition( root, [ 1 ] ), new ModelPosition( root, [ 1 ] ) )
 				);
-				writer.setMarker( 'foo', ModelRange.createFromParentsAndOffsets( root, 0, root, 2 ) );
-				writer.setMarker( 'bar', ModelRange.createFromParentsAndOffsets( root, 0, root, 2 ) );
+
+				const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 2 );
+				writer.addMarker( 'foo', { range, usingOperation: false } );
+				writer.addMarker( 'bar', { range, usingOperation: false } );
 			} );
 
 			sinon.spy( dispatcher, 'fire' );

+ 1 - 1
packages/ckeditor5-engine/tests/manual/highlight.js

@@ -128,7 +128,7 @@ ClassicEditor.create( global.document.querySelector( '#editor' ), {
 function addMarker( editor, color ) {
 	editor.model.change( writer => {
 		const range = ModelRange.createFromRange( editor.model.document.selection.getFirstRange() );
-		writer.setMarker( 'marker:' + color, range );
+		writer.addMarker( 'marker:' + color, { range, usingOperation: false } );
 	} );
 }
 

+ 2 - 2
packages/ckeditor5-engine/tests/manual/markers.js

@@ -83,7 +83,7 @@ ClassicEditor
 			const name = 'highlight:yellow:' + uid();
 
 			markerNames.push( name );
-			writer.setMarker( name, range );
+			writer.addMarker( name, { range, usingOperation: false } );
 		} );
 	} )
 	.catch( err => {
@@ -100,7 +100,7 @@ function addHighlight( color ) {
 		const name = 'highlight:' + color + ':' + uid();
 
 		markerNames.push( name );
-		writer.setMarker( name, range );
+		writer.addMarker( name, { range, usingOperation: false } );
 	} );
 }
 

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

@@ -234,7 +234,8 @@ describe( 'Document', () => {
 			sinon.spy( doc.differ, 'bufferMarkerChange' );
 
 			model.change( writer => {
-				writer.setMarker( 'marker', Range.createCollapsedAt( doc.getRoot(), 0 ) );
+				const range = Range.createCollapsedAt( doc.getRoot(), 0 );
+				writer.addMarker( 'marker', { range, usingOperation: false } );
 			} );
 
 			expect( doc.differ.bufferMarkerChange.called ).to.be.true;
@@ -287,7 +288,6 @@ describe( 'Document', () => {
 
 			const callB = sinon.stub();
 			callB.onFirstCall().returns( true ).onSecondCall().returns( false );
-
 			const callC = sinon.spy();
 
 			doc.registerPostFixer( callA );
@@ -298,9 +298,9 @@ describe( 'Document', () => {
 				writer.insertText( 'foo', doc.getRoot(), 0 );
 			} );
 
-			expect( callA.calledTwice ).to.be.true;
-			expect( callB.calledTwice ).to.be.true;
-			expect( callC.calledOnce ).to.be.true;
+			sinon.assert.calledTwice( callA );
+			sinon.assert.calledTwice( callB );
+			sinon.assert.calledOnce( callC );
 		} );
 	} );
 

+ 1 - 1
packages/ckeditor5-engine/tests/model/operation/markeroperation.js

@@ -87,7 +87,7 @@ describe( 'MarkerOperation', () => {
 
 	it( 'should not fire document markers set event if newRange is same as current marker range', () => {
 		model.change( writer => {
-			writer.setMarker( 'name', range, { usingOperation: true } );
+			writer.addMarker( 'name', { range, usingOperation: true } );
 		} );
 
 		sinon.spy( model.markers, 'fire' );

+ 188 - 62
packages/ckeditor5-engine/tests/model/writer.js

@@ -1933,7 +1933,7 @@ describe( 'Writer', () => {
 		} );
 	} );
 
-	describe( 'setMarker()', () => {
+	describe( 'addMarker()', () => {
 		let root, range;
 
 		beforeEach( () => {
@@ -1942,50 +1942,123 @@ describe( 'Writer', () => {
 			range = Range.createIn( root );
 		} );
 
+		it( 'should throw if options.usingOperations is not defined', () => {
+			expect( () => {
+				addMarker( 'name' );
+			} ).to.throw( CKEditorError, /^writer-addMarker-no-usingOperations/ );
+		} );
+
+		it( 'should throw if name and range is defined but options.usingOperations is not defined', () => {
+			expect( () => {
+				addMarker( 'name', { range } );
+			} ).to.throw( CKEditorError, /^writer-addMarker-no-usingOperations/ );
+		} );
+
 		it( 'should add marker to the document marker collection', () => {
-			setMarker( 'name', range );
+			addMarker( 'name', { range, usingOperation: false } );
 
 			expect( model.markers.get( 'name' ).getRange().isEqual( range ) ).to.be.true;
 		} );
 
 		it( 'should return marker if that was set directly', () => {
-			const marker = setMarker( 'name', range );
+			const marker = addMarker( 'name', { range, usingOperation: false } );
 
 			expect( model.markers.get( 'name' ) ).to.equal( marker );
 		} );
 
 		it( 'should return marker if that was set using operation API', () => {
-			const marker = setMarker( 'name', range, { usingOperation: true } );
+			const marker = addMarker( 'name', { range, usingOperation: true } );
 
 			expect( model.markers.get( 'name' ) ).to.equal( marker );
 		} );
 
 		it( 'should return marker with properly set managedUsingOperations (to true)', () => {
-			const marker = setMarker( 'name', range, { usingOperation: true } );
+			const marker = addMarker( 'name', { range, usingOperation: true } );
 
 			expect( marker.managedUsingOperations ).to.equal( true );
 		} );
 
 		it( 'should return marker with properly set managedUsingOperations (to false)', () => {
-			const marker = setMarker( 'name', range );
+			const marker = addMarker( 'name', { range, usingOperation: false } );
 
 			expect( marker.managedUsingOperations ).to.equal( false );
 		} );
 
-		it( 'should update marker in the document marker collection', () => {
-			const marker = setMarker( 'name', range );
+		it( 'should throw when trying to update existing marker in the document marker collection', () => {
+			addMarker( 'name', { range, usingOperation: false } );
 
 			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
-			setMarker( 'name', range2 );
+
+			expect( () => {
+				addMarker( 'name', { range: range2, usingOperation: false } );
+			} ).to.throw( CKEditorError, /^writer-addMarker-marker-exists/ );
+		} );
+
+		it( 'should use operations when having set usingOperation to true', () => {
+			const spy = sinon.spy();
+
+			model.on( 'applyOperation', spy );
+
+			addMarker( 'name', { range, usingOperation: true } );
+			const op = batch.deltas[ 0 ].operations[ 0 ];
+
+			sinon.assert.calledOnce( spy );
+			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
+			expect( op.oldRange ).to.be.null;
+			expect( op.newRange.isEqual( range ) ).to.be.true;
+		} );
+
+		it( 'should throw if marker with given name does not exist and range is not passed', () => {
+			expect( () => {
+				addMarker( 'name', { usingOperation: true } );
+			} ).to.throw( CKEditorError, /^writer-addMarker-no-range/ );
+		} );
+
+		it( 'should throw if marker is set directly and range is not passed', () => {
+			expect( () => {
+				addMarker( 'name', { usingOperation: false } );
+			} ).to.throw( CKEditorError, /^writer-addMarker-no-range/ );
+		} );
+
+		it( 'should throw when trying to use detached writer', () => {
+			const marker = addMarker( 'name', { range, usingOperation: false } );
+			const writer = new Writer( model, batch );
+
+			expect( () => {
+				writer.addMarker( marker, null, { usingOperation: true } );
+			} ).to.throw( CKEditorError, /^writer-incorrect-use/ );
+		} );
+	} );
+
+	describe( 'updateMarker()', () => {
+		let root, range;
+
+		beforeEach( () => {
+			root = doc.createRoot();
+			root._appendChildren( new Text( 'foo' ) );
+			range = Range.createIn( root );
+		} );
+
+		it( 'should update managed marker\'s range by marker instance using operations', () => {
+			const marker = addMarker( 'name', { range, usingOperation: true } );
+			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
+
+			updateMarker( marker, { range: range2 } );
+
+			expect( batch.deltas.length ).to.equal( 2 );
+
+			const op = batch.deltas[ 1 ].operations[ 0 ];
 
 			expect( marker.getRange().isEqual( range2 ) ).to.be.true;
+			expect( op.oldRange.isEqual( range ) ).to.be.true;
+			expect( op.newRange.isEqual( range2 ) ).to.be.true;
 		} );
 
-		it( 'should accept marker instance', () => {
-			const marker = setMarker( 'name', range, { usingOperation: true } );
+		it( 'should update managed marker\'s range by marker name using operations', () => {
+			const marker = addMarker( 'name', { range, usingOperation: true } );
 			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
 
-			setMarker( marker, range2, { usingOperation: true } );
+			updateMarker( 'name', { range: range2 } );
 
 			expect( batch.deltas.length ).to.equal( 2 );
 
@@ -1996,74 +2069,87 @@ describe( 'Writer', () => {
 			expect( op.newRange.isEqual( range2 ) ).to.be.true;
 		} );
 
-		it( 'should accept empty range parameter if marker instance is passed and usingOperation is set to true', () => {
-			const marker = setMarker( 'name', range, { usingOperation: true } );
-			const spy = sinon.spy();
+		it( 'should update managed marker\'s range by marker instance using operations and usingOperation explicitly passed', () => {
+			const marker = addMarker( 'name', { range, usingOperation: true } );
+			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
 
-			model.on( 'applyOperation', spy );
+			updateMarker( marker, { range: range2, usingOperation: true } );
 
-			setMarker( marker, { usingOperation: true } );
+			expect( batch.deltas.length ).to.equal( 2 );
 
-			const op = batch.deltas[ 0 ].operations[ 0 ];
+			const op = batch.deltas[ 1 ].operations[ 0 ];
 
-			expect( spy.calledOnce ).to.be.true;
-			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
-			expect( op.oldRange ).to.be.null;
-			expect( op.newRange.isEqual( range ) ).to.be.true;
+			expect( marker.getRange().isEqual( range2 ) ).to.be.true;
+			expect( op.oldRange.isEqual( range ) ).to.be.true;
+			expect( op.newRange.isEqual( range2 ) ).to.be.true;
 		} );
 
-		it( 'should create a unique id if the first param is type of range', () => {
-			const marker = setMarker( range );
+		it( 'should update managed marker\'s range by marker name using operations and usingOperation explicitly passed', () => {
+			const marker = addMarker( 'name', { range, usingOperation: true } );
+			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
+
+			updateMarker( 'name', { range: range2, usingOperation: true } );
 
-			expect( marker.name ).to.be.a( 'string' );
+			expect( batch.deltas.length ).to.equal( 2 );
+
+			const op = batch.deltas[ 1 ].operations[ 0 ];
+
+			expect( marker.getRange().isEqual( range2 ) ).to.be.true;
+			expect( op.oldRange.isEqual( range ) ).to.be.true;
+			expect( op.newRange.isEqual( range2 ) ).to.be.true;
 		} );
 
-		it( 'should create a unique id if the first param is type of range when usingOperation is set to true', () => {
+		it( 'should not use operations when updating marker which does not use operations', () => {
 			const spy = sinon.spy();
 			model.on( 'applyOperation', spy );
 
-			const marker = setMarker( range, { usingOperation: true } );
+			const marker = addMarker( 'name', { range, usingOperation: false } );
+			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
+
+			updateMarker( marker, { range: range2 } );
 
-			expect( marker.name ).to.be.a( 'string' );
-			expect( spy.calledOnce ).to.be.true;
+			sinon.assert.notCalled( spy );
 		} );
 
-		it( 'should use operations when having set usingOperation to true', () => {
+		it( 'should create additional operation when marker type changes to not managed using operation', () => {
 			const spy = sinon.spy();
-
 			model.on( 'applyOperation', spy );
 
-			setMarker( 'name', range, { usingOperation: true } );
-			const op = batch.deltas[ 0 ].operations[ 0 ];
+			addMarker( 'name', { range, usingOperation: true } );
+			updateMarker( 'name', { usingOperation: false } );
+
+			const marker = model.markers.get( 'name' );
+
+			const op1 = batch.deltas[ 0 ].operations[ 0 ];
+			const op2 = batch.deltas[ 1 ].operations[ 0 ];
 
-			expect( spy.calledOnce ).to.be.true;
+			sinon.assert.calledTwice( spy );
 			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
-			expect( op.oldRange ).to.be.null;
-			expect( op.newRange.isEqual( range ) ).to.be.true;
-		} );
+			expect( spy.secondCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
 
-		it( 'should throw if marker with given name does not exist and range is not passed', () => {
-			expect( () => {
-				setMarker( 'name', { usingOperation: true } );
-			} ).to.throw( CKEditorError, /^writer-setMarker-no-range/ );
-		} );
+			expect( op1.oldRange ).to.be.null;
+			expect( op1.newRange.isEqual( range ) ).to.be.true;
 
-		it( 'should throw if marker is set directly and range is not passed', () => {
-			expect( () => {
-				setMarker( 'name' );
-			} ).to.throw( CKEditorError, /^writer-setMarker-no-range/ );
+			expect( op2.oldRange.isEqual( range ) ).to.be.true;
+			expect( op2.newRange ).to.be.null;
+
+			expect( marker.managedUsingOperations ).to.be.false;
 		} );
 
-		it( 'should create additional operation when marker type changes to not managed using operation', () => {
+		it( 'should create additional operation when marker type changes to not managed using operation and changing its range', () => {
 			const spy = sinon.spy();
 			model.on( 'applyOperation', spy );
+			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
+
+			addMarker( 'name', { range, usingOperation: true } );
+			updateMarker( 'name', { range: range2, usingOperation: false } );
+
+			const marker = model.markers.get( 'name' );
 
-			setMarker( 'name', range, { usingOperation: true } );
-			const marker = setMarker( 'name', range );
 			const op1 = batch.deltas[ 0 ].operations[ 0 ];
 			const op2 = batch.deltas[ 1 ].operations[ 0 ];
 
-			expect( spy.calledTwice ).to.be.true;
+			sinon.assert.calledTwice( spy );
 			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
 			expect( spy.secondCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
 
@@ -2073,28 +2159,62 @@ describe( 'Writer', () => {
 			expect( op2.oldRange.isEqual( range ) ).to.be.true;
 			expect( op2.newRange ).to.be.null;
 
+			expect( marker.getRange().isEqual( range2 ) );
+
 			expect( marker.managedUsingOperations ).to.be.false;
 		} );
 
-		it( 'should enable changing marker to be not managed using operation', () => {
+		it( 'should enable changing marker to be managed using operation', () => {
+			const spy = sinon.spy();
+			model.on( 'applyOperation', spy );
+
+			addMarker( 'name', { range, usingOperation: false } );
+			updateMarker( 'name', { usingOperation: true } );
+
+			const marker = model.markers.get( 'name' );
+
+			sinon.assert.calledOnce( spy );
+			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
+
+			expect( marker.managedUsingOperations ).to.be.true;
+		} );
+
+		it( 'should enable changing marker to be managed using operation while changing range', () => {
 			const spy = sinon.spy();
 			model.on( 'applyOperation', spy );
+			const range2 = Range.createFromParentsAndOffsets( root, 0, root, 0 );
 
-			setMarker( 'name', range );
-			const marker = setMarker( 'name', range, { usingOperation: true } );
+			addMarker( 'name', { range, usingOperation: false } );
+			updateMarker( 'name', { range: range2, usingOperation: true } );
 
-			expect( spy.calledOnce ).to.be.true;
+			const marker = model.markers.get( 'name' );
+
+			sinon.assert.calledOnce( spy );
 			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
+			expect( marker.getRange().isEqual( range2 ) ).to.be.true;
 
 			expect( marker.managedUsingOperations ).to.be.true;
 		} );
 
+		it( 'should throw when range and usingOperations were not provided', () => {
+			expect( () => {
+				addMarker( 'name', { range, usingOperation: false } );
+				updateMarker( 'name' );
+			} ).to.throw( CKEditorError, /^writer-updateMarker-wrong-options/ );
+		} );
+
+		it( 'should throw when marker provided by name does not exists', () => {
+			expect( () => {
+				updateMarker( 'name', { usingOperation: false } );
+			} ).to.throw( CKEditorError, /^writer-updateMarker-marker-not-exists/ );
+		} );
+
 		it( 'should throw when trying to use detached writer', () => {
-			const marker = setMarker( 'name', range );
+			const marker = addMarker( 'name', { range, usingOperation: false } );
 			const writer = new Writer( model, batch );
 
 			expect( () => {
-				writer.setMarker( marker, null, { usingOperation: true } );
+				writer.updateMarker( marker, { usingOperation: true } );
 			} ).to.throw( CKEditorError, /^writer-incorrect-use/ );
 		} );
 	} );
@@ -2109,7 +2229,7 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'should remove marker from the document marker collection', () => {
-			setMarker( 'name', range );
+			addMarker( 'name', { range, usingOperation: false } );
 			removeMarker( 'name' );
 
 			expect( model.markers.get( 'name' ) ).to.be.null;
@@ -2130,7 +2250,7 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'should accept marker instance', () => {
-			setMarker( 'name', range );
+			addMarker( 'name', { range, usingOperation: false } );
 			const marker = model.markers.get( 'name' );
 
 			removeMarker( marker );
@@ -2139,7 +2259,7 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'should use MarkerOperation when marker was created using operation', () => {
-			setMarker( 'name', range, { usingOperation: true } );
+			addMarker( 'name', { range, usingOperation: true } );
 
 			const marker = model.markers.get( 'name' );
 			const spy = sinon.spy();
@@ -2148,7 +2268,7 @@ describe( 'Writer', () => {
 
 			removeMarker( marker );
 
-			expect( spy.calledOnce ).to.be.true;
+			sinon.assert.calledOnce( spy );
 			expect( spy.firstCall.args[ 1 ][ 0 ].type ).to.equal( 'marker' );
 			expect( model.markers.get( 'name' ) ).to.be.null;
 		} );
@@ -2528,16 +2648,22 @@ describe( 'Writer', () => {
 		} );
 	}
 
-	function setMarker( markerOrNameOrRange, rangeOrManagedUsingOperations, options ) {
+	function addMarker( name, options ) {
 		let marker;
 
 		model.enqueueChange( batch, writer => {
-			marker = writer.setMarker( markerOrNameOrRange, rangeOrManagedUsingOperations, options );
+			marker = writer.addMarker( name, options );
 		} );
 
 		return marker;
 	}
 
+	function updateMarker( markerOrName, options ) {
+		model.enqueueChange( batch, writer => {
+			writer.updateMarker( markerOrName, options );
+		} );
+	}
+
 	function removeMarker( markerOrName, options ) {
 		model.enqueueChange( batch, writer => {
 			writer.removeMarker( markerOrName, options );