Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 836dfd8

Browse files
authored
Merge pull request #1175 from ckeditor/t/897
Other: Make `Position` and `Range` immutable in model and view. Closes #897.
2 parents fc7da80 + a2db1b0 commit 836dfd8

28 files changed

+854
-594
lines changed

src/conversion/viewconversiondispatcher.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,11 @@ function extractMarkersFromModelFragment( modelItem ) {
303303

304304
// When marker of given name is not stored it means that we have found the beginning of the range.
305305
if ( !markers.has( markerName ) ) {
306-
markers.set( markerName, new ModelRange( ModelPosition.createFromPosition( currentPosition ) ) );
306+
markers.set( markerName, new ModelRange( currentPosition ) );
307307
// Otherwise is means that we have found end of the marker range.
308308
} else {
309-
markers.get( markerName ).end = ModelPosition.createFromPosition( currentPosition );
309+
const oldMarker = markers.get( markerName );
310+
markers.set( markerName, new ModelRange( oldMarker.start, currentPosition ) );
310311
}
311312

312313
// Remove marker element from DocumentFragment.

src/dev-utils/view.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,16 +541,15 @@ class RangeParser {
541541
throw new Error( `Parse error - end of range was found '${ item.bracket }' but range was not started before.` );
542542
}
543543

544-
// When second start of range is found when one is already opened - selection does not allow intersecting
545-
// ranges.
544+
// When second start of range is found when one is already opened - selection does not allow intersecting ranges.
546545
if ( range && ( item.bracket == ELEMENT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_START_TOKEN ) ) {
547546
throw new Error( `Parse error - start of range was found '${ item.bracket }' but one range is already started.` );
548547
}
549548

550549
if ( item.bracket == ELEMENT_RANGE_START_TOKEN || item.bracket == TEXT_RANGE_START_TOKEN ) {
551550
range = new Range( item.position, item.position );
552551
} else {
553-
range.end = item.position;
552+
range = new Range( range.start, item.position );
554553
ranges.push( range );
555554
range = null;
556555
}

src/model/delta/basic-transformations.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ addTransformationCase( AttributeDelta, SplitDelta, ( a, b, context ) => {
7373
const additionalAttributeDelta = new AttributeDelta();
7474

7575
const rangeStart = splitPosition.getShiftedBy( 1 );
76-
const rangeEnd = Position.createFromPosition( rangeStart );
77-
rangeEnd.path.push( 0 );
76+
77+
const rangeEndPath = rangeStart.path.slice();
78+
rangeEndPath.push( 0 );
79+
const rangeEnd = new Position( rangeStart.root, rangeEndPath );
7880

7981
const oldValue = b._cloneOperation.nodes.getNode( 0 ).getAttribute( operation.key );
8082

@@ -236,7 +238,7 @@ addTransformationCase( SplitDelta, SplitDelta, ( a, b, context ) => {
236238
a._cloneOperation instanceof ReinsertOperation && b._cloneOperation instanceof ReinsertOperation &&
237239
a._cloneOperation.sourcePosition.offset > b._cloneOperation.sourcePosition.offset
238240
) {
239-
a._cloneOperation.sourcePosition.offset--;
241+
a._cloneOperation.sourcePosition = a._cloneOperation.sourcePosition.getShiftedBy( -1 );
240242
}
241243

242244
// `a` splits closer or at same offset.
@@ -317,29 +319,33 @@ addTransformationCase( SplitDelta, WrapDelta, ( a, b, context ) => {
317319
// Wrapping element is the element inserted by WrapDelta (re)insert operation.
318320
// It is inserted after the wrapped range, but the wrapped range will be moved inside it.
319321
// Having this in mind, it is correct to use wrapped range start position as the position before wrapping element.
320-
const splitNodePos = Position.createFromPosition( b.range.start );
322+
321323
// Now, `splitNodePos` points before wrapping element.
322324
// To get a position before last children of that element, we expand position's `path` member by proper offset.
323-
splitNodePos.path.push( b.howMany - 1 );
325+
const splitPath = b.range.start.path.slice();
326+
splitPath.push( b.howMany - 1 );
327+
328+
const splitNodePos = new Position( b.range.start.root, splitPath );
324329

325330
// SplitDelta insert operation position should be right after the node we split.
326-
const insertPos = splitNodePos.getShiftedBy( 1 );
327-
delta._cloneOperation.position = insertPos;
331+
delta._cloneOperation.position = splitNodePos.getShiftedBy( 1 );
328332

329333
// 2. Fix move operation source position.
330334
// Nodes moved by SplitDelta will be moved from new position, modified by WrapDelta.
331335
// To obtain that new position, `splitNodePos` will be used, as this is the node we are extracting children from.
332-
const sourcePos = Position.createFromPosition( splitNodePos );
333336
// Nothing changed inside split node so it is correct to use the original split position offset.
334-
sourcePos.path.push( a.position.offset );
335-
delta._moveOperation.sourcePosition = sourcePos;
337+
const sourcePath = splitNodePos.path.slice();
338+
sourcePath.push( a.position.offset );
339+
340+
delta._moveOperation.sourcePosition = new Position( splitNodePos.root, sourcePath );
336341

337342
// 3. Fix move operation target position.
338343
// SplitDelta move operation target position should be inside the node inserted by operation above.
339344
// Since the node is empty, we will insert at offset 0.
340-
const targetPos = Position.createFromPosition( insertPos );
341-
targetPos.path.push( 0 );
342-
delta._moveOperation.targetPosition = targetPos;
345+
const targetPath = splitNodePos.getShiftedBy( 1 ).path.slice();
346+
targetPath.push( 0 );
347+
348+
delta._moveOperation.targetPosition = new Position( splitNodePos.root, targetPath );
343349

344350
return [ delta ];
345351
}
@@ -434,13 +440,17 @@ addTransformationCase( WrapDelta, SplitDelta, ( a, b, context ) => {
434440
const delta = a.clone();
435441

436442
// Move wrapping element insert position one node further so it is after the split node insertion.
437-
delta._insertOperation.position.offset++;
443+
delta._insertOperation.position = delta._insertOperation.position.getShiftedBy( 1 );
438444

439445
// Include the split node copy.
440446
delta._moveOperation.howMany++;
441447

442448
// Change the path to wrapping element in move operation.
443-
delta._moveOperation.targetPosition.path[ delta._moveOperation.targetPosition.path.length - 2 ]++;
449+
const index = delta._moveOperation.targetPosition.path.length - 2;
450+
451+
const path = delta._moveOperation.targetPosition.path.slice();
452+
path[ index ] += 1;
453+
delta._moveOperation.targetPosition = new Position( delta._moveOperation.targetPosition.root, path );
444454

445455
return [ delta ];
446456
}

src/model/documentselection.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
* @module engine/model/documentselection
88
*/
99

10-
import Position from './position';
1110
import Range from './range';
1211
import LiveRange from './liverange';
1312
import Text from './text';
@@ -666,10 +665,9 @@ export default class DocumentSelection extends Selection {
666665
_fixGraveyardSelection( liveRange, removedRangeStart ) {
667666
// The start of the removed range is the closest position to the `liveRange` - the original selection range.
668667
// This is a good candidate for a fixed selection range.
669-
const positionCandidate = Position.createFromPosition( removedRangeStart );
670668

671669
// Find a range that is a correct selection range and is closest to the start of removed range.
672-
const selectionRange = this._document.getNearestSelectionRange( positionCandidate );
670+
const selectionRange = this._document.getNearestSelectionRange( removedRangeStart );
673671

674672
// Remove the old selection range before preparing and adding new selection range. This order is important,
675673
// because new range, in some cases, may intersect with old range (it depends on `getNearestSelectionRange()` result).

src/model/liveposition.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ function transform( type, range, position ) {
194194
if ( !this.isEqual( transformed ) ) {
195195
const oldPosition = Position.createFromPosition( this );
196196

197-
this.path = transformed.path;
198-
this.root = transformed.root;
197+
this._path = transformed.path;
198+
this._root = transformed.root;
199199

200200
this.fire( 'change', oldPosition );
201201
}

src/model/liverange.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ function transform( changeType, deltaType, batch, targetRange, sourcePosition )
178178
// If range boundaries have changed, fire `change:range` event.
179179
const oldRange = Range.createFromRange( this );
180180

181-
this.start = updated.start;
182-
this.end = updated.end;
181+
this._start = updated.start;
182+
this._end = updated.end;
183183

184184
this.fire( 'change:range', oldRange, {
185185
type: changeType,

src/model/operation/insertoperation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default class InsertOperation extends Operation {
3737
* @readonly
3838
* @member {module:engine/model/position~Position} module:engine/model/operation/insertoperation~InsertOperation#position
3939
*/
40-
this.position = Position.createFromPosition( position );
40+
this.position = position;
4141

4242
/**
4343
* List of nodes to insert.

src/model/operation/renameoperation.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default class RenameOperation extends Operation {
6666
* @returns {module:engine/model/operation/renameoperation~RenameOperation} Clone of this operation.
6767
*/
6868
clone() {
69-
return new RenameOperation( Position.createFromPosition( this.position ), this.oldName, this.newName, this.baseVersion );
69+
return new RenameOperation( this.position, this.oldName, this.newName, this.baseVersion );
7070
}
7171

7272
/**
@@ -75,7 +75,7 @@ export default class RenameOperation extends Operation {
7575
* @returns {module:engine/model/operation/renameoperation~RenameOperation}
7676
*/
7777
getReversed() {
78-
return new RenameOperation( Position.createFromPosition( this.position ), this.newName, this.oldName, this.baseVersion + 1 );
78+
return new RenameOperation( this.position, this.newName, this.oldName, this.baseVersion + 1 );
7979
}
8080

8181
/**

src/model/operation/transform.js

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -179,25 +179,29 @@ const ot = {
179179
// Take the start and the end of the range and transform them by deletion of moved nodes.
180180
// Note that if rangeB was inside AttributeOperation range, only difference.end will be transformed.
181181
// This nicely covers the joining simplification we did in the previous step.
182-
difference.start = difference.start._getTransformedByDeletion( b.sourcePosition, b.howMany );
183-
difference.end = difference.end._getTransformedByDeletion( b.sourcePosition, b.howMany );
182+
const differenceTransformed = new Range(
183+
difference.start._getTransformedByDeletion( b.sourcePosition, b.howMany ),
184+
difference.end._getTransformedByDeletion( b.sourcePosition, b.howMany )
185+
);
184186

185187
// MoveOperation pastes nodes into target position. We acknowledge this by proper transformation.
186188
// Note that since we operate on transformed difference range, we should transform by
187189
// previously transformed target position.
188190
// Note that we do not use Position._getTransformedByMove on range boundaries because we need to
189191
// transform by insertion a range as a whole, since newTargetPosition might be inside that range.
190-
ranges = difference._getTransformedByInsertion( b.getMovedRangeStart(), b.howMany, true, false ).reverse();
192+
ranges = differenceTransformed._getTransformedByInsertion( b.getMovedRangeStart(), b.howMany, true, false ).reverse();
191193
}
192194

193195
if ( common !== null ) {
194196
// Here we do not need to worry that newTargetPosition is inside moved range, because that
195197
// would mean that the MoveOperation targets into itself, and that is incorrect operation.
196198
// Instead, we calculate the new position of that part of original range.
197-
common.start = common.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
198-
common.end = common.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
199+
const commonTransformed = new Range(
200+
common.start._getCombined( b.sourcePosition, b.getMovedRangeStart() ),
201+
common.end._getCombined( b.sourcePosition, b.getMovedRangeStart() )
202+
);
199203

200-
ranges.push( common );
204+
ranges.push( commonTransformed );
201205
}
202206

203207
// Map transformed range(s) to operations and return them.
@@ -376,7 +380,7 @@ const ot = {
376380
// Setting and evaluating some variables that will be used in special cases and default algorithm.
377381
//
378382
// Create ranges from `MoveOperations` properties.
379-
const rangeA = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
383+
let rangeA = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
380384
const rangeB = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
381385

382386
// Assign `context.isStrong` to a different variable, because the value may change during execution of
@@ -428,8 +432,10 @@ const ot = {
428432
if ( bTargetsToA && rangeA.containsRange( rangeB, true ) ) {
429433
// There is a mini-special case here, where `rangeB` is on other level than `rangeA`. That's why
430434
// we need to transform `a` operation anyway.
431-
rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, !includeB );
432-
rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, includeB );
435+
rangeA = new Range(
436+
rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, !includeB ),
437+
rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, includeB )
438+
);
433439

434440
return makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition, a );
435441
}
@@ -444,8 +450,10 @@ const ot = {
444450
if ( aTargetsToB && rangeB.containsRange( rangeA, true ) ) {
445451
// `a` operation is "moved together" with `b` operation.
446452
// Here, just move `rangeA` "inside" `rangeB`.
447-
rangeA.start = rangeA.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
448-
rangeA.end = rangeA.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
453+
rangeA = new Range(
454+
rangeA.start._getCombined( b.sourcePosition, b.getMovedRangeStart() ),
455+
rangeA.end._getCombined( b.sourcePosition, b.getMovedRangeStart() )
456+
);
449457

450458
return makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition, a );
451459
}
@@ -466,8 +474,10 @@ const ot = {
466474
// Transform `rangeA` by `b` operation and make operation out of it, and that's all.
467475
// Note that this is a simplified version of default case, but here we treat the common part (whole `rangeA`)
468476
// like a one difference part.
469-
rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, !includeB );
470-
rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, includeB );
477+
rangeA = new Range(
478+
rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, !includeB ),
479+
rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, includeB )
480+
);
471481

472482
return makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition, a );
473483
}
@@ -500,10 +510,12 @@ const ot = {
500510
// This is an array with one or two ranges. Two ranges if `rangeB` is inside `rangeA`.
501511
const difference = rangeA.getDifference( rangeB );
502512

503-
for ( const range of difference ) {
513+
for ( const rangeInDiff of difference ) {
504514
// Transform those ranges by `b` operation. For example if `b` moved range from before those ranges, fix those ranges.
505-
range.start = range.start._getTransformedByDeletion( b.sourcePosition, b.howMany );
506-
range.end = range.end._getTransformedByDeletion( b.sourcePosition, b.howMany );
515+
const range = new Range(
516+
rangeInDiff.start._getTransformedByDeletion( b.sourcePosition, b.howMany ),
517+
rangeInDiff.end._getTransformedByDeletion( b.sourcePosition, b.howMany )
518+
);
507519

508520
// If `b` operation targets into `rangeA` on the same level, spread `rangeA` into two ranges.
509521
const shouldSpread = compareArrays( range.start.getParentPath(), b.getMovedRangeStart().getParentPath() ) == 'same';
@@ -513,12 +525,14 @@ const ot = {
513525
}
514526

515527
// Then, we have to manage the "common part" of both move ranges.
516-
const common = rangeA.getIntersection( rangeB );
528+
const intersectionRange = rangeA.getIntersection( rangeB );
517529

518-
if ( common !== null && isStrong && !bTargetsToA ) {
530+
if ( intersectionRange !== null && isStrong && !bTargetsToA ) {
519531
// Calculate the new position of that part of original range.
520-
common.start = common.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
521-
common.end = common.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
532+
const common = new Range(
533+
intersectionRange.start._getCombined( b.sourcePosition, b.getMovedRangeStart() ),
534+
intersectionRange.end._getCombined( b.sourcePosition, b.getMovedRangeStart() )
535+
);
522536

523537
// Take care of proper range order.
524538
//
@@ -627,9 +641,10 @@ function joinRanges( ranges ) {
627641
} else if ( ranges.length == 1 ) {
628642
return ranges[ 0 ];
629643
} else {
630-
ranges[ 0 ].end = ranges[ ranges.length - 1 ].end;
631-
632-
return ranges[ 0 ];
644+
return new Range(
645+
ranges[ 0 ].start,
646+
ranges[ ranges.length - 1 ].end
647+
);
633648
}
634649
}
635650

0 commit comments

Comments
 (0)