Kaynağa Gözat

Changed: Added `view.Selection#isSimilar`.

Szymon Cofalik 8 yıl önce
ebeveyn
işleme
91c4d73046

+ 55 - 0
packages/ckeditor5-engine/src/view/selection.js

@@ -13,6 +13,7 @@ import Position from './position';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import Element from './element';
+import count from '@ckeditor/ckeditor5-utils/src/count';
 
 /**
  * Class representing selection in tree view.
@@ -207,6 +208,7 @@ export default class Selection {
 	 *
 	 * @fires change
 	 * @param {module:engine/view/range~Range} range
+	 * @param {Boolean} isBackward
 	 */
 	addRange( range, isBackward ) {
 		if ( !( range instanceof Range ) ) {
@@ -338,6 +340,59 @@ export default class Selection {
 		return true;
 	}
 
+	/**
+	 * Checks whether this selection is similar to given selection. Selections are similar if they have same directions, same
+	 * number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
+	 * "touching" any trimmed range from other selection.
+	 *
+	 * Ranges touch if their start positions and end positions {@link module:engine/view/position~Position#isTouching are touching}.
+	 *
+	 * @param {module:engine/view/selection~Selection} otherSelection Selection to compare with.
+	 * @returns {Boolean} `true` if selections are similar, `false` otherwise.
+	 */
+	isSimilar( otherSelection ) {
+		if ( this.isBackward != otherSelection.isBackward ) {
+			return false;
+		}
+
+		const numOfRangesA = count( this.getRanges() );
+		const numOfRangesB = count( otherSelection.getRanges() );
+
+		// If selections have different number of ranges, they cannot be similar.
+		if ( numOfRangesA != numOfRangesB ) {
+			return false;
+		}
+
+		// If both selections have no ranges, they are similar.
+		if ( numOfRangesA == 0 ) {
+			return true;
+		}
+
+		// Check if each range in one selection has a similar range in other selection.
+		for ( let rangeA of this.getRanges() ) {
+			rangeA = rangeA.getTrimmed();
+
+			let found = false;
+
+			for ( let rangeB of otherSelection.getRanges() ) {
+				rangeB = rangeB.getTrimmed();
+
+				if ( rangeA.start.isEqual( rangeB.start ) && rangeA.end.isEqual( rangeB.end ) ) {
+					found = true;
+					break;
+				}
+			}
+
+			// For `rangeA`, neither range in `otherSelection` was similar. So selections are not similar.
+			if ( !found ) {
+				return false;
+			}
+		}
+
+		// There were no ranges that weren't matched. Selections are similar.
+		return true;
+	}
+
 	/**
 	 * Removes all ranges that were added to the selection.
 	 *

+ 89 - 5
packages/ckeditor5-engine/tests/view/selection.js

@@ -17,11 +17,14 @@ describe( 'Selection', () => {
 	let selection, el, range1, range2, range3;
 
 	beforeEach( () => {
+		const text = new Text( 'xxxxxxxxxxxxxxxxxxxx' );
+		el = new Element( 'p', null, text );
+
 		selection = new Selection();
-		el = new Element( 'p' );
-		range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-		range2 = Range.createFromParentsAndOffsets( el, 1, el, 2 );
-		range3 = Range.createFromParentsAndOffsets( el, 12, el, 14 );
+
+		range1 = Range.createFromParentsAndOffsets( text, 5, text, 10 );
+		range2 = Range.createFromParentsAndOffsets( text, 1, text, 2 );
+		range3 = Range.createFromParentsAndOffsets( text, 12, text, 14 );
 	} );
 
 	describe( 'constructor()', () => {
@@ -335,7 +338,8 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should throw when range is intersecting with already added range', () => {
-			const range2 = Range.createFromParentsAndOffsets( el, 7, el, 15 );
+			const text = el.getChild( 0 );
+			const range2 = Range.createFromParentsAndOffsets( text, 7, text, 15 );
 			selection.addRange( range1 );
 			expect( () => {
 				selection.addRange( range2 );
@@ -516,6 +520,86 @@ describe( 'Selection', () => {
 		} );
 	} );
 
+	describe( 'isSimilar', () => {
+		it( 'should return true if selections equal', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1 );
+			otherSelection.addRange( range2 );
+
+			expect( selection.isSimilar( otherSelection ) ).to.be.true;
+		} );
+
+		it( 'should return false if ranges count does not equal', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1 );
+
+			expect( selection.isSimilar( otherSelection ) ).to.be.false;
+		} );
+
+		it( 'should return false if trimmed ranges (other than the last added one) are not equal', () => {
+			selection.addRange( range1 );
+			selection.addRange( range3 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range2 );
+			otherSelection.addRange( range3 );
+
+			expect( selection.isSimilar( otherSelection ) ).to.be.false;
+		} );
+
+		it( 'should return false if directions are not equal', () => {
+			selection.addRange( range1 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1, true );
+
+			expect( selection.isSimilar( otherSelection ) ).to.be.false;
+		} );
+
+		it( 'should return true if both selections are empty', () => {
+			const otherSelection = new Selection();
+
+			expect( selection.isSimilar( otherSelection ) ).to.be.true;
+		} );
+
+		it( 'should return true if all ranges trimmed from both selections are equal', () => {
+			const view = parse(
+				'<container:p><attribute:span></attribute:span></container:p>' +
+				'<container:p><attribute:span>xx</attribute:span></container:p>'
+			);
+
+			const p1 = view.getChild( 0 );
+			const p2 = view.getChild( 1 );
+			const span1 = p1.getChild( 0 );
+			const span2 = p2.getChild( 0 );
+
+			// <p>[<span>{]</span>}</p><p>[<span>{xx}</span>]</p>
+			const rangeA1 = Range.createFromParentsAndOffsets( p1, 0, span1, 0 );
+			const rangeB1 = Range.createFromParentsAndOffsets( span1, 0, p1, 1 );
+			const rangeA2 = Range.createFromParentsAndOffsets( p2, 0, p2, 1 );
+			const rangeB2 = Range.createFromParentsAndOffsets( span2, 0, span2, 1 );
+
+			selection.addRange( rangeA1 );
+			selection.addRange( rangeA2 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( rangeB2 );
+			otherSelection.addRange( rangeB1 );
+
+			expect( selection.isSimilar( otherSelection ) ).to.be.true;
+			expect( otherSelection.isSimilar( selection ) ).to.be.true;
+
+			expect( selection.isEqual( otherSelection ) ).to.be.false;
+			expect( otherSelection.isEqual( selection ) ).to.be.false;
+		} );
+	} );
+
 	describe( 'removeAllRanges', () => {
 		it( 'should remove all ranges and fire change event', done => {
 			selection.addRange( range1 );