Browse Source

Moved buffer back to input feature.

Krzysztof Krztoń 8 years ago
parent
commit
f6eaab83be

+ 46 - 14
packages/ckeditor5-typing/src/input.js

@@ -15,11 +15,12 @@ import diff from '@ckeditor/ckeditor5-utils/src/diff';
 import diffToChanges from '@ckeditor/ckeditor5-utils/src/difftochanges';
 import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
 import InputCommand from './inputcommand';
+import ChangeBuffer from './changebuffer';
 
 /**
  * Handles text input coming from the keyboard or other input methods.
  *
- * @extends core.Plugin
+ * @extends module:core/plugin~Plugin
  */
 export default class Input extends Plugin {
 	/**
@@ -28,12 +29,21 @@ export default class Input extends Plugin {
 	init() {
 		const editor = this.editor;
 		const editingView = editor.editing.view;
-		const inputCommand = new InputCommand( editor );
 
-		editor.commands.set( 'input', inputCommand );
+		/**
+		 * Typing's change buffer used to group subsequent changes into batches.
+		 *
+		 * @protected
+		 * @member {module:typing/changebuffer~ChangeBuffer} #_buffer
+		 */
+		this._buffer = new ChangeBuffer( editor.document, editor.config.get( 'typing.undoStep' ) || 20 );
+
+		// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
+
+		editor.commands.set( 'input', new InputCommand( editor ) );
 
 		this.listenTo( editingView, 'keydown', ( evt, data ) => {
-			this._handleKeydown( data, inputCommand.buffer );
+			this._handleKeydown( data );
 		}, { priority: 'lowest' } );
 
 		this.listenTo( editingView, 'mutations', ( evt, mutations, viewSelection ) => {
@@ -42,6 +52,16 @@ export default class Input extends Plugin {
 	}
 
 	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		super.destroy();
+
+		this._buffer.destroy();
+		this._buffer = null;
+	}
+
+	/**
 	 * Handles the keydown event. We need to guess whether such keystroke is going to result
 	 * in typing. If so, then before character insertion happens, any selected content needs
 	 * to be deleted. Otherwise the default browser deletion mechanism would be
@@ -55,9 +75,8 @@ export default class Input extends Plugin {
 	 *
 	 * @private
 	 * @param {module:engine/view/observer/keyobserver~KeyEventData} evtData
-	 * @param {typing.ChangeBuffer} buffer
 	 */
-	_handleKeydown( evtData, buffer ) {
+	_handleKeydown( evtData ) {
 		const doc = this.editor.document;
 
 		if ( isSafeKeystroke( evtData ) || doc.selection.isCollapsed ) {
@@ -65,7 +84,7 @@ export default class Input extends Plugin {
 		}
 
 		doc.enqueueChanges( () => {
-			this.editor.data.deleteContent( doc.selection, buffer.batch );
+			this.editor.data.deleteContent( doc.selection, this._buffer.batch );
 		} );
 	}
 
@@ -73,10 +92,10 @@ export default class Input extends Plugin {
 	 * Handles DOM mutations.
 	 *
 	 * @param {Array.<module:engine/view/document~MutatatedText|module:engine/view/document~MutatatedChildren>} mutations
-	 * @param {module:engine/controller/editingcontroller~EditingController} viewSelection
+	 * @param {module:engine/view/selection~Selection|null} viewSelection
 	 */
 	_handleMutations( mutations, viewSelection ) {
-		new MutationHandler( this.editor ).handle( mutations, viewSelection );
+		new MutationHandler( this.editor, this._buffer ).handle( mutations, viewSelection );
 	}
 }
 
@@ -90,8 +109,9 @@ class MutationHandler {
 	 * Creates an instance of the mutation handler.
 	 *
 	 * @param {module:core/editor/editor~Editor} editor
+	 * @param {module:typing/changebuffer~ChangeBuffer} buffer
 	 */
-	constructor( editor ) {
+	constructor( editor, buffer ) {
 		/**
 		 * Editor instance for which mutations are handled.
 		 *
@@ -106,12 +126,20 @@ class MutationHandler {
 		 * @member {module:engine/controller/editingcontroller~EditingController} #editing
 		 */
 		this.editing = this.editor.editing;
+
+		/**
+		 * The change buffer;
+		 *
+		 * @type {module:typing/changebuffer~ChangeBuffer} #buffer
+		 */
+		this.buffer = buffer;
 	}
 
 	/**
 	 * Handles given mutations.
 	 *
 	 * @param {Array.<module:engine/view/document~MutatatedText|module:engine/view/document~MutatatedChildren>} mutations
+	 * @param {module:engine/view/selection~Selection|null} viewSelection
 	 */
 	handle( mutations, viewSelection ) {
 		for ( let mutation of mutations ) {
@@ -183,11 +211,14 @@ class MutationHandler {
 		// Get the position in view and model where the changes will happen.
 		const viewPos = new ViewPosition( mutation.node, firstChangeAt );
 		const modelPos = this.editing.mapper.toModelPosition( viewPos );
+		const removeRange = deletions > 0 ? ModelRange.createFromPositionAndShift( modelPos, deletions ) : null;
+		const insertText = mutation.newText.substr( firstChangeAt, insertions );
 
 		this.editor.execute( 'input', {
-			text: mutation.newText.substr( firstChangeAt, insertions ),
-			range: deletions > 0 ? ModelRange.createFromPositionAndShift( modelPos, deletions ) : null,
-			selectionAnchor: modelSelectionPosition
+			text: insertText,
+			range: removeRange,
+			selectionAnchor: modelSelectionPosition,
+			buffer: this.buffer
 		} );
 	}
 
@@ -227,7 +258,8 @@ class MutationHandler {
 			// In this case we don't need to do this before `diff` because we diff whole nodes.
 			// Just change &nbsp; in case there are some.
 			text: insertedText.replace( /\u00A0/g, ' ' ),
-			range: new ModelRange( modelPos )
+			range: new ModelRange( modelPos ),
+			buffer: this.buffer
 		} );
 	}
 }

+ 9 - 46
packages/ckeditor5-typing/src/inputcommand.js

@@ -8,82 +8,45 @@
  */
 
 import Command from '@ckeditor/ckeditor5-core/src/command/command';
-import ChangeBuffer from './changebuffer';
 
 /**
  * The input command. Used by the {@link module:typing/input~Input input feature} to handle typing.
  *
- * @extends core.command.Command
+ * @extends module:core/command/command~Command
  */
 export default class InputCommand extends Command {
 	/**
-	 * Creates an instance of the command.
-	 *
-	 * @param {module:core/editor/editor~Editor} editor
-	 */
-	constructor( editor ) {
-		super( editor );
-
-		/**
-		 * Typing's change buffer used to group subsequent changes into batches.
-		 *
-		 * @protected
-		 * @member {typing.ChangeBuffer} #_buffer
-		 */
-		this._buffer = new ChangeBuffer( editor.document, editor.config.get( 'typing.undoStep' ) || 20 );
-
-		// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	destroy() {
-		super.destroy();
-
-		this._buffer.destroy();
-		this._buffer = null;
-	}
-
-	/**
-	 * Returns the current buffer.
-	 *
-	 * @type {typing.ChangeBuffer}
-	 */
-	get buffer() {
-		return this._buffer;
-	}
-
-	/**
 	 * Executes the input command. It replaces the content within the given range with the given text.
 	 * Replacing is a two step process, first content within the range is removed and then new text is inserted
 	 * on the beginning of the range (which after removal is a collapsed range).
 	 *
 	 * @param {Object} [options] The command options.
 	 * @param {String} [options.text=''] Text to be inserted.
-	 * @param {module:engine/model/range~Range} [options.range=null] Range in which the text is inserted. Defaults
-	 * to first range in the current selection.
+	 * @param {module:engine/model/range~Range} [options.range] Range in which the text is inserted. Defaults
+	 * to the first range in the current selection.
 	 * @param {module:engine/model/position~Position} [options.selectionAnchor] Selection anchor which will be used
 	 * to set selection on a data model.
+	 * @param {module:typing/changebuffer~ChangeBuffer} [options.buffer]
 	 */
 	_doExecute( options = {} ) {
 		const doc = this.editor.document;
 		const range = options.range || doc.selection.getFirstRange();
 		const text = options.text || '';
 		const selectionAnchor = options.selectionAnchor;
+		const buffer = options.buffer;
 		let textInsertions = 0;
 
-		if ( range ) {
+		if ( range && buffer ) {
 			doc.enqueueChanges( () => {
 				const isCollapsedRange = range.isCollapsed;
 
 				if ( !isCollapsedRange ) {
-					this._buffer.batch.remove( range );
+					buffer.batch.remove( range );
 				}
 
 				if ( text ) {
 					textInsertions = text.length;
-					this._buffer.batch.weakInsert( range.start, text );
+					buffer.batch.weakInsert( range.start, text );
 				}
 
 				if ( selectionAnchor ) {
@@ -93,7 +56,7 @@ export default class InputCommand extends Command {
 					this.editor.data.model.selection.collapse( range.start.getShiftedBy( textInsertions ) );
 				}
 
-				this._buffer.input( textInsertions );
+				buffer.input( textInsertions );
 			} );
 		}
 	}

+ 38 - 3
packages/ckeditor5-typing/tests/input.js

@@ -4,8 +4,9 @@
  */
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-import Input from '../src/input';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Input from '../src/input';
 
 import Batch from '@ckeditor/ckeditor5-engine/src/model/batch';
 import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
@@ -25,6 +26,8 @@ import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils
 describe( 'Input feature', () => {
 	let editor, model, modelRoot, view, viewRoot, listenter;
 
+	testUtils.createSinonSandbox();
+
 	before( () => {
 		listenter = Object.create( EmitterMixin );
 
@@ -65,6 +68,24 @@ describe( 'Input feature', () => {
 		listenter.stopListening();
 	} );
 
+	describe( 'buffer', () => {
+		it( 'has a buffer configured to default value of config.typing.undoStep', () => {
+			expect( editor.plugins.get( Input )._buffer ).to.have.property( 'limit', 20 );
+		} );
+
+		it( 'has a buffer configured to config.typing.undoStep', () => {
+			return VirtualTestEditor.create( {
+				plugins: [ Input ],
+				typing: {
+					undoStep: 5
+				}
+			} )
+				.then( editor => {
+					expect( editor.plugins.get( Input )._buffer ).to.have.property( 'limit', 5 );
+				} );
+		} );
+	} );
+
 	describe( 'mutations handling', () => {
 		it( 'should handle text mutation', () => {
 			view.fire( 'mutations', [
@@ -212,8 +233,8 @@ describe( 'Input feature', () => {
 			const viewSelection = new ViewSelection();
 			viewSelection.collapse( viewRoot.getChild( 0 ).getChild( 0 ), 6 );
 
-			sinon.spy( Batch.prototype, 'weakInsert' );
-			sinon.spy( Batch.prototype, 'remove' );
+			testUtils.sinon.spy( Batch.prototype, 'weakInsert' );
+			testUtils.sinon.spy( Batch.prototype, 'remove' );
 
 			view.fire( 'mutations',
 				[ {
@@ -309,5 +330,19 @@ describe( 'Input feature', () => {
 			expect( getModelData( model ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
 		} );
 	} );
+
+	describe( 'destroy', () => {
+		it( 'should destroy change buffer', () => {
+			const typing = new Input( new VirtualTestEditor() );
+			typing.init();
+
+			const destroy = typing._buffer.destroy = testUtils.sinon.spy();
+
+			typing.destroy();
+
+			expect( destroy.calledOnce ).to.be.true;
+			expect( typing._buffer ).to.be.null;
+		} );
+	} );
 } );
 

+ 43 - 49
packages/ckeditor5-typing/tests/inputcommand.js

@@ -3,16 +3,14 @@
  * For licensing, see LICENSE.md.
  */
 
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-import InputCommand from '../src/inputcommand';
 import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import InputCommand from '../src/inputcommand';
 import ChangeBuffer from '../src/changebuffer';
-import Input from '../src/input';
 
 describe( 'InputCommand', () => {
-	let editor, doc;
+	let editor, doc, buffer;
 
 	testUtils.createSinonSandbox();
 
@@ -21,9 +19,9 @@ describe( 'InputCommand', () => {
 			.then( newEditor => {
 				editor = newEditor;
 				doc = editor.document;
+				buffer = new ChangeBuffer( doc, 20 );
 
-				const command = new InputCommand( editor );
-				editor.commands.set( 'input', command );
+				editor.commands.set( 'input', new InputCommand( editor ) );
 
 				doc.schema.registerItem( 'p', '$block' );
 				doc.schema.registerItem( 'h1', '$block' );
@@ -31,29 +29,7 @@ describe( 'InputCommand', () => {
 	} );
 
 	beforeEach( () => {
-		editor.commands.get( 'input' )._buffer.size = 0;
-	} );
-
-	describe( 'buffer', () => {
-		it( 'has buffer getter', () => {
-			expect( editor.commands.get( 'input' ).buffer ).to.be.an.instanceof( ChangeBuffer );
-		} );
-
-		it( 'has a buffer configured to default value of config.typing.undoStep', () => {
-			expect( editor.commands.get( 'input' )._buffer ).to.have.property( 'limit', 20 );
-		} );
-
-		it( 'has a buffer configured to config.typing.undoStep', () => {
-			return VirtualTestEditor.create( {
-				plugins: [ Input ],
-				typing: {
-					undoStep: 5
-				}
-			} )
-				.then( editor => {
-					expect( editor.commands.get( 'input' )._buffer ).to.have.property( 'limit', 5 );
-				} );
-		} );
+		buffer.size = 0;
 	} );
 
 	describe( 'execute', () => {
@@ -62,7 +38,9 @@ describe( 'InputCommand', () => {
 
 			const spy = testUtils.sinon.spy( doc, 'enqueueChanges' );
 
-			editor.execute( 'input' );
+			editor.execute( 'input', {
+				buffer: buffer
+			} );
 
 			expect( spy.calledOnce ).to.be.true;
 		} );
@@ -71,82 +49,89 @@ describe( 'InputCommand', () => {
 			setData( doc, '<p>foo[]</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'bar',
 				range: editor.document.selection.getFirstRange()
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>foobar[]</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 3 );
+			expect( buffer.size ).to.be.equal( 3 );
 		} );
 
 		it( 'replaces text for range within single element on the beginning', () => {
 			setData( doc, '<p>[fooba]r</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'rab',
 				range: editor.document.selection.getFirstRange()
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>rab[]r</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 3 );
+			expect( buffer.size ).to.be.equal( 3 );
 		} );
 
 		it( 'replaces text for range within single element in the middle', () => {
 			setData( doc, '<p>fo[oba]r</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'bazz',
 				range: editor.document.selection.getFirstRange()
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>fobazz[]r</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 4 );
+			expect( buffer.size ).to.be.equal( 4 );
 		} );
 
 		it( 'replaces text for range within single element on the end', () => {
 			setData( doc, '<p>fooba[r]</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'zzz',
 				range: editor.document.selection.getFirstRange()
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>foobazzz[]</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 3 );
+			expect( buffer.size ).to.be.equal( 3 );
 		} );
 
 		it( 'replaces text for range within multiple elements', () => {
 			setData( doc, '<h1>F[OO</h1><p>b]ar</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'unny c',
 				range: editor.document.selection.getFirstRange()
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<h1>Funny c[</h1><p>]ar</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 6 );
+			expect( buffer.size ).to.be.equal( 6 );
 		} );
 
 		it( 'uses current selection when range is not given', () => {
 			setData( doc, '<p>foob[ar]</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'az'
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>foobaz[]</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 2 );
+			expect( buffer.size ).to.be.equal( 2 );
 		} );
 
 		it( 'only removes content when text is not given', () => {
 			setData( doc, '<p>[fo]obar</p>' );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				range: editor.document.selection.getFirstRange()
 			} );
 
 			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>[]obar</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 0 );
+			expect( buffer.size ).to.be.equal( 0 );
 		} );
 
 		it( 'does nothing when there is no range', () => {
@@ -155,27 +140,36 @@ describe( 'InputCommand', () => {
 			testUtils.sinon.stub( editor.document.selection, 'getFirstRange' ).returns( null );
 
 			editor.execute( 'input', {
+				buffer: buffer,
 				text: 'baz'
 			} );
 
-			const data = getData( doc, { selection: true } );
+			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>[fo]obar</p>' );
+			expect( buffer.size ).to.be.equal( 0 );
+		} );
+
+		it( 'does nothing when there is no buffer', () => {
+			setData( doc, '<p>[fo]obar</p>' );
 
-			editor.document.selection.getFirstRange.restore();
+			editor.execute( 'input', {
+				text: 'baz',
+				range: editor.document.selection.getFirstRange()
+			} );
 
-			expect( data ).to.be.equal( '<p>[fo]obar</p>' );
-			expect( editor.commands.get( 'input' ).buffer.size ).to.be.equal( 0 );
+			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>[fo]obar</p>' );
+			expect( buffer.size ).to.be.equal( 0 );
 		} );
-	} );
 
-	describe( 'destroy', () => {
-		it( 'should destroy change buffer', () => {
-			const command = editor.commands.get( 'input' );
-			const destroy = command._buffer.destroy = testUtils.sinon.spy();
+		it( 'does nothing when there is no options object provided', () => {
+			setData( doc, '<p>[fo]obar</p>' );
 
-			command.destroy();
+			const spy = testUtils.sinon.spy( doc, 'enqueueChanges' );
+
+			editor.execute( 'input' );
 
-			expect( destroy.calledOnce ).to.be.true;
-			expect( command._buffer ).to.be.null;
+			expect( spy.callCount ).to.be.equal( 0 );
+			expect( getData( doc, { selection: true } ) ).to.be.equal( '<p>[fo]obar</p>' );
+			expect( buffer.size ).to.be.equal( 0 );
 		} );
 	} );
 } );