Procházet zdrojové kódy

Fix: Reduce the TextWatcher instances creation to only one

This commit also changes the TextTransformation's API adding:
- #configuredTransformations
- #_normalizedConfiguredTransformations
- #_setNormalizedTransformations()
panr před 5 roky
rodič
revize
672dbc7006
1 změnil soubory, kde provedl 80 přidání a 33 odebrání
  1. 80 33
      packages/ckeditor5-typing/src/texttransformation.js

+ 80 - 33
packages/ckeditor5-typing/src/texttransformation.js

@@ -94,6 +94,21 @@ export default class TextTransformation extends Plugin {
 		} );
 
 		this.editor = editor;
+
+		/**
+		 * Stores all configured transformations.
+		 *
+		 * @member {Array} #configuredTransformations
+		 */
+		this.configuredTransformations = getConfiguredTransformations( editor.config.get( 'typing.transformations' ) );
+
+		/**
+		 * Stores all normalized versions of configured transformations.
+		 *
+		 * @private
+		 * @member {Map<from:String|to:String,String>} #_normalizedConfiguredTransformations
+		 */
+		this._normalizedConfiguredTransformations = new Map();
 	}
 
 	/**
@@ -108,11 +123,33 @@ export default class TextTransformation extends Plugin {
 			this.isEnabled = !modelSelection.anchor.parent.is( 'codeBlock' );
 		} );
 
+		// Set normalized versions of configured transformations once, early on the initialization, so
+		// we don't have to iterate over the configuration and normalize each instance of `from` and `to` of the transformation,
+		// inside the TextWatcher's text testing callback.
+		this._setNormalizedTransformations( this.configuredTransformations );
+
 		this._enableTransformationWatchers();
 	}
 
 	/**
-	 * Create new set of TextWatchers listening to the editor for typing and selection events.
+	 * A helper for setting normalized configured transformations.
+	 *
+	 * @private
+	 * @param {Array} transformations Configured transformations data.
+	 */
+	_setNormalizedTransformations( transformations ) {
+		for ( const transformation of transformations ) {
+			// The key is a text from we should normalize the transformation,
+			// eg: `{ key: '(c)', value: { from: '(c)', to: '©' } }`.
+			this._normalizedConfiguredTransformations.set( transformation.from, {
+				from: normalizeFrom( transformation.from ),
+				to: normalizeTo( transformation.to )
+			} );
+		}
+	}
+
+	/**
+	 * Create new TextWatcher listening to the editor for typing and selection events.
 	 *
 	 * @private
 	 */
@@ -121,51 +158,61 @@ export default class TextTransformation extends Plugin {
 		const model = editor.model;
 		const input = editor.plugins.get( 'Input' );
 
-		const configuredTransformations = getConfiguredTransformations( editor.config.get( 'typing.transformations' ) );
+		const testCallback = text => {
+			for ( const transformation of this.configuredTransformations ) {
+				// Get the normalized version of the configured transformation.
+				// See `_setNormalizedTransformations()`, to get more information how key-value pairs are created.
+				const normalizedTransformation = this._normalizedConfiguredTransformations.get( transformation.from );
+				const from = normalizedTransformation.from;
+				const match = from.test( text );
 
-		for ( const transformation of configuredTransformations ) {
-			const from = normalizeFrom( transformation.from );
-			const to = normalizeTo( transformation.to );
+				if ( match ) {
+					return { match, transformation };
+				}
+			}
+		};
 
-			const watcher = new TextWatcher( editor.model, text => from.test( text ) );
+		const watcherCallback = ( evt, data ) => {
+			if ( !input.isInput( data.batch ) ) {
+				return;
+			}
 
-			const watcherCallback = ( evt, data ) => {
-				if ( !input.isInput( data.batch ) ) {
-					return;
-				}
+			const from = normalizeFrom( data.transformation.from );
+			const to = normalizeTo( data.transformation.to );
 
-				const matches = from.exec( data.text );
-				const replaces = to( matches.slice( 1 ) );
+			const matches = from.exec( data.text );
+			const replaces = to( matches.slice( 1 ) );
 
-				const matchedRange = data.range;
+			const matchedRange = data.range;
 
-				let changeIndex = matches.index;
+			let changeIndex = matches.index;
 
-				model.enqueueChange( writer => {
-					for ( let i = 1; i < matches.length; i++ ) {
-						const match = matches[ i ];
-						const replaceWith = replaces[ i - 1 ];
+			model.enqueueChange( writer => {
+				for ( let i = 1; i < matches.length; i++ ) {
+					const match = matches[ i ];
+					const replaceWith = replaces[ i - 1 ];
 
-						if ( replaceWith == null ) {
-							changeIndex += match.length;
+					if ( replaceWith == null ) {
+						changeIndex += match.length;
 
-							continue;
-						}
+						continue;
+					}
 
-						const replacePosition = matchedRange.start.getShiftedBy( changeIndex );
-						const replaceRange = model.createRange( replacePosition, replacePosition.getShiftedBy( match.length ) );
-						const attributes = getTextAttributesAfterPosition( replacePosition );
+					const replacePosition = matchedRange.start.getShiftedBy( changeIndex );
+					const replaceRange = model.createRange( replacePosition, replacePosition.getShiftedBy( match.length ) );
+					const attributes = getTextAttributesAfterPosition( replacePosition );
 
-						model.insertContent( writer.createText( replaceWith, attributes ), replaceRange );
+					model.insertContent( writer.createText( replaceWith, attributes ), replaceRange );
 
-						changeIndex += replaceWith.length;
-					}
-				} );
-			};
+					changeIndex += replaceWith.length;
+				}
+			} );
+		};
 
-			watcher.on( 'matched:data', watcherCallback );
-			watcher.bind( 'isEnabled' ).to( this );
-		}
+		const watcher = new TextWatcher( editor.model, testCallback );
+
+		watcher.on( 'matched:data', watcherCallback );
+		watcher.bind( 'isEnabled' ).to( this );
 	}
 }