Parcourir la source

Introduced model.TreeWalker#skip, model.Position#getFurtherPosition, model.Position#getPriorPosition.

Piotr Jasiun il y a 8 ans
Parent
commit
7e26937be2

+ 39 - 0
packages/ckeditor5-engine/src/model/position.js

@@ -9,6 +9,7 @@
 
 import DocumentFragment from './documentfragment';
 import Element from './element';
+import TreeWalker from './treewalker';
 import last from '@ckeditor/ckeditor5-utils/src/lib/lodash/last';
 import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -245,6 +246,44 @@ export default class Position {
 	}
 
 	/**
+	 * Use forward {@link module:engine/model/treewalker~TreeWalker TreeWalker} to get the farthest position which
+	 * matches the callback.
+	 *
+	 * For example:
+	 *
+	 * 		getFurtherPosition( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
+	 * 		getFurtherPosition( value => false ); // Do not move the position.
+	 *
+	 * @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
+	 * return `true` if the value should be skipped or `false` if not.
+	 */
+	getFurtherPosition( skip ) {
+		const treeWalker = new TreeWalker( { startPosition: this } );
+		treeWalker.skip( skip );
+
+		return treeWalker.position;
+	}
+
+	/**
+	 * Use backward {@link module:engine/model/treewalker~TreeWalker TreeWalker} to get the farthest position which
+	 * matches the callback.
+	 *
+	 * For example:
+	 *
+	 * 		getPriorPosition( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
+	 * 		getPriorPosition( value => false ); // Do not move the position.
+	 *
+	 * @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
+	 * return `true` if the value should be skipped or `false` if not.
+	 */
+	getPriorPosition( skip ) {
+		const treeWalker = new TreeWalker( { startPosition: this, direction: 'backward' } );
+		treeWalker.skip( skip );
+
+		return treeWalker.position;
+	}
+
+	/**
 	 * Returns a path to this position's parent. Parent path is equal to position {@link module:engine/model/position~Position#path path}
 	 * but without the last item.
 	 *

+ 28 - 0
packages/ckeditor5-engine/src/model/treewalker.js

@@ -153,6 +153,34 @@ export default class TreeWalker {
 	}
 
 	/**
+	 * Move {@link #position} in the {@link #direction} skipping values as long as the callback function returns `true`.
+	 *
+	 * For example:
+	 *
+	 * 		walker.skip( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
+	 * 		walker.skip( value => true ); // Move the position to the end: <paragraph>[]foo</paragraph> -> <paragraph>foo</paragraph>[]
+	 * 		walker.skip( value => false ); // Do not move the position.
+	 *
+	 * @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
+	 * return `true` if the value should be skipped or `false` if not.
+	 */
+	skip( skip ) {
+		let done, value, prevPosition, prevVisitedParent;
+
+		do {
+			prevPosition = this.position;
+			prevVisitedParent = this._visitedParent;
+
+			( { done, value } = this.next() );
+		} while ( !done && skip( value ) );
+
+		if ( !done ) {
+			this.position = prevPosition;
+			this._visitedParent = prevVisitedParent;
+		}
+	}
+
+	/**
 	 * Iterator interface method.
 	 * Detects walking direction and makes step forward or backward.
 	 *

+ 20 - 0
packages/ckeditor5-engine/tests/model/position.js

@@ -578,6 +578,26 @@ describe( 'position', () => {
 		} );
 	} );
 
+	describe( 'getFurtherPosition', () => {
+		it( 'should return skip', () => {
+			let position = new Position( root, [ 1, 0, 0 ] );
+
+			position = position.getFurtherPosition( ( value ) => value.type == 'text' );
+
+			expect( position.path ).to.deep.equal( [ 1, 0, 3 ] );
+		} );
+	} );
+
+	describe( 'getPriorPosition', () => {
+		it( 'should return skip', () => {
+			let position = new Position( root, [ 1, 0, 2 ] );
+
+			position = position.getPriorPosition( ( value ) => value.type == 'text' );
+
+			expect( position.path ).to.deep.equal( [ 1, 0, 0 ] );
+		} );
+	} );
+
 	describe( '_getTransformedByInsertion', () => {
 		it( 'should return a new Position instance', () => {
 			const position = new Position( root, [ 0 ] );

+ 75 - 0
packages/ckeditor5-engine/tests/model/treewalker.js

@@ -562,6 +562,81 @@ describe( 'TreeWalker', () => {
 			expectValue( value, expected[ i++ ], { ignoreElementEnd: true } );
 		}
 	} );
+
+	describe( 'skip', () => {
+		describe( 'forward treewalker', () => {
+			it( 'should jump over all text nodes', () => {
+				const walker = new TreeWalker( {
+					startPosition: Position.createFromParentAndOffset( paragraph, 0 )
+				} );
+
+				walker.skip( value => value.type == 'text' );
+
+				expect( walker.position.parent ).to.equal( paragraph );
+				expect( walker.position.offset ).to.equal( 3 );
+			} );
+
+			it( 'should do not move if the condition is false', () => {
+				const walker = new TreeWalker( {
+					startPosition: Position.createFromParentAndOffset( paragraph, 1 )
+				} );
+
+				walker.skip( () => false );
+
+				expect( walker.position.parent ).to.equal( paragraph );
+				expect( walker.position.offset ).to.equal( 1 );
+			} );
+
+			it( 'should move to the end if the condition is true', () => {
+				const walker = new TreeWalker( {
+					startPosition: Position.createFromParentAndOffset( paragraph, 1 )
+				} );
+
+				walker.skip( () => true );
+
+				expect( walker.position.parent ).to.equal( rootEnding.parent );
+				expect( walker.position.offset ).to.equal( rootEnding.offset );
+			} );
+		} );
+
+		describe( 'backward treewalker', () => {
+			it( 'should jump over all text nodes', () => {
+				const walker = new TreeWalker( {
+					startPosition: Position.createFromParentAndOffset( paragraph, 3 ),
+					direction: 'backward'
+				} );
+
+				walker.skip( value => value.type == 'text' );
+
+				expect( walker.position.parent ).to.equal( paragraph );
+				expect( walker.position.offset ).to.equal( 0 );
+			} );
+
+			it( 'should do not move if the condition is false', () => {
+				const walker = new TreeWalker( {
+					startPosition: Position.createFromParentAndOffset( paragraph, 1 ),
+					direction: 'backward'
+				} );
+
+				walker.skip( () => false );
+
+				expect( walker.position.parent ).to.equal( paragraph );
+				expect( walker.position.offset ).to.equal( 1 );
+			} );
+
+			it( 'should move to the end if the condition is true', () => {
+				const walker = new TreeWalker( {
+					startPosition: Position.createFromParentAndOffset( paragraph, 1 ),
+					direction: 'backward'
+				} );
+
+				walker.skip( () => true );
+
+				expect( walker.position.parent ).to.equal( rootBeginning.parent );
+				expect( walker.position.offset ).to.equal( rootBeginning.offset );
+			} );
+		} );
+	} );
 } );
 
 function expectValue( value, expected, options ) {