@fluidframework/sequence 2.0.0-dev.4.4.0.162574 → 2.0.0-dev.5.3.2.178189
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/defaultMap.d.ts +3 -2
- package/dist/defaultMap.d.ts.map +1 -1
- package/dist/defaultMap.js +4 -3
- package/dist/defaultMap.js.map +1 -1
- package/dist/defaultMapInterfaces.d.ts +12 -1
- package/dist/defaultMapInterfaces.d.ts.map +1 -1
- package/dist/defaultMapInterfaces.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/intervalCollection.d.ts +240 -78
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +313 -190
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalIndex/index.d.ts +8 -0
- package/dist/intervalIndex/index.d.ts.map +1 -0
- package/dist/intervalIndex/index.js +12 -0
- package/dist/intervalIndex/index.js.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.js +103 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +33 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.js +7 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/revertibles.d.ts +104 -0
- package/dist/revertibles.d.ts.map +1 -0
- package/dist/revertibles.js +414 -0
- package/dist/revertibles.js.map +1 -0
- package/dist/sequence.d.ts +4 -4
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +3 -3
- package/dist/sequence.js.map +1 -1
- package/dist/sharedIntervalCollection.d.ts +3 -3
- package/dist/sharedIntervalCollection.d.ts.map +1 -1
- package/dist/sharedIntervalCollection.js +1 -1
- package/dist/sharedIntervalCollection.js.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/lib/defaultMap.d.ts +3 -2
- package/lib/defaultMap.d.ts.map +1 -1
- package/lib/defaultMap.js +4 -3
- package/lib/defaultMap.js.map +1 -1
- package/lib/defaultMapInterfaces.d.ts +12 -1
- package/lib/defaultMapInterfaces.d.ts.map +1 -1
- package/lib/defaultMapInterfaces.js.map +1 -1
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/intervalCollection.d.ts +240 -78
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +310 -190
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalIndex/index.d.ts +8 -0
- package/lib/intervalIndex/index.d.ts.map +1 -0
- package/lib/intervalIndex/index.js +7 -0
- package/lib/intervalIndex/index.js.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.js +98 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +29 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.js +6 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/revertibles.d.ts +104 -0
- package/lib/revertibles.d.ts.map +1 -0
- package/lib/revertibles.js +404 -0
- package/lib/revertibles.js.map +1 -0
- package/lib/sequence.d.ts +4 -4
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +3 -3
- package/lib/sequence.js.map +1 -1
- package/lib/sharedIntervalCollection.d.ts +3 -3
- package/lib/sharedIntervalCollection.d.ts.map +1 -1
- package/lib/sharedIntervalCollection.js +1 -1
- package/lib/sharedIntervalCollection.js.map +1 -1
- package/package.json +22 -24
- package/src/defaultMap.ts +4 -1
- package/src/defaultMapInterfaces.ts +13 -1
- package/src/index.ts +27 -5
- package/src/intervalCollection.ts +660 -216
- package/src/intervalIndex/index.ts +11 -0
- package/src/intervalIndex/overlappingIntervalsIndex.ts +166 -0
- package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +71 -0
- package/src/intervalIndex/sequenceIntervalIndexes.ts +32 -0
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +626 -0
- package/src/sequence.ts +12 -2
- package/src/sharedIntervalCollection.ts +4 -2
|
@@ -16,11 +16,22 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
16
16
|
/* eslint-disable no-bitwise */
|
|
17
17
|
import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
18
18
|
import { UsageError } from "@fluidframework/container-utils";
|
|
19
|
-
import { addProperties, compareReferencePositions, createMap, MergeTreeDeltaType, minReferencePosition, PropertiesManager, RedBlackTree, ReferenceType, refTypeIncludesFlag, reservedRangeLabelsKey, UnassignedSequenceNumber, maxReferencePosition, createDetachedLocalReferencePosition, DetachedReferencePosition, } from "@fluidframework/merge-tree";
|
|
19
|
+
import { addProperties, compareReferencePositions, createMap, getSlideToSegoff, MergeTreeDeltaType, minReferencePosition, PropertiesManager, RedBlackTree, ReferenceType, refTypeIncludesFlag, reservedRangeLabelsKey, UnassignedSequenceNumber, maxReferencePosition, createDetachedLocalReferencePosition, DetachedReferencePosition, SlidingPreference, } from "@fluidframework/merge-tree";
|
|
20
20
|
import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
21
21
|
import { v4 as uuid } from "uuid";
|
|
22
|
-
import {
|
|
22
|
+
import { createOverlappingIntervalsIndex } from "./intervalIndex";
|
|
23
23
|
const reservedIntervalIdKey = "intervalId";
|
|
24
|
+
/**
|
|
25
|
+
* Values are used in persisted formats (ops) and revertibles.
|
|
26
|
+
* @alpha
|
|
27
|
+
*/
|
|
28
|
+
export const IntervalOpType = {
|
|
29
|
+
ADD: "add",
|
|
30
|
+
DELETE: "delete",
|
|
31
|
+
CHANGE: "change",
|
|
32
|
+
PROPERTY_CHANGED: "propertyChanged",
|
|
33
|
+
POSITION_REMOVE: "positionRemove",
|
|
34
|
+
};
|
|
24
35
|
export var IntervalType;
|
|
25
36
|
(function (IntervalType) {
|
|
26
37
|
IntervalType[IntervalType["Simple"] = 0] = "Simple";
|
|
@@ -28,7 +39,7 @@ export var IntervalType;
|
|
|
28
39
|
/**
|
|
29
40
|
* SlideOnRemove indicates that the ends of the interval will slide if the segment
|
|
30
41
|
* they reference is removed and acked.
|
|
31
|
-
* See `packages\dds\merge-tree\REFERENCEPOSITIONS.md` for details
|
|
42
|
+
* See `packages\dds\merge-tree\docs\REFERENCEPOSITIONS.md` for details
|
|
32
43
|
* SlideOnRemove is the default interval behavior and does not need to be specified.
|
|
33
44
|
*/
|
|
34
45
|
IntervalType[IntervalType["SlideOnRemove"] = 2] = "SlideOnRemove";
|
|
@@ -49,6 +60,7 @@ function decompressInterval(interval, label) {
|
|
|
49
60
|
sequenceNumber: interval[2],
|
|
50
61
|
intervalType: interval[3],
|
|
51
62
|
properties: Object.assign(Object.assign({}, interval[4]), { [reservedRangeLabelsKey]: [label] }),
|
|
63
|
+
stickiness: interval[5],
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
@@ -57,14 +69,57 @@ function decompressInterval(interval, label) {
|
|
|
57
69
|
*/
|
|
58
70
|
function compressInterval(interval) {
|
|
59
71
|
const { start, end, sequenceNumber, intervalType, properties } = interval;
|
|
60
|
-
|
|
72
|
+
const base = [
|
|
61
73
|
start,
|
|
62
74
|
end,
|
|
63
75
|
sequenceNumber,
|
|
64
76
|
intervalType,
|
|
65
77
|
Object.assign(Object.assign({}, properties), { [reservedRangeLabelsKey]: undefined }),
|
|
66
78
|
];
|
|
79
|
+
if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
|
|
80
|
+
base.push(interval.stickiness);
|
|
81
|
+
}
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
function startReferenceSlidingPreference(stickiness) {
|
|
85
|
+
// if any start stickiness, prefer sliding backwards
|
|
86
|
+
return (stickiness & IntervalStickiness.START) !== 0
|
|
87
|
+
? SlidingPreference.BACKWARD
|
|
88
|
+
: SlidingPreference.FORWARD;
|
|
89
|
+
}
|
|
90
|
+
function endReferenceSlidingPreference(stickiness) {
|
|
91
|
+
// if any end stickiness, prefer sliding forwards
|
|
92
|
+
return (stickiness & IntervalStickiness.END) !== 0
|
|
93
|
+
? SlidingPreference.FORWARD
|
|
94
|
+
: SlidingPreference.BACKWARD;
|
|
67
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Determines how an interval should expand when segments are inserted adjacent
|
|
98
|
+
* to the range it spans
|
|
99
|
+
*
|
|
100
|
+
* Note that interval stickiness is currently an experimental feature and must
|
|
101
|
+
* be explicitly enabled with the `intervalStickinessEnabled` flag
|
|
102
|
+
*/
|
|
103
|
+
export const IntervalStickiness = {
|
|
104
|
+
/**
|
|
105
|
+
* Interval does not expand to include adjacent segments
|
|
106
|
+
*/
|
|
107
|
+
NONE: 0b00,
|
|
108
|
+
/**
|
|
109
|
+
* Interval expands to include segments inserted adjacent to the start
|
|
110
|
+
*/
|
|
111
|
+
START: 0b01,
|
|
112
|
+
/**
|
|
113
|
+
* Interval expands to include segments inserted adjacent to the end
|
|
114
|
+
*
|
|
115
|
+
* This is the default stickiness
|
|
116
|
+
*/
|
|
117
|
+
END: 0b10,
|
|
118
|
+
/**
|
|
119
|
+
* Interval expands to include all segments inserted adjacent to it
|
|
120
|
+
*/
|
|
121
|
+
FULL: 0b11,
|
|
122
|
+
};
|
|
68
123
|
/**
|
|
69
124
|
* Serializable interval whose endpoints are plain-old numbers.
|
|
70
125
|
*/
|
|
@@ -98,7 +153,7 @@ export class Interval {
|
|
|
98
153
|
* Adds an auxiliary set of properties to this interval.
|
|
99
154
|
* These properties can be recovered using `getAdditionalPropertySets`
|
|
100
155
|
* @param props - set of properties to add
|
|
101
|
-
* @remarks - This gets called as part of the default conflict resolver for `
|
|
156
|
+
* @remarks - This gets called as part of the default conflict resolver for `IIntervalCollection<Interval>`
|
|
102
157
|
* (i.e. non-sequence-based interval collections). However, the additional properties don't get serialized.
|
|
103
158
|
* This functionality seems half-baked.
|
|
104
159
|
*/
|
|
@@ -223,7 +278,7 @@ export class Interval {
|
|
|
223
278
|
}
|
|
224
279
|
}
|
|
225
280
|
/**
|
|
226
|
-
* Interval
|
|
281
|
+
* Interval implementation whose ends are associated with positions in a mutatable sequence.
|
|
227
282
|
* As such, when content is inserted into the middle of the interval, the interval expands to
|
|
228
283
|
* include that content.
|
|
229
284
|
*
|
|
@@ -254,11 +309,12 @@ export class SequenceInterval {
|
|
|
254
309
|
* End endpoint of this interval.
|
|
255
310
|
* @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
|
|
256
311
|
*/
|
|
257
|
-
end, intervalType, props) {
|
|
312
|
+
end, intervalType, props, stickiness = IntervalStickiness.END) {
|
|
258
313
|
this.client = client;
|
|
259
314
|
this.start = start;
|
|
260
315
|
this.end = end;
|
|
261
316
|
this.intervalType = intervalType;
|
|
317
|
+
this.stickiness = stickiness;
|
|
262
318
|
this.propertyManager = new PropertiesManager();
|
|
263
319
|
this.properties = {};
|
|
264
320
|
if (props) {
|
|
@@ -310,13 +366,16 @@ export class SequenceInterval {
|
|
|
310
366
|
if (this.properties) {
|
|
311
367
|
serializedInterval.properties = this.properties;
|
|
312
368
|
}
|
|
369
|
+
if (this.stickiness !== IntervalStickiness.END) {
|
|
370
|
+
serializedInterval.stickiness = this.stickiness;
|
|
371
|
+
}
|
|
313
372
|
return serializedInterval;
|
|
314
373
|
}
|
|
315
374
|
/**
|
|
316
375
|
* {@inheritDoc IInterval.clone}
|
|
317
376
|
*/
|
|
318
377
|
clone() {
|
|
319
|
-
return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties);
|
|
378
|
+
return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties, this.stickiness);
|
|
320
379
|
}
|
|
321
380
|
/**
|
|
322
381
|
* {@inheritDoc IInterval.compare}
|
|
@@ -400,7 +459,7 @@ export class SequenceInterval {
|
|
|
400
459
|
* {@inheritDoc IInterval.modify}
|
|
401
460
|
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
402
461
|
*/
|
|
403
|
-
modify(label, start, end, op, localSeq) {
|
|
462
|
+
modify(label, start, end, op, localSeq, stickiness = IntervalStickiness.END) {
|
|
404
463
|
const getRefType = (baseType) => {
|
|
405
464
|
let refType = baseType;
|
|
406
465
|
if (op === undefined) {
|
|
@@ -411,14 +470,14 @@ export class SequenceInterval {
|
|
|
411
470
|
};
|
|
412
471
|
let startRef = this.start;
|
|
413
472
|
if (start !== undefined) {
|
|
414
|
-
startRef = createPositionReference(this.client, start, getRefType(this.start.refType), op, undefined, localSeq);
|
|
473
|
+
startRef = createPositionReference(this.client, start, getRefType(this.start.refType), op, undefined, localSeq, startReferenceSlidingPreference(stickiness));
|
|
415
474
|
if (this.start.properties) {
|
|
416
475
|
startRef.addProperties(this.start.properties);
|
|
417
476
|
}
|
|
418
477
|
}
|
|
419
478
|
let endRef = this.end;
|
|
420
479
|
if (end !== undefined) {
|
|
421
|
-
endRef = createPositionReference(this.client, end, getRefType(this.end.refType), op, undefined, localSeq);
|
|
480
|
+
endRef = createPositionReference(this.client, end, getRefType(this.end.refType), op, undefined, localSeq, endReferenceSlidingPreference(stickiness));
|
|
422
481
|
if (this.end.properties) {
|
|
423
482
|
endRef.addProperties(this.end.properties);
|
|
424
483
|
}
|
|
@@ -439,9 +498,9 @@ export class SequenceInterval {
|
|
|
439
498
|
}
|
|
440
499
|
}
|
|
441
500
|
}
|
|
442
|
-
function createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot) {
|
|
501
|
+
export function createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot, slidingPreference) {
|
|
443
502
|
if (segoff.segment) {
|
|
444
|
-
const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
|
|
503
|
+
const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined, slidingPreference);
|
|
445
504
|
return ref;
|
|
446
505
|
}
|
|
447
506
|
// Creating references on detached segments is allowed for:
|
|
@@ -457,7 +516,7 @@ function createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq
|
|
|
457
516
|
}
|
|
458
517
|
return createDetachedLocalReferencePosition(refType);
|
|
459
518
|
}
|
|
460
|
-
function createPositionReference(client, pos, refType, op, fromSnapshot, localSeq) {
|
|
519
|
+
function createPositionReference(client, pos, refType, op, fromSnapshot, localSeq, slidingPreference) {
|
|
461
520
|
let segoff;
|
|
462
521
|
if (op) {
|
|
463
522
|
assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
|
|
@@ -465,15 +524,15 @@ function createPositionReference(client, pos, refType, op, fromSnapshot, localSe
|
|
|
465
524
|
referenceSequenceNumber: op.referenceSequenceNumber,
|
|
466
525
|
clientId: op.clientId,
|
|
467
526
|
});
|
|
468
|
-
segoff =
|
|
527
|
+
segoff = getSlideToSegoff(segoff);
|
|
469
528
|
}
|
|
470
529
|
else {
|
|
471
530
|
assert((refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot, 0x2f6 /* SlideOnRemove references must be op created */);
|
|
472
531
|
segoff = client.getContainingSegment(pos, undefined, localSeq);
|
|
473
532
|
}
|
|
474
|
-
return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot);
|
|
533
|
+
return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot, slidingPreference);
|
|
475
534
|
}
|
|
476
|
-
export function createSequenceInterval(label, start, end, client, intervalType, op, fromSnapshot) {
|
|
535
|
+
export function createSequenceInterval(label, start, end, client, intervalType, op, fromSnapshot, stickiness = IntervalStickiness.END) {
|
|
477
536
|
let beginRefType = ReferenceType.RangeBegin;
|
|
478
537
|
let endRefType = ReferenceType.RangeEnd;
|
|
479
538
|
if (intervalType === IntervalType.Transient) {
|
|
@@ -497,14 +556,14 @@ export function createSequenceInterval(label, start, end, client, intervalType,
|
|
|
497
556
|
endRefType |= ReferenceType.StayOnRemove;
|
|
498
557
|
}
|
|
499
558
|
}
|
|
500
|
-
const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot);
|
|
501
|
-
const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot);
|
|
559
|
+
const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot, undefined, startReferenceSlidingPreference(stickiness));
|
|
560
|
+
const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot, undefined, endReferenceSlidingPreference(stickiness));
|
|
502
561
|
const rangeProp = {
|
|
503
562
|
[reservedRangeLabelsKey]: [label],
|
|
504
563
|
};
|
|
505
564
|
startLref.addProperties(rangeProp);
|
|
506
565
|
endLref.addProperties(rangeProp);
|
|
507
|
-
const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp);
|
|
566
|
+
const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp, stickiness);
|
|
508
567
|
return ival;
|
|
509
568
|
}
|
|
510
569
|
export function createIntervalIndex() {
|
|
@@ -515,98 +574,6 @@ export function createIntervalIndex() {
|
|
|
515
574
|
const lc = new LocalIntervalCollection(undefined, "", helpers);
|
|
516
575
|
return lc;
|
|
517
576
|
}
|
|
518
|
-
class OverlappingIntervalsIndex {
|
|
519
|
-
constructor(client, helpers) {
|
|
520
|
-
this.client = client;
|
|
521
|
-
this.helpers = helpers;
|
|
522
|
-
this.intervalTree = new IntervalTree();
|
|
523
|
-
}
|
|
524
|
-
map(fn) {
|
|
525
|
-
this.intervalTree.map(fn);
|
|
526
|
-
}
|
|
527
|
-
mapUntil(fn) {
|
|
528
|
-
this.intervalTree.mapUntil(fn);
|
|
529
|
-
}
|
|
530
|
-
gatherIterationResults(results, iteratesForward, start, end) {
|
|
531
|
-
if (this.intervalTree.intervals.isEmpty()) {
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
if (start === undefined && end === undefined) {
|
|
535
|
-
// No start/end provided. Gather the whole tree in the specified order.
|
|
536
|
-
if (iteratesForward) {
|
|
537
|
-
this.intervalTree.map((interval) => {
|
|
538
|
-
results.push(interval);
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
else {
|
|
542
|
-
this.intervalTree.mapBackward((interval) => {
|
|
543
|
-
results.push(interval);
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
const transientInterval = this.helpers.create("transient", start, end, this.client, IntervalType.Transient);
|
|
549
|
-
if (start === undefined) {
|
|
550
|
-
// Only end position provided. Since the tree is not sorted by end position,
|
|
551
|
-
// walk the whole tree in the specified order, gathering intervals that match the end.
|
|
552
|
-
if (iteratesForward) {
|
|
553
|
-
this.intervalTree.map((interval) => {
|
|
554
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
555
|
-
results.push(interval);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
this.intervalTree.mapBackward((interval) => {
|
|
561
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
562
|
-
results.push(interval);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
// Start and (possibly) end provided. Walk the subtrees that may contain
|
|
569
|
-
// this start position.
|
|
570
|
-
const compareFn = end === undefined
|
|
571
|
-
? (node) => {
|
|
572
|
-
return transientInterval.compareStart(node.key);
|
|
573
|
-
}
|
|
574
|
-
: (node) => {
|
|
575
|
-
return transientInterval.compare(node.key);
|
|
576
|
-
};
|
|
577
|
-
const continueLeftFn = (cmpResult) => cmpResult <= 0;
|
|
578
|
-
const continueRightFn = (cmpResult) => cmpResult >= 0;
|
|
579
|
-
const actionFn = (node) => {
|
|
580
|
-
results.push(node.key);
|
|
581
|
-
};
|
|
582
|
-
if (iteratesForward) {
|
|
583
|
-
this.intervalTree.intervals.walkExactMatchesForward(compareFn, actionFn, continueLeftFn, continueRightFn);
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
this.intervalTree.intervals.walkExactMatchesBackward(compareFn, actionFn, continueLeftFn, continueRightFn);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* @returns an array of all intervals contained in this collection that overlap the range
|
|
593
|
-
* `[startPosition, endPosition)`.
|
|
594
|
-
*/
|
|
595
|
-
findOverlappingIntervals(startPosition, endPosition) {
|
|
596
|
-
if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
|
|
597
|
-
return [];
|
|
598
|
-
}
|
|
599
|
-
const transientInterval = this.helpers.create("transient", startPosition, endPosition, this.client, IntervalType.Transient);
|
|
600
|
-
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
601
|
-
return overlappingIntervalNodes.map((node) => node.key);
|
|
602
|
-
}
|
|
603
|
-
remove(interval) {
|
|
604
|
-
this.intervalTree.removeExisting(interval);
|
|
605
|
-
}
|
|
606
|
-
add(interval) {
|
|
607
|
-
this.intervalTree.put(interval);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
577
|
class IdIntervalIndex {
|
|
611
578
|
constructor() {
|
|
612
579
|
this.intervalIdMap = new Map();
|
|
@@ -662,6 +629,117 @@ class EndpointIndex {
|
|
|
662
629
|
this.endIntervalTree.remove(interval);
|
|
663
630
|
}
|
|
664
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Interface for intervals that have comparison override properties.
|
|
634
|
+
*/
|
|
635
|
+
const forceCompare = Symbol();
|
|
636
|
+
/**
|
|
637
|
+
* Compares two objects based on their comparison override properties.
|
|
638
|
+
* @returns A number indicating the order of the intervals (negative for a is lower than b, 0 for tie, positive for a is greater than b).
|
|
639
|
+
*/
|
|
640
|
+
function compareOverrideables(a, b) {
|
|
641
|
+
var _a, _b;
|
|
642
|
+
const forceCompareA = (_a = a[forceCompare]) !== null && _a !== void 0 ? _a : 0;
|
|
643
|
+
const forceCompareB = (_b = b[forceCompare]) !== null && _b !== void 0 ? _b : 0;
|
|
644
|
+
return forceCompareA - forceCompareB;
|
|
645
|
+
}
|
|
646
|
+
class EndpointInRangeIndex {
|
|
647
|
+
constructor(helpers, client) {
|
|
648
|
+
this.helpers = helpers;
|
|
649
|
+
this.client = client;
|
|
650
|
+
this.intervalTree = new RedBlackTree((a, b) => {
|
|
651
|
+
const compareEndsResult = helpers.compareEnds(a, b);
|
|
652
|
+
if (compareEndsResult !== 0) {
|
|
653
|
+
return compareEndsResult;
|
|
654
|
+
}
|
|
655
|
+
const overrideablesComparison = compareOverrideables(a, b);
|
|
656
|
+
if (overrideablesComparison !== 0) {
|
|
657
|
+
return overrideablesComparison;
|
|
658
|
+
}
|
|
659
|
+
const aId = a.getIntervalId();
|
|
660
|
+
const bId = b.getIntervalId();
|
|
661
|
+
if (aId !== undefined && bId !== undefined) {
|
|
662
|
+
return aId.localeCompare(bId);
|
|
663
|
+
}
|
|
664
|
+
return 0;
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
add(interval) {
|
|
668
|
+
this.intervalTree.put(interval, interval);
|
|
669
|
+
}
|
|
670
|
+
remove(interval) {
|
|
671
|
+
this.intervalTree.remove(interval);
|
|
672
|
+
}
|
|
673
|
+
findIntervalsWithEndpointInRange(start, end) {
|
|
674
|
+
if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
|
|
675
|
+
return [];
|
|
676
|
+
}
|
|
677
|
+
const results = [];
|
|
678
|
+
const action = (node) => {
|
|
679
|
+
results.push(node.data);
|
|
680
|
+
return true;
|
|
681
|
+
};
|
|
682
|
+
const transientStartInterval = this.helpers.create("transient", start, start, this.client, IntervalType.Transient);
|
|
683
|
+
const transientEndInterval = this.helpers.create("transient", end, end, this.client, IntervalType.Transient);
|
|
684
|
+
// Add comparison overrides to the transient intervals
|
|
685
|
+
transientStartInterval[forceCompare] = -1;
|
|
686
|
+
transientEndInterval[forceCompare] = 1;
|
|
687
|
+
this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
|
|
688
|
+
return results;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
class StartpointInRangeIndex {
|
|
692
|
+
constructor(helpers, client) {
|
|
693
|
+
this.helpers = helpers;
|
|
694
|
+
this.client = client;
|
|
695
|
+
this.intervalTree = new RedBlackTree((a, b) => {
|
|
696
|
+
assert(typeof helpers.compareStarts === "function", 0x6d1 /* compareStarts does not exist in the helpers */);
|
|
697
|
+
const compareStartsResult = helpers.compareStarts(a, b);
|
|
698
|
+
if (compareStartsResult !== 0) {
|
|
699
|
+
return compareStartsResult;
|
|
700
|
+
}
|
|
701
|
+
const overrideablesComparison = compareOverrideables(a, b);
|
|
702
|
+
if (overrideablesComparison !== 0) {
|
|
703
|
+
return overrideablesComparison;
|
|
704
|
+
}
|
|
705
|
+
const aId = a.getIntervalId();
|
|
706
|
+
const bId = b.getIntervalId();
|
|
707
|
+
if (aId !== undefined && bId !== undefined) {
|
|
708
|
+
return aId.localeCompare(bId);
|
|
709
|
+
}
|
|
710
|
+
return 0;
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
add(interval) {
|
|
714
|
+
this.intervalTree.put(interval, interval);
|
|
715
|
+
}
|
|
716
|
+
remove(interval) {
|
|
717
|
+
this.intervalTree.remove(interval);
|
|
718
|
+
}
|
|
719
|
+
findIntervalsWithStartpointInRange(start, end) {
|
|
720
|
+
if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
723
|
+
const results = [];
|
|
724
|
+
const action = (node) => {
|
|
725
|
+
results.push(node.data);
|
|
726
|
+
return true;
|
|
727
|
+
};
|
|
728
|
+
const transientStartInterval = this.helpers.create("transient", start, start, this.client, IntervalType.Transient);
|
|
729
|
+
const transientEndInterval = this.helpers.create("transient", end, end, this.client, IntervalType.Transient);
|
|
730
|
+
// Add comparison overrides to the transient intervals
|
|
731
|
+
transientStartInterval[forceCompare] = -1;
|
|
732
|
+
transientEndInterval[forceCompare] = 1;
|
|
733
|
+
this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
|
|
734
|
+
return results;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
export function createEndpointInRangeIndex(helpers, client) {
|
|
738
|
+
return new EndpointInRangeIndex(helpers, client);
|
|
739
|
+
}
|
|
740
|
+
export function createStartpointInRangeIndex(helpers, client) {
|
|
741
|
+
return new StartpointInRangeIndex(helpers, client);
|
|
742
|
+
}
|
|
665
743
|
export class LocalIntervalCollection {
|
|
666
744
|
constructor(client, label, helpers,
|
|
667
745
|
/** Callback invoked each time one of the endpoints of an interval slides. */
|
|
@@ -670,14 +748,14 @@ export class LocalIntervalCollection {
|
|
|
670
748
|
this.label = label;
|
|
671
749
|
this.helpers = helpers;
|
|
672
750
|
this.onPositionChange = onPositionChange;
|
|
673
|
-
this.overlappingIntervalsIndex =
|
|
751
|
+
this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
|
|
674
752
|
this.idIntervalIndex = new IdIntervalIndex();
|
|
675
753
|
this.endIntervalIndex = new EndpointIndex(client, helpers);
|
|
676
|
-
this.indexes = [
|
|
754
|
+
this.indexes = new Set([
|
|
677
755
|
this.overlappingIntervalsIndex,
|
|
678
756
|
this.idIntervalIndex,
|
|
679
757
|
this.endIntervalIndex,
|
|
680
|
-
];
|
|
758
|
+
]);
|
|
681
759
|
}
|
|
682
760
|
createLegacyId(start, end) {
|
|
683
761
|
// Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
|
|
@@ -717,22 +795,35 @@ export class LocalIntervalCollection {
|
|
|
717
795
|
index.remove(interval);
|
|
718
796
|
}
|
|
719
797
|
}
|
|
798
|
+
appendIndex(index) {
|
|
799
|
+
this.indexes.add(index);
|
|
800
|
+
}
|
|
801
|
+
removeIndex(index) {
|
|
802
|
+
return this.indexes.delete(index);
|
|
803
|
+
}
|
|
720
804
|
removeExistingInterval(interval) {
|
|
721
805
|
this.removeIntervalFromIndexes(interval);
|
|
722
806
|
this.removeIntervalListeners(interval);
|
|
723
807
|
}
|
|
724
|
-
createInterval(start, end, intervalType, op) {
|
|
725
|
-
return this.helpers.create(this.label, start, end, this.client, intervalType, op);
|
|
808
|
+
createInterval(start, end, intervalType, op, stickiness = IntervalStickiness.END) {
|
|
809
|
+
return this.helpers.create(this.label, start, end, this.client, intervalType, op, undefined, stickiness);
|
|
726
810
|
}
|
|
727
|
-
addInterval(start, end, intervalType, props, op) {
|
|
811
|
+
addInterval(start, end, intervalType, props, op, stickiness = IntervalStickiness.END) {
|
|
728
812
|
var _a;
|
|
729
813
|
var _b;
|
|
730
|
-
const interval = this.createInterval(start, end, intervalType, op);
|
|
814
|
+
const interval = this.createInterval(start, end, intervalType, op, stickiness);
|
|
731
815
|
if (interval) {
|
|
732
816
|
if (!interval.properties) {
|
|
733
817
|
interval.properties = createMap();
|
|
734
818
|
}
|
|
735
819
|
if (props) {
|
|
820
|
+
// This check is intended to prevent scenarios where a random interval is created and then
|
|
821
|
+
// inserted into a collection. The aim is to ensure that the collection is created first
|
|
822
|
+
// then the user can create/add intervals based on the collection
|
|
823
|
+
if (props[reservedRangeLabelsKey] !== undefined &&
|
|
824
|
+
props[reservedRangeLabelsKey][0] !== this.label) {
|
|
825
|
+
throw new LoggingError("Adding an interval that belongs to another interval collection is not permitted");
|
|
826
|
+
}
|
|
736
827
|
interval.addProperties(props);
|
|
737
828
|
}
|
|
738
829
|
(_a = (_b = interval.properties)[reservedIntervalIdKey]) !== null && _a !== void 0 ? _a : (_b[reservedIntervalIdKey] = uuid());
|
|
@@ -780,7 +871,7 @@ export class LocalIntervalCollection {
|
|
|
780
871
|
// either, so this must be special-cased.
|
|
781
872
|
return ref;
|
|
782
873
|
}
|
|
783
|
-
return this.client.createLocalReferencePosition(segment, ref.getOffset(), ReferenceType.Transient, ref.properties);
|
|
874
|
+
return this.client.createLocalReferencePosition(segment, ref.getOffset(), ReferenceType.Transient, ref.properties, ref.slidingPreference);
|
|
784
875
|
};
|
|
785
876
|
if (interval instanceof SequenceInterval) {
|
|
786
877
|
let previousInterval;
|
|
@@ -814,13 +905,20 @@ export class LocalIntervalCollection {
|
|
|
814
905
|
}
|
|
815
906
|
LocalIntervalCollection.legacyIdPrefix = "legacy";
|
|
816
907
|
export const compareSequenceIntervalEnds = (a, b) => compareReferencePositions(a.end, b.end);
|
|
908
|
+
export const compareSequenceIntervalStarts = (a, b) => compareReferencePositions(a.start, b.start);
|
|
909
|
+
export const sequenceIntervalHelpers = {
|
|
910
|
+
compareEnds: compareSequenceIntervalEnds,
|
|
911
|
+
compareStarts: compareSequenceIntervalStarts,
|
|
912
|
+
create: createSequenceInterval,
|
|
913
|
+
};
|
|
914
|
+
export const intervalHelpers = {
|
|
915
|
+
compareEnds: (a, b) => a.end - b.end,
|
|
916
|
+
compareStarts: (a, b) => a.start - b.start,
|
|
917
|
+
create: createInterval,
|
|
918
|
+
};
|
|
817
919
|
class SequenceIntervalCollectionFactory {
|
|
818
|
-
load(emitter, raw = []) {
|
|
819
|
-
|
|
820
|
-
compareEnds: compareSequenceIntervalEnds,
|
|
821
|
-
create: createSequenceInterval,
|
|
822
|
-
};
|
|
823
|
-
return new IntervalCollection(helpers, true, emitter, raw);
|
|
920
|
+
load(emitter, raw = [], options) {
|
|
921
|
+
return new IntervalCollection(sequenceIntervalHelpers, true, emitter, raw, options);
|
|
824
922
|
}
|
|
825
923
|
store(value) {
|
|
826
924
|
return value.serializeInternal();
|
|
@@ -841,7 +939,7 @@ SequenceIntervalCollectionValueType.Name = "sharedStringIntervalCollection";
|
|
|
841
939
|
SequenceIntervalCollectionValueType._factory = new SequenceIntervalCollectionFactory();
|
|
842
940
|
SequenceIntervalCollectionValueType._ops = makeOpsMap();
|
|
843
941
|
const compareIntervalEnds = (a, b) => a.end - b.end;
|
|
844
|
-
function createInterval(label, start, end, client) {
|
|
942
|
+
function createInterval(label, start, end, client, intervalType, op, fromSnapshot) {
|
|
845
943
|
const rangeProp = {};
|
|
846
944
|
if (label && label.length > 0) {
|
|
847
945
|
rangeProp[reservedRangeLabelsKey] = [label];
|
|
@@ -849,12 +947,12 @@ function createInterval(label, start, end, client) {
|
|
|
849
947
|
return new Interval(start, end, rangeProp);
|
|
850
948
|
}
|
|
851
949
|
class IntervalCollectionFactory {
|
|
852
|
-
load(emitter, raw = []) {
|
|
950
|
+
load(emitter, raw = [], options) {
|
|
853
951
|
const helpers = {
|
|
854
952
|
compareEnds: compareIntervalEnds,
|
|
855
953
|
create: createInterval,
|
|
856
954
|
};
|
|
857
|
-
const collection = new IntervalCollection(helpers, false, emitter, raw);
|
|
955
|
+
const collection = new IntervalCollection(helpers, false, emitter, raw, options);
|
|
858
956
|
collection.attachGraph(undefined, "");
|
|
859
957
|
return collection;
|
|
860
958
|
}
|
|
@@ -885,7 +983,7 @@ export function makeOpsMap() {
|
|
|
885
983
|
};
|
|
886
984
|
return new Map([
|
|
887
985
|
[
|
|
888
|
-
|
|
986
|
+
IntervalOpType.ADD,
|
|
889
987
|
{
|
|
890
988
|
process: (collection, params, local, op, localOpMetadata) => {
|
|
891
989
|
// if params is undefined, the interval was deleted during
|
|
@@ -900,7 +998,7 @@ export function makeOpsMap() {
|
|
|
900
998
|
},
|
|
901
999
|
],
|
|
902
1000
|
[
|
|
903
|
-
|
|
1001
|
+
IntervalOpType.DELETE,
|
|
904
1002
|
{
|
|
905
1003
|
process: (collection, params, local, op) => {
|
|
906
1004
|
assert(op !== undefined, 0x3fc /* op should exist here */);
|
|
@@ -913,7 +1011,7 @@ export function makeOpsMap() {
|
|
|
913
1011
|
},
|
|
914
1012
|
],
|
|
915
1013
|
[
|
|
916
|
-
|
|
1014
|
+
IntervalOpType.CHANGE,
|
|
917
1015
|
{
|
|
918
1016
|
process: (collection, params, local, op, localOpMetadata) => {
|
|
919
1017
|
// if params is undefined, the interval was deleted during
|
|
@@ -929,7 +1027,7 @@ export function makeOpsMap() {
|
|
|
929
1027
|
],
|
|
930
1028
|
]);
|
|
931
1029
|
}
|
|
932
|
-
|
|
1030
|
+
class IntervalCollectionIterator {
|
|
933
1031
|
constructor(collection, iteratesForward = true, start, end) {
|
|
934
1032
|
this.results = [];
|
|
935
1033
|
this.index = 0;
|
|
@@ -949,19 +1047,16 @@ export class IntervalCollectionIterator {
|
|
|
949
1047
|
}
|
|
950
1048
|
}
|
|
951
1049
|
/**
|
|
952
|
-
*
|
|
953
|
-
* This class is not a DDS in its own right, but emits events on mutating operations such that it's possible to
|
|
954
|
-
* integrate into a DDS.
|
|
955
|
-
* This aligns with its usage in `SharedSegmentSequence`, which allows associating intervals to positions in the
|
|
956
|
-
* sequence DDS which are broadcast to all other clients in an eventually consistent fashion.
|
|
1050
|
+
* {@inheritdoc IIntervalCollection}
|
|
957
1051
|
*/
|
|
958
1052
|
export class IntervalCollection extends TypedEventEmitter {
|
|
959
1053
|
/** @internal */
|
|
960
|
-
constructor(helpers, requiresClient, emitter, serializedIntervals) {
|
|
1054
|
+
constructor(helpers, requiresClient, emitter, serializedIntervals, options = {}) {
|
|
961
1055
|
super();
|
|
962
1056
|
this.helpers = helpers;
|
|
963
1057
|
this.requiresClient = requiresClient;
|
|
964
1058
|
this.emitter = emitter;
|
|
1059
|
+
this.options = options;
|
|
965
1060
|
this.localSeqToSerializedInterval = new Map();
|
|
966
1061
|
this.localSeqToRebasedInterval = new Map();
|
|
967
1062
|
this.pendingChangesStart = new Map();
|
|
@@ -973,6 +1068,36 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
973
1068
|
get attached() {
|
|
974
1069
|
return !!this.localCollection;
|
|
975
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* {@inheritdoc IIntervalCollection.attachIndex}
|
|
1073
|
+
*/
|
|
1074
|
+
attachIndex(index) {
|
|
1075
|
+
var _a;
|
|
1076
|
+
if (!this.attached) {
|
|
1077
|
+
throw new LoggingError("The local interval collection must exist");
|
|
1078
|
+
}
|
|
1079
|
+
for (const interval of this) {
|
|
1080
|
+
index.add(interval);
|
|
1081
|
+
}
|
|
1082
|
+
(_a = this.localCollection) === null || _a === void 0 ? void 0 : _a.appendIndex(index);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* {@inheritdoc IIntervalCollection.detachIndex}
|
|
1086
|
+
*/
|
|
1087
|
+
detachIndex(index) {
|
|
1088
|
+
var _a;
|
|
1089
|
+
if (!this.attached) {
|
|
1090
|
+
throw new LoggingError("The local interval collection must exist");
|
|
1091
|
+
}
|
|
1092
|
+
// Avoid removing intervals if the index does not exist
|
|
1093
|
+
if (!((_a = this.localCollection) === null || _a === void 0 ? void 0 : _a.removeIndex(index))) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
for (const interval of this) {
|
|
1097
|
+
index.remove(interval);
|
|
1098
|
+
}
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
976
1101
|
rebasePositionWithSegmentSlide(pos, seqNumberFrom, localSeq) {
|
|
977
1102
|
var _a;
|
|
978
1103
|
if (!this.client) {
|
|
@@ -985,7 +1110,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
985
1110
|
}, localSeq);
|
|
986
1111
|
// if segment is undefined, it slid off the string
|
|
987
1112
|
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
988
|
-
const segoff = (_a =
|
|
1113
|
+
const segoff = (_a = getSlideToSegoff({ segment, offset })) !== null && _a !== void 0 ? _a : segment;
|
|
989
1114
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
990
1115
|
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
991
1116
|
return DetachedReferencePosition;
|
|
@@ -1024,12 +1149,12 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1024
1149
|
}
|
|
1025
1150
|
});
|
|
1026
1151
|
}
|
|
1027
|
-
this.localCollection = new LocalIntervalCollection(client, label, this.helpers, (interval, previousInterval) => this.emitChange(interval, previousInterval, true));
|
|
1152
|
+
this.localCollection = new LocalIntervalCollection(client, label, this.helpers, (interval, previousInterval) => this.emitChange(interval, previousInterval, true, true));
|
|
1028
1153
|
if (this.savedSerializedIntervals) {
|
|
1029
1154
|
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
1030
1155
|
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1031
|
-
const { start, end, intervalType, properties } = serializedInterval;
|
|
1032
|
-
const interval = this.helpers.create(label, start, end, client, intervalType, undefined, true);
|
|
1156
|
+
const { start, end, intervalType, properties, stickiness } = serializedInterval;
|
|
1157
|
+
const interval = this.helpers.create(label, start, end, client, intervalType, undefined, true, stickiness);
|
|
1033
1158
|
if (properties) {
|
|
1034
1159
|
interval.addProperties(properties);
|
|
1035
1160
|
}
|
|
@@ -1047,7 +1172,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1047
1172
|
}
|
|
1048
1173
|
return 0;
|
|
1049
1174
|
}
|
|
1050
|
-
emitChange(interval, previousInterval, local, op) {
|
|
1175
|
+
emitChange(interval, previousInterval, local, slide, op) {
|
|
1051
1176
|
// Temporarily make references transient so that positional queries work (non-transient refs
|
|
1052
1177
|
// on resolve to DetachedPosition on any segments that don't contain them). The original refType
|
|
1053
1178
|
// is restored as single-endpoint changes re-use previous references.
|
|
@@ -1058,17 +1183,16 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1058
1183
|
endRefType = previousInterval.end.refType;
|
|
1059
1184
|
previousInterval.start.refType = ReferenceType.Transient;
|
|
1060
1185
|
previousInterval.end.refType = ReferenceType.Transient;
|
|
1061
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
1186
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
1062
1187
|
previousInterval.start.refType = startRefType;
|
|
1063
1188
|
previousInterval.end.refType = endRefType;
|
|
1064
1189
|
}
|
|
1065
1190
|
else {
|
|
1066
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
1191
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
1067
1192
|
}
|
|
1068
1193
|
}
|
|
1069
1194
|
/**
|
|
1070
|
-
* @
|
|
1071
|
-
* If no interval in the collection has this `id`, returns `undefined`.
|
|
1195
|
+
* {@inheritdoc IIntervalCollection.getIntervalById}
|
|
1072
1196
|
*/
|
|
1073
1197
|
getIntervalById(id) {
|
|
1074
1198
|
if (!this.localCollection) {
|
|
@@ -1077,16 +1201,9 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1077
1201
|
return this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1078
1202
|
}
|
|
1079
1203
|
/**
|
|
1080
|
-
*
|
|
1081
|
-
* @param start - interval start position (inclusive)
|
|
1082
|
-
* @param end - interval end position (exclusive)
|
|
1083
|
-
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
1084
|
-
* @param props - properties of the interval
|
|
1085
|
-
* @returns - the created interval
|
|
1086
|
-
* @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
|
|
1087
|
-
* with how the current half-open behavior is represented.
|
|
1204
|
+
* {@inheritdoc IIntervalCollection.add}
|
|
1088
1205
|
*/
|
|
1089
|
-
add(start, end, intervalType, props) {
|
|
1206
|
+
add(start, end, intervalType, props, stickiness = IntervalStickiness.END) {
|
|
1090
1207
|
var _a, _b;
|
|
1091
1208
|
if (!this.localCollection) {
|
|
1092
1209
|
throw new LoggingError("attach must be called prior to adding intervals");
|
|
@@ -1094,7 +1211,10 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1094
1211
|
if (intervalType & IntervalType.Transient) {
|
|
1095
1212
|
throw new LoggingError("Can not add transient intervals");
|
|
1096
1213
|
}
|
|
1097
|
-
|
|
1214
|
+
if (stickiness !== IntervalStickiness.END && !this.options.intervalStickinessEnabled) {
|
|
1215
|
+
throw new UsageError("attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag");
|
|
1216
|
+
}
|
|
1217
|
+
const interval = this.localCollection.addInterval(start, end, intervalType, props, undefined, stickiness);
|
|
1098
1218
|
if (interval) {
|
|
1099
1219
|
const serializedInterval = {
|
|
1100
1220
|
end,
|
|
@@ -1102,6 +1222,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1102
1222
|
properties: interval.properties,
|
|
1103
1223
|
sequenceNumber: (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.getCurrentSeq()) !== null && _b !== void 0 ? _b : 0,
|
|
1104
1224
|
start,
|
|
1225
|
+
stickiness,
|
|
1105
1226
|
};
|
|
1106
1227
|
const localSeq = this.getNextLocalSeq();
|
|
1107
1228
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
@@ -1133,9 +1254,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1133
1254
|
this.emit("deleteInterval", interval, local, op);
|
|
1134
1255
|
}
|
|
1135
1256
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
* @param id - Id of the interval to remove
|
|
1138
|
-
* @returns the removed interval
|
|
1257
|
+
* {@inheritdoc IIntervalCollection.removeIntervalById}
|
|
1139
1258
|
*/
|
|
1140
1259
|
removeIntervalById(id) {
|
|
1141
1260
|
if (!this.localCollection) {
|
|
@@ -1148,10 +1267,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1148
1267
|
return interval;
|
|
1149
1268
|
}
|
|
1150
1269
|
/**
|
|
1151
|
-
*
|
|
1152
|
-
* @param id - Id of the interval whose properties should be changed
|
|
1153
|
-
* @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
|
|
1154
|
-
* and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
|
|
1270
|
+
* {@inheritdoc IIntervalCollection.changeProperties}
|
|
1155
1271
|
*/
|
|
1156
1272
|
changeProperties(id, props) {
|
|
1157
1273
|
if (!this.attached) {
|
|
@@ -1163,6 +1279,11 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1163
1279
|
if (!props) {
|
|
1164
1280
|
throw new LoggingError("changeProperties should be called with a property set");
|
|
1165
1281
|
}
|
|
1282
|
+
// prevent the overwriting of an interval label, it should remain unchanged
|
|
1283
|
+
// once it has been inserted into the collection.
|
|
1284
|
+
if (props[reservedRangeLabelsKey] !== undefined) {
|
|
1285
|
+
throw new LoggingError("The label property should not be modified once inserted to the collection");
|
|
1286
|
+
}
|
|
1166
1287
|
const interval = this.getIntervalById(id);
|
|
1167
1288
|
if (interval) {
|
|
1168
1289
|
// Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
|
|
@@ -1181,11 +1302,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1181
1302
|
}
|
|
1182
1303
|
}
|
|
1183
1304
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
* @param id - Id of the interval to change
|
|
1186
|
-
* @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
|
|
1187
|
-
* @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
|
|
1188
|
-
* @returns the interval that was changed, if it existed in the collection.
|
|
1305
|
+
* {@inheritdoc IIntervalCollection.change}
|
|
1189
1306
|
*/
|
|
1190
1307
|
change(id, start, end) {
|
|
1191
1308
|
if (!this.localCollection) {
|
|
@@ -1212,7 +1329,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1212
1329
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1213
1330
|
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
1214
1331
|
this.addPendingChange(id, serializedInterval);
|
|
1215
|
-
this.emitChange(newInterval, interval, true);
|
|
1332
|
+
this.emitChange(newInterval, interval, true, false);
|
|
1216
1333
|
return newInterval;
|
|
1217
1334
|
}
|
|
1218
1335
|
// No interval to change
|
|
@@ -1320,7 +1437,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1320
1437
|
this.onDeserialize(newInterval);
|
|
1321
1438
|
}
|
|
1322
1439
|
if (newInterval !== interval) {
|
|
1323
|
-
this.emitChange(newInterval, interval, local, op);
|
|
1440
|
+
this.emitChange(newInterval, interval, local, false, op);
|
|
1324
1441
|
}
|
|
1325
1442
|
const changedProperties = Object.keys(newProps).length > 0;
|
|
1326
1443
|
if (changedProperties) {
|
|
@@ -1340,6 +1457,9 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1340
1457
|
throw new LoggingError("attachSequence must be called");
|
|
1341
1458
|
}
|
|
1342
1459
|
}
|
|
1460
|
+
/**
|
|
1461
|
+
* {@inheritdoc IIntervalCollection.attachDeserializer}
|
|
1462
|
+
*/
|
|
1343
1463
|
attachDeserializer(onDeserialize) {
|
|
1344
1464
|
// If no deserializer is specified can skip all processing work
|
|
1345
1465
|
if (!onDeserialize) {
|
|
@@ -1410,7 +1530,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1410
1530
|
if (((_b = (_a = segoff.segment) === null || _a === void 0 ? void 0 : _a.localRefs) === null || _b === void 0 ? void 0 : _b.has(lref)) !== true) {
|
|
1411
1531
|
return undefined;
|
|
1412
1532
|
}
|
|
1413
|
-
const newSegoff =
|
|
1533
|
+
const newSegoff = getSlideToSegoff(segoff);
|
|
1414
1534
|
const value = segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
1415
1535
|
? undefined
|
|
1416
1536
|
: newSegoff;
|
|
@@ -1460,7 +1580,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1460
1580
|
}
|
|
1461
1581
|
if (needsStartUpdate) {
|
|
1462
1582
|
const props = interval.start.properties;
|
|
1463
|
-
interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
|
|
1583
|
+
interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op, startReferenceSlidingPreference(interval.stickiness));
|
|
1464
1584
|
if (props) {
|
|
1465
1585
|
interval.start.addProperties(props);
|
|
1466
1586
|
}
|
|
@@ -1472,7 +1592,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1472
1592
|
}
|
|
1473
1593
|
if (needsEndUpdate) {
|
|
1474
1594
|
const props = interval.end.properties;
|
|
1475
|
-
interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
|
|
1595
|
+
interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op, endReferenceSlidingPreference(interval.stickiness));
|
|
1476
1596
|
if (props) {
|
|
1477
1597
|
interval.end.addProperties(props);
|
|
1478
1598
|
}
|
|
@@ -1483,7 +1603,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1483
1603
|
(_b = oldSeg === null || oldSeg === void 0 ? void 0 : oldSeg.localRefs) === null || _b === void 0 ? void 0 : _b.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
|
|
1484
1604
|
}
|
|
1485
1605
|
this.localCollection.add(interval);
|
|
1486
|
-
this.emitChange(interval, oldInterval, true, op);
|
|
1606
|
+
this.emitChange(interval, oldInterval, true, true, op);
|
|
1487
1607
|
}
|
|
1488
1608
|
}
|
|
1489
1609
|
/** @internal */
|
|
@@ -1503,7 +1623,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1503
1623
|
throw new LoggingError("attachSequence must be called");
|
|
1504
1624
|
}
|
|
1505
1625
|
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1506
|
-
const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op);
|
|
1626
|
+
const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op, serializedInterval.stickiness);
|
|
1507
1627
|
if (interval) {
|
|
1508
1628
|
if (this.onDeserialize) {
|
|
1509
1629
|
this.onDeserialize(interval);
|
|
@@ -1546,40 +1666,35 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1546
1666
|
return iterator;
|
|
1547
1667
|
}
|
|
1548
1668
|
/**
|
|
1549
|
-
* @
|
|
1669
|
+
* {@inheritdoc IIntervalCollection.CreateForwardIteratorWithStartPosition}
|
|
1550
1670
|
*/
|
|
1551
1671
|
CreateForwardIteratorWithStartPosition(startPosition) {
|
|
1552
1672
|
const iterator = new IntervalCollectionIterator(this, true, startPosition);
|
|
1553
1673
|
return iterator;
|
|
1554
1674
|
}
|
|
1555
1675
|
/**
|
|
1556
|
-
* @
|
|
1676
|
+
* {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithStartPosition}
|
|
1557
1677
|
*/
|
|
1558
1678
|
CreateBackwardIteratorWithStartPosition(startPosition) {
|
|
1559
1679
|
const iterator = new IntervalCollectionIterator(this, false, startPosition);
|
|
1560
1680
|
return iterator;
|
|
1561
1681
|
}
|
|
1562
1682
|
/**
|
|
1563
|
-
* @
|
|
1683
|
+
* {@inheritdoc IIntervalCollection.CreateForwardIteratorWithEndPosition}
|
|
1564
1684
|
*/
|
|
1565
1685
|
CreateForwardIteratorWithEndPosition(endPosition) {
|
|
1566
1686
|
const iterator = new IntervalCollectionIterator(this, true, undefined, endPosition);
|
|
1567
1687
|
return iterator;
|
|
1568
1688
|
}
|
|
1569
1689
|
/**
|
|
1570
|
-
* @
|
|
1690
|
+
* {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithEndPosition}
|
|
1571
1691
|
*/
|
|
1572
1692
|
CreateBackwardIteratorWithEndPosition(endPosition) {
|
|
1573
1693
|
const iterator = new IntervalCollectionIterator(this, false, undefined, endPosition);
|
|
1574
1694
|
return iterator;
|
|
1575
1695
|
}
|
|
1576
1696
|
/**
|
|
1577
|
-
*
|
|
1578
|
-
* @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
|
|
1579
|
-
* intervals matching the query upon edit.
|
|
1580
|
-
* @param iteratesForward - whether or not iteration should be in the forward direction
|
|
1581
|
-
* @param start - If provided, only match intervals whose start point is equal to `start`.
|
|
1582
|
-
* @param end - If provided, only match intervals whose end point is equal to `end`.
|
|
1697
|
+
* {@inheritdoc IIntervalCollection.gatherIterationResults}
|
|
1583
1698
|
*/
|
|
1584
1699
|
gatherIterationResults(results, iteratesForward, start, end) {
|
|
1585
1700
|
if (!this.localCollection) {
|
|
@@ -1588,8 +1703,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1588
1703
|
this.localCollection.overlappingIntervalsIndex.gatherIterationResults(results, iteratesForward, start, end);
|
|
1589
1704
|
}
|
|
1590
1705
|
/**
|
|
1591
|
-
* @
|
|
1592
|
-
* `[startPosition, endPosition]`.
|
|
1706
|
+
* {@inheritdoc IIntervalCollection.findOverlappingIntervals}
|
|
1593
1707
|
*/
|
|
1594
1708
|
findOverlappingIntervals(startPosition, endPosition) {
|
|
1595
1709
|
if (!this.localCollection) {
|
|
@@ -1598,7 +1712,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1598
1712
|
return this.localCollection.overlappingIntervalsIndex.findOverlappingIntervals(startPosition, endPosition);
|
|
1599
1713
|
}
|
|
1600
1714
|
/**
|
|
1601
|
-
*
|
|
1715
|
+
* {@inheritdoc IIntervalCollection.map}
|
|
1602
1716
|
*/
|
|
1603
1717
|
map(fn) {
|
|
1604
1718
|
if (!this.localCollection) {
|
|
@@ -1608,12 +1722,18 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
1608
1722
|
fn(interval);
|
|
1609
1723
|
}
|
|
1610
1724
|
}
|
|
1725
|
+
/**
|
|
1726
|
+
* {@inheritdoc IIntervalCollection.previousInterval}
|
|
1727
|
+
*/
|
|
1611
1728
|
previousInterval(pos) {
|
|
1612
1729
|
if (!this.localCollection) {
|
|
1613
1730
|
throw new LoggingError("attachSequence must be called");
|
|
1614
1731
|
}
|
|
1615
1732
|
return this.localCollection.endIntervalIndex.previousInterval(pos);
|
|
1616
1733
|
}
|
|
1734
|
+
/**
|
|
1735
|
+
* {@inheritdoc IIntervalCollection.nextInterval}
|
|
1736
|
+
*/
|
|
1617
1737
|
nextInterval(pos) {
|
|
1618
1738
|
if (!this.localCollection) {
|
|
1619
1739
|
throw new LoggingError("attachSequence must be called");
|