@fluidframework/sequence 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258
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 +142 -0
- package/README.md +4 -3
- package/dist/defaultMap.d.ts +1 -1
- package/dist/defaultMap.d.ts.map +1 -1
- package/dist/defaultMap.js +9 -10
- package/dist/defaultMap.js.map +1 -1
- package/dist/defaultMapInterfaces.d.ts +1 -1
- package/dist/defaultMapInterfaces.d.ts.map +1 -1
- package/dist/defaultMapInterfaces.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -9
- package/dist/index.js.map +1 -1
- package/dist/intervalCollection.d.ts +14 -437
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +96 -916
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalIndex/endpointInRangeIndex.d.ts +20 -0
- package/dist/intervalIndex/endpointInRangeIndex.d.ts.map +1 -0
- package/dist/intervalIndex/endpointInRangeIndex.js +60 -0
- package/dist/intervalIndex/endpointInRangeIndex.js.map +1 -0
- package/dist/intervalIndex/endpointIndex.d.ts +21 -0
- package/dist/intervalIndex/endpointIndex.d.ts.map +1 -0
- package/dist/intervalIndex/endpointIndex.js +42 -0
- package/dist/intervalIndex/endpointIndex.js.map +1 -0
- package/dist/intervalIndex/idIntervalIndex.d.ts +12 -0
- package/dist/intervalIndex/idIntervalIndex.d.ts.map +1 -0
- package/dist/intervalIndex/idIntervalIndex.js +41 -0
- package/dist/intervalIndex/idIntervalIndex.js.map +1 -0
- package/dist/intervalIndex/index.d.ts +13 -0
- package/dist/intervalIndex/index.d.ts.map +1 -0
- package/dist/intervalIndex/index.js +20 -0
- package/dist/intervalIndex/index.js.map +1 -0
- package/dist/intervalIndex/intervalIndex.d.ts +29 -0
- package/dist/intervalIndex/intervalIndex.d.ts.map +1 -0
- package/dist/intervalIndex/intervalIndex.js +7 -0
- package/dist/intervalIndex/intervalIndex.js.map +1 -0
- package/dist/intervalIndex/intervalIndexUtils.d.ts +17 -0
- package/dist/intervalIndex/intervalIndexUtils.d.ts.map +1 -0
- package/dist/intervalIndex/intervalIndexUtils.js +22 -0
- package/dist/intervalIndex/intervalIndexUtils.js.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +33 -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/intervalIndex/startpointInRangeIndex.d.ts +20 -0
- package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -0
- package/dist/intervalIndex/startpointInRangeIndex.js +62 -0
- package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -0
- package/dist/intervalTree.d.ts +2 -56
- package/dist/intervalTree.d.ts.map +1 -1
- package/dist/intervalTree.js +2 -11
- package/dist/intervalTree.js.map +1 -1
- package/dist/intervals/index.d.ts +8 -0
- package/dist/intervals/index.d.ts.map +1 -0
- package/dist/intervals/index.js +23 -0
- package/dist/intervals/index.js.map +1 -0
- package/dist/intervals/interval.d.ts +88 -0
- package/dist/intervals/interval.d.ts.map +1 -0
- package/dist/intervals/interval.js +180 -0
- package/dist/intervals/interval.js.map +1 -0
- package/dist/intervals/intervalUtils.d.ts +200 -0
- package/dist/intervals/intervalUtils.d.ts.map +1 -0
- package/dist/intervals/intervalUtils.js +79 -0
- package/dist/intervals/intervalUtils.js.map +1 -0
- package/dist/intervals/sequenceInterval.d.ts +132 -0
- package/dist/intervals/sequenceInterval.d.ts.map +1 -0
- package/dist/intervals/sequenceInterval.js +313 -0
- package/dist/intervals/sequenceInterval.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 +1 -1
- package/dist/revertibles.d.ts.map +1 -1
- package/dist/revertibles.js +85 -52
- package/dist/revertibles.js.map +1 -1
- package/dist/sequence.d.ts +33 -4
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +91 -47
- package/dist/sequence.js.map +1 -1
- package/dist/sequenceDeltaEvent.d.ts +8 -3
- package/dist/sequenceDeltaEvent.d.ts.map +1 -1
- package/dist/sequenceDeltaEvent.js +3 -4
- package/dist/sequenceDeltaEvent.js.map +1 -1
- package/dist/sharedIntervalCollection.d.ts +2 -1
- package/dist/sharedIntervalCollection.d.ts.map +1 -1
- package/dist/sharedIntervalCollection.js +2 -2
- package/dist/sharedIntervalCollection.js.map +1 -1
- package/dist/sharedSequence.d.ts +9 -0
- package/dist/sharedSequence.d.ts.map +1 -1
- package/dist/sharedSequence.js +9 -6
- package/dist/sharedSequence.js.map +1 -1
- package/dist/sharedString.d.ts.map +1 -1
- package/dist/sharedString.js +9 -29
- package/dist/sharedString.js.map +1 -1
- package/lib/defaultMap.d.ts +1 -1
- package/lib/defaultMap.d.ts.map +1 -1
- package/lib/defaultMap.js +5 -6
- package/lib/defaultMap.js.map +1 -1
- package/lib/defaultMapInterfaces.d.ts +1 -1
- package/lib/defaultMapInterfaces.d.ts.map +1 -1
- package/lib/defaultMapInterfaces.js.map +1 -1
- package/lib/index.d.ts +3 -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 +14 -437
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +64 -877
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalIndex/endpointInRangeIndex.d.ts +20 -0
- package/lib/intervalIndex/endpointInRangeIndex.d.ts.map +1 -0
- package/lib/intervalIndex/endpointInRangeIndex.js +56 -0
- package/lib/intervalIndex/endpointInRangeIndex.js.map +1 -0
- package/lib/intervalIndex/endpointIndex.d.ts +21 -0
- package/lib/intervalIndex/endpointIndex.d.ts.map +1 -0
- package/lib/intervalIndex/endpointIndex.js +38 -0
- package/lib/intervalIndex/endpointIndex.js.map +1 -0
- package/lib/intervalIndex/idIntervalIndex.d.ts +12 -0
- package/lib/intervalIndex/idIntervalIndex.d.ts.map +1 -0
- package/lib/intervalIndex/idIntervalIndex.js +37 -0
- package/lib/intervalIndex/idIntervalIndex.js.map +1 -0
- package/lib/intervalIndex/index.d.ts +13 -0
- package/lib/intervalIndex/index.d.ts.map +1 -0
- package/lib/intervalIndex/index.js +11 -0
- package/lib/intervalIndex/index.js.map +1 -0
- package/lib/intervalIndex/intervalIndex.d.ts +29 -0
- package/lib/intervalIndex/intervalIndex.d.ts.map +1 -0
- package/lib/intervalIndex/intervalIndex.js +6 -0
- package/lib/intervalIndex/intervalIndex.js.map +1 -0
- package/lib/intervalIndex/intervalIndexUtils.d.ts +17 -0
- package/lib/intervalIndex/intervalIndexUtils.d.ts.map +1 -0
- package/lib/intervalIndex/intervalIndexUtils.js +18 -0
- package/lib/intervalIndex/intervalIndexUtils.js.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +33 -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/intervalIndex/startpointInRangeIndex.d.ts +20 -0
- package/lib/intervalIndex/startpointInRangeIndex.d.ts.map +1 -0
- package/lib/intervalIndex/startpointInRangeIndex.js +58 -0
- package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -0
- package/lib/intervalTree.d.ts +2 -56
- package/lib/intervalTree.d.ts.map +1 -1
- package/lib/intervalTree.js +2 -11
- package/lib/intervalTree.js.map +1 -1
- package/lib/intervals/index.d.ts +8 -0
- package/lib/intervals/index.d.ts.map +1 -0
- package/lib/intervals/index.js +8 -0
- package/lib/intervals/index.js.map +1 -0
- package/lib/intervals/interval.d.ts +88 -0
- package/lib/intervals/interval.d.ts.map +1 -0
- package/lib/intervals/interval.js +175 -0
- package/lib/intervals/interval.js.map +1 -0
- package/lib/intervals/intervalUtils.d.ts +200 -0
- package/lib/intervals/intervalUtils.d.ts.map +1 -0
- package/lib/intervals/intervalUtils.js +74 -0
- package/lib/intervals/intervalUtils.js.map +1 -0
- package/lib/intervals/sequenceInterval.d.ts +132 -0
- package/lib/intervals/sequenceInterval.d.ts.map +1 -0
- package/lib/intervals/sequenceInterval.js +305 -0
- package/lib/intervals/sequenceInterval.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 +1 -1
- package/lib/revertibles.d.ts.map +1 -1
- package/lib/revertibles.js +69 -36
- package/lib/revertibles.js.map +1 -1
- package/lib/sequence.d.ts +33 -4
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +86 -41
- package/lib/sequence.js.map +1 -1
- package/lib/sequenceDeltaEvent.d.ts +8 -3
- package/lib/sequenceDeltaEvent.d.ts.map +1 -1
- package/lib/sequenceDeltaEvent.js +2 -3
- package/lib/sequenceDeltaEvent.js.map +1 -1
- package/lib/sharedIntervalCollection.d.ts +2 -1
- package/lib/sharedIntervalCollection.d.ts.map +1 -1
- package/lib/sharedIntervalCollection.js +1 -1
- package/lib/sharedIntervalCollection.js.map +1 -1
- package/lib/sharedSequence.d.ts +9 -0
- package/lib/sharedSequence.d.ts.map +1 -1
- package/lib/sharedSequence.js +8 -5
- package/lib/sharedSequence.js.map +1 -1
- package/lib/sharedString.d.ts.map +1 -1
- package/lib/sharedString.js +9 -29
- package/lib/sharedString.js.map +1 -1
- package/package.json +31 -34
- package/src/defaultMap.ts +2 -1
- package/src/defaultMapInterfaces.ts +1 -1
- package/src/index.ts +21 -9
- package/src/intervalCollection.ts +118 -1403
- package/src/intervalIndex/endpointInRangeIndex.ts +104 -0
- package/src/intervalIndex/endpointIndex.ts +78 -0
- package/src/intervalIndex/idIntervalIndex.ts +58 -0
- package/src/intervalIndex/index.ts +16 -0
- package/src/intervalIndex/intervalIndex.ts +31 -0
- package/src/intervalIndex/intervalIndexUtils.ts +27 -0
- package/src/intervalIndex/overlappingIntervalsIndex.ts +162 -0
- package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +71 -0
- package/src/intervalIndex/sequenceIntervalIndexes.ts +32 -0
- package/src/intervalIndex/startpointInRangeIndex.ts +109 -0
- package/src/intervalTree.ts +3 -75
- package/src/intervals/index.ts +25 -0
- package/src/intervals/interval.ts +230 -0
- package/src/intervals/intervalUtils.ts +256 -0
- package/src/intervals/sequenceInterval.ts +494 -0
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +81 -16
- package/src/sequence.ts +100 -35
- package/src/sequenceDeltaEvent.ts +12 -4
- package/src/sharedIntervalCollection.ts +2 -3
- package/src/sharedSequence.ts +11 -5
- package/src/sharedString.ts +8 -25
|
@@ -5,34 +5,26 @@
|
|
|
5
5
|
|
|
6
6
|
/* eslint-disable no-bitwise */
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
9
|
+
import { assert } from "@fluidframework/core-utils";
|
|
10
|
+
import { IEvent } from "@fluidframework/core-interfaces";
|
|
11
11
|
import {
|
|
12
12
|
addProperties,
|
|
13
13
|
Client,
|
|
14
|
-
compareReferencePositions,
|
|
15
14
|
createMap,
|
|
16
|
-
|
|
15
|
+
getSlideToSegoff,
|
|
17
16
|
ISegment,
|
|
18
17
|
MergeTreeDeltaType,
|
|
19
|
-
minReferencePosition,
|
|
20
|
-
PropertiesManager,
|
|
21
18
|
PropertySet,
|
|
22
|
-
RedBlackTree,
|
|
23
19
|
LocalReferencePosition,
|
|
24
20
|
ReferenceType,
|
|
25
21
|
refTypeIncludesFlag,
|
|
26
22
|
reservedRangeLabelsKey,
|
|
27
23
|
UnassignedSequenceNumber,
|
|
28
|
-
maxReferencePosition,
|
|
29
|
-
createDetachedLocalReferencePosition,
|
|
30
24
|
DetachedReferencePosition,
|
|
31
|
-
SlidingPreference,
|
|
32
|
-
PropertyAction,
|
|
33
25
|
} from "@fluidframework/merge-tree";
|
|
34
26
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
35
|
-
import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
27
|
+
import { LoggingError, UsageError } from "@fluidframework/telemetry-utils";
|
|
36
28
|
import { v4 as uuid } from "uuid";
|
|
37
29
|
import {
|
|
38
30
|
IMapMessageLocalMetadata,
|
|
@@ -43,1346 +35,97 @@ import {
|
|
|
43
35
|
IValueTypeOperationValue,
|
|
44
36
|
SequenceOptions,
|
|
45
37
|
} from "./defaultMapInterfaces";
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
SlideOnRemove = 0x2, // SlideOnRemove is default behavior - all intervals are SlideOnRemove
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* A temporary interval, used internally
|
|
76
|
-
* @internal
|
|
77
|
-
*/
|
|
78
|
-
Transient = 0x4,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Serialized object representation of an interval.
|
|
83
|
-
* This representation is used for ops that create or change intervals.
|
|
84
|
-
* @internal
|
|
85
|
-
*/
|
|
86
|
-
export interface ISerializedInterval {
|
|
87
|
-
/**
|
|
88
|
-
* Sequence number at which `start` and `end` should be interpreted
|
|
89
|
-
*
|
|
90
|
-
* @remarks - It's unclear that this is necessary to store here.
|
|
91
|
-
* This should just be the refSeq on the op that modified the interval, which should be available via other means.
|
|
92
|
-
* At the time of writing, it's not plumbed through to the reconnect/rebase code, however, which does need it.
|
|
93
|
-
*/
|
|
94
|
-
sequenceNumber: number;
|
|
95
|
-
/** Start position of the interval */
|
|
96
|
-
start: number;
|
|
97
|
-
/** End position of the interval */
|
|
98
|
-
end: number;
|
|
99
|
-
/** Interval type to create */
|
|
100
|
-
intervalType: IntervalType;
|
|
101
|
-
stickiness?: IntervalStickiness;
|
|
102
|
-
/** Any properties the interval has */
|
|
103
|
-
properties?: PropertySet;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Represents a change that should be applied to an existing interval.
|
|
108
|
-
* Changes can modify any of start/end/properties, with `undefined` signifying no change should be made.
|
|
109
|
-
* @internal
|
|
110
|
-
*/
|
|
111
|
-
export type SerializedIntervalDelta = Omit<ISerializedInterval, "start" | "end" | "properties"> &
|
|
112
|
-
Partial<Pick<ISerializedInterval, "start" | "end" | "properties">>;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* A size optimization to avoid redundantly storing keys when serializing intervals
|
|
116
|
-
* as JSON for summaries.
|
|
117
|
-
*
|
|
118
|
-
* Intervals are of the format:
|
|
119
|
-
*
|
|
120
|
-
* [start, end, sequenceNumber, intervalType, properties, stickiness?]
|
|
121
|
-
*/
|
|
122
|
-
export type CompressedSerializedInterval =
|
|
123
|
-
| [number, number, number, IntervalType, PropertySet, IntervalStickiness]
|
|
124
|
-
| [number, number, number, IntervalType, PropertySet];
|
|
125
|
-
|
|
126
|
-
export interface ISerializedIntervalCollectionV2 {
|
|
127
|
-
label: string;
|
|
128
|
-
version: 2;
|
|
129
|
-
intervals: CompressedSerializedInterval[];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Decompress an interval after loading a summary from JSON. The exact format
|
|
134
|
-
* of this compression is unspecified and subject to change
|
|
135
|
-
*/
|
|
136
|
-
function decompressInterval(
|
|
137
|
-
interval: CompressedSerializedInterval,
|
|
138
|
-
label?: string,
|
|
139
|
-
): ISerializedInterval {
|
|
140
|
-
return {
|
|
141
|
-
start: interval[0],
|
|
142
|
-
end: interval[1],
|
|
143
|
-
sequenceNumber: interval[2],
|
|
144
|
-
intervalType: interval[3],
|
|
145
|
-
properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
|
|
146
|
-
stickiness: interval[5],
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Compress an interval prior to serialization as JSON. The exact format of this
|
|
152
|
-
* compression is unspecified and subject to change
|
|
153
|
-
*/
|
|
154
|
-
function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
|
|
155
|
-
const { start, end, sequenceNumber, intervalType, properties } = interval;
|
|
156
|
-
|
|
157
|
-
const base: CompressedSerializedInterval = [
|
|
158
|
-
start,
|
|
159
|
-
end,
|
|
160
|
-
sequenceNumber,
|
|
161
|
-
intervalType,
|
|
162
|
-
// remove the `referenceRangeLabels` property as it is already stored
|
|
163
|
-
// in the `label` field of the summary
|
|
164
|
-
{ ...properties, [reservedRangeLabelsKey]: undefined },
|
|
165
|
-
];
|
|
166
|
-
|
|
167
|
-
if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
|
|
168
|
-
base.push(interval.stickiness);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return base;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function startReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
|
|
175
|
-
// if any start stickiness, prefer sliding backwards
|
|
176
|
-
return (stickiness & IntervalStickiness.START) !== 0
|
|
177
|
-
? SlidingPreference.BACKWARD
|
|
178
|
-
: SlidingPreference.FORWARD;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function endReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
|
|
182
|
-
// if any end stickiness, prefer sliding forwards
|
|
183
|
-
return (stickiness & IntervalStickiness.END) !== 0
|
|
184
|
-
? SlidingPreference.FORWARD
|
|
185
|
-
: SlidingPreference.BACKWARD;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export interface ISerializableInterval extends IInterval {
|
|
189
|
-
/** Serializable bag of properties associated with the interval. */
|
|
190
|
-
properties: PropertySet;
|
|
191
|
-
/** @internal */
|
|
192
|
-
propertyManager: PropertiesManager;
|
|
193
|
-
/** @internal */
|
|
194
|
-
serialize(): ISerializedInterval;
|
|
195
|
-
/** @internal */
|
|
196
|
-
addProperties(
|
|
197
|
-
props: PropertySet,
|
|
198
|
-
collaborating?: boolean,
|
|
199
|
-
seq?: number,
|
|
200
|
-
): PropertySet | undefined;
|
|
201
|
-
/**
|
|
202
|
-
* Gets the id associated with this interval.
|
|
203
|
-
* When the interval is used as part of an interval collection, this id can be used to modify or remove the
|
|
204
|
-
* interval.
|
|
205
|
-
* @remarks - This signature includes `undefined` strictly for backwards-compatibility reasons, as older versions
|
|
206
|
-
* of Fluid didn't always write interval ids.
|
|
207
|
-
*/
|
|
208
|
-
getIntervalId(): string | undefined;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* @sealed
|
|
213
|
-
*/
|
|
214
|
-
export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
|
|
215
|
-
compareEnds(a: TInterval, b: TInterval): number;
|
|
216
|
-
compareStarts?(a: TInterval, b: TInterval): number;
|
|
217
|
-
/**
|
|
218
|
-
*
|
|
219
|
-
* @param label - label of the interval collection this interval is being added to. This parameter is
|
|
220
|
-
* irrelevant for transient intervals.
|
|
221
|
-
* @param start - numerical start position of the interval
|
|
222
|
-
* @param end - numerical end position of the interval
|
|
223
|
-
* @param client - client creating the interval
|
|
224
|
-
* @param intervalType - Type of interval to create. Default is SlideOnRemove
|
|
225
|
-
* @param op - If this create came from a remote client, op that created it. Default is undefined (i.e. local)
|
|
226
|
-
* @param fromSnapshot - If this create came from loading a snapshot. Default is false.
|
|
227
|
-
*/
|
|
228
|
-
create(
|
|
229
|
-
label: string,
|
|
230
|
-
start: number | undefined,
|
|
231
|
-
end: number | undefined,
|
|
232
|
-
client: Client | undefined,
|
|
233
|
-
intervalType: IntervalType,
|
|
234
|
-
op?: ISequencedDocumentMessage,
|
|
235
|
-
fromSnapshot?: boolean,
|
|
236
|
-
stickiness?: IntervalStickiness,
|
|
237
|
-
): TInterval;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Determines how an interval should expand when segments are inserted adjacent
|
|
242
|
-
* to the range it spans
|
|
243
|
-
*
|
|
244
|
-
* Note that interval stickiness is currently an experimental feature and must
|
|
245
|
-
* be explicitly enabled with the `intervalStickinessEnabled` flag
|
|
246
|
-
*/
|
|
247
|
-
export const IntervalStickiness = {
|
|
248
|
-
/**
|
|
249
|
-
* Interval does not expand to include adjacent segments
|
|
250
|
-
*/
|
|
251
|
-
NONE: 0b00,
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Interval expands to include segments inserted adjacent to the start
|
|
255
|
-
*/
|
|
256
|
-
START: 0b01,
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Interval expands to include segments inserted adjacent to the end
|
|
260
|
-
*
|
|
261
|
-
* This is the default stickiness
|
|
262
|
-
*/
|
|
263
|
-
END: 0b10,
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Interval expands to include all segments inserted adjacent to it
|
|
267
|
-
*/
|
|
268
|
-
FULL: 0b11,
|
|
269
|
-
} as const;
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Determines how an interval should expand when segments are inserted adjacent
|
|
273
|
-
* to the range it spans
|
|
274
|
-
*
|
|
275
|
-
* Note that interval stickiness is currently an experimental feature and must
|
|
276
|
-
* be explicitly enabled with the `intervalStickinessEnabled` flag
|
|
277
|
-
*/
|
|
278
|
-
export type IntervalStickiness = typeof IntervalStickiness[keyof typeof IntervalStickiness];
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Serializable interval whose endpoints are plain-old numbers.
|
|
282
|
-
*/
|
|
283
|
-
export class Interval implements ISerializableInterval {
|
|
284
|
-
/**
|
|
285
|
-
* {@inheritDoc ISerializableInterval.properties}
|
|
286
|
-
*/
|
|
287
|
-
public properties: PropertySet;
|
|
288
|
-
/** @internal */
|
|
289
|
-
public auxProps: PropertySet[] | undefined;
|
|
290
|
-
/**
|
|
291
|
-
* {@inheritDoc ISerializableInterval.propertyManager}
|
|
292
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
293
|
-
*/
|
|
294
|
-
public propertyManager: PropertiesManager;
|
|
295
|
-
constructor(public start: number, public end: number, props?: PropertySet) {
|
|
296
|
-
this.propertyManager = new PropertiesManager();
|
|
297
|
-
this.properties = {};
|
|
298
|
-
|
|
299
|
-
if (props) {
|
|
300
|
-
this.addProperties(props);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* {@inheritDoc ISerializableInterval.getIntervalId}
|
|
306
|
-
*/
|
|
307
|
-
public getIntervalId(): string {
|
|
308
|
-
const id = this.properties?.[reservedIntervalIdKey];
|
|
309
|
-
assert(id !== undefined, 0x5e1 /* interval ID should not be undefined */);
|
|
310
|
-
return `${id}`;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* @returns an array containing any auxiliary property sets added with `addPropertySet`.
|
|
315
|
-
*/
|
|
316
|
-
public getAdditionalPropertySets(): PropertySet[] {
|
|
317
|
-
return this.auxProps ?? [];
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Adds an auxiliary set of properties to this interval.
|
|
322
|
-
* These properties can be recovered using `getAdditionalPropertySets`
|
|
323
|
-
* @param props - set of properties to add
|
|
324
|
-
* @remarks - This gets called as part of the default conflict resolver for `IIntervalCollection<Interval>`
|
|
325
|
-
* (i.e. non-sequence-based interval collections). However, the additional properties don't get serialized.
|
|
326
|
-
* This functionality seems half-baked.
|
|
327
|
-
*/
|
|
328
|
-
public addPropertySet(props: PropertySet) {
|
|
329
|
-
if (this.auxProps === undefined) {
|
|
330
|
-
this.auxProps = [];
|
|
331
|
-
}
|
|
332
|
-
this.auxProps.push(props);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* {@inheritDoc ISerializableInterval.serialize}
|
|
337
|
-
* @internal
|
|
338
|
-
*/
|
|
339
|
-
public serialize(): ISerializedInterval {
|
|
340
|
-
const serializedInterval: ISerializedInterval = {
|
|
341
|
-
end: this.end,
|
|
342
|
-
intervalType: 0,
|
|
343
|
-
sequenceNumber: 0,
|
|
344
|
-
start: this.start,
|
|
345
|
-
};
|
|
346
|
-
if (this.properties) {
|
|
347
|
-
serializedInterval.properties = this.properties;
|
|
348
|
-
}
|
|
349
|
-
return serializedInterval;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* {@inheritDoc IInterval.clone}
|
|
354
|
-
*/
|
|
355
|
-
public clone() {
|
|
356
|
-
return new Interval(this.start, this.end, this.properties);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* {@inheritDoc IInterval.compare}
|
|
361
|
-
*/
|
|
362
|
-
public compare(b: Interval) {
|
|
363
|
-
const startResult = this.compareStart(b);
|
|
364
|
-
if (startResult === 0) {
|
|
365
|
-
const endResult = this.compareEnd(b);
|
|
366
|
-
if (endResult === 0) {
|
|
367
|
-
const thisId = this.getIntervalId();
|
|
368
|
-
if (thisId) {
|
|
369
|
-
const bId = b.getIntervalId();
|
|
370
|
-
if (bId) {
|
|
371
|
-
return thisId > bId ? 1 : thisId < bId ? -1 : 0;
|
|
372
|
-
}
|
|
373
|
-
return 0;
|
|
374
|
-
}
|
|
375
|
-
return 0;
|
|
376
|
-
} else {
|
|
377
|
-
return endResult;
|
|
378
|
-
}
|
|
379
|
-
} else {
|
|
380
|
-
return startResult;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* {@inheritDoc IInterval.compareStart}
|
|
386
|
-
*/
|
|
387
|
-
public compareStart(b: Interval) {
|
|
388
|
-
return this.start - b.start;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* {@inheritDoc IInterval.compareEnd}
|
|
393
|
-
*/
|
|
394
|
-
public compareEnd(b: Interval) {
|
|
395
|
-
return this.end - b.end;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* {@inheritDoc IInterval.overlaps}
|
|
400
|
-
*/
|
|
401
|
-
public overlaps(b: Interval) {
|
|
402
|
-
const result = this.start <= b.end && this.end >= b.start;
|
|
403
|
-
return result;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* {@inheritDoc IInterval.union}
|
|
408
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
409
|
-
*/
|
|
410
|
-
public union(b: Interval) {
|
|
411
|
-
return new Interval(
|
|
412
|
-
Math.min(this.start, b.start),
|
|
413
|
-
Math.max(this.end, b.end),
|
|
414
|
-
this.properties,
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
public getProperties() {
|
|
419
|
-
return this.properties;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* {@inheritDoc ISerializableInterval.addProperties}
|
|
424
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
425
|
-
*/
|
|
426
|
-
public addProperties(
|
|
427
|
-
newProps: PropertySet,
|
|
428
|
-
collaborating: boolean = false,
|
|
429
|
-
seq?: number,
|
|
430
|
-
op?: ICombiningOp,
|
|
431
|
-
): PropertySet | undefined {
|
|
432
|
-
if (newProps) {
|
|
433
|
-
this.initializeProperties();
|
|
434
|
-
return this.propertyManager.addProperties(
|
|
435
|
-
this.properties,
|
|
436
|
-
newProps,
|
|
437
|
-
op,
|
|
438
|
-
seq,
|
|
439
|
-
collaborating,
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* {@inheritDoc IInterval.modify}
|
|
446
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
447
|
-
*/
|
|
448
|
-
public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
|
|
449
|
-
const startPos = start ?? this.start;
|
|
450
|
-
const endPos = end ?? this.end;
|
|
451
|
-
if (this.start === startPos && this.end === endPos) {
|
|
452
|
-
// Return undefined to indicate that no change is necessary.
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
const newInterval = new Interval(startPos, endPos);
|
|
456
|
-
if (this.properties) {
|
|
457
|
-
newInterval.initializeProperties();
|
|
458
|
-
this.propertyManager.copyTo(
|
|
459
|
-
this.properties,
|
|
460
|
-
newInterval.properties,
|
|
461
|
-
newInterval.propertyManager,
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
return newInterval;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
private initializeProperties(): void {
|
|
468
|
-
if (!this.propertyManager) {
|
|
469
|
-
this.propertyManager = new PropertiesManager();
|
|
470
|
-
}
|
|
471
|
-
if (!this.properties) {
|
|
472
|
-
this.properties = createMap<any>();
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Interval implementation whose ends are associated with positions in a mutatable sequence.
|
|
479
|
-
* As such, when content is inserted into the middle of the interval, the interval expands to
|
|
480
|
-
* include that content.
|
|
481
|
-
*
|
|
482
|
-
* @remarks - The endpoint's position should be treated exclusively to get reasonable behavior--i.e.
|
|
483
|
-
* an interval referring to "hello" in "hello world" should have a start position of 0 and an end
|
|
484
|
-
* position of 5.
|
|
485
|
-
*
|
|
486
|
-
* To see why, consider what happens if "llo wor" is removed from the string to make "held".
|
|
487
|
-
* The interval's startpoint remains on the "h" (it isn't altered), but the interval's endpoint
|
|
488
|
-
* slides forward to the next unremoved position, which is the "l" in "held".
|
|
489
|
-
* Users would generally expect the interval to now refer to "he" (as it is the subset of content
|
|
490
|
-
* remaining after the removal), hence the "l" should be excluded.
|
|
491
|
-
* If the interval endpoint was treated inclusively, the interval would now refer to "hel", which
|
|
492
|
-
* is undesirable.
|
|
493
|
-
*
|
|
494
|
-
* Since the end of an interval is treated exclusively but cannot be greater than or equal to the
|
|
495
|
-
* length of the associated sequence, application models which leverage interval collections should
|
|
496
|
-
* consider inserting a marker at the end of the sequence to represent the end of the content.
|
|
497
|
-
*/
|
|
498
|
-
export class SequenceInterval implements ISerializableInterval {
|
|
499
|
-
/**
|
|
500
|
-
* {@inheritDoc ISerializableInterval.properties}
|
|
501
|
-
*/
|
|
502
|
-
public properties: PropertySet;
|
|
503
|
-
/**
|
|
504
|
-
* {@inheritDoc ISerializableInterval.propertyManager}
|
|
505
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
506
|
-
*/
|
|
507
|
-
public propertyManager: PropertiesManager;
|
|
508
|
-
|
|
509
|
-
constructor(
|
|
510
|
-
private readonly client: Client,
|
|
511
|
-
/**
|
|
512
|
-
* Start endpoint of this interval.
|
|
513
|
-
* @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
|
|
514
|
-
*/
|
|
515
|
-
public start: LocalReferencePosition,
|
|
516
|
-
/**
|
|
517
|
-
* End endpoint of this interval.
|
|
518
|
-
* @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
|
|
519
|
-
*/
|
|
520
|
-
public end: LocalReferencePosition,
|
|
521
|
-
public intervalType: IntervalType,
|
|
522
|
-
props?: PropertySet,
|
|
523
|
-
public readonly stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
524
|
-
) {
|
|
525
|
-
this.propertyManager = new PropertiesManager();
|
|
526
|
-
this.properties = {};
|
|
527
|
-
|
|
528
|
-
if (props) {
|
|
529
|
-
this.addProperties(props);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
private callbacks?: Record<"beforePositionChange" | "afterPositionChange", () => void>;
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Subscribes to position change events on this interval if there are no current listeners.
|
|
537
|
-
* @internal
|
|
538
|
-
*/
|
|
539
|
-
public addPositionChangeListeners(
|
|
540
|
-
beforePositionChange: () => void,
|
|
541
|
-
afterPositionChange: () => void,
|
|
542
|
-
): void {
|
|
543
|
-
if (this.callbacks === undefined) {
|
|
544
|
-
this.callbacks = {
|
|
545
|
-
beforePositionChange,
|
|
546
|
-
afterPositionChange,
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
const startCbs = (this.start.callbacks ??= {});
|
|
550
|
-
const endCbs = (this.end.callbacks ??= {});
|
|
551
|
-
startCbs.beforeSlide = endCbs.beforeSlide = beforePositionChange;
|
|
552
|
-
startCbs.afterSlide = endCbs.afterSlide = afterPositionChange;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Removes the currently subscribed position change listeners.
|
|
558
|
-
* @internal
|
|
559
|
-
*/
|
|
560
|
-
public removePositionChangeListeners(): void {
|
|
561
|
-
if (this.callbacks) {
|
|
562
|
-
this.callbacks = undefined;
|
|
563
|
-
this.start.callbacks = undefined;
|
|
564
|
-
this.end.callbacks = undefined;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* {@inheritDoc ISerializableInterval.serialize}
|
|
570
|
-
* @internal
|
|
571
|
-
*/
|
|
572
|
-
public serialize(): ISerializedInterval {
|
|
573
|
-
const startPosition = this.client.localReferencePositionToPosition(this.start);
|
|
574
|
-
const endPosition = this.client.localReferencePositionToPosition(this.end);
|
|
575
|
-
const serializedInterval: ISerializedInterval = {
|
|
576
|
-
end: endPosition,
|
|
577
|
-
intervalType: this.intervalType,
|
|
578
|
-
sequenceNumber: this.client.getCurrentSeq(),
|
|
579
|
-
start: startPosition,
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
if (this.properties) {
|
|
583
|
-
serializedInterval.properties = this.properties;
|
|
584
|
-
}
|
|
585
|
-
if (this.stickiness !== IntervalStickiness.END) {
|
|
586
|
-
serializedInterval.stickiness = this.stickiness;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return serializedInterval;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* {@inheritDoc IInterval.clone}
|
|
594
|
-
*/
|
|
595
|
-
public clone() {
|
|
596
|
-
return new SequenceInterval(
|
|
597
|
-
this.client,
|
|
598
|
-
this.start,
|
|
599
|
-
this.end,
|
|
600
|
-
this.intervalType,
|
|
601
|
-
this.properties,
|
|
602
|
-
this.stickiness,
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* {@inheritDoc IInterval.compare}
|
|
608
|
-
*/
|
|
609
|
-
public compare(b: SequenceInterval) {
|
|
610
|
-
const startResult = this.compareStart(b);
|
|
611
|
-
if (startResult === 0) {
|
|
612
|
-
const endResult = this.compareEnd(b);
|
|
613
|
-
if (endResult === 0) {
|
|
614
|
-
const thisId = this.getIntervalId();
|
|
615
|
-
if (thisId) {
|
|
616
|
-
const bId = b.getIntervalId();
|
|
617
|
-
if (bId) {
|
|
618
|
-
return thisId > bId ? 1 : thisId < bId ? -1 : 0;
|
|
619
|
-
}
|
|
620
|
-
return 0;
|
|
621
|
-
}
|
|
622
|
-
return 0;
|
|
623
|
-
} else {
|
|
624
|
-
return endResult;
|
|
625
|
-
}
|
|
626
|
-
} else {
|
|
627
|
-
return startResult;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* {@inheritDoc IInterval.compareStart}
|
|
633
|
-
*/
|
|
634
|
-
public compareStart(b: SequenceInterval) {
|
|
635
|
-
return compareReferencePositions(this.start, b.start);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* {@inheritDoc IInterval.compareEnd}
|
|
640
|
-
*/
|
|
641
|
-
public compareEnd(b: SequenceInterval) {
|
|
642
|
-
return compareReferencePositions(this.end, b.end);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* {@inheritDoc IInterval.overlaps}
|
|
647
|
-
*/
|
|
648
|
-
public overlaps(b: SequenceInterval) {
|
|
649
|
-
const result =
|
|
650
|
-
compareReferencePositions(this.start, b.end) <= 0 &&
|
|
651
|
-
compareReferencePositions(this.end, b.start) >= 0;
|
|
652
|
-
return result;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* {@inheritDoc ISerializableInterval.getIntervalId}
|
|
657
|
-
*/
|
|
658
|
-
public getIntervalId(): string {
|
|
659
|
-
const id = this.properties?.[reservedIntervalIdKey];
|
|
660
|
-
assert(id !== undefined, 0x5e2 /* interval ID should not be undefined */);
|
|
661
|
-
return `${id}`;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* {@inheritDoc IInterval.union}
|
|
666
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
667
|
-
*/
|
|
668
|
-
public union(b: SequenceInterval) {
|
|
669
|
-
return new SequenceInterval(
|
|
670
|
-
this.client,
|
|
671
|
-
minReferencePosition(this.start, b.start),
|
|
672
|
-
maxReferencePosition(this.end, b.end),
|
|
673
|
-
this.intervalType,
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* {@inheritDoc ISerializableInterval.addProperties}
|
|
679
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
680
|
-
*/
|
|
681
|
-
public addProperties(
|
|
682
|
-
newProps: PropertySet,
|
|
683
|
-
collab: boolean = false,
|
|
684
|
-
seq?: number,
|
|
685
|
-
op?: ICombiningOp,
|
|
686
|
-
): PropertySet | undefined {
|
|
687
|
-
this.initializeProperties();
|
|
688
|
-
return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* @returns whether this interval overlaps two numerical positions.
|
|
693
|
-
*/
|
|
694
|
-
public overlapsPos(bstart: number, bend: number) {
|
|
695
|
-
const startPos = this.client.localReferencePositionToPosition(this.start);
|
|
696
|
-
const endPos = this.client.localReferencePositionToPosition(this.end);
|
|
697
|
-
return endPos > bstart && startPos < bend;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* {@inheritDoc IInterval.modify}
|
|
702
|
-
* @deprecated - This API was never intended to be public and will be marked internal in a future release.
|
|
703
|
-
*/
|
|
704
|
-
public modify(
|
|
705
|
-
label: string,
|
|
706
|
-
start: number,
|
|
707
|
-
end: number,
|
|
708
|
-
op?: ISequencedDocumentMessage,
|
|
709
|
-
localSeq?: number,
|
|
710
|
-
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
711
|
-
) {
|
|
712
|
-
const getRefType = (baseType: ReferenceType): ReferenceType => {
|
|
713
|
-
let refType = baseType;
|
|
714
|
-
if (op === undefined) {
|
|
715
|
-
refType &= ~ReferenceType.SlideOnRemove;
|
|
716
|
-
refType |= ReferenceType.StayOnRemove;
|
|
717
|
-
}
|
|
718
|
-
return refType;
|
|
719
|
-
};
|
|
720
|
-
|
|
721
|
-
let startRef = this.start;
|
|
722
|
-
if (start !== undefined) {
|
|
723
|
-
startRef = createPositionReference(
|
|
724
|
-
this.client,
|
|
725
|
-
start,
|
|
726
|
-
getRefType(this.start.refType),
|
|
727
|
-
op,
|
|
728
|
-
undefined,
|
|
729
|
-
localSeq,
|
|
730
|
-
startReferenceSlidingPreference(stickiness),
|
|
731
|
-
);
|
|
732
|
-
if (this.start.properties) {
|
|
733
|
-
startRef.addProperties(this.start.properties);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
let endRef = this.end;
|
|
738
|
-
if (end !== undefined) {
|
|
739
|
-
endRef = createPositionReference(
|
|
740
|
-
this.client,
|
|
741
|
-
end,
|
|
742
|
-
getRefType(this.end.refType),
|
|
743
|
-
op,
|
|
744
|
-
undefined,
|
|
745
|
-
localSeq,
|
|
746
|
-
endReferenceSlidingPreference(stickiness),
|
|
747
|
-
);
|
|
748
|
-
if (this.end.properties) {
|
|
749
|
-
endRef.addProperties(this.end.properties);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const newInterval = new SequenceInterval(this.client, startRef, endRef, this.intervalType);
|
|
754
|
-
if (this.properties) {
|
|
755
|
-
newInterval.initializeProperties();
|
|
756
|
-
this.propertyManager.copyTo(
|
|
757
|
-
this.properties,
|
|
758
|
-
newInterval.properties,
|
|
759
|
-
newInterval.propertyManager,
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
return newInterval;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
private initializeProperties(): void {
|
|
766
|
-
if (!this.propertyManager) {
|
|
767
|
-
this.propertyManager = new PropertiesManager();
|
|
768
|
-
}
|
|
769
|
-
if (!this.properties) {
|
|
770
|
-
this.properties = createMap<any>();
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
function createPositionReferenceFromSegoff(
|
|
776
|
-
client: Client,
|
|
777
|
-
segoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
778
|
-
refType: ReferenceType,
|
|
779
|
-
op?: ISequencedDocumentMessage,
|
|
780
|
-
localSeq?: number,
|
|
781
|
-
fromSnapshot?: boolean,
|
|
782
|
-
slidingPreference?: SlidingPreference,
|
|
783
|
-
): LocalReferencePosition {
|
|
784
|
-
if (segoff.segment) {
|
|
785
|
-
const ref = client.createLocalReferencePosition(
|
|
786
|
-
segoff.segment,
|
|
787
|
-
segoff.offset,
|
|
788
|
-
refType,
|
|
789
|
-
undefined,
|
|
790
|
-
slidingPreference,
|
|
791
|
-
);
|
|
792
|
-
return ref;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Creating references on detached segments is allowed for:
|
|
796
|
-
// - Transient segments
|
|
797
|
-
// - References coming from a remote client (location may have been concurrently removed)
|
|
798
|
-
// - References being rebased to a new sequence number
|
|
799
|
-
// (segment they originally referred to may have been removed with no suitable replacement)
|
|
800
|
-
if (
|
|
801
|
-
!op &&
|
|
802
|
-
!localSeq &&
|
|
803
|
-
!fromSnapshot &&
|
|
804
|
-
!refTypeIncludesFlag(refType, ReferenceType.Transient)
|
|
805
|
-
) {
|
|
806
|
-
throw new UsageError("Non-transient references need segment");
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
return createDetachedLocalReferencePosition(refType);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
function createPositionReference(
|
|
813
|
-
client: Client,
|
|
814
|
-
pos: number,
|
|
815
|
-
refType: ReferenceType,
|
|
816
|
-
op?: ISequencedDocumentMessage,
|
|
817
|
-
fromSnapshot?: boolean,
|
|
818
|
-
localSeq?: number,
|
|
819
|
-
slidingPreference?: SlidingPreference,
|
|
820
|
-
): LocalReferencePosition {
|
|
821
|
-
let segoff;
|
|
822
|
-
if (op) {
|
|
823
|
-
assert(
|
|
824
|
-
(refType & ReferenceType.SlideOnRemove) !== 0,
|
|
825
|
-
0x2f5 /* op create references must be SlideOnRemove */,
|
|
826
|
-
);
|
|
827
|
-
segoff = client.getContainingSegment(pos, {
|
|
828
|
-
referenceSequenceNumber: op.referenceSequenceNumber,
|
|
829
|
-
clientId: op.clientId,
|
|
830
|
-
});
|
|
831
|
-
segoff = client.getSlideToSegment(segoff);
|
|
832
|
-
} else {
|
|
833
|
-
assert(
|
|
834
|
-
(refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
|
|
835
|
-
0x2f6 /* SlideOnRemove references must be op created */,
|
|
836
|
-
);
|
|
837
|
-
segoff = client.getContainingSegment(pos, undefined, localSeq);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
return createPositionReferenceFromSegoff(
|
|
841
|
-
client,
|
|
842
|
-
segoff,
|
|
843
|
-
refType,
|
|
844
|
-
op,
|
|
845
|
-
localSeq,
|
|
846
|
-
fromSnapshot,
|
|
847
|
-
slidingPreference,
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
export function createSequenceInterval(
|
|
852
|
-
label: string,
|
|
853
|
-
start: number,
|
|
854
|
-
end: number,
|
|
855
|
-
client: Client,
|
|
856
|
-
intervalType: IntervalType,
|
|
857
|
-
op?: ISequencedDocumentMessage,
|
|
858
|
-
fromSnapshot?: boolean,
|
|
859
|
-
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
860
|
-
): SequenceInterval {
|
|
861
|
-
let beginRefType = ReferenceType.RangeBegin;
|
|
862
|
-
let endRefType = ReferenceType.RangeEnd;
|
|
863
|
-
if (intervalType === IntervalType.Transient) {
|
|
864
|
-
beginRefType = ReferenceType.Transient;
|
|
865
|
-
endRefType = ReferenceType.Transient;
|
|
866
|
-
} else {
|
|
867
|
-
if (intervalType === IntervalType.Nest) {
|
|
868
|
-
beginRefType = ReferenceType.NestBegin;
|
|
869
|
-
endRefType = ReferenceType.NestEnd;
|
|
870
|
-
}
|
|
871
|
-
// All non-transient interval references must eventually be SlideOnRemove
|
|
872
|
-
// To ensure eventual consistency, they must start as StayOnRemove when
|
|
873
|
-
// pending (created locally and creation op is not acked)
|
|
874
|
-
if (op || fromSnapshot) {
|
|
875
|
-
beginRefType |= ReferenceType.SlideOnRemove;
|
|
876
|
-
endRefType |= ReferenceType.SlideOnRemove;
|
|
877
|
-
} else {
|
|
878
|
-
beginRefType |= ReferenceType.StayOnRemove;
|
|
879
|
-
endRefType |= ReferenceType.StayOnRemove;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
const startLref = createPositionReference(
|
|
884
|
-
client,
|
|
885
|
-
start,
|
|
886
|
-
beginRefType,
|
|
887
|
-
op,
|
|
888
|
-
fromSnapshot,
|
|
889
|
-
undefined,
|
|
890
|
-
startReferenceSlidingPreference(stickiness),
|
|
891
|
-
);
|
|
892
|
-
const endLref = createPositionReference(
|
|
893
|
-
client,
|
|
894
|
-
end,
|
|
895
|
-
endRefType,
|
|
896
|
-
op,
|
|
897
|
-
fromSnapshot,
|
|
898
|
-
undefined,
|
|
899
|
-
endReferenceSlidingPreference(stickiness),
|
|
900
|
-
);
|
|
901
|
-
const rangeProp = {
|
|
902
|
-
[reservedRangeLabelsKey]: [label],
|
|
903
|
-
};
|
|
904
|
-
startLref.addProperties(rangeProp);
|
|
905
|
-
endLref.addProperties(rangeProp);
|
|
906
|
-
|
|
907
|
-
const ival = new SequenceInterval(
|
|
908
|
-
client,
|
|
909
|
-
startLref,
|
|
910
|
-
endLref,
|
|
911
|
-
intervalType,
|
|
912
|
-
rangeProp,
|
|
913
|
-
stickiness,
|
|
914
|
-
);
|
|
915
|
-
return ival;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
export function createIntervalIndex() {
|
|
919
|
-
const helpers: IIntervalHelpers<Interval> = {
|
|
920
|
-
compareEnds: compareIntervalEnds,
|
|
921
|
-
create: createInterval,
|
|
922
|
-
};
|
|
923
|
-
const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
|
|
924
|
-
return lc;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* Collection of intervals.
|
|
929
|
-
*
|
|
930
|
-
* Implementers of this interface will typically implement additional APIs to support efficiently querying a collection
|
|
931
|
-
* of intervals in some manner, for example:
|
|
932
|
-
* - "find all intervals with start endpoint between these two points"
|
|
933
|
-
* - "find all intervals which overlap this range"
|
|
934
|
-
* etc.
|
|
935
|
-
*/
|
|
936
|
-
export interface IntervalIndex<TInterval extends ISerializableInterval> {
|
|
937
|
-
/**
|
|
938
|
-
* Adds an interval to the index.
|
|
939
|
-
* @remarks - Application code should never need to invoke this method on their index for production scenarios:
|
|
940
|
-
* Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
|
|
941
|
-
*/
|
|
942
|
-
add(interval: TInterval): void;
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Removes an interval from the index.
|
|
946
|
-
* @remarks - Application code should never need to invoke this method on their index for production scenarios:
|
|
947
|
-
* Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
|
|
948
|
-
*/
|
|
949
|
-
remove(interval: TInterval): void;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
|
|
953
|
-
implements IntervalIndex<TInterval>
|
|
954
|
-
{
|
|
955
|
-
private readonly intervalTree = new IntervalTree<TInterval>();
|
|
956
|
-
|
|
957
|
-
constructor(
|
|
958
|
-
private readonly client: Client,
|
|
959
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
960
|
-
) {}
|
|
961
|
-
|
|
962
|
-
public map(fn: (interval: TInterval) => void) {
|
|
963
|
-
this.intervalTree.map(fn);
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
public mapUntil(fn: (interval: TInterval) => boolean) {
|
|
967
|
-
this.intervalTree.mapUntil(fn);
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
public gatherIterationResults(
|
|
971
|
-
results: TInterval[],
|
|
972
|
-
iteratesForward: boolean,
|
|
973
|
-
start?: number,
|
|
974
|
-
end?: number,
|
|
975
|
-
) {
|
|
976
|
-
if (this.intervalTree.intervals.isEmpty()) {
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (start === undefined && end === undefined) {
|
|
981
|
-
// No start/end provided. Gather the whole tree in the specified order.
|
|
982
|
-
if (iteratesForward) {
|
|
983
|
-
this.intervalTree.map((interval: TInterval) => {
|
|
984
|
-
results.push(interval);
|
|
985
|
-
});
|
|
986
|
-
} else {
|
|
987
|
-
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
988
|
-
results.push(interval);
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
const transientInterval: TInterval = this.helpers.create(
|
|
993
|
-
"transient",
|
|
994
|
-
start,
|
|
995
|
-
end,
|
|
996
|
-
this.client,
|
|
997
|
-
IntervalType.Transient,
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
if (start === undefined) {
|
|
1001
|
-
// Only end position provided. Since the tree is not sorted by end position,
|
|
1002
|
-
// walk the whole tree in the specified order, gathering intervals that match the end.
|
|
1003
|
-
if (iteratesForward) {
|
|
1004
|
-
this.intervalTree.map((interval: TInterval) => {
|
|
1005
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
1006
|
-
results.push(interval);
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
} else {
|
|
1010
|
-
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
1011
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
1012
|
-
results.push(interval);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
} else {
|
|
1017
|
-
// Start and (possibly) end provided. Walk the subtrees that may contain
|
|
1018
|
-
// this start position.
|
|
1019
|
-
const compareFn =
|
|
1020
|
-
end === undefined
|
|
1021
|
-
? (node: IntervalNode<TInterval>) => {
|
|
1022
|
-
return transientInterval.compareStart(node.key);
|
|
1023
|
-
}
|
|
1024
|
-
: (node: IntervalNode<TInterval>) => {
|
|
1025
|
-
return transientInterval.compare(node.key);
|
|
1026
|
-
};
|
|
1027
|
-
const continueLeftFn = (cmpResult: number) => cmpResult <= 0;
|
|
1028
|
-
const continueRightFn = (cmpResult: number) => cmpResult >= 0;
|
|
1029
|
-
const actionFn = (node: IntervalNode<TInterval>) => {
|
|
1030
|
-
results.push(node.key);
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
if (iteratesForward) {
|
|
1034
|
-
this.intervalTree.intervals.walkExactMatchesForward(
|
|
1035
|
-
compareFn,
|
|
1036
|
-
actionFn,
|
|
1037
|
-
continueLeftFn,
|
|
1038
|
-
continueRightFn,
|
|
1039
|
-
);
|
|
1040
|
-
} else {
|
|
1041
|
-
this.intervalTree.intervals.walkExactMatchesBackward(
|
|
1042
|
-
compareFn,
|
|
1043
|
-
actionFn,
|
|
1044
|
-
continueLeftFn,
|
|
1045
|
-
continueRightFn,
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
/**
|
|
1053
|
-
* @returns an array of all intervals contained in this collection that overlap the range
|
|
1054
|
-
* `[startPosition, endPosition)`.
|
|
1055
|
-
*/
|
|
1056
|
-
public findOverlappingIntervals(startPosition: number, endPosition: number) {
|
|
1057
|
-
if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
|
|
1058
|
-
return [];
|
|
1059
|
-
}
|
|
1060
|
-
const transientInterval = this.helpers.create(
|
|
1061
|
-
"transient",
|
|
1062
|
-
startPosition,
|
|
1063
|
-
endPosition,
|
|
1064
|
-
this.client,
|
|
1065
|
-
IntervalType.Transient,
|
|
1066
|
-
);
|
|
1067
|
-
|
|
1068
|
-
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
1069
|
-
return overlappingIntervalNodes.map((node) => node.key);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
public remove(interval: TInterval) {
|
|
1073
|
-
this.intervalTree.removeExisting(interval);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
public add(interval: TInterval) {
|
|
1077
|
-
this.intervalTree.put(interval);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
class IdIntervalIndex<TInterval extends ISerializableInterval>
|
|
1082
|
-
implements IntervalIndex<TInterval>, Iterable<TInterval>
|
|
1083
|
-
{
|
|
1084
|
-
private readonly intervalIdMap: Map<string, TInterval> = new Map();
|
|
1085
|
-
|
|
1086
|
-
public add(interval: TInterval) {
|
|
1087
|
-
const id = interval.getIntervalId();
|
|
1088
|
-
assert(
|
|
1089
|
-
id !== undefined,
|
|
1090
|
-
0x2c0 /* "ID must be created before adding interval to collection" */,
|
|
1091
|
-
);
|
|
1092
|
-
// Make the ID immutable.
|
|
1093
|
-
Object.defineProperty(interval.properties, reservedIntervalIdKey, {
|
|
1094
|
-
configurable: false,
|
|
1095
|
-
enumerable: true,
|
|
1096
|
-
writable: false,
|
|
1097
|
-
});
|
|
1098
|
-
this.intervalIdMap.set(id, interval);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
public remove(interval: TInterval) {
|
|
1102
|
-
const id = interval.getIntervalId();
|
|
1103
|
-
assert(id !== undefined, 0x311 /* expected id to exist on interval */);
|
|
1104
|
-
this.intervalIdMap.delete(id);
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
public getIntervalById(id: string) {
|
|
1108
|
-
return this.intervalIdMap.get(id);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
public [Symbol.iterator]() {
|
|
1112
|
-
return this.intervalIdMap.values();
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
class EndpointIndex<TInterval extends ISerializableInterval> implements IntervalIndex<TInterval> {
|
|
1117
|
-
private readonly endIntervalTree: RedBlackTree<TInterval, TInterval>;
|
|
1118
|
-
|
|
1119
|
-
constructor(
|
|
1120
|
-
private readonly client: Client,
|
|
1121
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1122
|
-
) {
|
|
1123
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
1124
|
-
this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
public previousInterval(pos: number) {
|
|
1128
|
-
const transientInterval = this.helpers.create(
|
|
1129
|
-
"transient",
|
|
1130
|
-
pos,
|
|
1131
|
-
pos,
|
|
1132
|
-
this.client,
|
|
1133
|
-
IntervalType.Transient,
|
|
1134
|
-
);
|
|
1135
|
-
const rbNode = this.endIntervalTree.floor(transientInterval);
|
|
1136
|
-
if (rbNode) {
|
|
1137
|
-
return rbNode.data;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
public nextInterval(pos: number) {
|
|
1142
|
-
const transientInterval = this.helpers.create(
|
|
1143
|
-
"transient",
|
|
1144
|
-
pos,
|
|
1145
|
-
pos,
|
|
1146
|
-
this.client,
|
|
1147
|
-
IntervalType.Transient,
|
|
1148
|
-
);
|
|
1149
|
-
const rbNode = this.endIntervalTree.ceil(transientInterval);
|
|
1150
|
-
if (rbNode) {
|
|
1151
|
-
return rbNode.data;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
public add(interval: TInterval): void {
|
|
1156
|
-
this.endIntervalTree.put(interval, interval);
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
public remove(interval: TInterval): void {
|
|
1160
|
-
this.endIntervalTree.remove(interval);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
38
|
+
import {
|
|
39
|
+
CompressedSerializedInterval,
|
|
40
|
+
IIntervalHelpers,
|
|
41
|
+
Interval,
|
|
42
|
+
IntervalOpType,
|
|
43
|
+
IntervalStickiness,
|
|
44
|
+
IntervalType,
|
|
45
|
+
ISerializableInterval,
|
|
46
|
+
ISerializedInterval,
|
|
47
|
+
SequenceInterval,
|
|
48
|
+
SerializedIntervalDelta,
|
|
49
|
+
createPositionReferenceFromSegoff,
|
|
50
|
+
endReferenceSlidingPreference,
|
|
51
|
+
startReferenceSlidingPreference,
|
|
52
|
+
sequenceIntervalHelpers,
|
|
53
|
+
createInterval,
|
|
54
|
+
} from "./intervals";
|
|
55
|
+
import {
|
|
56
|
+
IEndpointIndex,
|
|
57
|
+
IIdIntervalIndex,
|
|
58
|
+
IOverlappingIntervalsIndex,
|
|
59
|
+
IntervalIndex,
|
|
60
|
+
createEndpointIndex,
|
|
61
|
+
createIdIntervalIndex,
|
|
62
|
+
createOverlappingIntervalsIndex,
|
|
63
|
+
} from "./intervalIndex";
|
|
1163
64
|
|
|
1164
|
-
|
|
1165
|
-
* Collection of intervals.
|
|
1166
|
-
*
|
|
1167
|
-
* Provide additional APIs to support efficiently querying a collection of intervals whose endpoints fall within a specified range.
|
|
1168
|
-
*/
|
|
1169
|
-
export interface IEndpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1170
|
-
extends IntervalIndex<TInterval> {
|
|
1171
|
-
/**
|
|
1172
|
-
* @returns an array of all intervals contained in this collection whose endpoints locate in the range [start, end] (includes both ends)
|
|
1173
|
-
*/
|
|
1174
|
-
findIntervalsWithEndpointInRange(start: number, end: number);
|
|
1175
|
-
}
|
|
65
|
+
const reservedIntervalIdKey = "intervalId";
|
|
1176
66
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
*/
|
|
1182
|
-
export interface IStartpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1183
|
-
extends IntervalIndex<TInterval> {
|
|
1184
|
-
/**
|
|
1185
|
-
* @returns an array of all intervals contained in this collection whose startpoints locate in the range [start, end] (includes both ends)
|
|
1186
|
-
*/
|
|
1187
|
-
findIntervalsWithStartpointInRange(start: number, end: number);
|
|
67
|
+
export interface ISerializedIntervalCollectionV2 {
|
|
68
|
+
label: string;
|
|
69
|
+
version: 2;
|
|
70
|
+
intervals: CompressedSerializedInterval[];
|
|
1188
71
|
}
|
|
1189
72
|
|
|
1190
73
|
/**
|
|
1191
|
-
*
|
|
74
|
+
* Decompress an interval after loading a summary from JSON. The exact format
|
|
75
|
+
* of this compression is unspecified and subject to change
|
|
1192
76
|
*/
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
77
|
+
function decompressInterval(
|
|
78
|
+
interval: CompressedSerializedInterval,
|
|
79
|
+
label?: string,
|
|
80
|
+
): ISerializedInterval {
|
|
81
|
+
return {
|
|
82
|
+
start: interval[0],
|
|
83
|
+
end: interval[1],
|
|
84
|
+
sequenceNumber: interval[2],
|
|
85
|
+
intervalType: interval[3],
|
|
86
|
+
properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
|
|
87
|
+
stickiness: interval[5],
|
|
88
|
+
};
|
|
1197
89
|
}
|
|
1198
90
|
|
|
1199
91
|
/**
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
92
|
+
* Compress an interval prior to serialization as JSON. The exact format of this
|
|
93
|
+
* compression is unspecified and subject to change
|
|
1202
94
|
*/
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1205
|
-
b: Partial<HasComparisonOverride>,
|
|
1206
|
-
): number {
|
|
1207
|
-
const forceCompareA = a[forceCompare] ?? 0;
|
|
1208
|
-
const forceCompareB = b[forceCompare] ?? 0;
|
|
1209
|
-
|
|
1210
|
-
return forceCompareA - forceCompareB;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
class EndpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1214
|
-
implements IEndpointInRangeIndex<TInterval>
|
|
1215
|
-
{
|
|
1216
|
-
private readonly intervalTree;
|
|
1217
|
-
|
|
1218
|
-
constructor(
|
|
1219
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1220
|
-
private readonly client: Client,
|
|
1221
|
-
) {
|
|
1222
|
-
this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
|
|
1223
|
-
const compareEndsResult = helpers.compareEnds(a, b);
|
|
1224
|
-
if (compareEndsResult !== 0) {
|
|
1225
|
-
return compareEndsResult;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
const overrideablesComparison = compareOverrideables(
|
|
1229
|
-
a as Partial<HasComparisonOverride>,
|
|
1230
|
-
b as Partial<HasComparisonOverride>,
|
|
1231
|
-
);
|
|
1232
|
-
if (overrideablesComparison !== 0) {
|
|
1233
|
-
return overrideablesComparison;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
const aId = a.getIntervalId();
|
|
1237
|
-
const bId = b.getIntervalId();
|
|
1238
|
-
if (aId !== undefined && bId !== undefined) {
|
|
1239
|
-
return aId.localeCompare(bId);
|
|
1240
|
-
}
|
|
1241
|
-
return 0;
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
public add(interval: TInterval): void {
|
|
1246
|
-
this.intervalTree.put(interval, interval);
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
public remove(interval: TInterval): void {
|
|
1250
|
-
this.intervalTree.remove(interval);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
public findIntervalsWithEndpointInRange(start: number, end: number) {
|
|
1254
|
-
if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
|
|
1255
|
-
return [];
|
|
1256
|
-
}
|
|
1257
|
-
const results: TInterval[] = [];
|
|
1258
|
-
const action: PropertyAction<TInterval, TInterval> = (node) => {
|
|
1259
|
-
results.push(node.data);
|
|
1260
|
-
return true;
|
|
1261
|
-
};
|
|
1262
|
-
|
|
1263
|
-
const transientStartInterval = this.helpers.create(
|
|
1264
|
-
"transient",
|
|
1265
|
-
start,
|
|
1266
|
-
start,
|
|
1267
|
-
this.client,
|
|
1268
|
-
IntervalType.Transient,
|
|
1269
|
-
);
|
|
1270
|
-
|
|
1271
|
-
const transientEndInterval = this.helpers.create(
|
|
1272
|
-
"transient",
|
|
1273
|
-
end,
|
|
1274
|
-
end,
|
|
1275
|
-
this.client,
|
|
1276
|
-
IntervalType.Transient,
|
|
1277
|
-
);
|
|
1278
|
-
|
|
1279
|
-
// Add comparison overrides to the transient intervals
|
|
1280
|
-
(transientStartInterval as Partial<HasComparisonOverride>)[forceCompare] = -1;
|
|
1281
|
-
(transientEndInterval as Partial<HasComparisonOverride>)[forceCompare] = 1;
|
|
1282
|
-
|
|
1283
|
-
this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
|
|
1284
|
-
return results;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
class StartpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
1289
|
-
implements IStartpointInRangeIndex<TInterval>
|
|
1290
|
-
{
|
|
1291
|
-
private readonly intervalTree;
|
|
1292
|
-
|
|
1293
|
-
constructor(
|
|
1294
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1295
|
-
private readonly client: Client,
|
|
1296
|
-
) {
|
|
1297
|
-
this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
|
|
1298
|
-
assert(
|
|
1299
|
-
typeof helpers.compareStarts === "function",
|
|
1300
|
-
0x6d1 /* compareStarts does not exist in the helpers */,
|
|
1301
|
-
);
|
|
1302
|
-
|
|
1303
|
-
const compareStartsResult = helpers.compareStarts(a, b);
|
|
1304
|
-
if (compareStartsResult !== 0) {
|
|
1305
|
-
return compareStartsResult;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
const overrideablesComparison = compareOverrideables(
|
|
1309
|
-
a as Partial<HasComparisonOverride>,
|
|
1310
|
-
b as Partial<HasComparisonOverride>,
|
|
1311
|
-
);
|
|
1312
|
-
if (overrideablesComparison !== 0) {
|
|
1313
|
-
return overrideablesComparison;
|
|
1314
|
-
}
|
|
1315
|
-
const aId = a.getIntervalId();
|
|
1316
|
-
const bId = b.getIntervalId();
|
|
1317
|
-
if (aId !== undefined && bId !== undefined) {
|
|
1318
|
-
return aId.localeCompare(bId);
|
|
1319
|
-
}
|
|
1320
|
-
return 0;
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
public add(interval: TInterval): void {
|
|
1325
|
-
this.intervalTree.put(interval, interval);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
public remove(interval: TInterval): void {
|
|
1329
|
-
this.intervalTree.remove(interval);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
public findIntervalsWithStartpointInRange(start: number, end: number) {
|
|
1333
|
-
if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
|
|
1334
|
-
return [];
|
|
1335
|
-
}
|
|
1336
|
-
const results: TInterval[] = [];
|
|
1337
|
-
const action: PropertyAction<TInterval, TInterval> = (node) => {
|
|
1338
|
-
results.push(node.data);
|
|
1339
|
-
return true;
|
|
1340
|
-
};
|
|
1341
|
-
|
|
1342
|
-
const transientStartInterval = this.helpers.create(
|
|
1343
|
-
"transient",
|
|
1344
|
-
start,
|
|
1345
|
-
start,
|
|
1346
|
-
this.client,
|
|
1347
|
-
IntervalType.Transient,
|
|
1348
|
-
);
|
|
1349
|
-
|
|
1350
|
-
const transientEndInterval = this.helpers.create(
|
|
1351
|
-
"transient",
|
|
1352
|
-
end,
|
|
1353
|
-
end,
|
|
1354
|
-
this.client,
|
|
1355
|
-
IntervalType.Transient,
|
|
1356
|
-
);
|
|
95
|
+
function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
|
|
96
|
+
const { start, end, sequenceNumber, intervalType, properties } = interval;
|
|
1357
97
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
98
|
+
const base: CompressedSerializedInterval = [
|
|
99
|
+
start,
|
|
100
|
+
end,
|
|
101
|
+
sequenceNumber,
|
|
102
|
+
intervalType,
|
|
103
|
+
// remove the `referenceRangeLabels` property as it is already stored
|
|
104
|
+
// in the `label` field of the summary
|
|
105
|
+
{ ...properties, [reservedRangeLabelsKey]: undefined },
|
|
106
|
+
];
|
|
1361
107
|
|
|
1362
|
-
|
|
1363
|
-
|
|
108
|
+
if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
|
|
109
|
+
base.push(interval.stickiness);
|
|
1364
110
|
}
|
|
1365
|
-
}
|
|
1366
111
|
|
|
1367
|
-
|
|
1368
|
-
helpers: IIntervalHelpers<TInterval>,
|
|
1369
|
-
client: Client,
|
|
1370
|
-
): IEndpointInRangeIndex<TInterval> {
|
|
1371
|
-
return new EndpointInRangeIndex<TInterval>(helpers, client);
|
|
112
|
+
return base;
|
|
1372
113
|
}
|
|
1373
114
|
|
|
1374
|
-
export function
|
|
1375
|
-
helpers: IIntervalHelpers<
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
115
|
+
export function createIntervalIndex() {
|
|
116
|
+
const helpers: IIntervalHelpers<Interval> = {
|
|
117
|
+
compareEnds: (a: Interval, b: Interval) => a.end - b.end,
|
|
118
|
+
create: createInterval,
|
|
119
|
+
};
|
|
120
|
+
const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
|
|
121
|
+
return lc;
|
|
1379
122
|
}
|
|
1380
123
|
|
|
1381
124
|
export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
1382
125
|
private static readonly legacyIdPrefix = "legacy";
|
|
1383
|
-
public readonly overlappingIntervalsIndex:
|
|
1384
|
-
public readonly idIntervalIndex:
|
|
1385
|
-
public readonly endIntervalIndex:
|
|
126
|
+
public readonly overlappingIntervalsIndex: IOverlappingIntervalsIndex<TInterval>;
|
|
127
|
+
public readonly idIntervalIndex: IIdIntervalIndex<TInterval>;
|
|
128
|
+
public readonly endIntervalIndex: IEndpointIndex<TInterval>;
|
|
1386
129
|
private readonly indexes: Set<IntervalIndex<TInterval>>;
|
|
1387
130
|
|
|
1388
131
|
constructor(
|
|
@@ -1395,9 +138,9 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1395
138
|
previousInterval: TInterval,
|
|
1396
139
|
) => void,
|
|
1397
140
|
) {
|
|
1398
|
-
this.overlappingIntervalsIndex =
|
|
1399
|
-
this.idIntervalIndex =
|
|
1400
|
-
this.endIntervalIndex =
|
|
141
|
+
this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
|
|
142
|
+
this.idIntervalIndex = createIdIntervalIndex<TInterval>();
|
|
143
|
+
this.endIntervalIndex = createEndpointIndex(client, helpers);
|
|
1401
144
|
this.indexes = new Set([
|
|
1402
145
|
this.overlappingIntervalsIndex,
|
|
1403
146
|
this.idIntervalIndex,
|
|
@@ -1493,6 +236,17 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1493
236
|
}
|
|
1494
237
|
|
|
1495
238
|
if (props) {
|
|
239
|
+
// This check is intended to prevent scenarios where a random interval is created and then
|
|
240
|
+
// inserted into a collection. The aim is to ensure that the collection is created first
|
|
241
|
+
// then the user can create/add intervals based on the collection
|
|
242
|
+
if (
|
|
243
|
+
props[reservedRangeLabelsKey] !== undefined &&
|
|
244
|
+
props[reservedRangeLabelsKey][0] !== this.label
|
|
245
|
+
) {
|
|
246
|
+
throw new LoggingError(
|
|
247
|
+
"Adding an interval that belongs to another interval collection is not permitted",
|
|
248
|
+
);
|
|
249
|
+
}
|
|
1496
250
|
interval.addProperties(props);
|
|
1497
251
|
}
|
|
1498
252
|
interval.properties[reservedIntervalIdKey] ??= uuid();
|
|
@@ -1602,24 +356,6 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1602
356
|
}
|
|
1603
357
|
}
|
|
1604
358
|
|
|
1605
|
-
export const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
|
|
1606
|
-
compareReferencePositions(a.end, b.end);
|
|
1607
|
-
|
|
1608
|
-
export const compareSequenceIntervalStarts = (a: SequenceInterval, b: SequenceInterval): number =>
|
|
1609
|
-
compareReferencePositions(a.start, b.start);
|
|
1610
|
-
|
|
1611
|
-
export const sequenceIntervalHelpers: IIntervalHelpers<SequenceInterval> = {
|
|
1612
|
-
compareEnds: compareSequenceIntervalEnds,
|
|
1613
|
-
compareStarts: compareSequenceIntervalStarts,
|
|
1614
|
-
create: createSequenceInterval,
|
|
1615
|
-
};
|
|
1616
|
-
|
|
1617
|
-
export const intervalHelpers: IIntervalHelpers<Interval> = {
|
|
1618
|
-
compareEnds: (a: Interval, b: Interval) => a.end - b.end,
|
|
1619
|
-
compareStarts: (a: Interval, b: Interval) => a.start - b.start,
|
|
1620
|
-
create: createInterval,
|
|
1621
|
-
};
|
|
1622
|
-
|
|
1623
359
|
class SequenceIntervalCollectionFactory
|
|
1624
360
|
implements IValueFactory<IntervalCollection<SequenceInterval>>
|
|
1625
361
|
{
|
|
@@ -1667,26 +403,6 @@ export class SequenceIntervalCollectionValueType
|
|
|
1667
403
|
private static readonly _ops = makeOpsMap<SequenceInterval>();
|
|
1668
404
|
}
|
|
1669
405
|
|
|
1670
|
-
const compareIntervalEnds = (a: Interval, b: Interval) => a.end - b.end;
|
|
1671
|
-
|
|
1672
|
-
function createInterval(
|
|
1673
|
-
label: string,
|
|
1674
|
-
start: number,
|
|
1675
|
-
end: number,
|
|
1676
|
-
client: Client,
|
|
1677
|
-
intervalType?: IntervalType,
|
|
1678
|
-
op?: ISequencedDocumentMessage,
|
|
1679
|
-
fromSnapshot?: boolean,
|
|
1680
|
-
): Interval {
|
|
1681
|
-
const rangeProp: PropertySet = {};
|
|
1682
|
-
|
|
1683
|
-
if (label && label.length > 0) {
|
|
1684
|
-
rangeProp[reservedRangeLabelsKey] = [label];
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
return new Interval(start, end, rangeProp);
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
406
|
class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Interval>> {
|
|
1691
407
|
public load(
|
|
1692
408
|
emitter: IValueOpEmitter,
|
|
@@ -1694,7 +410,7 @@ class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Inte
|
|
|
1694
410
|
options?: Partial<SequenceOptions>,
|
|
1695
411
|
): IntervalCollection<Interval> {
|
|
1696
412
|
const helpers: IIntervalHelpers<Interval> = {
|
|
1697
|
-
compareEnds:
|
|
413
|
+
compareEnds: (a: Interval, b: Interval) => a.end - b.end,
|
|
1698
414
|
create: createInterval,
|
|
1699
415
|
};
|
|
1700
416
|
const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
|
|
@@ -1839,6 +555,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1839
555
|
* endpoints. These references should be used for position information only.
|
|
1840
556
|
* `local` reflects whether the change originated locally.
|
|
1841
557
|
* `op` is defined if and only if the server has acked this change.
|
|
558
|
+
* `slide` is true if the change is due to sliding on removal of position
|
|
1842
559
|
*/
|
|
1843
560
|
(
|
|
1844
561
|
event: "changeInterval",
|
|
@@ -1847,6 +564,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1847
564
|
previousInterval: TInterval,
|
|
1848
565
|
local: boolean,
|
|
1849
566
|
op: ISequencedDocumentMessage | undefined,
|
|
567
|
+
slide: boolean,
|
|
1850
568
|
) => void,
|
|
1851
569
|
);
|
|
1852
570
|
/**
|
|
@@ -1894,7 +612,7 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1894
612
|
* All intervals which are part of this collection will be added to the index, and the index will automatically
|
|
1895
613
|
* be updated when this collection updates due to local or remote changes.
|
|
1896
614
|
*
|
|
1897
|
-
* @remarks
|
|
615
|
+
* @remarks After attaching an index to an interval collection, applications should typically store this
|
|
1898
616
|
* index somewhere in their in-memory data model for future reference and querying.
|
|
1899
617
|
*/
|
|
1900
618
|
attachIndex(index: IntervalIndex<TInterval>): void;
|
|
@@ -1903,7 +621,7 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1903
621
|
* All intervals which are part of this collection will be removed from the index, and updates to this collection
|
|
1904
622
|
* due to local or remote changes will no longer incur updates to the index.
|
|
1905
623
|
*
|
|
1906
|
-
* @returns
|
|
624
|
+
* @returns `false` if the target index cannot be found in the indexes, otherwise remove all intervals in the index and return `true`.
|
|
1907
625
|
*/
|
|
1908
626
|
detachIndex(index: IntervalIndex<TInterval>): boolean;
|
|
1909
627
|
/**
|
|
@@ -1918,8 +636,8 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1918
636
|
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
1919
637
|
* @param props - properties of the interval
|
|
1920
638
|
* @param stickiness - {@link (IntervalStickiness:type)} to apply to the added interval.
|
|
1921
|
-
* @returns
|
|
1922
|
-
* @remarks
|
|
639
|
+
* @returns The created interval
|
|
640
|
+
* @remarks See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
|
|
1923
641
|
* with how the current half-open behavior is represented.
|
|
1924
642
|
*/
|
|
1925
643
|
add(
|
|
@@ -2112,7 +830,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2112
830
|
// if segment is undefined, it slid off the string
|
|
2113
831
|
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
2114
832
|
|
|
2115
|
-
const segoff =
|
|
833
|
+
const segoff = getSlideToSegoff({ segment, offset }) ?? segment;
|
|
2116
834
|
|
|
2117
835
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
2118
836
|
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
@@ -2176,7 +894,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2176
894
|
client,
|
|
2177
895
|
label,
|
|
2178
896
|
this.helpers,
|
|
2179
|
-
(interval, previousInterval) => this.emitChange(interval, previousInterval, true),
|
|
897
|
+
(interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
|
|
2180
898
|
);
|
|
2181
899
|
if (this.savedSerializedIntervals) {
|
|
2182
900
|
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
@@ -2216,6 +934,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2216
934
|
interval: TInterval,
|
|
2217
935
|
previousInterval: TInterval,
|
|
2218
936
|
local: boolean,
|
|
937
|
+
slide: boolean,
|
|
2219
938
|
op?: ISequencedDocumentMessage,
|
|
2220
939
|
): void {
|
|
2221
940
|
// Temporarily make references transient so that positional queries work (non-transient refs
|
|
@@ -2228,18 +947,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2228
947
|
endRefType = previousInterval.end.refType;
|
|
2229
948
|
previousInterval.start.refType = ReferenceType.Transient;
|
|
2230
949
|
previousInterval.end.refType = ReferenceType.Transient;
|
|
2231
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
950
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
2232
951
|
previousInterval.start.refType = startRefType;
|
|
2233
952
|
previousInterval.end.refType = endRefType;
|
|
2234
953
|
} else {
|
|
2235
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
954
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
2236
955
|
}
|
|
2237
956
|
}
|
|
2238
957
|
|
|
2239
958
|
/**
|
|
2240
959
|
* {@inheritdoc IIntervalCollection.getIntervalById}
|
|
2241
960
|
*/
|
|
2242
|
-
public getIntervalById(id: string) {
|
|
961
|
+
public getIntervalById(id: string): TInterval | undefined {
|
|
2243
962
|
if (!this.localCollection) {
|
|
2244
963
|
throw new LoggingError("attach must be called before accessing intervals");
|
|
2245
964
|
}
|
|
@@ -2327,7 +1046,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2327
1046
|
/**
|
|
2328
1047
|
* {@inheritdoc IIntervalCollection.removeIntervalById}
|
|
2329
1048
|
*/
|
|
2330
|
-
public removeIntervalById(id: string) {
|
|
1049
|
+
public removeIntervalById(id: string): TInterval | undefined {
|
|
2331
1050
|
if (!this.localCollection) {
|
|
2332
1051
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
2333
1052
|
}
|
|
@@ -2351,6 +1070,13 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2351
1070
|
if (!props) {
|
|
2352
1071
|
throw new LoggingError("changeProperties should be called with a property set");
|
|
2353
1072
|
}
|
|
1073
|
+
// prevent the overwriting of an interval label, it should remain unchanged
|
|
1074
|
+
// once it has been inserted into the collection.
|
|
1075
|
+
if (props[reservedRangeLabelsKey] !== undefined) {
|
|
1076
|
+
throw new LoggingError(
|
|
1077
|
+
"The label property should not be modified once inserted to the collection",
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
2354
1080
|
|
|
2355
1081
|
const interval = this.getIntervalById(id);
|
|
2356
1082
|
if (interval) {
|
|
@@ -2402,7 +1128,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2402
1128
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
2403
1129
|
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
2404
1130
|
this.addPendingChange(id, serializedInterval);
|
|
2405
|
-
this.emitChange(newInterval, interval, true);
|
|
1131
|
+
this.emitChange(newInterval, interval, true, false);
|
|
2406
1132
|
return newInterval;
|
|
2407
1133
|
}
|
|
2408
1134
|
// No interval to change
|
|
@@ -2538,7 +1264,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2538
1264
|
}
|
|
2539
1265
|
|
|
2540
1266
|
if (newInterval !== interval) {
|
|
2541
|
-
this.emitChange(newInterval, interval, local, op);
|
|
1267
|
+
this.emitChange(newInterval, interval, local, false, op);
|
|
2542
1268
|
}
|
|
2543
1269
|
|
|
2544
1270
|
const changedProperties = Object.keys(newProps).length > 0;
|
|
@@ -2548,19 +1274,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2548
1274
|
}
|
|
2549
1275
|
}
|
|
2550
1276
|
|
|
2551
|
-
/**
|
|
2552
|
-
* @deprecated - This functionality was useful when adding two intervals at the same start/end positions resulted
|
|
2553
|
-
* in a conflict. This is no longer the case (as of PR#6407), as interval collections support multiple intervals
|
|
2554
|
-
* at the same location and gives each interval a unique id.
|
|
2555
|
-
*
|
|
2556
|
-
* As such, the conflict resolver is never invoked and unnecessary. This API will be removed in an upcoming release.
|
|
2557
|
-
*/
|
|
2558
|
-
public addConflictResolver(_: IntervalConflictResolver<TInterval>): void {
|
|
2559
|
-
if (!this.localCollection) {
|
|
2560
|
-
throw new LoggingError("attachSequence must be called");
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
|
|
2564
1277
|
/**
|
|
2565
1278
|
* {@inheritdoc IIntervalCollection.attachDeserializer}
|
|
2566
1279
|
*/
|
|
@@ -2654,7 +1367,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2654
1367
|
return rebased;
|
|
2655
1368
|
}
|
|
2656
1369
|
|
|
2657
|
-
private getSlideToSegment(
|
|
1370
|
+
private getSlideToSegment(
|
|
1371
|
+
lref: LocalReferencePosition,
|
|
1372
|
+
): { segment: ISegment | undefined; offset: number | undefined } | undefined {
|
|
2658
1373
|
if (!this.client) {
|
|
2659
1374
|
throw new LoggingError("client does not exist");
|
|
2660
1375
|
}
|
|
@@ -2662,7 +1377,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2662
1377
|
if (segoff.segment?.localRefs?.has(lref) !== true) {
|
|
2663
1378
|
return undefined;
|
|
2664
1379
|
}
|
|
2665
|
-
const newSegoff =
|
|
1380
|
+
const newSegoff = getSlideToSegoff(segoff);
|
|
2666
1381
|
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
2667
1382
|
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
2668
1383
|
? undefined
|
|
@@ -2761,7 +1476,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2761
1476
|
oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
|
|
2762
1477
|
}
|
|
2763
1478
|
this.localCollection.add(interval);
|
|
2764
|
-
this.emitChange(interval, oldInterval as TInterval, true, op);
|
|
1479
|
+
this.emitChange(interval, oldInterval as TInterval, true, true, op);
|
|
2765
1480
|
}
|
|
2766
1481
|
}
|
|
2767
1482
|
|