@fluidframework/sequence 2.0.0-internal.6.4.0 → 2.0.0-internal.7.0.0
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 +60 -0
- package/README.md +130 -0
- package/dist/defaultMap.d.ts +1 -1
- package/dist/defaultMap.d.ts.map +1 -1
- package/dist/defaultMap.js +6 -6
- package/dist/defaultMap.js.map +1 -1
- package/dist/defaultMapInterfaces.d.ts +21 -2
- package/dist/defaultMapInterfaces.d.ts.map +1 -1
- package/dist/defaultMapInterfaces.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/intervalCollection.d.ts +136 -18
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +120 -37
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalIndex/endpointInRangeIndex.js +1 -1
- package/dist/intervalIndex/endpointInRangeIndex.js.map +1 -1
- package/dist/intervalIndex/endpointIndex.d.ts.map +1 -1
- package/dist/intervalIndex/endpointIndex.js +1 -2
- package/dist/intervalIndex/endpointIndex.js.map +1 -1
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +5 -4
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
- package/dist/intervalIndex/overlappingIntervalsIndex.js +7 -2
- package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
- package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
- package/dist/intervalIndex/startpointInRangeIndex.js +1 -3
- package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -1
- package/dist/intervalTree.d.ts +1 -1
- package/dist/intervalTree.d.ts.map +1 -1
- package/dist/intervals/interval.d.ts +3 -2
- package/dist/intervals/interval.d.ts.map +1 -1
- package/dist/intervals/interval.js +12 -5
- package/dist/intervals/interval.js.map +1 -1
- package/dist/intervals/intervalUtils.d.ts +39 -18
- package/dist/intervals/intervalUtils.d.ts.map +1 -1
- package/dist/intervals/intervalUtils.js +12 -10
- package/dist/intervals/intervalUtils.js.map +1 -1
- package/dist/intervals/sequenceInterval.d.ts +23 -13
- package/dist/intervals/sequenceInterval.d.ts.map +1 -1
- package/dist/intervals/sequenceInterval.js +117 -42
- package/dist/intervals/sequenceInterval.js.map +1 -1
- 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 +3 -15
- package/dist/revertibles.d.ts.map +1 -1
- package/dist/revertibles.js +6 -17
- package/dist/revertibles.js.map +1 -1
- package/dist/sequence.d.ts +1 -1
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +43 -43
- package/dist/sequence.js.map +1 -1
- package/dist/sharedIntervalCollection.js +9 -9
- package/dist/sharedIntervalCollection.js.map +1 -1
- package/dist/sharedSequence.js +6 -6
- package/dist/sharedSequence.js.map +1 -1
- package/dist/sharedString.d.ts +1 -1
- package/dist/sharedString.d.ts.map +1 -1
- package/dist/sharedString.js +5 -5
- package/dist/sharedString.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/defaultMap.d.ts +1 -1
- package/lib/defaultMap.d.ts.map +1 -1
- package/lib/defaultMap.js +6 -6
- package/lib/defaultMap.js.map +1 -1
- package/lib/defaultMapInterfaces.d.ts +21 -2
- package/lib/defaultMapInterfaces.d.ts.map +1 -1
- package/lib/defaultMapInterfaces.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/intervalCollection.d.ts +136 -18
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +117 -37
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalIndex/endpointInRangeIndex.js +1 -1
- package/lib/intervalIndex/endpointInRangeIndex.js.map +1 -1
- package/lib/intervalIndex/endpointIndex.d.ts.map +1 -1
- package/lib/intervalIndex/endpointIndex.js +1 -2
- package/lib/intervalIndex/endpointIndex.js.map +1 -1
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +5 -4
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
- package/lib/intervalIndex/overlappingIntervalsIndex.js +7 -2
- package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
- package/lib/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
- package/lib/intervalIndex/startpointInRangeIndex.js +1 -3
- package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -1
- package/lib/intervalTree.d.ts +1 -1
- package/lib/intervalTree.d.ts.map +1 -1
- package/lib/intervals/interval.d.ts +3 -2
- package/lib/intervals/interval.d.ts.map +1 -1
- package/lib/intervals/interval.js +12 -5
- package/lib/intervals/interval.js.map +1 -1
- package/lib/intervals/intervalUtils.d.ts +39 -18
- package/lib/intervals/intervalUtils.d.ts.map +1 -1
- package/lib/intervals/intervalUtils.js +8 -6
- package/lib/intervals/intervalUtils.js.map +1 -1
- package/lib/intervals/sequenceInterval.d.ts +23 -13
- package/lib/intervals/sequenceInterval.d.ts.map +1 -1
- package/lib/intervals/sequenceInterval.js +118 -41
- package/lib/intervals/sequenceInterval.js.map +1 -1
- 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 +3 -15
- package/lib/revertibles.d.ts.map +1 -1
- package/lib/revertibles.js +6 -17
- package/lib/revertibles.js.map +1 -1
- package/lib/sequence.d.ts +1 -1
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +43 -43
- package/lib/sequence.js.map +1 -1
- package/lib/sharedIntervalCollection.js +9 -9
- package/lib/sharedIntervalCollection.js.map +1 -1
- package/lib/sharedSequence.js +6 -6
- package/lib/sharedSequence.js.map +1 -1
- package/lib/sharedString.d.ts +1 -1
- package/lib/sharedString.d.ts.map +1 -1
- package/lib/sharedString.js +5 -5
- package/lib/sharedString.js.map +1 -1
- package/package.json +48 -20
- package/src/defaultMapInterfaces.ts +21 -2
- package/src/index.ts +3 -0
- package/src/intervalCollection.ts +309 -66
- package/src/intervalIndex/endpointInRangeIndex.ts +1 -1
- package/src/intervalIndex/endpointIndex.ts +1 -2
- package/src/intervalIndex/overlappingIntervalsIndex.ts +17 -9
- package/src/intervalIndex/startpointInRangeIndex.ts +1 -7
- package/src/intervals/interval.ts +28 -7
- package/src/intervals/intervalUtils.ts +47 -26
- package/src/intervals/sequenceInterval.ts +190 -46
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +8 -33
- package/src/sequence.ts +2 -0
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
UnassignedSequenceNumber,
|
|
25
25
|
DetachedReferencePosition,
|
|
26
26
|
UniversalSequenceNumber,
|
|
27
|
+
SlidingPreference,
|
|
27
28
|
} from "@fluidframework/merge-tree";
|
|
28
29
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
29
30
|
import { LoggingError, UsageError } from "@fluidframework/telemetry-utils";
|
|
@@ -64,6 +65,52 @@ import {
|
|
|
64
65
|
createIdIntervalIndex,
|
|
65
66
|
} from "./intervalIndex";
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Defines a position and side relative to a character in a sequence.
|
|
70
|
+
*
|
|
71
|
+
* For this purpose, sequences look like:
|
|
72
|
+
*
|
|
73
|
+
* `{start} - {character 0} - {character 1} - ... - {character N} - {end}`
|
|
74
|
+
*
|
|
75
|
+
* Each `{value}` in the diagram is a character within a sequence.
|
|
76
|
+
* Each `-` in the above diagram is a position where text could be inserted.
|
|
77
|
+
* Each position between a `{value}` and a `-` is a `SequencePlace`.
|
|
78
|
+
*
|
|
79
|
+
* The special endpoints `{start}` and `{end}` refer to positions outside the
|
|
80
|
+
* contents of the string.
|
|
81
|
+
*
|
|
82
|
+
* This gives us 2N + 2 possible positions to refer to within a string, where N
|
|
83
|
+
* is the number of characters.
|
|
84
|
+
*
|
|
85
|
+
* If the position is specified with a bare number, the side defaults to
|
|
86
|
+
* `Side.Before`.
|
|
87
|
+
*
|
|
88
|
+
* If a SequencePlace is the endpoint of a range (e.g. start/end of an interval or search range),
|
|
89
|
+
* the Side value means it is exclusive if it is nearer to the other position and inclusive if it is farther.
|
|
90
|
+
* E.g. the start of a range with Side.After is exclusive of the character at the position.
|
|
91
|
+
*/
|
|
92
|
+
export type SequencePlace = number | "start" | "end" | InteriorSequencePlace;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A sequence place that does not refer to the special endpoint segments.
|
|
96
|
+
*
|
|
97
|
+
* See {@link SequencePlace} for additional context.
|
|
98
|
+
*/
|
|
99
|
+
export interface InteriorSequencePlace {
|
|
100
|
+
pos: number;
|
|
101
|
+
side: Side;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Defines a side relative to a character in a sequence.
|
|
106
|
+
*
|
|
107
|
+
* @remarks See {@link SequencePlace} for additional context on usage.
|
|
108
|
+
*/
|
|
109
|
+
export enum Side {
|
|
110
|
+
Before = 0,
|
|
111
|
+
After = 1,
|
|
112
|
+
}
|
|
113
|
+
|
|
67
114
|
const reservedIntervalIdKey = "intervalId";
|
|
68
115
|
|
|
69
116
|
export interface ISerializedIntervalCollectionV2 {
|
|
@@ -72,6 +119,13 @@ export interface ISerializedIntervalCollectionV2 {
|
|
|
72
119
|
intervals: CompressedSerializedInterval[];
|
|
73
120
|
}
|
|
74
121
|
|
|
122
|
+
export function sidesFromStickiness(stickiness: IntervalStickiness) {
|
|
123
|
+
const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
|
|
124
|
+
const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
|
|
125
|
+
|
|
126
|
+
return { startSide, endSide };
|
|
127
|
+
}
|
|
128
|
+
|
|
75
129
|
/**
|
|
76
130
|
* Decompress an interval after loading a summary from JSON. The exact format
|
|
77
131
|
* of this compression is unspecified and subject to change
|
|
@@ -80,13 +134,17 @@ function decompressInterval(
|
|
|
80
134
|
interval: CompressedSerializedInterval,
|
|
81
135
|
label?: string,
|
|
82
136
|
): ISerializedInterval {
|
|
137
|
+
const stickiness = interval[5] ?? IntervalStickiness.END;
|
|
138
|
+
const { startSide, endSide } = sidesFromStickiness(stickiness);
|
|
83
139
|
return {
|
|
84
140
|
start: interval[0],
|
|
85
141
|
end: interval[1],
|
|
86
142
|
sequenceNumber: interval[2],
|
|
87
143
|
intervalType: interval[3],
|
|
88
144
|
properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
|
|
89
|
-
stickiness
|
|
145
|
+
stickiness,
|
|
146
|
+
startSide,
|
|
147
|
+
endSide,
|
|
90
148
|
};
|
|
91
149
|
}
|
|
92
150
|
|
|
@@ -97,7 +155,7 @@ function decompressInterval(
|
|
|
97
155
|
function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
|
|
98
156
|
const { start, end, sequenceNumber, intervalType, properties } = interval;
|
|
99
157
|
|
|
100
|
-
|
|
158
|
+
let base: CompressedSerializedInterval = [
|
|
101
159
|
start,
|
|
102
160
|
end,
|
|
103
161
|
sequenceNumber,
|
|
@@ -108,18 +166,69 @@ function compressInterval(interval: ISerializedInterval): CompressedSerializedIn
|
|
|
108
166
|
];
|
|
109
167
|
|
|
110
168
|
if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
|
|
111
|
-
|
|
169
|
+
// reassignment to make it easier for typescript to reason about types
|
|
170
|
+
base = [...base, interval.stickiness];
|
|
112
171
|
}
|
|
113
172
|
|
|
114
173
|
return base;
|
|
115
174
|
}
|
|
116
175
|
|
|
176
|
+
export function endpointPosAndSide(
|
|
177
|
+
start: SequencePlace | undefined,
|
|
178
|
+
end: SequencePlace | undefined,
|
|
179
|
+
) {
|
|
180
|
+
const startIsPlainEndpoint = typeof start === "number" || start === "start" || start === "end";
|
|
181
|
+
const endIsPlainEndpoint = typeof end === "number" || end === "start" || end === "end";
|
|
182
|
+
|
|
183
|
+
const startSide = startIsPlainEndpoint ? Side.Before : start?.side;
|
|
184
|
+
const endSide = endIsPlainEndpoint ? Side.Before : end?.side;
|
|
185
|
+
|
|
186
|
+
const startPos = startIsPlainEndpoint ? start : start?.pos;
|
|
187
|
+
const endPos = endIsPlainEndpoint ? end : end?.pos;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
startSide,
|
|
191
|
+
endSide,
|
|
192
|
+
startPos,
|
|
193
|
+
endPos,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toSequencePlace(pos: number | "start" | "end", side: Side): SequencePlace {
|
|
198
|
+
return typeof pos === "number" ? { pos, side } : pos;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function toOptionalSequencePlace(
|
|
202
|
+
pos: number | "start" | "end" | undefined,
|
|
203
|
+
side: Side = Side.Before,
|
|
204
|
+
): SequencePlace | undefined {
|
|
205
|
+
return typeof pos === "number" ? { pos, side } : pos;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function computeStickinessFromSide(
|
|
209
|
+
startPos: number | "start" | "end" | undefined = -1,
|
|
210
|
+
startSide: Side = Side.Before,
|
|
211
|
+
endPos: number | "start" | "end" | undefined = -1,
|
|
212
|
+
endSide: Side = Side.Before,
|
|
213
|
+
): IntervalStickiness {
|
|
214
|
+
let stickiness: IntervalStickiness = IntervalStickiness.NONE;
|
|
215
|
+
|
|
216
|
+
if (startSide === Side.After || startPos === "start") {
|
|
217
|
+
stickiness |= IntervalStickiness.START;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (endSide === Side.Before || endPos === "end") {
|
|
221
|
+
stickiness |= IntervalStickiness.END;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return stickiness as IntervalStickiness;
|
|
225
|
+
}
|
|
226
|
+
|
|
117
227
|
export function createIntervalIndex() {
|
|
118
228
|
const helpers: IIntervalHelpers<Interval> = {
|
|
119
|
-
compareEnds: (a: Interval, b: Interval) => a.end - b.end,
|
|
120
229
|
create: createInterval,
|
|
121
230
|
};
|
|
122
|
-
const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
|
|
231
|
+
const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers, {});
|
|
123
232
|
return lc;
|
|
124
233
|
}
|
|
125
234
|
|
|
@@ -134,6 +243,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
134
243
|
private readonly client: Client,
|
|
135
244
|
private readonly label: string,
|
|
136
245
|
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
246
|
+
private readonly options: Partial<SequenceOptions>,
|
|
137
247
|
/** Callback invoked each time one of the endpoints of an interval slides. */
|
|
138
248
|
private readonly onPositionChange?: (
|
|
139
249
|
interval: TInterval,
|
|
@@ -150,7 +260,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
150
260
|
]);
|
|
151
261
|
}
|
|
152
262
|
|
|
153
|
-
public createLegacyId(start: number, end: number): string {
|
|
263
|
+
public createLegacyId(start: number | "start" | "end", end: number | "start" | "end"): string {
|
|
154
264
|
// Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
|
|
155
265
|
// without ID's.
|
|
156
266
|
return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
|
|
@@ -205,11 +315,10 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
205
315
|
}
|
|
206
316
|
|
|
207
317
|
public createInterval(
|
|
208
|
-
start:
|
|
209
|
-
end:
|
|
318
|
+
start: SequencePlace,
|
|
319
|
+
end: SequencePlace,
|
|
210
320
|
intervalType: IntervalType,
|
|
211
321
|
op?: ISequencedDocumentMessage,
|
|
212
|
-
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
213
322
|
): TInterval {
|
|
214
323
|
return this.helpers.create(
|
|
215
324
|
this.label,
|
|
@@ -219,19 +328,18 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
219
328
|
intervalType,
|
|
220
329
|
op,
|
|
221
330
|
undefined,
|
|
222
|
-
|
|
331
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
223
332
|
);
|
|
224
333
|
}
|
|
225
334
|
|
|
226
335
|
public addInterval(
|
|
227
|
-
start:
|
|
228
|
-
end:
|
|
336
|
+
start: SequencePlace,
|
|
337
|
+
end: SequencePlace,
|
|
229
338
|
intervalType: IntervalType,
|
|
230
339
|
props?: PropertySet,
|
|
231
340
|
op?: ISequencedDocumentMessage,
|
|
232
|
-
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
233
341
|
) {
|
|
234
|
-
const interval: TInterval = this.createInterval(start, end, intervalType, op
|
|
342
|
+
const interval: TInterval = this.createInterval(start, end, intervalType, op);
|
|
235
343
|
if (interval) {
|
|
236
344
|
if (!interval.properties) {
|
|
237
345
|
interval.properties = createMap<any>();
|
|
@@ -278,14 +386,19 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
278
386
|
|
|
279
387
|
public changeInterval(
|
|
280
388
|
interval: TInterval,
|
|
281
|
-
start:
|
|
282
|
-
end:
|
|
389
|
+
start: SequencePlace | undefined,
|
|
390
|
+
end: SequencePlace | undefined,
|
|
283
391
|
op?: ISequencedDocumentMessage,
|
|
284
392
|
localSeq?: number,
|
|
285
393
|
) {
|
|
286
|
-
const newInterval = interval.modify(
|
|
287
|
-
|
|
288
|
-
|
|
394
|
+
const newInterval = interval.modify(
|
|
395
|
+
this.label,
|
|
396
|
+
start,
|
|
397
|
+
end,
|
|
398
|
+
op,
|
|
399
|
+
localSeq,
|
|
400
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
401
|
+
) as TInterval | undefined;
|
|
289
402
|
if (newInterval) {
|
|
290
403
|
this.removeExistingInterval(interval);
|
|
291
404
|
this.add(newInterval);
|
|
@@ -319,6 +432,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
319
432
|
ReferenceType.Transient,
|
|
320
433
|
ref.properties,
|
|
321
434
|
ref.slidingPreference,
|
|
435
|
+
ref.canSlideToEndpoint,
|
|
322
436
|
);
|
|
323
437
|
};
|
|
324
438
|
if (interval instanceof SequenceInterval) {
|
|
@@ -412,7 +526,6 @@ class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Inte
|
|
|
412
526
|
options?: Partial<SequenceOptions>,
|
|
413
527
|
): IntervalCollection<Interval> {
|
|
414
528
|
const helpers: IIntervalHelpers<Interval> = {
|
|
415
|
-
compareEnds: (a: Interval, b: Interval) => a.end - b.end,
|
|
416
529
|
create: createInterval,
|
|
417
530
|
};
|
|
418
531
|
const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
|
|
@@ -633,21 +746,82 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
|
633
746
|
getIntervalById(id: string): TInterval | undefined;
|
|
634
747
|
/**
|
|
635
748
|
* Creates a new interval and add it to the collection.
|
|
636
|
-
* @param start - interval start position
|
|
637
|
-
* @param end - interval end position
|
|
638
|
-
* @param intervalType - type of the interval. All intervals are SlideOnRemove.
|
|
749
|
+
* @param start - interval start position
|
|
750
|
+
* @param end - interval end position
|
|
751
|
+
* @param intervalType - type of the interval. All intervals are SlideOnRemove.
|
|
752
|
+
* Intervals may not be Transient.
|
|
639
753
|
* @param props - properties of the interval
|
|
640
|
-
* @param stickiness - {@link (IntervalStickiness:type)} to apply to the added interval.
|
|
641
754
|
* @returns The created interval
|
|
642
|
-
* @remarks See documentation on {@link SequenceInterval} for comments on
|
|
643
|
-
* with how the current
|
|
755
|
+
* @remarks See documentation on {@link SequenceInterval} for comments on
|
|
756
|
+
* interval endpoint semantics: there are subtleties with how the current
|
|
757
|
+
* half-open behavior is represented.
|
|
758
|
+
*
|
|
759
|
+
* Note that intervals may behave unexpectedly if the entire contents
|
|
760
|
+
* of the string are deleted. In this case, it is possible for one endpoint
|
|
761
|
+
* of the interval to become detached, while the other remains on the string.
|
|
762
|
+
*
|
|
763
|
+
* By adjusting the `side` and `pos` values of the `start` and `end` parameters,
|
|
764
|
+
* it is possible to control whether the interval expands to include content
|
|
765
|
+
* inserted at its start or end.
|
|
766
|
+
*
|
|
767
|
+
* See {@link SequencePlace} for more details on the model.
|
|
768
|
+
*
|
|
769
|
+
* @example
|
|
770
|
+
*
|
|
771
|
+
* Given the string "ABCD":
|
|
772
|
+
*
|
|
773
|
+
*```typescript
|
|
774
|
+
* // Refers to "BC". If any content is inserted before B or after C, this
|
|
775
|
+
* // interval will include that content
|
|
776
|
+
* //
|
|
777
|
+
* // Picture:
|
|
778
|
+
* // \{start\} - A[- B - C -]D - \{end\}
|
|
779
|
+
* // \{start\} - A - B - C - D - \{end\}
|
|
780
|
+
* collection.add(\{ pos: 0, side: Side.After \}, \{ pos: 3, side: Side.Before \}, IntervalType.SlideOnRemove);
|
|
781
|
+
* // Equivalent to specifying the same positions and Side.Before.
|
|
782
|
+
* // Refers to "ABC". Content inserted after C will be included in the
|
|
783
|
+
* // interval, but content inserted before A will not.
|
|
784
|
+
* // \{start\} -[A - B - C -]D - \{end\}
|
|
785
|
+
* // \{start\} - A - B - C - D - \{end\}
|
|
786
|
+
* collection.add(0, 3, IntervalType.SlideOnRemove);
|
|
787
|
+
*```
|
|
788
|
+
*
|
|
789
|
+
* In the case of the first example, if text is deleted,
|
|
790
|
+
*
|
|
791
|
+
* ```typescript
|
|
792
|
+
* // Delete the character "B"
|
|
793
|
+
* string.removeRange(1, 2);
|
|
794
|
+
* ```
|
|
795
|
+
*
|
|
796
|
+
* The start point of the interval will slide to the position immediately
|
|
797
|
+
* before "C", and the same will be true.
|
|
798
|
+
*
|
|
799
|
+
* ```
|
|
800
|
+
* \{start\} - A[- C -]D - \{end\}
|
|
801
|
+
* ```
|
|
802
|
+
*
|
|
803
|
+
* In this case, text inserted immediately before "C" would be included in
|
|
804
|
+
* the interval.
|
|
805
|
+
*
|
|
806
|
+
* ```typescript
|
|
807
|
+
* string.insertText(1, "EFG");
|
|
808
|
+
* ```
|
|
809
|
+
*
|
|
810
|
+
* With the string now being,
|
|
811
|
+
*
|
|
812
|
+
* ```
|
|
813
|
+
* \{start\} - A[- E - F - G - C -]D - \{end\}
|
|
814
|
+
* ```
|
|
815
|
+
*
|
|
816
|
+
* @privateRemarks TODO: ADO:5205 the above comment regarding behavior in
|
|
817
|
+
* the case that the entire interval has been deleted should be resolved at
|
|
818
|
+
* the same time as this ticket
|
|
644
819
|
*/
|
|
645
820
|
add(
|
|
646
|
-
start:
|
|
647
|
-
end:
|
|
821
|
+
start: SequencePlace,
|
|
822
|
+
end: SequencePlace,
|
|
648
823
|
intervalType: IntervalType,
|
|
649
824
|
props?: PropertySet,
|
|
650
|
-
stickiness?: IntervalStickiness,
|
|
651
825
|
): TInterval;
|
|
652
826
|
/**
|
|
653
827
|
* Removes an interval from the collection.
|
|
@@ -665,11 +839,11 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
|
665
839
|
/**
|
|
666
840
|
* Changes the endpoints of an existing interval.
|
|
667
841
|
* @param id - Id of the interval to change
|
|
668
|
-
* @param start - New start value
|
|
669
|
-
* @param end - New end value
|
|
842
|
+
* @param start - New start value. This can be the existing position to keep it unchanged.
|
|
843
|
+
* @param end - New end value. This can be the existing position to keep it unchanged.
|
|
670
844
|
* @returns the interval that was changed, if it existed in the collection.
|
|
671
845
|
*/
|
|
672
|
-
change(id: string, start
|
|
846
|
+
change(id: string, start: SequencePlace, end: SequencePlace): TInterval | undefined;
|
|
673
847
|
|
|
674
848
|
attachDeserializer(onDeserialize: DeserializeCallback): void;
|
|
675
849
|
/**
|
|
@@ -812,13 +986,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
812
986
|
}
|
|
813
987
|
|
|
814
988
|
private rebasePositionWithSegmentSlide(
|
|
815
|
-
pos: number,
|
|
989
|
+
pos: number | "start" | "end",
|
|
816
990
|
seqNumberFrom: number,
|
|
817
991
|
localSeq: number,
|
|
818
|
-
): number | undefined {
|
|
992
|
+
): number | "start" | "end" | undefined {
|
|
819
993
|
if (!this.client) {
|
|
820
994
|
throw new LoggingError("mergeTree client must exist");
|
|
821
995
|
}
|
|
996
|
+
|
|
997
|
+
if (pos === "start" || pos === "end") {
|
|
998
|
+
return pos;
|
|
999
|
+
}
|
|
1000
|
+
|
|
822
1001
|
const { clientId } = this.client.getCollabWindow();
|
|
823
1002
|
const { segment, offset } = this.client.getContainingSegment(
|
|
824
1003
|
pos,
|
|
@@ -832,7 +1011,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
832
1011
|
// if segment is undefined, it slid off the string
|
|
833
1012
|
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
834
1013
|
|
|
835
|
-
const segoff =
|
|
1014
|
+
const segoff =
|
|
1015
|
+
getSlideToSegoff(
|
|
1016
|
+
{ segment, offset },
|
|
1017
|
+
undefined,
|
|
1018
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1019
|
+
) ?? segment;
|
|
836
1020
|
|
|
837
1021
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
838
1022
|
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
@@ -896,12 +1080,28 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
896
1080
|
client,
|
|
897
1081
|
label,
|
|
898
1082
|
this.helpers,
|
|
1083
|
+
this.options,
|
|
899
1084
|
(interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
|
|
900
1085
|
);
|
|
901
1086
|
if (this.savedSerializedIntervals) {
|
|
902
1087
|
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
903
1088
|
this.localCollection.ensureSerializedId(serializedInterval);
|
|
904
|
-
const {
|
|
1089
|
+
const {
|
|
1090
|
+
start: startPos,
|
|
1091
|
+
end: endPos,
|
|
1092
|
+
intervalType,
|
|
1093
|
+
properties,
|
|
1094
|
+
startSide,
|
|
1095
|
+
endSide,
|
|
1096
|
+
} = serializedInterval;
|
|
1097
|
+
const start =
|
|
1098
|
+
typeof startPos === "number" && startSide !== undefined
|
|
1099
|
+
? { pos: startPos, side: startSide }
|
|
1100
|
+
: startPos;
|
|
1101
|
+
const end =
|
|
1102
|
+
typeof endPos === "number" && endSide !== undefined
|
|
1103
|
+
? { pos: endPos, side: endSide }
|
|
1104
|
+
: endPos;
|
|
905
1105
|
const interval = this.helpers.create(
|
|
906
1106
|
label,
|
|
907
1107
|
start,
|
|
@@ -910,7 +1110,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
910
1110
|
intervalType,
|
|
911
1111
|
undefined,
|
|
912
1112
|
true,
|
|
913
|
-
|
|
1113
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
914
1114
|
);
|
|
915
1115
|
if (properties) {
|
|
916
1116
|
interval.addProperties(properties);
|
|
@@ -967,15 +1167,25 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
967
1167
|
return this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
968
1168
|
}
|
|
969
1169
|
|
|
1170
|
+
private assertStickinessEnabled(start: SequencePlace, end: SequencePlace) {
|
|
1171
|
+
if (
|
|
1172
|
+
!(typeof start === "number" && typeof end === "number") &&
|
|
1173
|
+
!this.options.intervalStickinessEnabled
|
|
1174
|
+
) {
|
|
1175
|
+
throw new UsageError(
|
|
1176
|
+
"attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag",
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
970
1181
|
/**
|
|
971
1182
|
* {@inheritdoc IIntervalCollection.add}
|
|
972
1183
|
*/
|
|
973
1184
|
public add(
|
|
974
|
-
start:
|
|
975
|
-
end:
|
|
1185
|
+
start: SequencePlace,
|
|
1186
|
+
end: SequencePlace,
|
|
976
1187
|
intervalType: IntervalType,
|
|
977
1188
|
props?: PropertySet,
|
|
978
|
-
stickiness: IntervalStickiness = IntervalStickiness.END,
|
|
979
1189
|
): TInterval {
|
|
980
1190
|
if (!this.localCollection) {
|
|
981
1191
|
throw new LoggingError("attach must be called prior to adding intervals");
|
|
@@ -983,19 +1193,26 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
983
1193
|
if (intervalType & IntervalType.Transient) {
|
|
984
1194
|
throw new LoggingError("Can not add transient intervals");
|
|
985
1195
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1196
|
+
|
|
1197
|
+
const { startSide, endSide, startPos, endPos } = endpointPosAndSide(start, end);
|
|
1198
|
+
|
|
1199
|
+
assert(
|
|
1200
|
+
startPos !== undefined &&
|
|
1201
|
+
endPos !== undefined &&
|
|
1202
|
+
startSide !== undefined &&
|
|
1203
|
+
endSide !== undefined,
|
|
1204
|
+
0x793 /* start and end cannot be undefined because they were not passed in as undefined */,
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
|
|
1208
|
+
|
|
1209
|
+
this.assertStickinessEnabled(start, end);
|
|
991
1210
|
|
|
992
1211
|
const interval: TInterval = this.localCollection.addInterval(
|
|
993
|
-
|
|
994
|
-
|
|
1212
|
+
toSequencePlace(startPos, startSide),
|
|
1213
|
+
toSequencePlace(endPos, endSide),
|
|
995
1214
|
intervalType,
|
|
996
1215
|
props,
|
|
997
|
-
undefined,
|
|
998
|
-
stickiness,
|
|
999
1216
|
);
|
|
1000
1217
|
|
|
1001
1218
|
if (interval) {
|
|
@@ -1003,13 +1220,15 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1003
1220
|
setSlideOnRemove(interval.start);
|
|
1004
1221
|
setSlideOnRemove(interval.end);
|
|
1005
1222
|
}
|
|
1006
|
-
const serializedInterval = {
|
|
1007
|
-
|
|
1223
|
+
const serializedInterval: ISerializedInterval = {
|
|
1224
|
+
start: startPos,
|
|
1225
|
+
end: endPos,
|
|
1008
1226
|
intervalType,
|
|
1009
1227
|
properties: interval.properties,
|
|
1010
1228
|
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1011
|
-
start,
|
|
1012
1229
|
stickiness,
|
|
1230
|
+
startSide,
|
|
1231
|
+
endSide,
|
|
1013
1232
|
};
|
|
1014
1233
|
const localSeq = this.getNextLocalSeq();
|
|
1015
1234
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
@@ -1110,7 +1329,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1110
1329
|
/**
|
|
1111
1330
|
* {@inheritdoc IIntervalCollection.change}
|
|
1112
1331
|
*/
|
|
1113
|
-
public change(id: string, start
|
|
1332
|
+
public change(id: string, start: SequencePlace, end: SequencePlace): TInterval | undefined {
|
|
1114
1333
|
if (!this.localCollection) {
|
|
1115
1334
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1116
1335
|
}
|
|
@@ -1131,8 +1350,13 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1131
1350
|
setSlideOnRemove(newInterval.end);
|
|
1132
1351
|
}
|
|
1133
1352
|
const serializedInterval: SerializedIntervalDelta = interval.serialize();
|
|
1134
|
-
|
|
1135
|
-
|
|
1353
|
+
const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
|
|
1354
|
+
const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
|
|
1355
|
+
serializedInterval.start = startPos;
|
|
1356
|
+
serializedInterval.end = endPos;
|
|
1357
|
+
serializedInterval.startSide = startSide;
|
|
1358
|
+
serializedInterval.endSide = endSide;
|
|
1359
|
+
serializedInterval.stickiness = stickiness;
|
|
1136
1360
|
// Emit a property bag containing only the ID, as we don't intend for this op to change any properties.
|
|
1137
1361
|
serializedInterval.properties = {
|
|
1138
1362
|
[reservedIntervalIdKey]: interval.getIntervalId(),
|
|
@@ -1261,8 +1485,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1261
1485
|
} else {
|
|
1262
1486
|
// If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
|
|
1263
1487
|
// should be the winning change.
|
|
1264
|
-
let start: number | undefined;
|
|
1265
|
-
let end: number | undefined;
|
|
1488
|
+
let start: number | "start" | "end" | undefined;
|
|
1489
|
+
let end: number | "start" | "end" | undefined;
|
|
1266
1490
|
// Track pending start/end independently of one another.
|
|
1267
1491
|
if (!this.hasPendingChangeStart(id)) {
|
|
1268
1492
|
start = serializedInterval.start;
|
|
@@ -1276,7 +1500,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1276
1500
|
// If changeInterval gives us a new interval, work with that one. Otherwise keep working with
|
|
1277
1501
|
// the one we originally found in the tree.
|
|
1278
1502
|
newInterval =
|
|
1279
|
-
this.localCollection.changeInterval(
|
|
1503
|
+
this.localCollection.changeInterval(
|
|
1504
|
+
interval,
|
|
1505
|
+
toOptionalSequencePlace(start, serializedInterval.startSide),
|
|
1506
|
+
toOptionalSequencePlace(end, serializedInterval.endSide),
|
|
1507
|
+
op,
|
|
1508
|
+
) ?? interval;
|
|
1280
1509
|
}
|
|
1281
1510
|
const deltaProps = newInterval.addProperties(newProps, true, op.sequenceNumber);
|
|
1282
1511
|
if (this.onDeserialize) {
|
|
@@ -1332,7 +1561,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1332
1561
|
throw new LoggingError("attachSequence must be called");
|
|
1333
1562
|
}
|
|
1334
1563
|
|
|
1335
|
-
const { intervalType, properties } = serializedInterval;
|
|
1564
|
+
const { intervalType, properties, stickiness, startSide, endSide } = serializedInterval;
|
|
1336
1565
|
|
|
1337
1566
|
const { start: startRebased, end: endRebased } =
|
|
1338
1567
|
this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
|
|
@@ -1346,6 +1575,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1346
1575
|
intervalType,
|
|
1347
1576
|
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1348
1577
|
properties,
|
|
1578
|
+
stickiness,
|
|
1579
|
+
startSide,
|
|
1580
|
+
endSide,
|
|
1349
1581
|
};
|
|
1350
1582
|
|
|
1351
1583
|
if (
|
|
@@ -1377,8 +1609,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1377
1609
|
// updates the local client's state to be consistent with the emitted op.
|
|
1378
1610
|
this.localCollection?.changeInterval(
|
|
1379
1611
|
localInterval,
|
|
1380
|
-
startRebased,
|
|
1381
|
-
endRebased,
|
|
1612
|
+
toOptionalSequencePlace(startRebased, startSide),
|
|
1613
|
+
toOptionalSequencePlace(endRebased, endSide),
|
|
1382
1614
|
undefined,
|
|
1383
1615
|
localSeq,
|
|
1384
1616
|
);
|
|
@@ -1397,7 +1629,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1397
1629
|
if (segoff.segment?.localRefs?.has(lref) !== true) {
|
|
1398
1630
|
return undefined;
|
|
1399
1631
|
}
|
|
1400
|
-
const newSegoff = getSlideToSegoff(
|
|
1632
|
+
const newSegoff = getSlideToSegoff(
|
|
1633
|
+
segoff,
|
|
1634
|
+
undefined,
|
|
1635
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1636
|
+
);
|
|
1401
1637
|
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
1402
1638
|
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
1403
1639
|
? undefined
|
|
@@ -1405,7 +1641,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1405
1641
|
return value;
|
|
1406
1642
|
}
|
|
1407
1643
|
|
|
1408
|
-
private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
|
|
1644
|
+
private ackInterval(interval: TInterval, op: ISequencedDocumentMessage): void {
|
|
1409
1645
|
// Only SequenceIntervals need potential sliding
|
|
1410
1646
|
if (!(interval instanceof SequenceInterval)) {
|
|
1411
1647
|
return;
|
|
@@ -1459,7 +1695,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1459
1695
|
newStart,
|
|
1460
1696
|
interval.start.refType,
|
|
1461
1697
|
op,
|
|
1698
|
+
undefined,
|
|
1699
|
+
undefined,
|
|
1462
1700
|
startReferenceSlidingPreference(interval.stickiness),
|
|
1701
|
+
startReferenceSlidingPreference(interval.stickiness) ===
|
|
1702
|
+
SlidingPreference.BACKWARD,
|
|
1463
1703
|
);
|
|
1464
1704
|
if (props) {
|
|
1465
1705
|
interval.start.addProperties(props);
|
|
@@ -1477,7 +1717,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1477
1717
|
newEnd,
|
|
1478
1718
|
interval.end.refType,
|
|
1479
1719
|
op,
|
|
1720
|
+
undefined,
|
|
1721
|
+
undefined,
|
|
1480
1722
|
endReferenceSlidingPreference(interval.stickiness),
|
|
1723
|
+
endReferenceSlidingPreference(interval.stickiness) ===
|
|
1724
|
+
SlidingPreference.FORWARD,
|
|
1481
1725
|
);
|
|
1482
1726
|
if (props) {
|
|
1483
1727
|
interval.end.addProperties(props);
|
|
@@ -1521,12 +1765,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1521
1765
|
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1522
1766
|
|
|
1523
1767
|
const interval: TInterval = this.localCollection.addInterval(
|
|
1524
|
-
serializedInterval.start,
|
|
1525
|
-
serializedInterval.end,
|
|
1768
|
+
toSequencePlace(serializedInterval.start, serializedInterval.startSide ?? Side.Before),
|
|
1769
|
+
toSequencePlace(serializedInterval.end, serializedInterval.endSide ?? Side.Before),
|
|
1526
1770
|
serializedInterval.intervalType,
|
|
1527
1771
|
serializedInterval.properties,
|
|
1528
1772
|
op,
|
|
1529
|
-
serializedInterval.stickiness,
|
|
1530
1773
|
);
|
|
1531
1774
|
|
|
1532
1775
|
if (interval) {
|
|
@@ -39,7 +39,7 @@ export class EndpointInRangeIndex<TInterval extends ISerializableInterval>
|
|
|
39
39
|
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
40
40
|
) {
|
|
41
41
|
this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
|
|
42
|
-
const compareEndsResult =
|
|
42
|
+
const compareEndsResult = a.compareEnd(b);
|
|
43
43
|
if (compareEndsResult !== 0) {
|
|
44
44
|
return compareEndsResult;
|
|
45
45
|
}
|
|
@@ -39,8 +39,7 @@ export class EndpointIndex<TInterval extends ISerializableInterval>
|
|
|
39
39
|
private readonly client: Client,
|
|
40
40
|
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
41
41
|
) {
|
|
42
|
-
|
|
43
|
-
this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
|
|
42
|
+
this.endIntervalTree = new RedBlackTree<TInterval, TInterval>((a, b) => a.compareEnd(b));
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
public previousInterval(pos: number): TInterval | undefined {
|