@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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Client,
|
|
14
14
|
compareReferencePositions,
|
|
15
15
|
createMap,
|
|
16
|
+
getSlideToSegoff,
|
|
16
17
|
ICombiningOp,
|
|
17
18
|
ISegment,
|
|
18
19
|
MergeTreeDeltaType,
|
|
@@ -28,6 +29,8 @@ import {
|
|
|
28
29
|
maxReferencePosition,
|
|
29
30
|
createDetachedLocalReferencePosition,
|
|
30
31
|
DetachedReferencePosition,
|
|
32
|
+
SlidingPreference,
|
|
33
|
+
PropertyAction,
|
|
31
34
|
} from "@fluidframework/merge-tree";
|
|
32
35
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
33
36
|
import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
@@ -39,11 +42,25 @@ import {
|
|
|
39
42
|
IValueOperation,
|
|
40
43
|
IValueType,
|
|
41
44
|
IValueTypeOperationValue,
|
|
45
|
+
SequenceOptions,
|
|
42
46
|
} from "./defaultMapInterfaces";
|
|
43
|
-
import { IInterval, IntervalConflictResolver
|
|
47
|
+
import { IInterval, IntervalConflictResolver } from "./intervalTree";
|
|
48
|
+
import { IOverlappingIntervalsIndex, createOverlappingIntervalsIndex } from "./intervalIndex";
|
|
44
49
|
|
|
45
50
|
const reservedIntervalIdKey = "intervalId";
|
|
46
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Values are used in persisted formats (ops) and revertibles.
|
|
54
|
+
* @alpha
|
|
55
|
+
*/
|
|
56
|
+
export const IntervalOpType = {
|
|
57
|
+
ADD: "add",
|
|
58
|
+
DELETE: "delete",
|
|
59
|
+
CHANGE: "change",
|
|
60
|
+
PROPERTY_CHANGED: "propertyChanged",
|
|
61
|
+
POSITION_REMOVE: "positionRemove",
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
47
64
|
export enum IntervalType {
|
|
48
65
|
Simple = 0x0,
|
|
49
66
|
Nest = 0x1,
|
|
@@ -51,7 +68,7 @@ export enum IntervalType {
|
|
|
51
68
|
/**
|
|
52
69
|
* SlideOnRemove indicates that the ends of the interval will slide if the segment
|
|
53
70
|
* they reference is removed and acked.
|
|
54
|
-
* See `packages\dds\merge-tree\REFERENCEPOSITIONS.md` for details
|
|
71
|
+
* See `packages\dds\merge-tree\docs\REFERENCEPOSITIONS.md` for details
|
|
55
72
|
* SlideOnRemove is the default interval behavior and does not need to be specified.
|
|
56
73
|
*/
|
|
57
74
|
SlideOnRemove = 0x2, // SlideOnRemove is default behavior - all intervals are SlideOnRemove
|
|
@@ -83,6 +100,7 @@ export interface ISerializedInterval {
|
|
|
83
100
|
end: number;
|
|
84
101
|
/** Interval type to create */
|
|
85
102
|
intervalType: IntervalType;
|
|
103
|
+
stickiness?: IntervalStickiness;
|
|
86
104
|
/** Any properties the interval has */
|
|
87
105
|
properties?: PropertySet;
|
|
88
106
|
}
|
|
@@ -101,13 +119,12 @@ export type SerializedIntervalDelta = Omit<ISerializedInterval, "start" | "end"
|
|
|
101
119
|
*
|
|
102
120
|
* Intervals are of the format:
|
|
103
121
|
*
|
|
104
|
-
* [start, end, sequenceNumber, intervalType, properties]
|
|
122
|
+
* [start, end, sequenceNumber, intervalType, properties, stickiness?]
|
|
105
123
|
*/
|
|
106
|
-
export type CompressedSerializedInterval =
|
|
124
|
+
export type CompressedSerializedInterval =
|
|
125
|
+
| [number, number, number, IntervalType, PropertySet, IntervalStickiness]
|
|
126
|
+
| [number, number, number, IntervalType, PropertySet];
|
|
107
127
|
|
|
108
|
-
/**
|
|
109
|
-
* @internal
|
|
110
|
-
*/
|
|
111
128
|
export interface ISerializedIntervalCollectionV2 {
|
|
112
129
|
label: string;
|
|
113
130
|
version: 2;
|
|
@@ -128,6 +145,7 @@ function decompressInterval(
|
|
|
128
145
|
sequenceNumber: interval[2],
|
|
129
146
|
intervalType: interval[3],
|
|
130
147
|
properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
|
|
148
|
+
stickiness: interval[5],
|
|
131
149
|
};
|
|
132
150
|
}
|
|
133
151
|
|
|
@@ -138,7 +156,7 @@ function decompressInterval(
|
|
|
138
156
|
function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
|
|
139
157
|
const { start, end, sequenceNumber, intervalType, properties } = interval;
|
|
140
158
|
|
|
141
|
-
|
|
159
|
+
const base: CompressedSerializedInterval = [
|
|
142
160
|
start,
|
|
143
161
|
end,
|
|
144
162
|
sequenceNumber,
|
|
@@ -147,6 +165,26 @@ function compressInterval(interval: ISerializedInterval): CompressedSerializedIn
|
|
|
147
165
|
// in the `label` field of the summary
|
|
148
166
|
{ ...properties, [reservedRangeLabelsKey]: undefined },
|
|
149
167
|
];
|
|
168
|
+
|
|
169
|
+
if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
|
|
170
|
+
base.push(interval.stickiness);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return base;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function startReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
|
|
177
|
+
// if any start stickiness, prefer sliding backwards
|
|
178
|
+
return (stickiness & IntervalStickiness.START) !== 0
|
|
179
|
+
? SlidingPreference.BACKWARD
|
|
180
|
+
: SlidingPreference.FORWARD;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function endReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
|
|
184
|
+
// if any end stickiness, prefer sliding forwards
|
|
185
|
+
return (stickiness & IntervalStickiness.END) !== 0
|
|
186
|
+
? SlidingPreference.FORWARD
|
|
187
|
+
: SlidingPreference.BACKWARD;
|
|
150
188
|
}
|
|
151
189
|
|
|
152
190
|
export interface ISerializableInterval extends IInterval {
|
|
@@ -172,14 +210,18 @@ export interface ISerializableInterval extends IInterval {
|
|
|
172
210
|
getIntervalId(): string | undefined;
|
|
173
211
|
}
|
|
174
212
|
|
|
213
|
+
/**
|
|
214
|
+
* @sealed
|
|
215
|
+
*/
|
|
175
216
|
export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
|
|
176
217
|
compareEnds(a: TInterval, b: TInterval): number;
|
|
218
|
+
compareStarts?(a: TInterval, b: TInterval): number;
|
|
177
219
|
/**
|
|
178
220
|
*
|
|
179
221
|
* @param label - label of the interval collection this interval is being added to. This parameter is
|
|
180
222
|
* irrelevant for transient intervals.
|
|
181
223
|
* @param start - numerical start position of the interval
|
|
182
|
-
* @param end -
|
|
224
|
+
* @param end - numerical end position of the interval
|
|
183
225
|
* @param client - client creating the interval
|
|
184
226
|
* @param intervalType - Type of interval to create. Default is SlideOnRemove
|
|
185
227
|
* @param op - If this create came from a remote client, op that created it. Default is undefined (i.e. local)
|
|
@@ -193,9 +235,50 @@ export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
|
|
|
193
235
|
intervalType: IntervalType,
|
|
194
236
|
op?: ISequencedDocumentMessage,
|
|
195
237
|
fromSnapshot?: boolean,
|
|
238
|
+
stickiness?: IntervalStickiness,
|
|
196
239
|
): TInterval;
|
|
197
240
|
}
|
|
198
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Determines how an interval should expand when segments are inserted adjacent
|
|
244
|
+
* to the range it spans
|
|
245
|
+
*
|
|
246
|
+
* Note that interval stickiness is currently an experimental feature and must
|
|
247
|
+
* be explicitly enabled with the `intervalStickinessEnabled` flag
|
|
248
|
+
*/
|
|
249
|
+
export const IntervalStickiness = {
|
|
250
|
+
/**
|
|
251
|
+
* Interval does not expand to include adjacent segments
|
|
252
|
+
*/
|
|
253
|
+
NONE: 0b00,
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Interval expands to include segments inserted adjacent to the start
|
|
257
|
+
*/
|
|
258
|
+
START: 0b01,
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Interval expands to include segments inserted adjacent to the end
|
|
262
|
+
*
|
|
263
|
+
* This is the default stickiness
|
|
264
|
+
*/
|
|
265
|
+
END: 0b10,
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Interval expands to include all segments inserted adjacent to it
|
|
269
|
+
*/
|
|
270
|
+
FULL: 0b11,
|
|
271
|
+
} as const;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Determines how an interval should expand when segments are inserted adjacent
|
|
275
|
+
* to the range it spans
|
|
276
|
+
*
|
|
277
|
+
* Note that interval stickiness is currently an experimental feature and must
|
|
278
|
+
* be explicitly enabled with the `intervalStickinessEnabled` flag
|
|
279
|
+
*/
|
|
280
|
+
export type IntervalStickiness = typeof IntervalStickiness[keyof typeof IntervalStickiness];
|
|
281
|
+
|
|
199
282
|
/**
|
|
200
283
|
* Serializable interval whose endpoints are plain-old numbers.
|
|
201
284
|
*/
|
|
@@ -240,7 +323,7 @@ export class Interval implements ISerializableInterval {
|
|
|
240
323
|
* Adds an auxiliary set of properties to this interval.
|
|
241
324
|
* These properties can be recovered using `getAdditionalPropertySets`
|
|
242
325
|
* @param props - set of properties to add
|
|
243
|
-
* @remarks - This gets called as part of the default conflict resolver for `
|
|
326
|
+
* @remarks - This gets called as part of the default conflict resolver for `IIntervalCollection<Interval>`
|
|
244
327
|
* (i.e. non-sequence-based interval collections). However, the additional properties don't get serialized.
|
|
245
328
|
* This functionality seems half-baked.
|
|
246
329
|
*/
|
|
@@ -394,7 +477,7 @@ export class Interval implements ISerializableInterval {
|
|
|
394
477
|
}
|
|
395
478
|
|
|
396
479
|
/**
|
|
397
|
-
* Interval
|
|
480
|
+
* Interval implementation whose ends are associated with positions in a mutatable sequence.
|
|
398
481
|
* As such, when content is inserted into the middle of the interval, the interval expands to
|
|
399
482
|
* include that content.
|
|
400
483
|
*
|
|
@@ -439,6 +522,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
439
522
|
public end: LocalReferencePosition,
|
|
440
523
|
public intervalType: IntervalType,
|
|
441
524
|
props?: PropertySet,
|
|
525
|
+
public readonly stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
442
526
|
) {
|
|
443
527
|
this.propertyManager = new PropertiesManager();
|
|
444
528
|
this.properties = {};
|
|
@@ -500,6 +584,9 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
500
584
|
if (this.properties) {
|
|
501
585
|
serializedInterval.properties = this.properties;
|
|
502
586
|
}
|
|
587
|
+
if (this.stickiness !== IntervalStickiness.END) {
|
|
588
|
+
serializedInterval.stickiness = this.stickiness;
|
|
589
|
+
}
|
|
503
590
|
|
|
504
591
|
return serializedInterval;
|
|
505
592
|
}
|
|
@@ -514,6 +601,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
514
601
|
this.end,
|
|
515
602
|
this.intervalType,
|
|
516
603
|
this.properties,
|
|
604
|
+
this.stickiness,
|
|
517
605
|
);
|
|
518
606
|
}
|
|
519
607
|
|
|
@@ -621,6 +709,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
621
709
|
end: number,
|
|
622
710
|
op?: ISequencedDocumentMessage,
|
|
623
711
|
localSeq?: number,
|
|
712
|
+
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
624
713
|
) {
|
|
625
714
|
const getRefType = (baseType: ReferenceType): ReferenceType => {
|
|
626
715
|
let refType = baseType;
|
|
@@ -640,6 +729,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
640
729
|
op,
|
|
641
730
|
undefined,
|
|
642
731
|
localSeq,
|
|
732
|
+
startReferenceSlidingPreference(stickiness),
|
|
643
733
|
);
|
|
644
734
|
if (this.start.properties) {
|
|
645
735
|
startRef.addProperties(this.start.properties);
|
|
@@ -655,6 +745,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
655
745
|
op,
|
|
656
746
|
undefined,
|
|
657
747
|
localSeq,
|
|
748
|
+
endReferenceSlidingPreference(stickiness),
|
|
658
749
|
);
|
|
659
750
|
if (this.end.properties) {
|
|
660
751
|
endRef.addProperties(this.end.properties);
|
|
@@ -683,13 +774,14 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
683
774
|
}
|
|
684
775
|
}
|
|
685
776
|
|
|
686
|
-
function createPositionReferenceFromSegoff(
|
|
777
|
+
export function createPositionReferenceFromSegoff(
|
|
687
778
|
client: Client,
|
|
688
779
|
segoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
689
780
|
refType: ReferenceType,
|
|
690
781
|
op?: ISequencedDocumentMessage,
|
|
691
782
|
localSeq?: number,
|
|
692
783
|
fromSnapshot?: boolean,
|
|
784
|
+
slidingPreference?: SlidingPreference,
|
|
693
785
|
): LocalReferencePosition {
|
|
694
786
|
if (segoff.segment) {
|
|
695
787
|
const ref = client.createLocalReferencePosition(
|
|
@@ -697,6 +789,7 @@ function createPositionReferenceFromSegoff(
|
|
|
697
789
|
segoff.offset,
|
|
698
790
|
refType,
|
|
699
791
|
undefined,
|
|
792
|
+
slidingPreference,
|
|
700
793
|
);
|
|
701
794
|
return ref;
|
|
702
795
|
}
|
|
@@ -725,6 +818,7 @@ function createPositionReference(
|
|
|
725
818
|
op?: ISequencedDocumentMessage,
|
|
726
819
|
fromSnapshot?: boolean,
|
|
727
820
|
localSeq?: number,
|
|
821
|
+
slidingPreference?: SlidingPreference,
|
|
728
822
|
): LocalReferencePosition {
|
|
729
823
|
let segoff;
|
|
730
824
|
if (op) {
|
|
@@ -736,7 +830,7 @@ function createPositionReference(
|
|
|
736
830
|
referenceSequenceNumber: op.referenceSequenceNumber,
|
|
737
831
|
clientId: op.clientId,
|
|
738
832
|
});
|
|
739
|
-
segoff =
|
|
833
|
+
segoff = getSlideToSegoff(segoff);
|
|
740
834
|
} else {
|
|
741
835
|
assert(
|
|
742
836
|
(refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
|
|
@@ -744,7 +838,16 @@ function createPositionReference(
|
|
|
744
838
|
);
|
|
745
839
|
segoff = client.getContainingSegment(pos, undefined, localSeq);
|
|
746
840
|
}
|
|
747
|
-
|
|
841
|
+
|
|
842
|
+
return createPositionReferenceFromSegoff(
|
|
843
|
+
client,
|
|
844
|
+
segoff,
|
|
845
|
+
refType,
|
|
846
|
+
op,
|
|
847
|
+
localSeq,
|
|
848
|
+
fromSnapshot,
|
|
849
|
+
slidingPreference,
|
|
850
|
+
);
|
|
748
851
|
}
|
|
749
852
|
|
|
750
853
|
export function createSequenceInterval(
|
|
@@ -755,6 +858,7 @@ export function createSequenceInterval(
|
|
|
755
858
|
intervalType: IntervalType,
|
|
756
859
|
op?: ISequencedDocumentMessage,
|
|
757
860
|
fromSnapshot?: boolean,
|
|
861
|
+
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
758
862
|
): SequenceInterval {
|
|
759
863
|
let beginRefType = ReferenceType.RangeBegin;
|
|
760
864
|
let endRefType = ReferenceType.RangeEnd;
|
|
@@ -778,15 +882,40 @@ export function createSequenceInterval(
|
|
|
778
882
|
}
|
|
779
883
|
}
|
|
780
884
|
|
|
781
|
-
const startLref = createPositionReference(
|
|
782
|
-
|
|
885
|
+
const startLref = createPositionReference(
|
|
886
|
+
client,
|
|
887
|
+
start,
|
|
888
|
+
beginRefType,
|
|
889
|
+
op,
|
|
890
|
+
fromSnapshot,
|
|
891
|
+
undefined,
|
|
892
|
+
startReferenceSlidingPreference(stickiness),
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
const endLref = createPositionReference(
|
|
896
|
+
client,
|
|
897
|
+
end,
|
|
898
|
+
endRefType,
|
|
899
|
+
op,
|
|
900
|
+
fromSnapshot,
|
|
901
|
+
undefined,
|
|
902
|
+
endReferenceSlidingPreference(stickiness),
|
|
903
|
+
);
|
|
904
|
+
|
|
783
905
|
const rangeProp = {
|
|
784
906
|
[reservedRangeLabelsKey]: [label],
|
|
785
907
|
};
|
|
786
908
|
startLref.addProperties(rangeProp);
|
|
787
909
|
endLref.addProperties(rangeProp);
|
|
788
910
|
|
|
789
|
-
const ival = new SequenceInterval(
|
|
911
|
+
const ival = new SequenceInterval(
|
|
912
|
+
client,
|
|
913
|
+
startLref,
|
|
914
|
+
endLref,
|
|
915
|
+
intervalType,
|
|
916
|
+
rangeProp,
|
|
917
|
+
stickiness,
|
|
918
|
+
);
|
|
790
919
|
return ival;
|
|
791
920
|
}
|
|
792
921
|
|
|
@@ -824,135 +953,6 @@ export interface IntervalIndex<TInterval extends ISerializableInterval> {
|
|
|
824
953
|
remove(interval: TInterval): void;
|
|
825
954
|
}
|
|
826
955
|
|
|
827
|
-
class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
|
|
828
|
-
implements IntervalIndex<TInterval>
|
|
829
|
-
{
|
|
830
|
-
private readonly intervalTree = new IntervalTree<TInterval>();
|
|
831
|
-
|
|
832
|
-
constructor(
|
|
833
|
-
private readonly client: Client,
|
|
834
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
835
|
-
) {}
|
|
836
|
-
|
|
837
|
-
public map(fn: (interval: TInterval) => void) {
|
|
838
|
-
this.intervalTree.map(fn);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
public mapUntil(fn: (interval: TInterval) => boolean) {
|
|
842
|
-
this.intervalTree.mapUntil(fn);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
public gatherIterationResults(
|
|
846
|
-
results: TInterval[],
|
|
847
|
-
iteratesForward: boolean,
|
|
848
|
-
start?: number,
|
|
849
|
-
end?: number,
|
|
850
|
-
) {
|
|
851
|
-
if (this.intervalTree.intervals.isEmpty()) {
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (start === undefined && end === undefined) {
|
|
856
|
-
// No start/end provided. Gather the whole tree in the specified order.
|
|
857
|
-
if (iteratesForward) {
|
|
858
|
-
this.intervalTree.map((interval: TInterval) => {
|
|
859
|
-
results.push(interval);
|
|
860
|
-
});
|
|
861
|
-
} else {
|
|
862
|
-
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
863
|
-
results.push(interval);
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
} else {
|
|
867
|
-
const transientInterval: TInterval = this.helpers.create(
|
|
868
|
-
"transient",
|
|
869
|
-
start,
|
|
870
|
-
end,
|
|
871
|
-
this.client,
|
|
872
|
-
IntervalType.Transient,
|
|
873
|
-
);
|
|
874
|
-
|
|
875
|
-
if (start === undefined) {
|
|
876
|
-
// Only end position provided. Since the tree is not sorted by end position,
|
|
877
|
-
// walk the whole tree in the specified order, gathering intervals that match the end.
|
|
878
|
-
if (iteratesForward) {
|
|
879
|
-
this.intervalTree.map((interval: TInterval) => {
|
|
880
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
881
|
-
results.push(interval);
|
|
882
|
-
}
|
|
883
|
-
});
|
|
884
|
-
} else {
|
|
885
|
-
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
886
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
887
|
-
results.push(interval);
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
} else {
|
|
892
|
-
// Start and (possibly) end provided. Walk the subtrees that may contain
|
|
893
|
-
// this start position.
|
|
894
|
-
const compareFn =
|
|
895
|
-
end === undefined
|
|
896
|
-
? (node: IntervalNode<TInterval>) => {
|
|
897
|
-
return transientInterval.compareStart(node.key);
|
|
898
|
-
}
|
|
899
|
-
: (node: IntervalNode<TInterval>) => {
|
|
900
|
-
return transientInterval.compare(node.key);
|
|
901
|
-
};
|
|
902
|
-
const continueLeftFn = (cmpResult: number) => cmpResult <= 0;
|
|
903
|
-
const continueRightFn = (cmpResult: number) => cmpResult >= 0;
|
|
904
|
-
const actionFn = (node: IntervalNode<TInterval>) => {
|
|
905
|
-
results.push(node.key);
|
|
906
|
-
};
|
|
907
|
-
|
|
908
|
-
if (iteratesForward) {
|
|
909
|
-
this.intervalTree.intervals.walkExactMatchesForward(
|
|
910
|
-
compareFn,
|
|
911
|
-
actionFn,
|
|
912
|
-
continueLeftFn,
|
|
913
|
-
continueRightFn,
|
|
914
|
-
);
|
|
915
|
-
} else {
|
|
916
|
-
this.intervalTree.intervals.walkExactMatchesBackward(
|
|
917
|
-
compareFn,
|
|
918
|
-
actionFn,
|
|
919
|
-
continueLeftFn,
|
|
920
|
-
continueRightFn,
|
|
921
|
-
);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* @returns an array of all intervals contained in this collection that overlap the range
|
|
929
|
-
* `[startPosition, endPosition)`.
|
|
930
|
-
*/
|
|
931
|
-
public findOverlappingIntervals(startPosition: number, endPosition: number) {
|
|
932
|
-
if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
|
|
933
|
-
return [];
|
|
934
|
-
}
|
|
935
|
-
const transientInterval = this.helpers.create(
|
|
936
|
-
"transient",
|
|
937
|
-
startPosition,
|
|
938
|
-
endPosition,
|
|
939
|
-
this.client,
|
|
940
|
-
IntervalType.Transient,
|
|
941
|
-
);
|
|
942
|
-
|
|
943
|
-
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
944
|
-
return overlappingIntervalNodes.map((node) => node.key);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
public remove(interval: TInterval) {
|
|
948
|
-
this.intervalTree.removeExisting(interval);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
public add(interval: TInterval) {
|
|
952
|
-
this.intervalTree.put(interval);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
956
|
class IdIntervalIndex<TInterval extends ISerializableInterval>
|
|
957
957
|
implements IntervalIndex<TInterval>, Iterable<TInterval>
|
|
958
958
|
{
|
|
@@ -1036,12 +1036,229 @@ class EndpointIndex<TInterval extends ISerializableInterval> implements Interval
|
|
|
1036
1036
|
}
|
|
1037
1037
|
}
|
|
1038
1038
|
|
|
1039
|
+
/**
|
|
1040
|
+
* Collection of intervals.
|
|
1041
|
+
*
|
|
1042
|
+
* Provide additional APIs to support efficiently querying a collection of intervals whose endpoints fall within a specified range.
|
|
1043
|
+
*/
|
|
1044
|
+
export interface IEndpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1045
|
+
extends IntervalIndex<TInterval> {
|
|
1046
|
+
/**
|
|
1047
|
+
* @returns an array of all intervals contained in this collection whose endpoints locate in the range [start, end] (includes both ends)
|
|
1048
|
+
*/
|
|
1049
|
+
findIntervalsWithEndpointInRange(start: number, end: number);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Collection of intervals.
|
|
1054
|
+
*
|
|
1055
|
+
* Provide additional APIs to support efficiently querying a collection of intervals whose startpoints fall within a specified range.
|
|
1056
|
+
*/
|
|
1057
|
+
export interface IStartpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1058
|
+
extends IntervalIndex<TInterval> {
|
|
1059
|
+
/**
|
|
1060
|
+
* @returns an array of all intervals contained in this collection whose startpoints locate in the range [start, end] (includes both ends)
|
|
1061
|
+
*/
|
|
1062
|
+
findIntervalsWithStartpointInRange(start: number, end: number);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Interface for intervals that have comparison override properties.
|
|
1067
|
+
*/
|
|
1068
|
+
const forceCompare = Symbol();
|
|
1069
|
+
|
|
1070
|
+
interface HasComparisonOverride {
|
|
1071
|
+
[forceCompare]: number;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Compares two objects based on their comparison override properties.
|
|
1076
|
+
* @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).
|
|
1077
|
+
*/
|
|
1078
|
+
function compareOverrideables(
|
|
1079
|
+
a: Partial<HasComparisonOverride>,
|
|
1080
|
+
b: Partial<HasComparisonOverride>,
|
|
1081
|
+
): number {
|
|
1082
|
+
const forceCompareA = a[forceCompare] ?? 0;
|
|
1083
|
+
const forceCompareB = b[forceCompare] ?? 0;
|
|
1084
|
+
|
|
1085
|
+
return forceCompareA - forceCompareB;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
class EndpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1089
|
+
implements IEndpointInRangeIndex<TInterval>
|
|
1090
|
+
{
|
|
1091
|
+
private readonly intervalTree;
|
|
1092
|
+
|
|
1093
|
+
constructor(
|
|
1094
|
+
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1095
|
+
private readonly client: Client,
|
|
1096
|
+
) {
|
|
1097
|
+
this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
|
|
1098
|
+
const compareEndsResult = helpers.compareEnds(a, b);
|
|
1099
|
+
if (compareEndsResult !== 0) {
|
|
1100
|
+
return compareEndsResult;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const overrideablesComparison = compareOverrideables(
|
|
1104
|
+
a as Partial<HasComparisonOverride>,
|
|
1105
|
+
b as Partial<HasComparisonOverride>,
|
|
1106
|
+
);
|
|
1107
|
+
if (overrideablesComparison !== 0) {
|
|
1108
|
+
return overrideablesComparison;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const aId = a.getIntervalId();
|
|
1112
|
+
const bId = b.getIntervalId();
|
|
1113
|
+
if (aId !== undefined && bId !== undefined) {
|
|
1114
|
+
return aId.localeCompare(bId);
|
|
1115
|
+
}
|
|
1116
|
+
return 0;
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
public add(interval: TInterval): void {
|
|
1121
|
+
this.intervalTree.put(interval, interval);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
public remove(interval: TInterval): void {
|
|
1125
|
+
this.intervalTree.remove(interval);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
public findIntervalsWithEndpointInRange(start: number, end: number) {
|
|
1129
|
+
if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
|
|
1130
|
+
return [];
|
|
1131
|
+
}
|
|
1132
|
+
const results: TInterval[] = [];
|
|
1133
|
+
const action: PropertyAction<TInterval, TInterval> = (node) => {
|
|
1134
|
+
results.push(node.data);
|
|
1135
|
+
return true;
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
const transientStartInterval = this.helpers.create(
|
|
1139
|
+
"transient",
|
|
1140
|
+
start,
|
|
1141
|
+
start,
|
|
1142
|
+
this.client,
|
|
1143
|
+
IntervalType.Transient,
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1146
|
+
const transientEndInterval = this.helpers.create(
|
|
1147
|
+
"transient",
|
|
1148
|
+
end,
|
|
1149
|
+
end,
|
|
1150
|
+
this.client,
|
|
1151
|
+
IntervalType.Transient,
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
// Add comparison overrides to the transient intervals
|
|
1155
|
+
(transientStartInterval as Partial<HasComparisonOverride>)[forceCompare] = -1;
|
|
1156
|
+
(transientEndInterval as Partial<HasComparisonOverride>)[forceCompare] = 1;
|
|
1157
|
+
|
|
1158
|
+
this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
|
|
1159
|
+
return results;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
class StartpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1164
|
+
implements IStartpointInRangeIndex<TInterval>
|
|
1165
|
+
{
|
|
1166
|
+
private readonly intervalTree;
|
|
1167
|
+
|
|
1168
|
+
constructor(
|
|
1169
|
+
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1170
|
+
private readonly client: Client,
|
|
1171
|
+
) {
|
|
1172
|
+
this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
|
|
1173
|
+
assert(
|
|
1174
|
+
typeof helpers.compareStarts === "function",
|
|
1175
|
+
0x6d1 /* compareStarts does not exist in the helpers */,
|
|
1176
|
+
);
|
|
1177
|
+
|
|
1178
|
+
const compareStartsResult = helpers.compareStarts(a, b);
|
|
1179
|
+
if (compareStartsResult !== 0) {
|
|
1180
|
+
return compareStartsResult;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const overrideablesComparison = compareOverrideables(
|
|
1184
|
+
a as Partial<HasComparisonOverride>,
|
|
1185
|
+
b as Partial<HasComparisonOverride>,
|
|
1186
|
+
);
|
|
1187
|
+
if (overrideablesComparison !== 0) {
|
|
1188
|
+
return overrideablesComparison;
|
|
1189
|
+
}
|
|
1190
|
+
const aId = a.getIntervalId();
|
|
1191
|
+
const bId = b.getIntervalId();
|
|
1192
|
+
if (aId !== undefined && bId !== undefined) {
|
|
1193
|
+
return aId.localeCompare(bId);
|
|
1194
|
+
}
|
|
1195
|
+
return 0;
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
public add(interval: TInterval): void {
|
|
1200
|
+
this.intervalTree.put(interval, interval);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
public remove(interval: TInterval): void {
|
|
1204
|
+
this.intervalTree.remove(interval);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
public findIntervalsWithStartpointInRange(start: number, end: number) {
|
|
1208
|
+
if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
|
|
1209
|
+
return [];
|
|
1210
|
+
}
|
|
1211
|
+
const results: TInterval[] = [];
|
|
1212
|
+
const action: PropertyAction<TInterval, TInterval> = (node) => {
|
|
1213
|
+
results.push(node.data);
|
|
1214
|
+
return true;
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
const transientStartInterval = this.helpers.create(
|
|
1218
|
+
"transient",
|
|
1219
|
+
start,
|
|
1220
|
+
start,
|
|
1221
|
+
this.client,
|
|
1222
|
+
IntervalType.Transient,
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
const transientEndInterval = this.helpers.create(
|
|
1226
|
+
"transient",
|
|
1227
|
+
end,
|
|
1228
|
+
end,
|
|
1229
|
+
this.client,
|
|
1230
|
+
IntervalType.Transient,
|
|
1231
|
+
);
|
|
1232
|
+
|
|
1233
|
+
// Add comparison overrides to the transient intervals
|
|
1234
|
+
(transientStartInterval as Partial<HasComparisonOverride>)[forceCompare] = -1;
|
|
1235
|
+
(transientEndInterval as Partial<HasComparisonOverride>)[forceCompare] = 1;
|
|
1236
|
+
|
|
1237
|
+
this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
|
|
1238
|
+
return results;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
export function createEndpointInRangeIndex<TInterval extends ISerializableInterval>(
|
|
1243
|
+
helpers: IIntervalHelpers<TInterval>,
|
|
1244
|
+
client: Client,
|
|
1245
|
+
): IEndpointInRangeIndex<TInterval> {
|
|
1246
|
+
return new EndpointInRangeIndex<TInterval>(helpers, client);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
export function createStartpointInRangeIndex<TInterval extends ISerializableInterval>(
|
|
1250
|
+
helpers: IIntervalHelpers<TInterval>,
|
|
1251
|
+
client: Client,
|
|
1252
|
+
): IStartpointInRangeIndex<TInterval> {
|
|
1253
|
+
return new StartpointInRangeIndex<TInterval>(helpers, client);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1039
1256
|
export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
1040
1257
|
private static readonly legacyIdPrefix = "legacy";
|
|
1041
|
-
public readonly overlappingIntervalsIndex:
|
|
1258
|
+
public readonly overlappingIntervalsIndex: IOverlappingIntervalsIndex<TInterval>;
|
|
1042
1259
|
public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
|
|
1043
1260
|
public readonly endIntervalIndex: EndpointIndex<TInterval>;
|
|
1044
|
-
private readonly indexes: IntervalIndex<TInterval
|
|
1261
|
+
private readonly indexes: Set<IntervalIndex<TInterval>>;
|
|
1045
1262
|
|
|
1046
1263
|
constructor(
|
|
1047
1264
|
private readonly client: Client,
|
|
@@ -1053,14 +1270,14 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1053
1270
|
previousInterval: TInterval,
|
|
1054
1271
|
) => void,
|
|
1055
1272
|
) {
|
|
1056
|
-
this.overlappingIntervalsIndex =
|
|
1273
|
+
this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
|
|
1057
1274
|
this.idIntervalIndex = new IdIntervalIndex();
|
|
1058
1275
|
this.endIntervalIndex = new EndpointIndex(client, helpers);
|
|
1059
|
-
this.indexes = [
|
|
1276
|
+
this.indexes = new Set([
|
|
1060
1277
|
this.overlappingIntervalsIndex,
|
|
1061
1278
|
this.idIntervalIndex,
|
|
1062
1279
|
this.endIntervalIndex,
|
|
1063
|
-
];
|
|
1280
|
+
]);
|
|
1064
1281
|
}
|
|
1065
1282
|
|
|
1066
1283
|
public createLegacyId(start: number, end: number): string {
|
|
@@ -1104,6 +1321,14 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1104
1321
|
}
|
|
1105
1322
|
}
|
|
1106
1323
|
|
|
1324
|
+
public appendIndex(index: IntervalIndex<TInterval>) {
|
|
1325
|
+
this.indexes.add(index);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
public removeIndex(index: IntervalIndex<TInterval>): boolean {
|
|
1329
|
+
return this.indexes.delete(index);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1107
1332
|
public removeExistingInterval(interval: TInterval) {
|
|
1108
1333
|
this.removeIntervalFromIndexes(interval);
|
|
1109
1334
|
this.removeIntervalListeners(interval);
|
|
@@ -1114,8 +1339,18 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1114
1339
|
end: number,
|
|
1115
1340
|
intervalType: IntervalType,
|
|
1116
1341
|
op?: ISequencedDocumentMessage,
|
|
1342
|
+
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
1117
1343
|
): TInterval {
|
|
1118
|
-
return this.helpers.create(
|
|
1344
|
+
return this.helpers.create(
|
|
1345
|
+
this.label,
|
|
1346
|
+
start,
|
|
1347
|
+
end,
|
|
1348
|
+
this.client,
|
|
1349
|
+
intervalType,
|
|
1350
|
+
op,
|
|
1351
|
+
undefined,
|
|
1352
|
+
stickiness,
|
|
1353
|
+
);
|
|
1119
1354
|
}
|
|
1120
1355
|
|
|
1121
1356
|
public addInterval(
|
|
@@ -1124,14 +1359,26 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1124
1359
|
intervalType: IntervalType,
|
|
1125
1360
|
props?: PropertySet,
|
|
1126
1361
|
op?: ISequencedDocumentMessage,
|
|
1362
|
+
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
1127
1363
|
) {
|
|
1128
|
-
const interval: TInterval = this.createInterval(start, end, intervalType, op);
|
|
1364
|
+
const interval: TInterval = this.createInterval(start, end, intervalType, op, stickiness);
|
|
1129
1365
|
if (interval) {
|
|
1130
1366
|
if (!interval.properties) {
|
|
1131
1367
|
interval.properties = createMap<any>();
|
|
1132
1368
|
}
|
|
1133
1369
|
|
|
1134
1370
|
if (props) {
|
|
1371
|
+
// This check is intended to prevent scenarios where a random interval is created and then
|
|
1372
|
+
// inserted into a collection. The aim is to ensure that the collection is created first
|
|
1373
|
+
// then the user can create/add intervals based on the collection
|
|
1374
|
+
if (
|
|
1375
|
+
props[reservedRangeLabelsKey] !== undefined &&
|
|
1376
|
+
props[reservedRangeLabelsKey][0] !== this.label
|
|
1377
|
+
) {
|
|
1378
|
+
throw new LoggingError(
|
|
1379
|
+
"Adding an interval that belongs to another interval collection is not permitted",
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1135
1382
|
interval.addProperties(props);
|
|
1136
1383
|
}
|
|
1137
1384
|
interval.properties[reservedIntervalIdKey] ??= uuid();
|
|
@@ -1201,6 +1448,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1201
1448
|
ref.getOffset(),
|
|
1202
1449
|
ReferenceType.Transient,
|
|
1203
1450
|
ref.properties,
|
|
1451
|
+
ref.slidingPreference,
|
|
1204
1452
|
);
|
|
1205
1453
|
};
|
|
1206
1454
|
if (interval instanceof SequenceInterval) {
|
|
@@ -1243,18 +1491,36 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1243
1491
|
export const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
|
|
1244
1492
|
compareReferencePositions(a.end, b.end);
|
|
1245
1493
|
|
|
1494
|
+
export const compareSequenceIntervalStarts = (a: SequenceInterval, b: SequenceInterval): number =>
|
|
1495
|
+
compareReferencePositions(a.start, b.start);
|
|
1496
|
+
|
|
1497
|
+
export const sequenceIntervalHelpers: IIntervalHelpers<SequenceInterval> = {
|
|
1498
|
+
compareEnds: compareSequenceIntervalEnds,
|
|
1499
|
+
compareStarts: compareSequenceIntervalStarts,
|
|
1500
|
+
create: createSequenceInterval,
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
export const intervalHelpers: IIntervalHelpers<Interval> = {
|
|
1504
|
+
compareEnds: (a: Interval, b: Interval) => a.end - b.end,
|
|
1505
|
+
compareStarts: (a: Interval, b: Interval) => a.start - b.start,
|
|
1506
|
+
create: createInterval,
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1246
1509
|
class SequenceIntervalCollectionFactory
|
|
1247
1510
|
implements IValueFactory<IntervalCollection<SequenceInterval>>
|
|
1248
1511
|
{
|
|
1249
1512
|
public load(
|
|
1250
1513
|
emitter: IValueOpEmitter,
|
|
1251
1514
|
raw: ISerializedInterval[] | ISerializedIntervalCollectionV2 = [],
|
|
1515
|
+
options?: Partial<SequenceOptions>,
|
|
1252
1516
|
): IntervalCollection<SequenceInterval> {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1517
|
+
return new IntervalCollection<SequenceInterval>(
|
|
1518
|
+
sequenceIntervalHelpers,
|
|
1519
|
+
true,
|
|
1520
|
+
emitter,
|
|
1521
|
+
raw,
|
|
1522
|
+
options,
|
|
1523
|
+
);
|
|
1258
1524
|
}
|
|
1259
1525
|
|
|
1260
1526
|
public store(
|
|
@@ -1289,7 +1555,15 @@ export class SequenceIntervalCollectionValueType
|
|
|
1289
1555
|
|
|
1290
1556
|
const compareIntervalEnds = (a: Interval, b: Interval) => a.end - b.end;
|
|
1291
1557
|
|
|
1292
|
-
function createInterval(
|
|
1558
|
+
function createInterval(
|
|
1559
|
+
label: string,
|
|
1560
|
+
start: number,
|
|
1561
|
+
end: number,
|
|
1562
|
+
client: Client,
|
|
1563
|
+
intervalType?: IntervalType,
|
|
1564
|
+
op?: ISequencedDocumentMessage,
|
|
1565
|
+
fromSnapshot?: boolean,
|
|
1566
|
+
): Interval {
|
|
1293
1567
|
const rangeProp: PropertySet = {};
|
|
1294
1568
|
|
|
1295
1569
|
if (label && label.length > 0) {
|
|
@@ -1303,12 +1577,13 @@ class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Inte
|
|
|
1303
1577
|
public load(
|
|
1304
1578
|
emitter: IValueOpEmitter,
|
|
1305
1579
|
raw: ISerializedInterval[] | ISerializedIntervalCollectionV2 = [],
|
|
1580
|
+
options?: Partial<SequenceOptions>,
|
|
1306
1581
|
): IntervalCollection<Interval> {
|
|
1307
1582
|
const helpers: IIntervalHelpers<Interval> = {
|
|
1308
1583
|
compareEnds: compareIntervalEnds,
|
|
1309
1584
|
create: createInterval,
|
|
1310
1585
|
};
|
|
1311
|
-
const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw);
|
|
1586
|
+
const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
|
|
1312
1587
|
collection.attachGraph(undefined as any as Client, "");
|
|
1313
1588
|
return collection;
|
|
1314
1589
|
}
|
|
@@ -1355,7 +1630,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
|
|
|
1355
1630
|
|
|
1356
1631
|
return new Map<string, IValueOperation<IntervalCollection<T>>>([
|
|
1357
1632
|
[
|
|
1358
|
-
|
|
1633
|
+
IntervalOpType.ADD,
|
|
1359
1634
|
{
|
|
1360
1635
|
process: (collection, params, local, op, localOpMetadata) => {
|
|
1361
1636
|
// if params is undefined, the interval was deleted during
|
|
@@ -1370,7 +1645,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
|
|
|
1370
1645
|
},
|
|
1371
1646
|
],
|
|
1372
1647
|
[
|
|
1373
|
-
|
|
1648
|
+
IntervalOpType.DELETE,
|
|
1374
1649
|
{
|
|
1375
1650
|
process: (collection, params, local, op) => {
|
|
1376
1651
|
assert(op !== undefined, 0x3fc /* op should exist here */);
|
|
@@ -1383,7 +1658,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
|
|
|
1383
1658
|
},
|
|
1384
1659
|
],
|
|
1385
1660
|
[
|
|
1386
|
-
|
|
1661
|
+
IntervalOpType.CHANGE,
|
|
1387
1662
|
{
|
|
1388
1663
|
process: (collection, params, local, op, localOpMetadata) => {
|
|
1389
1664
|
// if params is undefined, the interval was deleted during
|
|
@@ -1402,7 +1677,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
|
|
|
1402
1677
|
|
|
1403
1678
|
export type DeserializeCallback = (properties: PropertySet) => void;
|
|
1404
1679
|
|
|
1405
|
-
|
|
1680
|
+
class IntervalCollectionIterator<TInterval extends ISerializableInterval>
|
|
1406
1681
|
implements Iterator<TInterval>
|
|
1407
1682
|
{
|
|
1408
1683
|
private readonly results: TInterval[];
|
|
@@ -1450,6 +1725,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1450
1725
|
* endpoints. These references should be used for position information only.
|
|
1451
1726
|
* `local` reflects whether the change originated locally.
|
|
1452
1727
|
* `op` is defined if and only if the server has acked this change.
|
|
1728
|
+
* `slide` is true if the change is due to sliding on removal of position
|
|
1453
1729
|
*/
|
|
1454
1730
|
(
|
|
1455
1731
|
event: "changeInterval",
|
|
@@ -1458,6 +1734,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1458
1734
|
previousInterval: TInterval,
|
|
1459
1735
|
local: boolean,
|
|
1460
1736
|
op: ISequencedDocumentMessage | undefined,
|
|
1737
|
+
slide: boolean,
|
|
1461
1738
|
) => void,
|
|
1462
1739
|
);
|
|
1463
1740
|
/**
|
|
@@ -1495,14 +1772,137 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1495
1772
|
|
|
1496
1773
|
/**
|
|
1497
1774
|
* Collection of intervals that supports addition, modification, removal, and efficient spatial querying.
|
|
1498
|
-
*
|
|
1499
|
-
* integrate into a DDS.
|
|
1500
|
-
* This aligns with its usage in `SharedSegmentSequence`, which allows associating intervals to positions in the
|
|
1501
|
-
* sequence DDS which are broadcast to all other clients in an eventually consistent fashion.
|
|
1775
|
+
* Changes to this collection will be incur updates on collaborating clients (i.e. they are not local-only).
|
|
1502
1776
|
*/
|
|
1503
|
-
export
|
|
1504
|
-
IIntervalCollectionEvent<TInterval
|
|
1505
|
-
|
|
1777
|
+
export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
1778
|
+
extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>> {
|
|
1779
|
+
readonly attached: boolean;
|
|
1780
|
+
/**
|
|
1781
|
+
* Attaches an index to this collection.
|
|
1782
|
+
* All intervals which are part of this collection will be added to the index, and the index will automatically
|
|
1783
|
+
* be updated when this collection updates due to local or remote changes.
|
|
1784
|
+
*
|
|
1785
|
+
* @remarks - After attaching an index to an interval collection, applications should typically store this
|
|
1786
|
+
* index somewhere in their in-memory data model for future reference and querying.
|
|
1787
|
+
*/
|
|
1788
|
+
attachIndex(index: IntervalIndex<TInterval>): void;
|
|
1789
|
+
/**
|
|
1790
|
+
* Detaches an index from this collection.
|
|
1791
|
+
* All intervals which are part of this collection will be removed from the index, and updates to this collection
|
|
1792
|
+
* due to local or remote changes will no longer incur updates to the index.
|
|
1793
|
+
*
|
|
1794
|
+
* @returns - Return false if the target index cannot be found in the indexes, otherwise remove all intervals in the index and return true
|
|
1795
|
+
*/
|
|
1796
|
+
detachIndex(index: IntervalIndex<TInterval>): boolean;
|
|
1797
|
+
/**
|
|
1798
|
+
* @returns the interval in this collection that has the provided `id`.
|
|
1799
|
+
* If no interval in the collection has this `id`, returns `undefined`.
|
|
1800
|
+
*/
|
|
1801
|
+
getIntervalById(id: string): TInterval | undefined;
|
|
1802
|
+
/**
|
|
1803
|
+
* Creates a new interval and add it to the collection.
|
|
1804
|
+
* @param start - interval start position (inclusive)
|
|
1805
|
+
* @param end - interval end position (exclusive)
|
|
1806
|
+
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
1807
|
+
* @param props - properties of the interval
|
|
1808
|
+
* @param stickiness - {@link (IntervalStickiness:type)} to apply to the added interval.
|
|
1809
|
+
* @returns - the created interval
|
|
1810
|
+
* @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
|
|
1811
|
+
* with how the current half-open behavior is represented.
|
|
1812
|
+
*/
|
|
1813
|
+
add(
|
|
1814
|
+
start: number,
|
|
1815
|
+
end: number,
|
|
1816
|
+
intervalType: IntervalType,
|
|
1817
|
+
props?: PropertySet,
|
|
1818
|
+
stickiness?: IntervalStickiness,
|
|
1819
|
+
): TInterval;
|
|
1820
|
+
/**
|
|
1821
|
+
* Removes an interval from the collection.
|
|
1822
|
+
* @param id - Id of the interval to remove
|
|
1823
|
+
* @returns the removed interval
|
|
1824
|
+
*/
|
|
1825
|
+
removeIntervalById(id: string): TInterval | undefined;
|
|
1826
|
+
/**
|
|
1827
|
+
* Changes the properties on an existing interval.
|
|
1828
|
+
* @param id - Id of the interval whose properties should be changed
|
|
1829
|
+
* @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
|
|
1830
|
+
* and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
|
|
1831
|
+
*/
|
|
1832
|
+
changeProperties(id: string, props: PropertySet);
|
|
1833
|
+
/**
|
|
1834
|
+
* Changes the endpoints of an existing interval.
|
|
1835
|
+
* @param id - Id of the interval to change
|
|
1836
|
+
* @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
|
|
1837
|
+
* @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
|
|
1838
|
+
* @returns the interval that was changed, if it existed in the collection.
|
|
1839
|
+
*/
|
|
1840
|
+
change(id: string, start?: number, end?: number): TInterval | undefined;
|
|
1841
|
+
|
|
1842
|
+
attachDeserializer(onDeserialize: DeserializeCallback): void;
|
|
1843
|
+
/**
|
|
1844
|
+
* @returns an iterator over all intervals in this collection.
|
|
1845
|
+
*/
|
|
1846
|
+
[Symbol.iterator](): Iterator<TInterval>;
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* @returns a forward iterator over all intervals in this collection with start point equal to `startPosition`.
|
|
1850
|
+
*/
|
|
1851
|
+
CreateForwardIteratorWithStartPosition(startPosition: number): Iterator<TInterval>;
|
|
1852
|
+
|
|
1853
|
+
/**
|
|
1854
|
+
* @returns a backward iterator over all intervals in this collection with start point equal to `startPosition`.
|
|
1855
|
+
*/
|
|
1856
|
+
CreateBackwardIteratorWithStartPosition(startPosition: number): Iterator<TInterval>;
|
|
1857
|
+
|
|
1858
|
+
/**
|
|
1859
|
+
* @returns a forward iterator over all intervals in this collection with end point equal to `endPosition`.
|
|
1860
|
+
*/
|
|
1861
|
+
CreateForwardIteratorWithEndPosition(endPosition: number): Iterator<TInterval>;
|
|
1862
|
+
|
|
1863
|
+
/**
|
|
1864
|
+
* @returns a backward iterator over all intervals in this collection with end point equal to `endPosition`.
|
|
1865
|
+
*/
|
|
1866
|
+
CreateBackwardIteratorWithEndPosition(endPosition: number): Iterator<TInterval>;
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Gathers iteration results that optionally match a start/end criteria into the provided array.
|
|
1870
|
+
* @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
|
|
1871
|
+
* intervals matching the query upon edit.
|
|
1872
|
+
* @param iteratesForward - whether or not iteration should be in the forward direction
|
|
1873
|
+
* @param start - If provided, only match intervals whose start point is equal to `start`.
|
|
1874
|
+
* @param end - If provided, only match intervals whose end point is equal to `end`.
|
|
1875
|
+
*/
|
|
1876
|
+
gatherIterationResults(
|
|
1877
|
+
results: TInterval[],
|
|
1878
|
+
iteratesForward: boolean,
|
|
1879
|
+
start?: number,
|
|
1880
|
+
end?: number,
|
|
1881
|
+
): void;
|
|
1882
|
+
|
|
1883
|
+
/**
|
|
1884
|
+
* @returns an array of all intervals in this collection that overlap with the interval
|
|
1885
|
+
* `[startPosition, endPosition]`.
|
|
1886
|
+
*/
|
|
1887
|
+
findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[];
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Applies a function to each interval in this collection.
|
|
1891
|
+
*/
|
|
1892
|
+
map(fn: (interval: TInterval) => void): void;
|
|
1893
|
+
|
|
1894
|
+
previousInterval(pos: number): TInterval | undefined;
|
|
1895
|
+
|
|
1896
|
+
nextInterval(pos: number): TInterval | undefined;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/**
|
|
1900
|
+
* {@inheritdoc IIntervalCollection}
|
|
1901
|
+
*/
|
|
1902
|
+
export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
1903
|
+
extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>>
|
|
1904
|
+
implements IIntervalCollection<TInterval>
|
|
1905
|
+
{
|
|
1506
1906
|
private savedSerializedIntervals?: ISerializedInterval[];
|
|
1507
1907
|
private localCollection: LocalIntervalCollection<TInterval> | undefined;
|
|
1508
1908
|
private onDeserialize: DeserializeCallback | undefined;
|
|
@@ -1534,6 +1934,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1534
1934
|
private readonly requiresClient: boolean,
|
|
1535
1935
|
private readonly emitter: IValueOpEmitter,
|
|
1536
1936
|
serializedIntervals: ISerializedInterval[] | ISerializedIntervalCollectionV2,
|
|
1937
|
+
private readonly options: Partial<SequenceOptions> = {},
|
|
1537
1938
|
) {
|
|
1538
1939
|
super();
|
|
1539
1940
|
|
|
@@ -1544,6 +1945,40 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1544
1945
|
);
|
|
1545
1946
|
}
|
|
1546
1947
|
|
|
1948
|
+
/**
|
|
1949
|
+
* {@inheritdoc IIntervalCollection.attachIndex}
|
|
1950
|
+
*/
|
|
1951
|
+
public attachIndex(index: IntervalIndex<TInterval>): void {
|
|
1952
|
+
if (!this.attached) {
|
|
1953
|
+
throw new LoggingError("The local interval collection must exist");
|
|
1954
|
+
}
|
|
1955
|
+
for (const interval of this) {
|
|
1956
|
+
index.add(interval);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
this.localCollection?.appendIndex(index);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* {@inheritdoc IIntervalCollection.detachIndex}
|
|
1964
|
+
*/
|
|
1965
|
+
public detachIndex(index: IntervalIndex<TInterval>): boolean {
|
|
1966
|
+
if (!this.attached) {
|
|
1967
|
+
throw new LoggingError("The local interval collection must exist");
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Avoid removing intervals if the index does not exist
|
|
1971
|
+
if (!this.localCollection?.removeIndex(index)) {
|
|
1972
|
+
return false;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
for (const interval of this) {
|
|
1976
|
+
index.remove(interval);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
return true;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1547
1982
|
private rebasePositionWithSegmentSlide(
|
|
1548
1983
|
pos: number,
|
|
1549
1984
|
seqNumberFrom: number,
|
|
@@ -1565,7 +2000,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1565
2000
|
// if segment is undefined, it slid off the string
|
|
1566
2001
|
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
1567
2002
|
|
|
1568
|
-
const segoff =
|
|
2003
|
+
const segoff = getSlideToSegoff({ segment, offset }) ?? segment;
|
|
1569
2004
|
|
|
1570
2005
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
1571
2006
|
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
@@ -1629,12 +2064,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1629
2064
|
client,
|
|
1630
2065
|
label,
|
|
1631
2066
|
this.helpers,
|
|
1632
|
-
(interval, previousInterval) => this.emitChange(interval, previousInterval, true),
|
|
2067
|
+
(interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
|
|
1633
2068
|
);
|
|
1634
2069
|
if (this.savedSerializedIntervals) {
|
|
1635
2070
|
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
1636
2071
|
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1637
|
-
const { start, end, intervalType, properties } = serializedInterval;
|
|
2072
|
+
const { start, end, intervalType, properties, stickiness } = serializedInterval;
|
|
1638
2073
|
const interval = this.helpers.create(
|
|
1639
2074
|
label,
|
|
1640
2075
|
start,
|
|
@@ -1643,6 +2078,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1643
2078
|
intervalType,
|
|
1644
2079
|
undefined,
|
|
1645
2080
|
true,
|
|
2081
|
+
stickiness,
|
|
1646
2082
|
);
|
|
1647
2083
|
if (properties) {
|
|
1648
2084
|
interval.addProperties(properties);
|
|
@@ -1668,6 +2104,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1668
2104
|
interval: TInterval,
|
|
1669
2105
|
previousInterval: TInterval,
|
|
1670
2106
|
local: boolean,
|
|
2107
|
+
slide: boolean,
|
|
1671
2108
|
op?: ISequencedDocumentMessage,
|
|
1672
2109
|
): void {
|
|
1673
2110
|
// Temporarily make references transient so that positional queries work (non-transient refs
|
|
@@ -1680,17 +2117,16 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1680
2117
|
endRefType = previousInterval.end.refType;
|
|
1681
2118
|
previousInterval.start.refType = ReferenceType.Transient;
|
|
1682
2119
|
previousInterval.end.refType = ReferenceType.Transient;
|
|
1683
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
2120
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
1684
2121
|
previousInterval.start.refType = startRefType;
|
|
1685
2122
|
previousInterval.end.refType = endRefType;
|
|
1686
2123
|
} else {
|
|
1687
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
2124
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
1688
2125
|
}
|
|
1689
2126
|
}
|
|
1690
2127
|
|
|
1691
2128
|
/**
|
|
1692
|
-
* @
|
|
1693
|
-
* If no interval in the collection has this `id`, returns `undefined`.
|
|
2129
|
+
* {@inheritdoc IIntervalCollection.getIntervalById}
|
|
1694
2130
|
*/
|
|
1695
2131
|
public getIntervalById(id: string) {
|
|
1696
2132
|
if (!this.localCollection) {
|
|
@@ -1700,20 +2136,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1700
2136
|
}
|
|
1701
2137
|
|
|
1702
2138
|
/**
|
|
1703
|
-
*
|
|
1704
|
-
* @param start - interval start position (inclusive)
|
|
1705
|
-
* @param end - interval end position (exclusive)
|
|
1706
|
-
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
1707
|
-
* @param props - properties of the interval
|
|
1708
|
-
* @returns - the created interval
|
|
1709
|
-
* @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
|
|
1710
|
-
* with how the current half-open behavior is represented.
|
|
2139
|
+
* {@inheritdoc IIntervalCollection.add}
|
|
1711
2140
|
*/
|
|
1712
2141
|
public add(
|
|
1713
2142
|
start: number,
|
|
1714
2143
|
end: number,
|
|
1715
2144
|
intervalType: IntervalType,
|
|
1716
2145
|
props?: PropertySet,
|
|
2146
|
+
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
1717
2147
|
): TInterval {
|
|
1718
2148
|
if (!this.localCollection) {
|
|
1719
2149
|
throw new LoggingError("attach must be called prior to adding intervals");
|
|
@@ -1721,12 +2151,19 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1721
2151
|
if (intervalType & IntervalType.Transient) {
|
|
1722
2152
|
throw new LoggingError("Can not add transient intervals");
|
|
1723
2153
|
}
|
|
2154
|
+
if (stickiness !== IntervalStickiness.END && !this.options.intervalStickinessEnabled) {
|
|
2155
|
+
throw new UsageError(
|
|
2156
|
+
"attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag",
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
1724
2159
|
|
|
1725
2160
|
const interval: TInterval = this.localCollection.addInterval(
|
|
1726
2161
|
start,
|
|
1727
2162
|
end,
|
|
1728
2163
|
intervalType,
|
|
1729
2164
|
props,
|
|
2165
|
+
undefined,
|
|
2166
|
+
stickiness,
|
|
1730
2167
|
);
|
|
1731
2168
|
|
|
1732
2169
|
if (interval) {
|
|
@@ -1736,6 +2173,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1736
2173
|
properties: interval.properties,
|
|
1737
2174
|
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1738
2175
|
start,
|
|
2176
|
+
stickiness,
|
|
1739
2177
|
};
|
|
1740
2178
|
const localSeq = this.getNextLocalSeq();
|
|
1741
2179
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
@@ -1776,9 +2214,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1776
2214
|
}
|
|
1777
2215
|
|
|
1778
2216
|
/**
|
|
1779
|
-
*
|
|
1780
|
-
* @param id - Id of the interval to remove
|
|
1781
|
-
* @returns the removed interval
|
|
2217
|
+
* {@inheritdoc IIntervalCollection.removeIntervalById}
|
|
1782
2218
|
*/
|
|
1783
2219
|
public removeIntervalById(id: string) {
|
|
1784
2220
|
if (!this.localCollection) {
|
|
@@ -1792,10 +2228,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1792
2228
|
}
|
|
1793
2229
|
|
|
1794
2230
|
/**
|
|
1795
|
-
*
|
|
1796
|
-
* @param id - Id of the interval whose properties should be changed
|
|
1797
|
-
* @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
|
|
1798
|
-
* and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
|
|
2231
|
+
* {@inheritdoc IIntervalCollection.changeProperties}
|
|
1799
2232
|
*/
|
|
1800
2233
|
public changeProperties(id: string, props: PropertySet) {
|
|
1801
2234
|
if (!this.attached) {
|
|
@@ -1807,6 +2240,13 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1807
2240
|
if (!props) {
|
|
1808
2241
|
throw new LoggingError("changeProperties should be called with a property set");
|
|
1809
2242
|
}
|
|
2243
|
+
// prevent the overwriting of an interval label, it should remain unchanged
|
|
2244
|
+
// once it has been inserted into the collection.
|
|
2245
|
+
if (props[reservedRangeLabelsKey] !== undefined) {
|
|
2246
|
+
throw new LoggingError(
|
|
2247
|
+
"The label property should not be modified once inserted to the collection",
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
1810
2250
|
|
|
1811
2251
|
const interval = this.getIntervalById(id);
|
|
1812
2252
|
if (interval) {
|
|
@@ -1829,11 +2269,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1829
2269
|
}
|
|
1830
2270
|
|
|
1831
2271
|
/**
|
|
1832
|
-
*
|
|
1833
|
-
* @param id - Id of the interval to change
|
|
1834
|
-
* @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
|
|
1835
|
-
* @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
|
|
1836
|
-
* @returns the interval that was changed, if it existed in the collection.
|
|
2272
|
+
* {@inheritdoc IIntervalCollection.change}
|
|
1837
2273
|
*/
|
|
1838
2274
|
public change(id: string, start?: number, end?: number): TInterval | undefined {
|
|
1839
2275
|
if (!this.localCollection) {
|
|
@@ -1862,7 +2298,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1862
2298
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1863
2299
|
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
1864
2300
|
this.addPendingChange(id, serializedInterval);
|
|
1865
|
-
this.emitChange(newInterval, interval, true);
|
|
2301
|
+
this.emitChange(newInterval, interval, true, false);
|
|
1866
2302
|
return newInterval;
|
|
1867
2303
|
}
|
|
1868
2304
|
// No interval to change
|
|
@@ -1998,7 +2434,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1998
2434
|
}
|
|
1999
2435
|
|
|
2000
2436
|
if (newInterval !== interval) {
|
|
2001
|
-
this.emitChange(newInterval, interval, local, op);
|
|
2437
|
+
this.emitChange(newInterval, interval, local, false, op);
|
|
2002
2438
|
}
|
|
2003
2439
|
|
|
2004
2440
|
const changedProperties = Object.keys(newProps).length > 0;
|
|
@@ -2021,6 +2457,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2021
2457
|
}
|
|
2022
2458
|
}
|
|
2023
2459
|
|
|
2460
|
+
/**
|
|
2461
|
+
* {@inheritdoc IIntervalCollection.attachDeserializer}
|
|
2462
|
+
*/
|
|
2024
2463
|
public attachDeserializer(onDeserialize: DeserializeCallback): void {
|
|
2025
2464
|
// If no deserializer is specified can skip all processing work
|
|
2026
2465
|
if (!onDeserialize) {
|
|
@@ -2111,7 +2550,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2111
2550
|
return rebased;
|
|
2112
2551
|
}
|
|
2113
2552
|
|
|
2114
|
-
private getSlideToSegment(
|
|
2553
|
+
private getSlideToSegment(
|
|
2554
|
+
lref: LocalReferencePosition,
|
|
2555
|
+
): { segment: ISegment | undefined; offset: number | undefined } | undefined {
|
|
2115
2556
|
if (!this.client) {
|
|
2116
2557
|
throw new LoggingError("client does not exist");
|
|
2117
2558
|
}
|
|
@@ -2119,7 +2560,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2119
2560
|
if (segoff.segment?.localRefs?.has(lref) !== true) {
|
|
2120
2561
|
return undefined;
|
|
2121
2562
|
}
|
|
2122
|
-
const newSegoff =
|
|
2563
|
+
const newSegoff = getSlideToSegoff(segoff);
|
|
2123
2564
|
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
2124
2565
|
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
2125
2566
|
? undefined
|
|
@@ -2188,6 +2629,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2188
2629
|
newStart,
|
|
2189
2630
|
interval.start.refType,
|
|
2190
2631
|
op,
|
|
2632
|
+
startReferenceSlidingPreference(interval.stickiness),
|
|
2191
2633
|
);
|
|
2192
2634
|
if (props) {
|
|
2193
2635
|
interval.start.addProperties(props);
|
|
@@ -2205,6 +2647,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2205
2647
|
newEnd,
|
|
2206
2648
|
interval.end.refType,
|
|
2207
2649
|
op,
|
|
2650
|
+
endReferenceSlidingPreference(interval.stickiness),
|
|
2208
2651
|
);
|
|
2209
2652
|
if (props) {
|
|
2210
2653
|
interval.end.addProperties(props);
|
|
@@ -2216,7 +2659,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2216
2659
|
oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
|
|
2217
2660
|
}
|
|
2218
2661
|
this.localCollection.add(interval);
|
|
2219
|
-
this.emitChange(interval, oldInterval as TInterval, true, op);
|
|
2662
|
+
this.emitChange(interval, oldInterval as TInterval, true, true, op);
|
|
2220
2663
|
}
|
|
2221
2664
|
}
|
|
2222
2665
|
|
|
@@ -2253,6 +2696,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2253
2696
|
serializedInterval.intervalType,
|
|
2254
2697
|
serializedInterval.properties,
|
|
2255
2698
|
op,
|
|
2699
|
+
serializedInterval.stickiness,
|
|
2256
2700
|
);
|
|
2257
2701
|
|
|
2258
2702
|
if (interval) {
|
|
@@ -2310,7 +2754,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2310
2754
|
}
|
|
2311
2755
|
|
|
2312
2756
|
/**
|
|
2313
|
-
* @
|
|
2757
|
+
* {@inheritdoc IIntervalCollection.CreateForwardIteratorWithStartPosition}
|
|
2314
2758
|
*/
|
|
2315
2759
|
public CreateForwardIteratorWithStartPosition(
|
|
2316
2760
|
startPosition: number,
|
|
@@ -2320,7 +2764,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2320
2764
|
}
|
|
2321
2765
|
|
|
2322
2766
|
/**
|
|
2323
|
-
* @
|
|
2767
|
+
* {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithStartPosition}
|
|
2324
2768
|
*/
|
|
2325
2769
|
public CreateBackwardIteratorWithStartPosition(
|
|
2326
2770
|
startPosition: number,
|
|
@@ -2330,7 +2774,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2330
2774
|
}
|
|
2331
2775
|
|
|
2332
2776
|
/**
|
|
2333
|
-
* @
|
|
2777
|
+
* {@inheritdoc IIntervalCollection.CreateForwardIteratorWithEndPosition}
|
|
2334
2778
|
*/
|
|
2335
2779
|
public CreateForwardIteratorWithEndPosition(
|
|
2336
2780
|
endPosition: number,
|
|
@@ -2345,7 +2789,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2345
2789
|
}
|
|
2346
2790
|
|
|
2347
2791
|
/**
|
|
2348
|
-
* @
|
|
2792
|
+
* {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithEndPosition}
|
|
2349
2793
|
*/
|
|
2350
2794
|
public CreateBackwardIteratorWithEndPosition(
|
|
2351
2795
|
endPosition: number,
|
|
@@ -2360,12 +2804,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2360
2804
|
}
|
|
2361
2805
|
|
|
2362
2806
|
/**
|
|
2363
|
-
*
|
|
2364
|
-
* @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
|
|
2365
|
-
* intervals matching the query upon edit.
|
|
2366
|
-
* @param iteratesForward - whether or not iteration should be in the forward direction
|
|
2367
|
-
* @param start - If provided, only match intervals whose start point is equal to `start`.
|
|
2368
|
-
* @param end - If provided, only match intervals whose end point is equal to `end`.
|
|
2807
|
+
* {@inheritdoc IIntervalCollection.gatherIterationResults}
|
|
2369
2808
|
*/
|
|
2370
2809
|
public gatherIterationResults(
|
|
2371
2810
|
results: TInterval[],
|
|
@@ -2386,8 +2825,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2386
2825
|
}
|
|
2387
2826
|
|
|
2388
2827
|
/**
|
|
2389
|
-
* @
|
|
2390
|
-
* `[startPosition, endPosition]`.
|
|
2828
|
+
* {@inheritdoc IIntervalCollection.findOverlappingIntervals}
|
|
2391
2829
|
*/
|
|
2392
2830
|
public findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[] {
|
|
2393
2831
|
if (!this.localCollection) {
|
|
@@ -2401,7 +2839,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2401
2839
|
}
|
|
2402
2840
|
|
|
2403
2841
|
/**
|
|
2404
|
-
*
|
|
2842
|
+
* {@inheritdoc IIntervalCollection.map}
|
|
2405
2843
|
*/
|
|
2406
2844
|
public map(fn: (interval: TInterval) => void) {
|
|
2407
2845
|
if (!this.localCollection) {
|
|
@@ -2413,6 +2851,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2413
2851
|
}
|
|
2414
2852
|
}
|
|
2415
2853
|
|
|
2854
|
+
/**
|
|
2855
|
+
* {@inheritdoc IIntervalCollection.previousInterval}
|
|
2856
|
+
*/
|
|
2416
2857
|
public previousInterval(pos: number): TInterval | undefined {
|
|
2417
2858
|
if (!this.localCollection) {
|
|
2418
2859
|
throw new LoggingError("attachSequence must be called");
|
|
@@ -2421,6 +2862,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2421
2862
|
return this.localCollection.endIntervalIndex.previousInterval(pos);
|
|
2422
2863
|
}
|
|
2423
2864
|
|
|
2865
|
+
/**
|
|
2866
|
+
* {@inheritdoc IIntervalCollection.nextInterval}
|
|
2867
|
+
*/
|
|
2424
2868
|
public nextInterval(pos: number): TInterval | undefined {
|
|
2425
2869
|
if (!this.localCollection) {
|
|
2426
2870
|
throw new LoggingError("attachSequence must be called");
|