@fluidframework/sequence 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.224419
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/.eslintrc.js +9 -11
- package/.mocharc.js +12 -0
- package/CHANGELOG.md +449 -0
- package/README.md +364 -183
- package/api-extractor-lint.json +4 -0
- package/api-extractor.json +2 -2
- package/api-report/sequence.api.md +741 -0
- package/dist/{defaultMap.js → defaultMap.cjs} +29 -22
- package/dist/defaultMap.cjs.map +1 -0
- package/dist/defaultMap.d.ts +7 -6
- package/dist/defaultMap.d.ts.map +1 -1
- package/dist/defaultMapInterfaces.cjs +7 -0
- package/dist/defaultMapInterfaces.cjs.map +1 -0
- package/dist/defaultMapInterfaces.d.ts +44 -12
- package/dist/defaultMapInterfaces.d.ts.map +1 -1
- package/dist/index.cjs +60 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +14 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/intervalCollection.cjs +1159 -0
- package/dist/intervalCollection.cjs.map +1 -0
- package/dist/intervalCollection.d.ts +461 -162
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalIndex/endpointInRangeIndex.cjs +66 -0
- package/dist/intervalIndex/endpointInRangeIndex.cjs.map +1 -0
- package/dist/intervalIndex/endpointInRangeIndex.d.ts +34 -0
- package/dist/intervalIndex/endpointInRangeIndex.d.ts.map +1 -0
- package/dist/intervalIndex/endpointIndex.cjs +47 -0
- package/dist/intervalIndex/endpointIndex.cjs.map +1 -0
- package/dist/intervalIndex/endpointIndex.d.ts +38 -0
- package/dist/intervalIndex/endpointIndex.d.ts.map +1 -0
- package/dist/intervalIndex/idIntervalIndex.cjs +44 -0
- package/dist/intervalIndex/idIntervalIndex.cjs.map +1 -0
- package/dist/intervalIndex/idIntervalIndex.d.ts +18 -0
- package/dist/intervalIndex/idIntervalIndex.d.ts.map +1 -0
- package/dist/intervalIndex/index.cjs +24 -0
- package/dist/intervalIndex/index.cjs.map +1 -0
- package/dist/intervalIndex/index.d.ts +13 -0
- package/dist/intervalIndex/index.d.ts.map +1 -0
- package/dist/{defaultMapInterfaces.js → intervalIndex/intervalIndex.cjs} +1 -1
- package/dist/intervalIndex/intervalIndex.cjs.map +1 -0
- package/dist/intervalIndex/intervalIndex.d.ts +30 -0
- package/dist/intervalIndex/intervalIndex.d.ts.map +1 -0
- package/dist/intervalIndex/intervalIndexUtils.cjs +22 -0
- package/dist/intervalIndex/intervalIndexUtils.cjs.map +1 -0
- package/dist/intervalIndex/intervalIndexUtils.d.ts +17 -0
- package/dist/intervalIndex/intervalIndexUtils.d.ts.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.cjs +116 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.cjs.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +44 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.cjs +41 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.cjs.map +1 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +11 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.cjs +7 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.cjs.map +1 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +35 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
- package/dist/intervalIndex/startpointInRangeIndex.cjs +66 -0
- package/dist/intervalIndex/startpointInRangeIndex.cjs.map +1 -0
- package/dist/intervalIndex/startpointInRangeIndex.d.ts +34 -0
- package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -0
- package/dist/intervalTree.cjs +80 -0
- package/dist/intervalTree.cjs.map +1 -0
- package/dist/intervalTree.d.ts +24 -0
- package/dist/intervalTree.d.ts.map +1 -0
- package/dist/intervals/index.cjs +23 -0
- package/dist/intervals/index.cjs.map +1 -0
- package/dist/intervals/index.d.ts +8 -0
- package/dist/intervals/index.d.ts.map +1 -0
- package/dist/intervals/interval.cjs +181 -0
- package/dist/intervals/interval.cjs.map +1 -0
- package/dist/intervals/interval.d.ts +84 -0
- package/dist/intervals/interval.d.ts.map +1 -0
- package/dist/intervals/intervalUtils.cjs +83 -0
- package/dist/intervals/intervalUtils.cjs.map +1 -0
- package/dist/intervals/intervalUtils.d.ts +230 -0
- package/dist/intervals/intervalUtils.d.ts.map +1 -0
- package/dist/intervals/sequenceInterval.cjs +378 -0
- package/dist/intervals/sequenceInterval.cjs.map +1 -0
- package/dist/intervals/sequenceInterval.d.ts +137 -0
- package/dist/intervals/sequenceInterval.d.ts.map +1 -0
- package/dist/{localValues.js → localValues.cjs} +1 -1
- package/dist/localValues.cjs.map +1 -0
- package/dist/localValues.d.ts +2 -1
- package/dist/localValues.d.ts.map +1 -1
- package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
- package/dist/packageVersion.cjs.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/revertibles.cjs +425 -0
- package/dist/revertibles.cjs.map +1 -0
- package/dist/revertibles.d.ts +86 -0
- package/dist/revertibles.d.ts.map +1 -0
- package/dist/sequence-alpha.d.ts +1315 -0
- package/dist/sequence-beta.d.ts +244 -0
- package/dist/sequence-public.d.ts +244 -0
- package/dist/sequence-untrimmed.d.ts +1803 -0
- package/dist/{sequence.js → sequence.cjs} +226 -156
- package/dist/sequence.cjs.map +1 -0
- package/dist/sequence.d.ts +125 -48
- package/dist/sequence.d.ts.map +1 -1
- package/dist/{sequenceDeltaEvent.js → sequenceDeltaEvent.cjs} +18 -8
- package/dist/sequenceDeltaEvent.cjs.map +1 -0
- package/dist/sequenceDeltaEvent.d.ts +24 -7
- package/dist/sequenceDeltaEvent.d.ts.map +1 -1
- package/dist/sequenceFactory.cjs +55 -0
- package/dist/sequenceFactory.cjs.map +1 -0
- package/dist/sequenceFactory.d.ts +3 -89
- package/dist/sequenceFactory.d.ts.map +1 -1
- package/dist/{sharedIntervalCollection.js → sharedIntervalCollection.cjs} +17 -22
- package/dist/sharedIntervalCollection.cjs.map +1 -0
- package/dist/sharedIntervalCollection.d.ts +12 -12
- package/dist/sharedIntervalCollection.d.ts.map +1 -1
- package/dist/{sharedSequence.js → sharedSequence.cjs} +29 -22
- package/dist/sharedSequence.cjs.map +1 -0
- package/dist/sharedSequence.d.ts +14 -2
- package/dist/sharedSequence.d.ts.map +1 -1
- package/dist/sharedString.cjs +286 -0
- package/dist/sharedString.cjs.map +1 -0
- package/dist/sharedString.d.ts +58 -22
- package/dist/sharedString.d.ts.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/lib/{defaultMap.d.ts → defaultMap.d.mts} +7 -6
- package/lib/defaultMap.d.mts.map +1 -0
- package/lib/{defaultMap.js → defaultMap.mjs} +28 -21
- package/lib/defaultMap.mjs.map +1 -0
- package/lib/{defaultMapInterfaces.d.ts → defaultMapInterfaces.d.mts} +44 -12
- package/lib/defaultMapInterfaces.d.mts.map +1 -0
- package/lib/defaultMapInterfaces.mjs +6 -0
- package/lib/defaultMapInterfaces.mjs.map +1 -0
- package/lib/index.d.mts +17 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +16 -0
- package/lib/index.mjs.map +1 -0
- package/lib/intervalCollection.d.mts +569 -0
- package/lib/intervalCollection.d.mts.map +1 -0
- package/lib/intervalCollection.mjs +1144 -0
- package/lib/intervalCollection.mjs.map +1 -0
- package/lib/intervalIndex/endpointInRangeIndex.d.mts +34 -0
- package/lib/intervalIndex/endpointInRangeIndex.d.mts.map +1 -0
- package/lib/intervalIndex/endpointInRangeIndex.mjs +61 -0
- package/lib/intervalIndex/endpointInRangeIndex.mjs.map +1 -0
- package/lib/intervalIndex/endpointIndex.d.mts +38 -0
- package/lib/intervalIndex/endpointIndex.d.mts.map +1 -0
- package/lib/intervalIndex/endpointIndex.mjs +42 -0
- package/lib/intervalIndex/endpointIndex.mjs.map +1 -0
- package/lib/intervalIndex/idIntervalIndex.d.mts +18 -0
- package/lib/intervalIndex/idIntervalIndex.d.mts.map +1 -0
- package/lib/intervalIndex/idIntervalIndex.mjs +40 -0
- package/lib/intervalIndex/idIntervalIndex.mjs.map +1 -0
- package/lib/intervalIndex/index.d.mts +13 -0
- package/lib/intervalIndex/index.d.mts.map +1 -0
- package/lib/intervalIndex/index.mjs +11 -0
- package/lib/intervalIndex/index.mjs.map +1 -0
- package/lib/intervalIndex/intervalIndex.d.mts +30 -0
- package/lib/intervalIndex/intervalIndex.d.mts.map +1 -0
- package/lib/{defaultMapInterfaces.js → intervalIndex/intervalIndex.mjs} +1 -1
- package/lib/intervalIndex/intervalIndex.mjs.map +1 -0
- package/lib/intervalIndex/intervalIndexUtils.d.mts +17 -0
- package/lib/intervalIndex/intervalIndexUtils.d.mts.map +1 -0
- package/lib/intervalIndex/intervalIndexUtils.mjs +18 -0
- package/lib/intervalIndex/intervalIndexUtils.mjs.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.mts +44 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.mts.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.mjs +111 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.mjs.map +1 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.mts +11 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.mts.map +1 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.mjs +37 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.mjs.map +1 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.d.mts +35 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.d.mts.map +1 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.mjs +6 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.mjs.map +1 -0
- package/lib/intervalIndex/startpointInRangeIndex.d.mts +34 -0
- package/lib/intervalIndex/startpointInRangeIndex.d.mts.map +1 -0
- package/lib/intervalIndex/startpointInRangeIndex.mjs +61 -0
- package/lib/intervalIndex/startpointInRangeIndex.mjs.map +1 -0
- package/lib/intervalTree.d.mts +24 -0
- package/lib/intervalTree.d.mts.map +1 -0
- package/lib/intervalTree.mjs +76 -0
- package/lib/intervalTree.mjs.map +1 -0
- package/lib/intervals/index.d.mts +8 -0
- package/lib/intervals/index.d.mts.map +1 -0
- package/lib/intervals/index.mjs +8 -0
- package/lib/intervals/index.mjs.map +1 -0
- package/lib/intervals/interval.d.mts +84 -0
- package/lib/intervals/interval.d.mts.map +1 -0
- package/lib/intervals/interval.mjs +176 -0
- package/lib/intervals/interval.mjs.map +1 -0
- package/lib/intervals/intervalUtils.d.mts +230 -0
- package/lib/intervals/intervalUtils.d.mts.map +1 -0
- package/lib/intervals/intervalUtils.mjs +77 -0
- package/lib/intervals/intervalUtils.mjs.map +1 -0
- package/lib/intervals/sequenceInterval.d.mts +137 -0
- package/lib/intervals/sequenceInterval.d.mts.map +1 -0
- package/lib/intervals/sequenceInterval.mjs +370 -0
- package/lib/intervals/sequenceInterval.mjs.map +1 -0
- package/lib/{localValues.d.ts → localValues.d.mts} +3 -2
- package/lib/localValues.d.mts.map +1 -0
- package/lib/{localValues.js → localValues.mjs} +2 -2
- package/lib/localValues.mjs.map +1 -0
- package/lib/{packageVersion.d.ts → packageVersion.d.mts} +1 -1
- package/lib/{packageVersion.d.ts.map → packageVersion.d.mts.map} +1 -1
- package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
- package/lib/packageVersion.mjs.map +1 -0
- package/lib/revertibles.d.mts +86 -0
- package/lib/revertibles.d.mts.map +1 -0
- package/lib/revertibles.mjs +416 -0
- package/lib/revertibles.mjs.map +1 -0
- package/lib/sequence-alpha.d.mts +1315 -0
- package/lib/sequence-beta.d.mts +244 -0
- package/lib/sequence-public.d.mts +244 -0
- package/lib/sequence-untrimmed.d.mts +1803 -0
- package/lib/{sequence.d.ts → sequence.d.mts} +127 -50
- package/lib/sequence.d.mts.map +1 -0
- package/lib/{sequence.js → sequence.mjs} +225 -152
- package/lib/sequence.mjs.map +1 -0
- package/lib/{sequenceDeltaEvent.d.ts → sequenceDeltaEvent.d.mts} +24 -7
- package/lib/sequenceDeltaEvent.d.mts.map +1 -0
- package/lib/{sequenceDeltaEvent.js → sequenceDeltaEvent.mjs} +20 -8
- package/lib/sequenceDeltaEvent.mjs.map +1 -0
- package/lib/sequenceFactory.d.mts +22 -0
- package/lib/sequenceFactory.d.mts.map +1 -0
- package/lib/sequenceFactory.mjs +51 -0
- package/lib/sequenceFactory.mjs.map +1 -0
- package/lib/{sharedIntervalCollection.d.ts → sharedIntervalCollection.d.mts} +12 -12
- package/lib/sharedIntervalCollection.d.mts.map +1 -0
- package/lib/{sharedIntervalCollection.js → sharedIntervalCollection.mjs} +16 -21
- package/lib/sharedIntervalCollection.mjs.map +1 -0
- package/lib/{sharedSequence.d.ts → sharedSequence.d.mts} +15 -3
- package/lib/sharedSequence.d.mts.map +1 -0
- package/lib/{sharedSequence.js → sharedSequence.mjs} +30 -23
- package/lib/sharedSequence.mjs.map +1 -0
- package/lib/{sharedString.d.ts → sharedString.d.mts} +60 -24
- package/lib/sharedString.d.mts.map +1 -0
- package/lib/sharedString.mjs +281 -0
- package/lib/sharedString.mjs.map +1 -0
- package/package.json +146 -75
- package/prettier.config.cjs +8 -0
- package/sequence.test-files.tar +0 -0
- package/src/defaultMap.ts +417 -403
- package/src/defaultMapInterfaces.ts +157 -117
- package/src/index.ts +86 -26
- package/src/intervalCollection.ts +2043 -1563
- package/src/intervalIndex/endpointInRangeIndex.ts +116 -0
- package/src/intervalIndex/endpointIndex.ts +91 -0
- package/src/intervalIndex/idIntervalIndex.ts +64 -0
- package/src/intervalIndex/index.ts +25 -0
- package/src/intervalIndex/intervalIndex.ts +32 -0
- package/src/intervalIndex/intervalIndexUtils.ts +27 -0
- package/src/intervalIndex/overlappingIntervalsIndex.ts +187 -0
- package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +80 -0
- package/src/intervalIndex/sequenceIntervalIndexes.ts +34 -0
- package/src/intervalIndex/startpointInRangeIndex.ts +114 -0
- package/src/intervalTree.ts +98 -0
- package/src/intervals/index.ts +25 -0
- package/src/intervals/interval.ts +238 -0
- package/src/intervals/intervalUtils.ts +288 -0
- package/src/intervals/sequenceInterval.ts +616 -0
- package/src/localValues.ts +68 -73
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +693 -0
- package/src/sequence.ts +845 -690
- package/src/sequenceDeltaEvent.ts +164 -131
- package/src/sequenceFactory.ts +58 -214
- package/src/sharedIntervalCollection.ts +161 -152
- package/src/sharedSequence.ts +181 -167
- package/src/sharedString.ts +390 -234
- package/tsc-multi.test.json +10 -0
- package/tsconfig.json +11 -13
- package/.editorconfig +0 -7
- package/.vscode/launch.json +0 -15
- package/dist/defaultMap.js.map +0 -1
- package/dist/defaultMapInterfaces.js.map +0 -1
- package/dist/index.js +0 -44
- package/dist/index.js.map +0 -1
- package/dist/intervalCollection.js +0 -1250
- package/dist/intervalCollection.js.map +0 -1
- package/dist/localValues.js.map +0 -1
- package/dist/packageVersion.js.map +0 -1
- package/dist/sequence.js.map +0 -1
- package/dist/sequenceDeltaEvent.js.map +0 -1
- package/dist/sequenceFactory.js +0 -192
- package/dist/sequenceFactory.js.map +0 -1
- package/dist/sharedIntervalCollection.js.map +0 -1
- package/dist/sharedNumberSequence.d.ts +0 -50
- package/dist/sharedNumberSequence.d.ts.map +0 -1
- package/dist/sharedNumberSequence.js +0 -61
- package/dist/sharedNumberSequence.js.map +0 -1
- package/dist/sharedObjectSequence.d.ts +0 -50
- package/dist/sharedObjectSequence.d.ts.map +0 -1
- package/dist/sharedObjectSequence.js +0 -61
- package/dist/sharedObjectSequence.js.map +0 -1
- package/dist/sharedSequence.js.map +0 -1
- package/dist/sharedString.js +0 -187
- package/dist/sharedString.js.map +0 -1
- package/dist/sparsematrix.d.ts +0 -139
- package/dist/sparsematrix.d.ts.map +0 -1
- package/dist/sparsematrix.js +0 -332
- package/dist/sparsematrix.js.map +0 -1
- package/lib/defaultMap.d.ts.map +0 -1
- package/lib/defaultMap.js.map +0 -1
- package/lib/defaultMapInterfaces.d.ts.map +0 -1
- package/lib/defaultMapInterfaces.js.map +0 -1
- package/lib/index.d.ts +0 -27
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -26
- package/lib/index.js.map +0 -1
- package/lib/intervalCollection.d.ts +0 -270
- package/lib/intervalCollection.d.ts.map +0 -1
- package/lib/intervalCollection.js +0 -1238
- package/lib/intervalCollection.js.map +0 -1
- package/lib/localValues.d.ts.map +0 -1
- package/lib/localValues.js.map +0 -1
- package/lib/packageVersion.js.map +0 -1
- package/lib/sequence.d.ts.map +0 -1
- package/lib/sequence.js.map +0 -1
- package/lib/sequenceDeltaEvent.d.ts.map +0 -1
- package/lib/sequenceDeltaEvent.js.map +0 -1
- package/lib/sequenceFactory.d.ts +0 -108
- package/lib/sequenceFactory.d.ts.map +0 -1
- package/lib/sequenceFactory.js +0 -186
- package/lib/sequenceFactory.js.map +0 -1
- package/lib/sharedIntervalCollection.d.ts.map +0 -1
- package/lib/sharedIntervalCollection.js.map +0 -1
- package/lib/sharedNumberSequence.d.ts +0 -50
- package/lib/sharedNumberSequence.d.ts.map +0 -1
- package/lib/sharedNumberSequence.js +0 -57
- package/lib/sharedNumberSequence.js.map +0 -1
- package/lib/sharedObjectSequence.d.ts +0 -50
- package/lib/sharedObjectSequence.d.ts.map +0 -1
- package/lib/sharedObjectSequence.js +0 -57
- package/lib/sharedObjectSequence.js.map +0 -1
- package/lib/sharedSequence.d.ts.map +0 -1
- package/lib/sharedSequence.js.map +0 -1
- package/lib/sharedString.d.ts.map +0 -1
- package/lib/sharedString.js +0 -183
- package/lib/sharedString.js.map +0 -1
- package/lib/sparsematrix.d.ts +0 -139
- package/lib/sparsematrix.d.ts.map +0 -1
- package/lib/sparsematrix.js +0 -323
- package/lib/sparsematrix.js.map +0 -1
- package/src/sharedNumberSequence.ts +0 -62
- package/src/sharedObjectSequence.ts +0 -62
- package/src/sparsematrix.ts +0 -421
- package/tsconfig.esnext.json +0 -7
|
@@ -4,99 +4,150 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/* eslint-disable no-bitwise */
|
|
7
|
+
/* eslint-disable import/no-deprecated */
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
10
|
+
import { assert } from "@fluidframework/core-utils";
|
|
11
|
+
import { IEvent } from "@fluidframework/core-interfaces";
|
|
11
12
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
ReferenceType,
|
|
28
|
-
refTypeIncludesFlag,
|
|
29
|
-
reservedRangeLabelsKey,
|
|
30
|
-
UnassignedSequenceNumber,
|
|
13
|
+
addProperties,
|
|
14
|
+
Client,
|
|
15
|
+
createMap,
|
|
16
|
+
getSlideToSegoff,
|
|
17
|
+
ISegment,
|
|
18
|
+
MergeTreeDeltaType,
|
|
19
|
+
PropertySet,
|
|
20
|
+
LocalReferencePosition,
|
|
21
|
+
ReferenceType,
|
|
22
|
+
refTypeIncludesFlag,
|
|
23
|
+
reservedRangeLabelsKey,
|
|
24
|
+
UnassignedSequenceNumber,
|
|
25
|
+
DetachedReferencePosition,
|
|
26
|
+
UniversalSequenceNumber,
|
|
27
|
+
SlidingPreference,
|
|
31
28
|
} from "@fluidframework/merge-tree";
|
|
32
29
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
33
|
-
import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
30
|
+
import { LoggingError, UsageError } from "@fluidframework/telemetry-utils";
|
|
34
31
|
import { v4 as uuid } from "uuid";
|
|
35
32
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
IMapMessageLocalMetadata,
|
|
34
|
+
IValueFactory,
|
|
35
|
+
IValueOpEmitter,
|
|
36
|
+
IValueOperation,
|
|
37
|
+
IValueType,
|
|
38
|
+
SequenceOptions,
|
|
42
39
|
} from "./defaultMapInterfaces";
|
|
40
|
+
import {
|
|
41
|
+
CompressedSerializedInterval,
|
|
42
|
+
IIntervalHelpers,
|
|
43
|
+
Interval,
|
|
44
|
+
IntervalOpType,
|
|
45
|
+
IntervalStickiness,
|
|
46
|
+
IntervalType,
|
|
47
|
+
ISerializableInterval,
|
|
48
|
+
ISerializedInterval,
|
|
49
|
+
SequenceInterval,
|
|
50
|
+
SerializedIntervalDelta,
|
|
51
|
+
createPositionReferenceFromSegoff,
|
|
52
|
+
endReferenceSlidingPreference,
|
|
53
|
+
startReferenceSlidingPreference,
|
|
54
|
+
sequenceIntervalHelpers,
|
|
55
|
+
createInterval,
|
|
56
|
+
} from "./intervals";
|
|
57
|
+
import {
|
|
58
|
+
EndpointIndex,
|
|
59
|
+
IEndpointIndex,
|
|
60
|
+
IIdIntervalIndex,
|
|
61
|
+
IOverlappingIntervalsIndex,
|
|
62
|
+
IntervalIndex,
|
|
63
|
+
OverlappingIntervalsIndex,
|
|
64
|
+
createIdIntervalIndex,
|
|
65
|
+
} from "./intervalIndex";
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Defines a position and side relative to a character in a sequence.
|
|
69
|
+
*
|
|
70
|
+
* For this purpose, sequences look like:
|
|
71
|
+
*
|
|
72
|
+
* `{start} - {character 0} - {character 1} - ... - {character N} - {end}`
|
|
73
|
+
*
|
|
74
|
+
* Each `{value}` in the diagram is a character within a sequence.
|
|
75
|
+
* Each `-` in the above diagram is a position where text could be inserted.
|
|
76
|
+
* Each position between a `{value}` and a `-` is a `SequencePlace`.
|
|
77
|
+
*
|
|
78
|
+
* The special endpoints `{start}` and `{end}` refer to positions outside the
|
|
79
|
+
* contents of the string.
|
|
80
|
+
*
|
|
81
|
+
* This gives us 2N + 2 possible positions to refer to within a string, where N
|
|
82
|
+
* is the number of characters.
|
|
83
|
+
*
|
|
84
|
+
* If the position is specified with a bare number, the side defaults to
|
|
85
|
+
* `Side.Before`.
|
|
86
|
+
*
|
|
87
|
+
* If a SequencePlace is the endpoint of a range (e.g. start/end of an interval or search range),
|
|
88
|
+
* the Side value means it is exclusive if it is nearer to the other position and inclusive if it is farther.
|
|
89
|
+
* E.g. the start of a range with Side.After is exclusive of the character at the position.
|
|
90
|
+
* @alpha
|
|
91
|
+
*/
|
|
92
|
+
export type SequencePlace = number | "start" | "end" | InteriorSequencePlace;
|
|
70
93
|
|
|
71
94
|
/**
|
|
72
|
-
* A
|
|
73
|
-
* as JSON. Intervals are of the format:
|
|
95
|
+
* A sequence place that does not refer to the special endpoint segments.
|
|
74
96
|
*
|
|
75
|
-
*
|
|
97
|
+
* See {@link SequencePlace} for additional context.
|
|
98
|
+
* @alpha
|
|
76
99
|
*/
|
|
77
|
-
export
|
|
100
|
+
export interface InteriorSequencePlace {
|
|
101
|
+
pos: number;
|
|
102
|
+
side: Side;
|
|
103
|
+
}
|
|
78
104
|
|
|
79
105
|
/**
|
|
80
|
-
*
|
|
106
|
+
* Defines a side relative to a character in a sequence.
|
|
107
|
+
*
|
|
108
|
+
* @remarks See {@link SequencePlace} for additional context on usage.
|
|
109
|
+
* @alpha
|
|
81
110
|
*/
|
|
111
|
+
export enum Side {
|
|
112
|
+
Before = 0,
|
|
113
|
+
After = 1,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const reservedIntervalIdKey = "intervalId";
|
|
117
|
+
|
|
82
118
|
export interface ISerializedIntervalCollectionV2 {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
label: string;
|
|
120
|
+
version: 2;
|
|
121
|
+
intervals: CompressedSerializedInterval[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function sidesFromStickiness(stickiness: IntervalStickiness) {
|
|
125
|
+
const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
|
|
126
|
+
const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
|
|
127
|
+
|
|
128
|
+
return { startSide, endSide };
|
|
86
129
|
}
|
|
87
130
|
|
|
88
131
|
/**
|
|
89
132
|
* Decompress an interval after loading a summary from JSON. The exact format
|
|
90
133
|
* of this compression is unspecified and subject to change
|
|
91
134
|
*/
|
|
92
|
-
function decompressInterval(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
135
|
+
function decompressInterval(
|
|
136
|
+
interval: CompressedSerializedInterval,
|
|
137
|
+
label?: string,
|
|
138
|
+
): ISerializedInterval {
|
|
139
|
+
const stickiness = interval[5] ?? IntervalStickiness.END;
|
|
140
|
+
const { startSide, endSide } = sidesFromStickiness(stickiness);
|
|
141
|
+
return {
|
|
142
|
+
start: interval[0],
|
|
143
|
+
end: interval[1],
|
|
144
|
+
sequenceNumber: interval[2],
|
|
145
|
+
intervalType: interval[3],
|
|
146
|
+
properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
|
|
147
|
+
stickiness,
|
|
148
|
+
startSide,
|
|
149
|
+
endSide,
|
|
150
|
+
};
|
|
100
151
|
}
|
|
101
152
|
|
|
102
153
|
/**
|
|
@@ -104,1544 +155,1973 @@ function decompressInterval(interval: CompressedSerializedInterval, label?: stri
|
|
|
104
155
|
* compression is unspecified and subject to change
|
|
105
156
|
*/
|
|
106
157
|
function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
const { start, end, sequenceNumber, intervalType, properties } = interval;
|
|
159
|
+
|
|
160
|
+
let base: CompressedSerializedInterval = [
|
|
161
|
+
start,
|
|
162
|
+
end,
|
|
163
|
+
sequenceNumber,
|
|
164
|
+
intervalType,
|
|
165
|
+
// remove the `referenceRangeLabels` property as it is already stored
|
|
166
|
+
// in the `label` field of the summary
|
|
167
|
+
{ ...properties, [reservedRangeLabelsKey]: undefined },
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
|
|
171
|
+
// reassignment to make it easier for typescript to reason about types
|
|
172
|
+
base = [...base, interval.stickiness];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return base;
|
|
118
176
|
}
|
|
119
177
|
|
|
120
|
-
export
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
178
|
+
export function endpointPosAndSide(
|
|
179
|
+
start: SequencePlace | undefined,
|
|
180
|
+
end: SequencePlace | undefined,
|
|
181
|
+
) {
|
|
182
|
+
const startIsPlainEndpoint = typeof start === "number" || start === "start" || start === "end";
|
|
183
|
+
const endIsPlainEndpoint = typeof end === "number" || end === "start" || end === "end";
|
|
184
|
+
|
|
185
|
+
const startSide = startIsPlainEndpoint ? Side.Before : start?.side;
|
|
186
|
+
const endSide = endIsPlainEndpoint ? Side.Before : end?.side;
|
|
187
|
+
|
|
188
|
+
const startPos = startIsPlainEndpoint ? start : start?.pos;
|
|
189
|
+
const endPos = endIsPlainEndpoint ? end : end?.pos;
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
startSide,
|
|
193
|
+
endSide,
|
|
194
|
+
startPos,
|
|
195
|
+
endPos,
|
|
196
|
+
};
|
|
127
197
|
}
|
|
128
198
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
create(label: string, start: number, end: number,
|
|
132
|
-
client: Client, intervalType?: IntervalType, op?: ISequencedDocumentMessage): TInterval;
|
|
199
|
+
function toSequencePlace(pos: number | "start" | "end", side: Side): SequencePlace {
|
|
200
|
+
return typeof pos === "number" ? { pos, side } : pos;
|
|
133
201
|
}
|
|
134
202
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
public start: number,
|
|
141
|
-
public end: number,
|
|
142
|
-
props?: PropertySet,
|
|
143
|
-
) {
|
|
144
|
-
this.propertyManager = new PropertiesManager();
|
|
145
|
-
this.properties = {};
|
|
146
|
-
|
|
147
|
-
if (props) {
|
|
148
|
-
this.addProperties(props);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
public getIntervalId(): string | undefined {
|
|
153
|
-
const id = this.properties?.[reservedIntervalIdKey];
|
|
154
|
-
if (id === undefined) {
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
return `${id}`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public getAdditionalPropertySets() {
|
|
161
|
-
return this.auxProps;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public addPropertySet(props: PropertySet) {
|
|
165
|
-
if (this.auxProps === undefined) {
|
|
166
|
-
this.auxProps = [];
|
|
167
|
-
}
|
|
168
|
-
this.auxProps.push(props);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public serialize(client: Client): ISerializedInterval {
|
|
172
|
-
let seq = 0;
|
|
173
|
-
if (client) {
|
|
174
|
-
seq = client.getCurrentSeq();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const serializedInterval: ISerializedInterval = {
|
|
178
|
-
end: this.end,
|
|
179
|
-
intervalType: 0,
|
|
180
|
-
sequenceNumber: seq,
|
|
181
|
-
start: this.start,
|
|
182
|
-
};
|
|
183
|
-
if (this.properties) {
|
|
184
|
-
serializedInterval.properties = this.properties;
|
|
185
|
-
}
|
|
186
|
-
return serializedInterval;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
public clone() {
|
|
190
|
-
return new Interval(this.start, this.end, this.properties);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
public compare(b: Interval) {
|
|
194
|
-
const startResult = this.compareStart(b);
|
|
195
|
-
if (startResult === 0) {
|
|
196
|
-
const endResult = this.compareEnd(b);
|
|
197
|
-
if (endResult === 0) {
|
|
198
|
-
const thisId = this.getIntervalId();
|
|
199
|
-
if (thisId) {
|
|
200
|
-
const bId = b.getIntervalId();
|
|
201
|
-
if (bId) {
|
|
202
|
-
return thisId > bId ? 1 : thisId < bId ? -1 : 0;
|
|
203
|
-
}
|
|
204
|
-
return 0;
|
|
205
|
-
}
|
|
206
|
-
return 0;
|
|
207
|
-
} else {
|
|
208
|
-
return endResult;
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
return startResult;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
public compareStart(b: Interval) {
|
|
216
|
-
return this.start - b.start;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
public compareEnd(b: Interval) {
|
|
220
|
-
return this.end - b.end;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
public overlaps(b: Interval) {
|
|
224
|
-
const result = (this.start <= b.end) &&
|
|
225
|
-
(this.end >= b.start);
|
|
226
|
-
return result;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
public union(b: Interval) {
|
|
230
|
-
return new Interval(Math.min(this.start, b.start),
|
|
231
|
-
Math.max(this.end, b.end), this.properties);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
public getProperties() {
|
|
235
|
-
return this.properties;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
public addProperties(
|
|
239
|
-
newProps: PropertySet,
|
|
240
|
-
collaborating: boolean = false,
|
|
241
|
-
seq?: number,
|
|
242
|
-
op?: ICombiningOp,
|
|
243
|
-
): PropertySet | undefined {
|
|
244
|
-
if (newProps) {
|
|
245
|
-
this.initializeProperties();
|
|
246
|
-
return this.propertyManager.addProperties(this.properties, newProps, op, seq, collaborating);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
|
|
251
|
-
const startPos = start ?? this.start;
|
|
252
|
-
const endPos = end ?? this.end;
|
|
253
|
-
if (this.start === startPos && this.end === endPos) {
|
|
254
|
-
// Return undefined to indicate that no change is necessary.
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const newInterval = new Interval(startPos, endPos);
|
|
258
|
-
if (this.properties) {
|
|
259
|
-
newInterval.initializeProperties();
|
|
260
|
-
this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
|
|
261
|
-
}
|
|
262
|
-
return newInterval;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private initializeProperties(): void {
|
|
266
|
-
if (!this.propertyManager) {
|
|
267
|
-
this.propertyManager = new PropertiesManager();
|
|
268
|
-
}
|
|
269
|
-
if (!this.properties) {
|
|
270
|
-
this.properties = createMap<any>();
|
|
271
|
-
}
|
|
272
|
-
}
|
|
203
|
+
function toOptionalSequencePlace(
|
|
204
|
+
pos: number | "start" | "end" | undefined,
|
|
205
|
+
side: Side = Side.Before,
|
|
206
|
+
): SequencePlace | undefined {
|
|
207
|
+
return typeof pos === "number" ? { pos, side } : pos;
|
|
273
208
|
}
|
|
274
209
|
|
|
275
|
-
export
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
public intervalType: IntervalType,
|
|
283
|
-
props?: PropertySet,
|
|
284
|
-
) {
|
|
285
|
-
this.propertyManager = new PropertiesManager();
|
|
286
|
-
this.properties = {};
|
|
287
|
-
|
|
288
|
-
if (props) {
|
|
289
|
-
this.addProperties(props);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private callbacks?: Record<"beforePositionChange" | "afterPositionChange", () => void>;
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* @internal
|
|
297
|
-
* Subscribes to position change events on this interval if there are no current listeners.
|
|
298
|
-
*/
|
|
299
|
-
public addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void {
|
|
300
|
-
if (this.callbacks === undefined) {
|
|
301
|
-
this.callbacks = {
|
|
302
|
-
beforePositionChange,
|
|
303
|
-
afterPositionChange,
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const startCbs = this.start.callbacks ??= {};
|
|
307
|
-
const endCbs = this.end.callbacks ??= {};
|
|
308
|
-
startCbs.beforeSlide = endCbs.beforeSlide = beforePositionChange;
|
|
309
|
-
startCbs.afterSlide = endCbs.afterSlide = afterPositionChange;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* @internal
|
|
315
|
-
* Removes the currently subscribed position change listeners.
|
|
316
|
-
*/
|
|
317
|
-
public removePositionChangeListeners(): void {
|
|
318
|
-
if (this.callbacks) {
|
|
319
|
-
this.callbacks = undefined;
|
|
320
|
-
this.start.callbacks = undefined;
|
|
321
|
-
this.end.callbacks = undefined;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
public serialize(client: Client): ISerializedInterval {
|
|
326
|
-
const startPosition = this.start.toPosition();
|
|
327
|
-
const endPosition = this.end.toPosition();
|
|
328
|
-
const serializedInterval: ISerializedInterval = {
|
|
329
|
-
end: endPosition,
|
|
330
|
-
intervalType: this.intervalType,
|
|
331
|
-
sequenceNumber: client.getCurrentSeq(),
|
|
332
|
-
start: startPosition,
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
if (this.properties) {
|
|
336
|
-
serializedInterval.properties = this.properties;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return serializedInterval;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
public clone() {
|
|
343
|
-
return new SequenceInterval(this.start, this.end, this.intervalType, this.properties);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
public compare(b: SequenceInterval) {
|
|
347
|
-
const startResult = this.compareStart(b);
|
|
348
|
-
if (startResult === 0) {
|
|
349
|
-
const endResult = this.compareEnd(b);
|
|
350
|
-
if (endResult === 0) {
|
|
351
|
-
const thisId = this.getIntervalId();
|
|
352
|
-
if (thisId) {
|
|
353
|
-
const bId = b.getIntervalId();
|
|
354
|
-
if (bId) {
|
|
355
|
-
return thisId > bId ? 1 : thisId < bId ? -1 : 0;
|
|
356
|
-
}
|
|
357
|
-
return 0;
|
|
358
|
-
}
|
|
359
|
-
return 0;
|
|
360
|
-
} else {
|
|
361
|
-
return endResult;
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
return startResult;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
public compareStart(b: SequenceInterval) {
|
|
369
|
-
return this.start.compare(b.start);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
public compareEnd(b: SequenceInterval) {
|
|
373
|
-
return this.end.compare(b.end);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
public overlaps(b: SequenceInterval) {
|
|
377
|
-
const result = (this.start.compare(b.end) <= 0) &&
|
|
378
|
-
(this.end.compare(b.start) >= 0);
|
|
379
|
-
return result;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
public getIntervalId(): string | undefined {
|
|
383
|
-
const id = this.properties?.[reservedIntervalIdKey];
|
|
384
|
-
if (id === undefined) {
|
|
385
|
-
return undefined;
|
|
386
|
-
}
|
|
387
|
-
return `${id}`;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
public union(b: SequenceInterval) {
|
|
391
|
-
return new SequenceInterval(this.start.min(b.start),
|
|
392
|
-
this.end.max(b.end), this.intervalType);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
public addProperties(
|
|
396
|
-
newProps: PropertySet,
|
|
397
|
-
collab: boolean = false,
|
|
398
|
-
seq?: number,
|
|
399
|
-
op?: ICombiningOp,
|
|
400
|
-
): PropertySet | undefined {
|
|
401
|
-
this.initializeProperties();
|
|
402
|
-
return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
public overlapsPos(bstart: number, bend: number) {
|
|
406
|
-
const startPos = this.start.toPosition();
|
|
407
|
-
const endPos = this.start.toPosition();
|
|
408
|
-
return (endPos > bstart) && (startPos < bend);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
|
|
412
|
-
const getRefType = (baseType: ReferenceType): ReferenceType => {
|
|
413
|
-
let refType = baseType;
|
|
414
|
-
if (op === undefined) {
|
|
415
|
-
refType &= ~ReferenceType.SlideOnRemove;
|
|
416
|
-
refType |= ReferenceType.StayOnRemove;
|
|
417
|
-
}
|
|
418
|
-
return refType;
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
let startRef = this.start;
|
|
422
|
-
if (start !== undefined) {
|
|
423
|
-
startRef = createPositionReference(this.start.getClient(), start, getRefType(this.start.refType), op);
|
|
424
|
-
startRef.addProperties(this.start.properties);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
let endRef = this.end;
|
|
428
|
-
if (end !== undefined) {
|
|
429
|
-
endRef = createPositionReference(this.end.getClient(), end, getRefType(this.end.refType), op);
|
|
430
|
-
endRef.addProperties(this.end.properties);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
startRef.pairedRef = endRef;
|
|
434
|
-
endRef.pairedRef = startRef;
|
|
435
|
-
|
|
436
|
-
const newInterval = new SequenceInterval(startRef, endRef, this.intervalType);
|
|
437
|
-
if (this.properties) {
|
|
438
|
-
newInterval.initializeProperties();
|
|
439
|
-
this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
|
|
440
|
-
}
|
|
441
|
-
return newInterval;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private initializeProperties(): void {
|
|
445
|
-
if (!this.propertyManager) {
|
|
446
|
-
this.propertyManager = new PropertiesManager();
|
|
447
|
-
}
|
|
448
|
-
if (!this.properties) {
|
|
449
|
-
this.properties = createMap<any>();
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function createPositionReferenceFromSegoff(
|
|
455
|
-
client: Client,
|
|
456
|
-
segoff: { segment: ISegment | undefined; offset: number | undefined; },
|
|
457
|
-
refType: ReferenceType,
|
|
458
|
-
op?: ISequencedDocumentMessage): LocalReference {
|
|
459
|
-
if (segoff.segment) {
|
|
460
|
-
const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
|
|
461
|
-
return ref as LocalReference;
|
|
462
|
-
} else {
|
|
463
|
-
if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
|
|
464
|
-
throw new UsageError("Non-transient references need segment");
|
|
465
|
-
}
|
|
466
|
-
return new LocalReference(client, undefined, 0, refType);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
210
|
+
export function computeStickinessFromSide(
|
|
211
|
+
startPos: number | "start" | "end" | undefined = -1,
|
|
212
|
+
startSide: Side = Side.Before,
|
|
213
|
+
endPos: number | "start" | "end" | undefined = -1,
|
|
214
|
+
endSide: Side = Side.Before,
|
|
215
|
+
): IntervalStickiness {
|
|
216
|
+
let stickiness: IntervalStickiness = IntervalStickiness.NONE;
|
|
469
217
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
refType: ReferenceType,
|
|
474
|
-
op?: ISequencedDocumentMessage): LocalReference {
|
|
475
|
-
let segoff;
|
|
476
|
-
if (op) {
|
|
477
|
-
assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
|
|
478
|
-
segoff = client.getContainingSegment(pos, op);
|
|
479
|
-
segoff = client.getSlideToSegment(segoff);
|
|
480
|
-
} else {
|
|
481
|
-
assert((refType & ReferenceType.SlideOnRemove) === 0, 0x2f6 /* SlideOnRemove references must be op created */);
|
|
482
|
-
segoff = client.getContainingSegment(pos);
|
|
483
|
-
}
|
|
484
|
-
return createPositionReferenceFromSegoff(client, segoff, refType, op);
|
|
485
|
-
}
|
|
218
|
+
if (startSide === Side.After || startPos === "start") {
|
|
219
|
+
stickiness |= IntervalStickiness.START;
|
|
220
|
+
}
|
|
486
221
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
end: number,
|
|
491
|
-
client: Client,
|
|
492
|
-
intervalType?: IntervalType,
|
|
493
|
-
op?: ISequencedDocumentMessage): SequenceInterval {
|
|
494
|
-
let beginRefType = ReferenceType.RangeBegin;
|
|
495
|
-
let endRefType = ReferenceType.RangeEnd;
|
|
496
|
-
if (intervalType === IntervalType.Transient) {
|
|
497
|
-
beginRefType = ReferenceType.Transient;
|
|
498
|
-
endRefType = ReferenceType.Transient;
|
|
499
|
-
} else {
|
|
500
|
-
if (intervalType === IntervalType.Nest) {
|
|
501
|
-
beginRefType = ReferenceType.NestBegin;
|
|
502
|
-
endRefType = ReferenceType.NestEnd;
|
|
503
|
-
}
|
|
504
|
-
// All non-transient interval references must eventually be SlideOnRemove
|
|
505
|
-
// To ensure eventual consistency, they must start as StayOnRemove when
|
|
506
|
-
// pending (created locally and creation op is not acked)
|
|
507
|
-
if (op) {
|
|
508
|
-
beginRefType |= ReferenceType.SlideOnRemove;
|
|
509
|
-
endRefType |= ReferenceType.SlideOnRemove;
|
|
510
|
-
} else {
|
|
511
|
-
beginRefType |= ReferenceType.StayOnRemove;
|
|
512
|
-
endRefType |= ReferenceType.StayOnRemove;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const startLref = createPositionReference(client, start, beginRefType, op);
|
|
517
|
-
const endLref = createPositionReference(client, end, endRefType, op);
|
|
518
|
-
startLref.pairedRef = endLref;
|
|
519
|
-
endLref.pairedRef = startLref;
|
|
520
|
-
const rangeProp = {
|
|
521
|
-
[reservedRangeLabelsKey]: [label],
|
|
522
|
-
};
|
|
523
|
-
startLref.addProperties(rangeProp);
|
|
524
|
-
endLref.addProperties(rangeProp);
|
|
525
|
-
|
|
526
|
-
const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
|
|
527
|
-
return ival;
|
|
528
|
-
}
|
|
222
|
+
if (endSide === Side.Before || endPos === "end") {
|
|
223
|
+
stickiness |= IntervalStickiness.END;
|
|
224
|
+
}
|
|
529
225
|
|
|
530
|
-
|
|
531
|
-
a.addPropertySet(b.properties);
|
|
532
|
-
return a;
|
|
226
|
+
return stickiness as IntervalStickiness;
|
|
533
227
|
}
|
|
534
228
|
|
|
535
|
-
export function createIntervalIndex(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (conflict) {
|
|
542
|
-
lc.addConflictResolver(conflict);
|
|
543
|
-
} else {
|
|
544
|
-
lc.addConflictResolver(defaultIntervalConflictResolver);
|
|
545
|
-
}
|
|
546
|
-
return lc;
|
|
229
|
+
export function createIntervalIndex() {
|
|
230
|
+
const helpers: IIntervalHelpers<Interval> = {
|
|
231
|
+
create: createInterval,
|
|
232
|
+
};
|
|
233
|
+
const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers, {});
|
|
234
|
+
return lc;
|
|
547
235
|
}
|
|
548
236
|
|
|
549
237
|
export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
private addIntervalToIndex(interval: TInterval) {
|
|
788
|
-
const id = interval.getIntervalId();
|
|
789
|
-
assert(id !== undefined, 0x2c0 /* "ID must be created before adding interval to collection" */);
|
|
790
|
-
// Make the ID immutable.
|
|
791
|
-
Object.defineProperty(interval.properties, reservedIntervalIdKey, {
|
|
792
|
-
configurable: false,
|
|
793
|
-
enumerable: true,
|
|
794
|
-
writable: false,
|
|
795
|
-
});
|
|
796
|
-
this.intervalTree.put(interval, this.conflictResolver);
|
|
797
|
-
this.endIntervalTree.put(interval, interval, this.endConflictResolver);
|
|
798
|
-
this.intervalIdMap.set(id, interval);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
public add(interval: TInterval) {
|
|
802
|
-
this.addIntervalToIndex(interval);
|
|
803
|
-
this.addIntervalListeners(interval);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
public getIntervalById(id: string) {
|
|
807
|
-
return this.intervalIdMap.get(id);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
public changeInterval(interval: TInterval, start: number, end: number, op?: ISequencedDocumentMessage) {
|
|
811
|
-
const newInterval = interval.modify(this.label, start, end, op) as TInterval | undefined;
|
|
812
|
-
if (newInterval) {
|
|
813
|
-
this.removeExistingInterval(interval);
|
|
814
|
-
this.add(newInterval);
|
|
815
|
-
}
|
|
816
|
-
return newInterval;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
public serialize(): ISerializedIntervalCollectionV2 {
|
|
820
|
-
const client = this.client;
|
|
821
|
-
const intervals = this.intervalTree.intervals.keys();
|
|
822
|
-
|
|
823
|
-
return {
|
|
824
|
-
label: this.label,
|
|
825
|
-
intervals: intervals.map((interval) => compressInterval(interval.serialize(client))),
|
|
826
|
-
version: 2,
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
private addIntervalListeners(interval: TInterval) {
|
|
831
|
-
if (interval instanceof SequenceInterval) {
|
|
832
|
-
interval.addPositionChangeListeners(
|
|
833
|
-
() => this.removeIntervalFromIndex(interval),
|
|
834
|
-
() => {
|
|
835
|
-
this.addIntervalToIndex(interval);
|
|
836
|
-
this.onPositionChange?.(interval);
|
|
837
|
-
},
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
private removeIntervalListeners(interval: TInterval) {
|
|
843
|
-
if (interval instanceof SequenceInterval) {
|
|
844
|
-
interval.removePositionChangeListeners();
|
|
845
|
-
}
|
|
846
|
-
}
|
|
238
|
+
private static readonly legacyIdPrefix = "legacy";
|
|
239
|
+
public readonly overlappingIntervalsIndex: IOverlappingIntervalsIndex<TInterval>;
|
|
240
|
+
public readonly idIntervalIndex: IIdIntervalIndex<TInterval>;
|
|
241
|
+
public readonly endIntervalIndex: IEndpointIndex<TInterval>;
|
|
242
|
+
private readonly indexes: Set<IntervalIndex<TInterval>>;
|
|
243
|
+
|
|
244
|
+
constructor(
|
|
245
|
+
private readonly client: Client,
|
|
246
|
+
private readonly label: string,
|
|
247
|
+
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
248
|
+
private readonly options: Partial<SequenceOptions>,
|
|
249
|
+
/** Callback invoked each time one of the endpoints of an interval slides. */
|
|
250
|
+
private readonly onPositionChange?: (
|
|
251
|
+
interval: TInterval,
|
|
252
|
+
previousInterval: TInterval,
|
|
253
|
+
) => void,
|
|
254
|
+
) {
|
|
255
|
+
this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
|
|
256
|
+
this.idIntervalIndex = createIdIntervalIndex<TInterval>();
|
|
257
|
+
this.endIntervalIndex = new EndpointIndex(client, helpers);
|
|
258
|
+
this.indexes = new Set([
|
|
259
|
+
this.overlappingIntervalsIndex,
|
|
260
|
+
this.idIntervalIndex,
|
|
261
|
+
this.endIntervalIndex,
|
|
262
|
+
]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public createLegacyId(start: number | "start" | "end", end: number | "start" | "end"): string {
|
|
266
|
+
// Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
|
|
267
|
+
// without ID's.
|
|
268
|
+
return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Validates that a serialized interval has the ID property. Creates an ID
|
|
273
|
+
* if one does not already exist
|
|
274
|
+
*
|
|
275
|
+
* @param serializedInterval - The interval to be checked
|
|
276
|
+
* @returns The interval's existing or newly created id
|
|
277
|
+
*/
|
|
278
|
+
public ensureSerializedId(serializedInterval: ISerializedInterval): string {
|
|
279
|
+
let id: string | undefined = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
280
|
+
if (id === undefined) {
|
|
281
|
+
// Back-compat: 0.39 and earlier did not have IDs on intervals. If an interval from such a client
|
|
282
|
+
// comes over the wire, create a non-unique one based on start/end.
|
|
283
|
+
// This will allow all clients to refer to this interval consistently.
|
|
284
|
+
id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
|
|
285
|
+
const newProps = {
|
|
286
|
+
[reservedIntervalIdKey]: id,
|
|
287
|
+
};
|
|
288
|
+
serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
|
|
289
|
+
}
|
|
290
|
+
// Make the ID immutable for safety's sake.
|
|
291
|
+
Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
|
|
292
|
+
configurable: false,
|
|
293
|
+
enumerable: true,
|
|
294
|
+
writable: false,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return id;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private removeIntervalFromIndexes(interval: TInterval) {
|
|
301
|
+
for (const index of this.indexes) {
|
|
302
|
+
index.remove(interval);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public appendIndex(index: IntervalIndex<TInterval>) {
|
|
307
|
+
this.indexes.add(index);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public removeIndex(index: IntervalIndex<TInterval>): boolean {
|
|
311
|
+
return this.indexes.delete(index);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public removeExistingInterval(interval: TInterval) {
|
|
315
|
+
this.removeIntervalFromIndexes(interval);
|
|
316
|
+
this.removeIntervalListeners(interval);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public createInterval(
|
|
320
|
+
start: SequencePlace,
|
|
321
|
+
end: SequencePlace,
|
|
322
|
+
intervalType: IntervalType,
|
|
323
|
+
op?: ISequencedDocumentMessage,
|
|
324
|
+
): TInterval {
|
|
325
|
+
return this.helpers.create(
|
|
326
|
+
this.label,
|
|
327
|
+
start,
|
|
328
|
+
end,
|
|
329
|
+
this.client,
|
|
330
|
+
intervalType,
|
|
331
|
+
op,
|
|
332
|
+
undefined,
|
|
333
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public addInterval(
|
|
338
|
+
start: SequencePlace,
|
|
339
|
+
end: SequencePlace,
|
|
340
|
+
intervalType: IntervalType,
|
|
341
|
+
props?: PropertySet,
|
|
342
|
+
op?: ISequencedDocumentMessage,
|
|
343
|
+
) {
|
|
344
|
+
const interval: TInterval = this.createInterval(start, end, intervalType, op);
|
|
345
|
+
if (interval) {
|
|
346
|
+
if (!interval.properties) {
|
|
347
|
+
interval.properties = createMap<any>();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (props) {
|
|
351
|
+
// This check is intended to prevent scenarios where a random interval is created and then
|
|
352
|
+
// inserted into a collection. The aim is to ensure that the collection is created first
|
|
353
|
+
// then the user can create/add intervals based on the collection
|
|
354
|
+
if (
|
|
355
|
+
props[reservedRangeLabelsKey] !== undefined &&
|
|
356
|
+
props[reservedRangeLabelsKey][0] !== this.label
|
|
357
|
+
) {
|
|
358
|
+
throw new LoggingError(
|
|
359
|
+
"Adding an interval that belongs to another interval collection is not permitted",
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
interval.addProperties(props);
|
|
363
|
+
}
|
|
364
|
+
interval.properties[reservedIntervalIdKey] ??= uuid();
|
|
365
|
+
this.add(interval);
|
|
366
|
+
}
|
|
367
|
+
return interval;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private linkEndpointsToInterval(interval: TInterval): void {
|
|
371
|
+
if (interval instanceof SequenceInterval) {
|
|
372
|
+
interval.start.addProperties({ interval });
|
|
373
|
+
interval.end.addProperties({ interval });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private addIntervalToIndexes(interval: TInterval) {
|
|
378
|
+
for (const index of this.indexes) {
|
|
379
|
+
index.add(interval);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
public add(interval: TInterval): void {
|
|
384
|
+
this.linkEndpointsToInterval(interval);
|
|
385
|
+
this.addIntervalToIndexes(interval);
|
|
386
|
+
this.addIntervalListeners(interval);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public changeInterval(
|
|
390
|
+
interval: TInterval,
|
|
391
|
+
start: SequencePlace | undefined,
|
|
392
|
+
end: SequencePlace | undefined,
|
|
393
|
+
op?: ISequencedDocumentMessage,
|
|
394
|
+
localSeq?: number,
|
|
395
|
+
) {
|
|
396
|
+
const newInterval = interval.modify(
|
|
397
|
+
this.label,
|
|
398
|
+
start,
|
|
399
|
+
end,
|
|
400
|
+
op,
|
|
401
|
+
localSeq,
|
|
402
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
403
|
+
) as TInterval | undefined;
|
|
404
|
+
if (newInterval) {
|
|
405
|
+
this.removeExistingInterval(interval);
|
|
406
|
+
this.add(newInterval);
|
|
407
|
+
}
|
|
408
|
+
return newInterval;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
public serialize(): ISerializedIntervalCollectionV2 {
|
|
412
|
+
return {
|
|
413
|
+
label: this.label,
|
|
414
|
+
intervals: Array.from(this.idIntervalIndex, (interval) =>
|
|
415
|
+
compressInterval(interval.serialize()),
|
|
416
|
+
),
|
|
417
|
+
version: 2,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private addIntervalListeners(interval: TInterval) {
|
|
422
|
+
const cloneRef = (ref: LocalReferencePosition) => {
|
|
423
|
+
const segment = ref.getSegment();
|
|
424
|
+
if (segment === undefined) {
|
|
425
|
+
// Cloning is unnecessary: refs which have slid off the string entirely
|
|
426
|
+
// never get slid back on. Creation code for refs doesn't accept undefined segment
|
|
427
|
+
// either, so this must be special-cased.
|
|
428
|
+
return ref;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return this.client.createLocalReferencePosition(
|
|
432
|
+
segment,
|
|
433
|
+
ref.getOffset(),
|
|
434
|
+
ReferenceType.Transient,
|
|
435
|
+
ref.properties,
|
|
436
|
+
ref.slidingPreference,
|
|
437
|
+
ref.canSlideToEndpoint,
|
|
438
|
+
);
|
|
439
|
+
};
|
|
440
|
+
if (interval instanceof SequenceInterval) {
|
|
441
|
+
let previousInterval: (TInterval & SequenceInterval) | undefined;
|
|
442
|
+
let pendingChanges = 0;
|
|
443
|
+
interval.addPositionChangeListeners(
|
|
444
|
+
() => {
|
|
445
|
+
pendingChanges++;
|
|
446
|
+
// Note: both start and end can change and invoke beforeSlide on each endpoint before afterSlide.
|
|
447
|
+
if (!previousInterval) {
|
|
448
|
+
previousInterval = interval.clone() as TInterval & SequenceInterval;
|
|
449
|
+
previousInterval.start = cloneRef(previousInterval.start);
|
|
450
|
+
previousInterval.end = cloneRef(previousInterval.end);
|
|
451
|
+
this.removeIntervalFromIndexes(interval);
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
() => {
|
|
455
|
+
assert(
|
|
456
|
+
previousInterval !== undefined,
|
|
457
|
+
0x3fa /* Invalid interleaving of before/after slide */,
|
|
458
|
+
);
|
|
459
|
+
pendingChanges--;
|
|
460
|
+
if (pendingChanges === 0) {
|
|
461
|
+
this.addIntervalToIndexes(interval);
|
|
462
|
+
this.onPositionChange?.(interval, previousInterval);
|
|
463
|
+
previousInterval = undefined;
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private removeIntervalListeners(interval: TInterval) {
|
|
471
|
+
if (interval instanceof SequenceInterval) {
|
|
472
|
+
interval.removePositionChangeListeners();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
847
475
|
}
|
|
848
476
|
|
|
849
|
-
const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number => a.end.compare(b.end);
|
|
850
|
-
|
|
851
477
|
class SequenceIntervalCollectionFactory
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
478
|
+
implements IValueFactory<IntervalCollection<SequenceInterval>>
|
|
479
|
+
{
|
|
480
|
+
public load(
|
|
481
|
+
emitter: IValueOpEmitter,
|
|
482
|
+
raw: ISerializedInterval[] | ISerializedIntervalCollectionV2 = [],
|
|
483
|
+
options?: Partial<SequenceOptions>,
|
|
484
|
+
): IntervalCollection<SequenceInterval> {
|
|
485
|
+
return new IntervalCollection<SequenceInterval>(
|
|
486
|
+
sequenceIntervalHelpers,
|
|
487
|
+
true,
|
|
488
|
+
emitter,
|
|
489
|
+
raw,
|
|
490
|
+
options,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
public store(
|
|
495
|
+
value: IntervalCollection<SequenceInterval>,
|
|
496
|
+
): ISerializedInterval[] | ISerializedIntervalCollectionV2 {
|
|
497
|
+
return value.serializeInternal();
|
|
498
|
+
}
|
|
867
499
|
}
|
|
868
500
|
|
|
869
501
|
export class SequenceIntervalCollectionValueType
|
|
870
|
-
|
|
871
|
-
|
|
502
|
+
implements IValueType<IntervalCollection<SequenceInterval>>
|
|
503
|
+
{
|
|
504
|
+
public static Name = "sharedStringIntervalCollection";
|
|
505
|
+
|
|
506
|
+
public get name(): string {
|
|
507
|
+
return SequenceIntervalCollectionValueType.Name;
|
|
508
|
+
}
|
|
872
509
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
510
|
+
public get factory(): IValueFactory<IntervalCollection<SequenceInterval>> {
|
|
511
|
+
return SequenceIntervalCollectionValueType._factory;
|
|
512
|
+
}
|
|
876
513
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
514
|
+
public get ops(): Map<IntervalOpType, IValueOperation<IntervalCollection<SequenceInterval>>> {
|
|
515
|
+
return SequenceIntervalCollectionValueType._ops;
|
|
516
|
+
}
|
|
880
517
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
518
|
+
private static readonly _factory: IValueFactory<IntervalCollection<SequenceInterval>> =
|
|
519
|
+
new SequenceIntervalCollectionFactory();
|
|
884
520
|
|
|
885
|
-
|
|
886
|
-
|
|
521
|
+
private static readonly _ops = makeOpsMap<SequenceInterval>();
|
|
522
|
+
}
|
|
887
523
|
|
|
888
|
-
|
|
524
|
+
class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Interval>> {
|
|
525
|
+
public load(
|
|
526
|
+
emitter: IValueOpEmitter,
|
|
527
|
+
raw: ISerializedInterval[] | ISerializedIntervalCollectionV2 = [],
|
|
528
|
+
options?: Partial<SequenceOptions>,
|
|
529
|
+
): IntervalCollection<Interval> {
|
|
530
|
+
const helpers: IIntervalHelpers<Interval> = {
|
|
531
|
+
create: createInterval,
|
|
532
|
+
};
|
|
533
|
+
const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
|
|
534
|
+
collection.attachGraph(undefined as any as Client, "");
|
|
535
|
+
return collection;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
public store(value: IntervalCollection<Interval>): ISerializedIntervalCollectionV2 {
|
|
539
|
+
return value.serializeInternal();
|
|
540
|
+
}
|
|
889
541
|
}
|
|
890
542
|
|
|
891
|
-
|
|
543
|
+
export class IntervalCollectionValueType implements IValueType<IntervalCollection<Interval>> {
|
|
544
|
+
public static Name = "sharedIntervalCollection";
|
|
545
|
+
|
|
546
|
+
public get name(): string {
|
|
547
|
+
return IntervalCollectionValueType.Name;
|
|
548
|
+
}
|
|
892
549
|
|
|
893
|
-
|
|
894
|
-
|
|
550
|
+
public get factory(): IValueFactory<IntervalCollection<Interval>> {
|
|
551
|
+
return IntervalCollectionValueType._factory;
|
|
552
|
+
}
|
|
895
553
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
554
|
+
public get ops(): Map<IntervalOpType, IValueOperation<IntervalCollection<Interval>>> {
|
|
555
|
+
return IntervalCollectionValueType._ops;
|
|
556
|
+
}
|
|
899
557
|
|
|
900
|
-
|
|
558
|
+
private static readonly _factory: IValueFactory<IntervalCollection<Interval>> =
|
|
559
|
+
new IntervalCollectionFactory();
|
|
560
|
+
private static readonly _ops = makeOpsMap<Interval>();
|
|
901
561
|
}
|
|
902
562
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
563
|
+
export function makeOpsMap<T extends ISerializableInterval>(): Map<
|
|
564
|
+
IntervalOpType,
|
|
565
|
+
IValueOperation<IntervalCollection<T>>
|
|
566
|
+
> {
|
|
567
|
+
const rebase: IValueOperation<IntervalCollection<T>>["rebase"] = (
|
|
568
|
+
collection,
|
|
569
|
+
op,
|
|
570
|
+
localOpMetadata,
|
|
571
|
+
) => {
|
|
572
|
+
const { localSeq } = localOpMetadata;
|
|
573
|
+
const rebasedValue = collection.rebaseLocalInterval(op.opName, op.value, localSeq);
|
|
574
|
+
if (rebasedValue === undefined) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
const rebasedOp = { ...op, value: rebasedValue };
|
|
578
|
+
return { rebasedOp, rebasedLocalOpMetadata: localOpMetadata };
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
return new Map<IntervalOpType, IValueOperation<IntervalCollection<T>>>([
|
|
582
|
+
[
|
|
583
|
+
IntervalOpType.ADD,
|
|
584
|
+
{
|
|
585
|
+
process: (collection, params, local, op, localOpMetadata) => {
|
|
586
|
+
// if params is undefined, the interval was deleted during
|
|
587
|
+
// rebasing
|
|
588
|
+
if (!params) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
assert(op !== undefined, 0x3fb /* op should exist here */);
|
|
592
|
+
collection.ackAdd(params, local, op, localOpMetadata);
|
|
593
|
+
},
|
|
594
|
+
rebase,
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
[
|
|
598
|
+
IntervalOpType.DELETE,
|
|
599
|
+
{
|
|
600
|
+
process: (collection, params, local, op) => {
|
|
601
|
+
assert(op !== undefined, 0x3fc /* op should exist here */);
|
|
602
|
+
collection.ackDelete(params, local, op);
|
|
603
|
+
},
|
|
604
|
+
rebase: (collection, op, localOpMetadata) => {
|
|
605
|
+
// Deletion of intervals is based on id, so requires no rebasing.
|
|
606
|
+
return { rebasedOp: op, rebasedLocalOpMetadata: localOpMetadata };
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
[
|
|
611
|
+
IntervalOpType.CHANGE,
|
|
612
|
+
{
|
|
613
|
+
process: (collection, params, local, op, localOpMetadata) => {
|
|
614
|
+
// if params is undefined, the interval was deleted during
|
|
615
|
+
// rebasing
|
|
616
|
+
if (!params) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
assert(op !== undefined, 0x3fd /* op should exist here */);
|
|
620
|
+
collection.ackChange(params, local, op, localOpMetadata);
|
|
621
|
+
},
|
|
622
|
+
rebase,
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
]);
|
|
921
626
|
}
|
|
922
627
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
628
|
+
/**
|
|
629
|
+
* @alpha
|
|
630
|
+
*/
|
|
631
|
+
export type DeserializeCallback = (properties: PropertySet) => void;
|
|
926
632
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
633
|
+
class IntervalCollectionIterator<TInterval extends ISerializableInterval>
|
|
634
|
+
implements Iterator<TInterval>
|
|
635
|
+
{
|
|
636
|
+
private readonly results: TInterval[];
|
|
637
|
+
private index: number;
|
|
638
|
+
|
|
639
|
+
constructor(
|
|
640
|
+
collection: IntervalCollection<TInterval>,
|
|
641
|
+
iteratesForward: boolean = true,
|
|
642
|
+
start?: number,
|
|
643
|
+
end?: number,
|
|
644
|
+
) {
|
|
645
|
+
this.results = [];
|
|
646
|
+
this.index = 0;
|
|
647
|
+
|
|
648
|
+
collection.gatherIterationResults(this.results, iteratesForward, start, end);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
public next(): IteratorResult<TInterval> {
|
|
652
|
+
if (this.index < this.results.length) {
|
|
653
|
+
return {
|
|
654
|
+
value: this.results[this.index++],
|
|
655
|
+
done: false,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
value: undefined,
|
|
661
|
+
done: true,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
}
|
|
930
665
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
666
|
+
/**
|
|
667
|
+
* Change events emitted by `IntervalCollection`s
|
|
668
|
+
* @alpha
|
|
669
|
+
*/
|
|
670
|
+
export interface IIntervalCollectionEvent<TInterval extends ISerializableInterval> extends IEvent {
|
|
671
|
+
/**
|
|
672
|
+
* This event is invoked whenever the endpoints of an interval may have changed.
|
|
673
|
+
* This can happen on:
|
|
674
|
+
* - local endpoint modification
|
|
675
|
+
* - ack of a remote endpoint modification
|
|
676
|
+
* - position change due to segment sliding (slides due to mergeTree segment deletion will always appear local)
|
|
677
|
+
* The `interval` argument reflects the new values.
|
|
678
|
+
* `previousInterval` contains transient `ReferencePosition`s at the same location as the interval's original
|
|
679
|
+
* endpoints. These references should be used for position information only.
|
|
680
|
+
* `local` reflects whether the change originated locally.
|
|
681
|
+
* `op` is defined if and only if the server has acked this change.
|
|
682
|
+
* `slide` is true if the change is due to sliding on removal of position
|
|
683
|
+
*/
|
|
684
|
+
(
|
|
685
|
+
event: "changeInterval",
|
|
686
|
+
listener: (
|
|
687
|
+
interval: TInterval,
|
|
688
|
+
previousInterval: TInterval,
|
|
689
|
+
local: boolean,
|
|
690
|
+
op: ISequencedDocumentMessage | undefined,
|
|
691
|
+
slide: boolean,
|
|
692
|
+
) => void,
|
|
693
|
+
): void;
|
|
694
|
+
/**
|
|
695
|
+
* This event is invoked whenever an interval is added or removed from the collection.
|
|
696
|
+
* `local` reflects whether the change originated locally.
|
|
697
|
+
* `op` is defined if and only if the server has acked this change.
|
|
698
|
+
*/
|
|
699
|
+
(
|
|
700
|
+
event: "addInterval" | "deleteInterval",
|
|
701
|
+
listener: (
|
|
702
|
+
interval: TInterval,
|
|
703
|
+
local: boolean,
|
|
704
|
+
op: ISequencedDocumentMessage | undefined,
|
|
705
|
+
) => void,
|
|
706
|
+
): void;
|
|
707
|
+
/**
|
|
708
|
+
* This event is invoked whenever an interval's properties have changed.
|
|
709
|
+
* `interval` reflects the state of the updated properties.
|
|
710
|
+
* `propertyDeltas` is a map-like whose keys contain all values that were changed, and whose
|
|
711
|
+
* values contain all previous values of the property set.
|
|
712
|
+
* This object can be used directly in a call to `changeProperties` to revert the property change if desired.
|
|
713
|
+
* `local` reflects whether the change originated locally.
|
|
714
|
+
* `op` is defined if and only if the server has acked this change.
|
|
715
|
+
*/
|
|
716
|
+
(
|
|
717
|
+
event: "propertyChanged",
|
|
718
|
+
listener: (
|
|
719
|
+
interval: TInterval,
|
|
720
|
+
propertyDeltas: PropertySet,
|
|
721
|
+
local: boolean,
|
|
722
|
+
op: ISequencedDocumentMessage | undefined,
|
|
723
|
+
) => void,
|
|
724
|
+
): void;
|
|
725
|
+
}
|
|
934
726
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
727
|
+
// solely for type checking in the implementation of add - will be removed once
|
|
728
|
+
// deprecated signatures are removed
|
|
729
|
+
const isSequencePlace = (place: any): place is SequencePlace => {
|
|
730
|
+
return typeof place === "number" || typeof place === "string" || place.pos !== undefined;
|
|
731
|
+
};
|
|
938
732
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
733
|
+
/**
|
|
734
|
+
* Collection of intervals that supports addition, modification, removal, and efficient spatial querying.
|
|
735
|
+
* Changes to this collection will be incur updates on collaborating clients (i.e. they are not local-only).
|
|
736
|
+
* @alpha
|
|
737
|
+
*/
|
|
738
|
+
export interface IIntervalCollection<TInterval extends ISerializableInterval>
|
|
739
|
+
extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>> {
|
|
740
|
+
readonly attached: boolean;
|
|
741
|
+
/**
|
|
742
|
+
* Attaches an index to this collection.
|
|
743
|
+
* All intervals which are part of this collection will be added to the index, and the index will automatically
|
|
744
|
+
* be updated when this collection updates due to local or remote changes.
|
|
745
|
+
*
|
|
746
|
+
* @remarks After attaching an index to an interval collection, applications should typically store this
|
|
747
|
+
* index somewhere in their in-memory data model for future reference and querying.
|
|
748
|
+
*/
|
|
749
|
+
attachIndex(index: IntervalIndex<TInterval>): void;
|
|
750
|
+
/**
|
|
751
|
+
* Detaches an index from this collection.
|
|
752
|
+
* All intervals which are part of this collection will be removed from the index, and updates to this collection
|
|
753
|
+
* due to local or remote changes will no longer incur updates to the index.
|
|
754
|
+
*
|
|
755
|
+
* @returns `false` if the target index cannot be found in the indexes, otherwise remove all intervals in the index and return `true`.
|
|
756
|
+
*/
|
|
757
|
+
detachIndex(index: IntervalIndex<TInterval>): boolean;
|
|
758
|
+
/**
|
|
759
|
+
* @returns the interval in this collection that has the provided `id`.
|
|
760
|
+
* If no interval in the collection has this `id`, returns `undefined`.
|
|
761
|
+
*/
|
|
762
|
+
getIntervalById(id: string): TInterval | undefined;
|
|
763
|
+
/**
|
|
764
|
+
* Creates a new interval and add it to the collection.
|
|
765
|
+
* @deprecated call IntervalCollection.add without specifying an intervalType
|
|
766
|
+
* @param start - interval start position (inclusive)
|
|
767
|
+
* @param end - interval end position (exclusive)
|
|
768
|
+
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
769
|
+
* @param props - properties of the interval
|
|
770
|
+
* @returns The created interval
|
|
771
|
+
* @remarks See documentation on {@link SequenceInterval} for comments on
|
|
772
|
+
* interval endpoint semantics: there are subtleties with how the current
|
|
773
|
+
* half-open behavior is represented.
|
|
774
|
+
*
|
|
775
|
+
* Note that intervals may behave unexpectedly if the entire contents
|
|
776
|
+
* of the string are deleted. In this case, it is possible for one endpoint
|
|
777
|
+
* of the interval to become detached, while the other remains on the string.
|
|
778
|
+
*
|
|
779
|
+
* By adjusting the `side` and `pos` values of the `start` and `end` parameters,
|
|
780
|
+
* it is possible to control whether the interval expands to include content
|
|
781
|
+
* inserted at its start or end.
|
|
782
|
+
*
|
|
783
|
+
* See {@link SequencePlace} for more details on the model.
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
*
|
|
787
|
+
* Given the string "ABCD":
|
|
788
|
+
*
|
|
789
|
+
*```typescript
|
|
790
|
+
* // Refers to "BC". If any content is inserted before B or after C, this
|
|
791
|
+
* // interval will include that content
|
|
792
|
+
* //
|
|
793
|
+
* // Picture:
|
|
794
|
+
* // \{start\} - A[- B - C -]D - \{end\}
|
|
795
|
+
* // \{start\} - A - B - C - D - \{end\}
|
|
796
|
+
* collection.add(\{ pos: 0, side: Side.After \}, \{ pos: 3, side: Side.Before \}, IntervalType.SlideOnRemove);
|
|
797
|
+
* // Equivalent to specifying the same positions and Side.Before.
|
|
798
|
+
* // Refers to "ABC". Content inserted after C will be included in the
|
|
799
|
+
* // interval, but content inserted before A will not.
|
|
800
|
+
* // \{start\} -[A - B - C -]D - \{end\}
|
|
801
|
+
* // \{start\} - A - B - C - D - \{end\}
|
|
802
|
+
* collection.add(0, 3, IntervalType.SlideOnRemove);
|
|
803
|
+
*```
|
|
804
|
+
*
|
|
805
|
+
* In the case of the first example, if text is deleted,
|
|
806
|
+
*
|
|
807
|
+
* ```typescript
|
|
808
|
+
* // Delete the character "B"
|
|
809
|
+
* string.removeRange(1, 2);
|
|
810
|
+
* ```
|
|
811
|
+
*
|
|
812
|
+
* The start point of the interval will slide to the position immediately
|
|
813
|
+
* before "C", and the same will be true.
|
|
814
|
+
*
|
|
815
|
+
* ```
|
|
816
|
+
* \{start\} - A[- C -]D - \{end\}
|
|
817
|
+
* ```
|
|
818
|
+
*
|
|
819
|
+
* In this case, text inserted immediately before "C" would be included in
|
|
820
|
+
* the interval.
|
|
821
|
+
*
|
|
822
|
+
* ```typescript
|
|
823
|
+
* string.insertText(1, "EFG");
|
|
824
|
+
* ```
|
|
825
|
+
*
|
|
826
|
+
* With the string now being,
|
|
827
|
+
*
|
|
828
|
+
* ```
|
|
829
|
+
* \{start\} - A[- E - F - G - C -]D - \{end\}
|
|
830
|
+
* ```
|
|
831
|
+
*
|
|
832
|
+
* @privateRemarks TODO: ADO:5205 the above comment regarding behavior in
|
|
833
|
+
* the case that the entire interval has been deleted should be resolved at
|
|
834
|
+
* the same time as this ticket
|
|
835
|
+
*/
|
|
836
|
+
add(
|
|
837
|
+
start: SequencePlace,
|
|
838
|
+
end: SequencePlace,
|
|
839
|
+
intervalType: IntervalType,
|
|
840
|
+
props?: PropertySet,
|
|
841
|
+
): TInterval;
|
|
842
|
+
/**
|
|
843
|
+
* Creates a new interval and add it to the collection.
|
|
844
|
+
* @param start - interval start position (inclusive)
|
|
845
|
+
* @param end - interval end position (exclusive)
|
|
846
|
+
* @param props - properties of the interval
|
|
847
|
+
* @returns - the created interval
|
|
848
|
+
* @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
|
|
849
|
+
* with how the current half-open behavior is represented.
|
|
850
|
+
*/
|
|
851
|
+
add({
|
|
852
|
+
start,
|
|
853
|
+
end,
|
|
854
|
+
props,
|
|
855
|
+
}: {
|
|
856
|
+
start: SequencePlace;
|
|
857
|
+
end: SequencePlace;
|
|
858
|
+
props?: PropertySet;
|
|
859
|
+
}): TInterval;
|
|
860
|
+
/**
|
|
861
|
+
* Removes an interval from the collection.
|
|
862
|
+
* @param id - Id of the interval to remove
|
|
863
|
+
* @returns the removed interval
|
|
864
|
+
*/
|
|
865
|
+
removeIntervalById(id: string): TInterval | undefined;
|
|
866
|
+
/**
|
|
867
|
+
* Changes the properties on an existing interval.
|
|
868
|
+
* @deprecated - call change with the id and and object containing the new properties
|
|
869
|
+
* @param id - Id of the interval whose properties should be changed
|
|
870
|
+
* @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
|
|
871
|
+
* and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
|
|
872
|
+
*/
|
|
873
|
+
changeProperties(id: string, props: PropertySet): void;
|
|
874
|
+
/**
|
|
875
|
+
* Changes the endpoints of an existing interval.
|
|
876
|
+
* @deprecated - call change with the start and end parameters encapsulated in an object
|
|
877
|
+
* @param id - Id of the interval to change
|
|
878
|
+
* @param start - New start value. To leave the endpoint unchanged, pass the current value.
|
|
879
|
+
* @param end - New end value. To leave the endpoint unchanged, pass the current value.
|
|
880
|
+
* @returns the interval that was changed, if it existed in the collection.
|
|
881
|
+
*/
|
|
882
|
+
change(id: string, start: SequencePlace, end: SequencePlace): TInterval | undefined;
|
|
883
|
+
/**
|
|
884
|
+
* Changes the endpoints, properties, or both of an existing interval.
|
|
885
|
+
* @param id - Id of the Interval to change
|
|
886
|
+
* @returns the interval that was changed, if it existed in the collection.
|
|
887
|
+
* Pass the desired new start position, end position, and/or properties in an object. Start and end positions must be changed
|
|
888
|
+
* simultaneously - they must either both be specified or both undefined. To only change the properties, leave both endpoints
|
|
889
|
+
* undefined. To only change the endpoints, leave the properties undefined.
|
|
890
|
+
*/
|
|
891
|
+
change(
|
|
892
|
+
id: string,
|
|
893
|
+
{ start, end, props }: { start?: SequencePlace; end?: SequencePlace; props?: PropertySet },
|
|
894
|
+
): TInterval | undefined;
|
|
895
|
+
|
|
896
|
+
attachDeserializer(onDeserialize: DeserializeCallback): void;
|
|
897
|
+
/**
|
|
898
|
+
* @returns an iterator over all intervals in this collection.
|
|
899
|
+
*/
|
|
900
|
+
[Symbol.iterator](): Iterator<TInterval>;
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* @returns a forward iterator over all intervals in this collection with start point equal to `startPosition`.
|
|
904
|
+
*/
|
|
905
|
+
CreateForwardIteratorWithStartPosition(startPosition: number): Iterator<TInterval>;
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* @returns a backward iterator over all intervals in this collection with start point equal to `startPosition`.
|
|
909
|
+
*/
|
|
910
|
+
CreateBackwardIteratorWithStartPosition(startPosition: number): Iterator<TInterval>;
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* @returns a forward iterator over all intervals in this collection with end point equal to `endPosition`.
|
|
914
|
+
*/
|
|
915
|
+
CreateForwardIteratorWithEndPosition(endPosition: number): Iterator<TInterval>;
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* @returns a backward iterator over all intervals in this collection with end point equal to `endPosition`.
|
|
919
|
+
*/
|
|
920
|
+
CreateBackwardIteratorWithEndPosition(endPosition: number): Iterator<TInterval>;
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Gathers iteration results that optionally match a start/end criteria into the provided array.
|
|
924
|
+
* @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
|
|
925
|
+
* intervals matching the query upon edit.
|
|
926
|
+
* @param iteratesForward - whether or not iteration should be in the forward direction
|
|
927
|
+
* @param start - If provided, only match intervals whose start point is equal to `start`.
|
|
928
|
+
* @param end - If provided, only match intervals whose end point is equal to `end`.
|
|
929
|
+
*/
|
|
930
|
+
gatherIterationResults(
|
|
931
|
+
results: TInterval[],
|
|
932
|
+
iteratesForward: boolean,
|
|
933
|
+
start?: number,
|
|
934
|
+
end?: number,
|
|
935
|
+
): void;
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* @deprecated - Users must manually attach the corresponding interval index to utilize this functionality, for instance:
|
|
939
|
+
*
|
|
940
|
+
* ```typescript
|
|
941
|
+
* const overlappingIntervalsIndex = createOverlappingIntervalsIndex(sharedString);
|
|
942
|
+
* collection.attachIndex(overlappingIntervalsIndex)
|
|
943
|
+
* const result = overlappingIntervalsIndex.findOverlappingIntervals(start, end);
|
|
944
|
+
* ```
|
|
945
|
+
*
|
|
946
|
+
* @returns an array of all intervals in this collection that overlap with the interval
|
|
947
|
+
* `[startPosition, endPosition]`.
|
|
948
|
+
*/
|
|
949
|
+
findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[];
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Applies a function to each interval in this collection.
|
|
953
|
+
*/
|
|
954
|
+
map(fn: (interval: TInterval) => void): void;
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* @deprecated - due to the forthcoming change where the endpointIndex will no longer be
|
|
958
|
+
* automatically added to the collection. Users are advised to independently attach the
|
|
959
|
+
* index to the collection and utilize the API accordingly, for instance:
|
|
960
|
+
* ```typescript
|
|
961
|
+
* const endpointIndex = createEndpointIndex(sharedString);
|
|
962
|
+
* collection.attachIndex(endpointIndex);
|
|
963
|
+
* const result1 = endpointIndex.previousInterval(pos);
|
|
964
|
+
* ```
|
|
965
|
+
* If an index is used repeatedly, applications should generally attach it once and store it in memory.
|
|
966
|
+
*/
|
|
967
|
+
previousInterval(pos: number): TInterval | undefined;
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* @deprecated - due to the forthcoming change where the endpointIndex will no longer be
|
|
971
|
+
* automatically added to the collection. Users are advised to independently attach the
|
|
972
|
+
* index to the collection and utilize the API accordingly, for instance:
|
|
973
|
+
* ```typescript
|
|
974
|
+
* const endpointIndex = createEndpointIndex(sharedString);
|
|
975
|
+
* collection.attachIndex(endpointIndex);
|
|
976
|
+
* const result2 = endpointIndex.nextInterval(pos);
|
|
977
|
+
* ```
|
|
978
|
+
*/
|
|
979
|
+
nextInterval(pos: number): TInterval | undefined;
|
|
942
980
|
}
|
|
943
981
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
982
|
+
/**
|
|
983
|
+
* {@inheritdoc IIntervalCollection}
|
|
984
|
+
*/
|
|
985
|
+
export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
986
|
+
extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>>
|
|
987
|
+
implements IIntervalCollection<TInterval>
|
|
988
|
+
{
|
|
989
|
+
private savedSerializedIntervals?: ISerializedInterval[];
|
|
990
|
+
private localCollection: LocalIntervalCollection<TInterval> | undefined;
|
|
991
|
+
private onDeserialize: DeserializeCallback | undefined;
|
|
992
|
+
private client: Client | undefined;
|
|
993
|
+
private readonly localSeqToSerializedInterval = new Map<
|
|
994
|
+
number,
|
|
995
|
+
ISerializedInterval | SerializedIntervalDelta
|
|
996
|
+
>();
|
|
997
|
+
private readonly localSeqToRebasedInterval = new Map<
|
|
998
|
+
number,
|
|
999
|
+
ISerializedInterval | SerializedIntervalDelta
|
|
1000
|
+
>();
|
|
1001
|
+
private readonly pendingChangesStart: Map<string, ISerializedInterval[]> = new Map<
|
|
1002
|
+
string,
|
|
1003
|
+
ISerializedInterval[]
|
|
1004
|
+
>();
|
|
1005
|
+
private readonly pendingChangesEnd: Map<string, ISerializedInterval[]> = new Map<
|
|
1006
|
+
string,
|
|
1007
|
+
ISerializedInterval[]
|
|
1008
|
+
>();
|
|
1009
|
+
|
|
1010
|
+
public get attached(): boolean {
|
|
1011
|
+
return !!this.localCollection;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/** @internal */
|
|
1015
|
+
constructor(
|
|
1016
|
+
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1017
|
+
private readonly requiresClient: boolean,
|
|
1018
|
+
private readonly emitter: IValueOpEmitter,
|
|
1019
|
+
serializedIntervals: ISerializedInterval[] | ISerializedIntervalCollectionV2,
|
|
1020
|
+
private readonly options: Partial<SequenceOptions> = {},
|
|
1021
|
+
) {
|
|
1022
|
+
super();
|
|
1023
|
+
|
|
1024
|
+
this.savedSerializedIntervals = Array.isArray(serializedIntervals)
|
|
1025
|
+
? serializedIntervals
|
|
1026
|
+
: serializedIntervals.intervals.map((i) =>
|
|
1027
|
+
decompressInterval(i, serializedIntervals.label),
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* {@inheritdoc IIntervalCollection.attachIndex}
|
|
1033
|
+
*/
|
|
1034
|
+
public attachIndex(index: IntervalIndex<TInterval>): void {
|
|
1035
|
+
if (!this.attached) {
|
|
1036
|
+
throw new LoggingError("The local interval collection must exist");
|
|
1037
|
+
}
|
|
1038
|
+
for (const interval of this) {
|
|
1039
|
+
index.add(interval);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
this.localCollection?.appendIndex(index);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* {@inheritdoc IIntervalCollection.detachIndex}
|
|
1047
|
+
*/
|
|
1048
|
+
public detachIndex(index: IntervalIndex<TInterval>): boolean {
|
|
1049
|
+
if (!this.attached) {
|
|
1050
|
+
throw new LoggingError("The local interval collection must exist");
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Avoid removing intervals if the index does not exist
|
|
1054
|
+
if (!this.localCollection?.removeIndex(index)) {
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
for (const interval of this) {
|
|
1059
|
+
index.remove(interval);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
private rebasePositionWithSegmentSlide(
|
|
1066
|
+
pos: number | "start" | "end",
|
|
1067
|
+
seqNumberFrom: number,
|
|
1068
|
+
localSeq: number,
|
|
1069
|
+
): number | "start" | "end" | undefined {
|
|
1070
|
+
if (!this.client) {
|
|
1071
|
+
throw new LoggingError("mergeTree client must exist");
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (pos === "start" || pos === "end") {
|
|
1075
|
+
return pos;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const { clientId } = this.client.getCollabWindow();
|
|
1079
|
+
const { segment, offset } = this.client.getContainingSegment(
|
|
1080
|
+
pos,
|
|
1081
|
+
{
|
|
1082
|
+
referenceSequenceNumber: seqNumberFrom,
|
|
1083
|
+
clientId: this.client.getLongClientId(clientId),
|
|
1084
|
+
},
|
|
1085
|
+
localSeq,
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
// if segment is undefined, it slid off the string
|
|
1089
|
+
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
1090
|
+
|
|
1091
|
+
const segoff =
|
|
1092
|
+
getSlideToSegoff(
|
|
1093
|
+
{ segment, offset },
|
|
1094
|
+
undefined,
|
|
1095
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1096
|
+
) ?? segment;
|
|
1097
|
+
|
|
1098
|
+
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
1099
|
+
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
1100
|
+
return DetachedReferencePosition;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
assert(
|
|
1104
|
+
offset !== undefined && 0 <= offset && offset < segment.cachedLength,
|
|
1105
|
+
0x54f /* Invalid offset */,
|
|
1106
|
+
);
|
|
1107
|
+
return this.client.findReconnectionPosition(segoff.segment, localSeq) + segoff.offset;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
private computeRebasedPositions(
|
|
1111
|
+
localSeq: number,
|
|
1112
|
+
): ISerializedInterval | SerializedIntervalDelta {
|
|
1113
|
+
assert(
|
|
1114
|
+
this.client !== undefined,
|
|
1115
|
+
0x550 /* Client should be defined when computing rebased position */,
|
|
1116
|
+
);
|
|
1117
|
+
const original = this.localSeqToSerializedInterval.get(localSeq);
|
|
1118
|
+
assert(
|
|
1119
|
+
original !== undefined,
|
|
1120
|
+
0x551 /* Failed to store pending serialized interval info for this localSeq. */,
|
|
1121
|
+
);
|
|
1122
|
+
const rebased = { ...original };
|
|
1123
|
+
const { start, end, sequenceNumber } = original;
|
|
1124
|
+
if (start !== undefined) {
|
|
1125
|
+
rebased.start = this.rebasePositionWithSegmentSlide(start, sequenceNumber, localSeq);
|
|
1126
|
+
}
|
|
1127
|
+
if (end !== undefined) {
|
|
1128
|
+
rebased.end = this.rebasePositionWithSegmentSlide(end, sequenceNumber, localSeq);
|
|
1129
|
+
}
|
|
1130
|
+
return rebased;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/** @internal */
|
|
1134
|
+
public attachGraph(client: Client, label: string) {
|
|
1135
|
+
if (this.attached) {
|
|
1136
|
+
throw new LoggingError("Only supports one Sequence attach");
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (client === undefined && this.requiresClient) {
|
|
1140
|
+
throw new LoggingError("Client required for this collection");
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Instantiate the local interval collection based on the saved intervals
|
|
1144
|
+
this.client = client;
|
|
1145
|
+
if (client) {
|
|
1146
|
+
client.on("normalize", () => {
|
|
1147
|
+
for (const localSeq of this.localSeqToSerializedInterval.keys()) {
|
|
1148
|
+
this.localSeqToRebasedInterval.set(
|
|
1149
|
+
localSeq,
|
|
1150
|
+
this.computeRebasedPositions(localSeq),
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
this.localCollection = new LocalIntervalCollection<TInterval>(
|
|
1157
|
+
client,
|
|
1158
|
+
label,
|
|
1159
|
+
this.helpers,
|
|
1160
|
+
this.options,
|
|
1161
|
+
(interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
|
|
1162
|
+
);
|
|
1163
|
+
if (this.savedSerializedIntervals) {
|
|
1164
|
+
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
1165
|
+
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1166
|
+
const {
|
|
1167
|
+
start: startPos,
|
|
1168
|
+
end: endPos,
|
|
1169
|
+
intervalType,
|
|
1170
|
+
properties,
|
|
1171
|
+
startSide,
|
|
1172
|
+
endSide,
|
|
1173
|
+
} = serializedInterval;
|
|
1174
|
+
const start =
|
|
1175
|
+
typeof startPos === "number" && startSide !== undefined
|
|
1176
|
+
? { pos: startPos, side: startSide }
|
|
1177
|
+
: startPos;
|
|
1178
|
+
const end =
|
|
1179
|
+
typeof endPos === "number" && endSide !== undefined
|
|
1180
|
+
? { pos: endPos, side: endSide }
|
|
1181
|
+
: endPos;
|
|
1182
|
+
const interval = this.helpers.create(
|
|
1183
|
+
label,
|
|
1184
|
+
start,
|
|
1185
|
+
end,
|
|
1186
|
+
client,
|
|
1187
|
+
intervalType,
|
|
1188
|
+
undefined,
|
|
1189
|
+
true,
|
|
1190
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1191
|
+
);
|
|
1192
|
+
if (properties) {
|
|
1193
|
+
interval.addProperties(properties);
|
|
1194
|
+
}
|
|
1195
|
+
this.localCollection.add(interval);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
this.savedSerializedIntervals = undefined;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Gets the next local sequence number, modifying this client's collab window in doing so.
|
|
1203
|
+
*/
|
|
1204
|
+
private getNextLocalSeq(): number {
|
|
1205
|
+
if (this.client) {
|
|
1206
|
+
return ++this.client.getCollabWindow().localSeq;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return 0;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
private emitChange(
|
|
1213
|
+
interval: TInterval,
|
|
1214
|
+
previousInterval: TInterval,
|
|
1215
|
+
local: boolean,
|
|
1216
|
+
slide: boolean,
|
|
1217
|
+
op?: ISequencedDocumentMessage,
|
|
1218
|
+
): void {
|
|
1219
|
+
// Temporarily make references transient so that positional queries work (non-transient refs
|
|
1220
|
+
// on resolve to DetachedPosition on any segments that don't contain them). The original refType
|
|
1221
|
+
// is restored as single-endpoint changes re-use previous references.
|
|
1222
|
+
let startRefType: ReferenceType;
|
|
1223
|
+
let endRefType: ReferenceType;
|
|
1224
|
+
if (previousInterval instanceof SequenceInterval) {
|
|
1225
|
+
startRefType = previousInterval.start.refType;
|
|
1226
|
+
endRefType = previousInterval.end.refType;
|
|
1227
|
+
previousInterval.start.refType = ReferenceType.Transient;
|
|
1228
|
+
previousInterval.end.refType = ReferenceType.Transient;
|
|
1229
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
1230
|
+
previousInterval.start.refType = startRefType;
|
|
1231
|
+
previousInterval.end.refType = endRefType;
|
|
1232
|
+
} else {
|
|
1233
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* {@inheritdoc IIntervalCollection.getIntervalById}
|
|
1239
|
+
*/
|
|
1240
|
+
public getIntervalById(id: string): TInterval | undefined {
|
|
1241
|
+
if (!this.localCollection) {
|
|
1242
|
+
throw new LoggingError("attach must be called before accessing intervals");
|
|
1243
|
+
}
|
|
1244
|
+
return this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
private assertStickinessEnabled(start: SequencePlace, end: SequencePlace) {
|
|
1248
|
+
if (
|
|
1249
|
+
!(typeof start === "number" && typeof end === "number") &&
|
|
1250
|
+
!this.options.intervalStickinessEnabled
|
|
1251
|
+
) {
|
|
1252
|
+
throw new UsageError(
|
|
1253
|
+
"attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag",
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* {@inheritdoc IIntervalCollection.add}
|
|
1260
|
+
* @deprecated call IntervalCollection.add without specifying an intervalType
|
|
1261
|
+
*/
|
|
1262
|
+
public add(
|
|
1263
|
+
start: SequencePlace,
|
|
1264
|
+
end: SequencePlace,
|
|
1265
|
+
intervalType: IntervalType,
|
|
1266
|
+
props?: PropertySet,
|
|
1267
|
+
): TInterval;
|
|
1268
|
+
|
|
1269
|
+
public add({
|
|
1270
|
+
start,
|
|
1271
|
+
end,
|
|
1272
|
+
props,
|
|
1273
|
+
}: {
|
|
1274
|
+
start: SequencePlace;
|
|
1275
|
+
end: SequencePlace;
|
|
1276
|
+
props?: PropertySet;
|
|
1277
|
+
}): TInterval;
|
|
1278
|
+
|
|
1279
|
+
public add(
|
|
1280
|
+
start:
|
|
1281
|
+
| SequencePlace
|
|
1282
|
+
| {
|
|
1283
|
+
start: SequencePlace;
|
|
1284
|
+
end: SequencePlace;
|
|
1285
|
+
props?: PropertySet;
|
|
1286
|
+
},
|
|
1287
|
+
end?: SequencePlace,
|
|
1288
|
+
intervalType?: IntervalType,
|
|
1289
|
+
props?: PropertySet,
|
|
1290
|
+
): TInterval {
|
|
1291
|
+
let intStart: SequencePlace;
|
|
1292
|
+
let intEnd: SequencePlace;
|
|
1293
|
+
let type: IntervalType;
|
|
1294
|
+
let properties: PropertySet | undefined;
|
|
1295
|
+
|
|
1296
|
+
if (isSequencePlace(start)) {
|
|
1297
|
+
intStart = start;
|
|
1298
|
+
assert(end !== undefined, 0x7c0 /* end must be defined */);
|
|
1299
|
+
intEnd = end;
|
|
1300
|
+
assert(intervalType !== undefined, 0x7c1 /* intervalType must be defined */);
|
|
1301
|
+
type = intervalType;
|
|
1302
|
+
properties = props;
|
|
1303
|
+
} else {
|
|
1304
|
+
intStart = start.start;
|
|
1305
|
+
intEnd = start.end;
|
|
1306
|
+
type = IntervalType.SlideOnRemove;
|
|
1307
|
+
properties = start.props;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
if (!this.localCollection) {
|
|
1311
|
+
throw new LoggingError("attach must be called prior to adding intervals");
|
|
1312
|
+
}
|
|
1313
|
+
if (type & IntervalType.Transient) {
|
|
1314
|
+
throw new LoggingError("Can not add transient intervals");
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const { startSide, endSide, startPos, endPos } = endpointPosAndSide(intStart, intEnd);
|
|
1318
|
+
|
|
1319
|
+
assert(
|
|
1320
|
+
startPos !== undefined &&
|
|
1321
|
+
endPos !== undefined &&
|
|
1322
|
+
startSide !== undefined &&
|
|
1323
|
+
endSide !== undefined,
|
|
1324
|
+
0x793 /* start and end cannot be undefined because they were not passed in as undefined */,
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
|
|
1328
|
+
|
|
1329
|
+
this.assertStickinessEnabled(intStart, intEnd);
|
|
1330
|
+
|
|
1331
|
+
const interval: TInterval = this.localCollection.addInterval(
|
|
1332
|
+
toSequencePlace(startPos, startSide),
|
|
1333
|
+
toSequencePlace(endPos, endSide),
|
|
1334
|
+
type,
|
|
1335
|
+
properties,
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
if (interval) {
|
|
1339
|
+
if (!this.isCollaborating && interval instanceof SequenceInterval) {
|
|
1340
|
+
setSlideOnRemove(interval.start);
|
|
1341
|
+
setSlideOnRemove(interval.end);
|
|
1342
|
+
}
|
|
1343
|
+
const serializedInterval: ISerializedInterval = {
|
|
1344
|
+
start: startPos,
|
|
1345
|
+
end: endPos,
|
|
1346
|
+
intervalType: type,
|
|
1347
|
+
properties: interval.properties,
|
|
1348
|
+
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1349
|
+
stickiness,
|
|
1350
|
+
startSide,
|
|
1351
|
+
endSide,
|
|
1352
|
+
};
|
|
1353
|
+
const localSeq = this.getNextLocalSeq();
|
|
1354
|
+
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1355
|
+
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1356
|
+
this.emitter.emit("add", undefined, serializedInterval, { localSeq });
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
this.emit("addInterval", interval, true, undefined);
|
|
1360
|
+
|
|
1361
|
+
return interval;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
private deleteExistingInterval(
|
|
1365
|
+
interval: TInterval,
|
|
1366
|
+
local: boolean,
|
|
1367
|
+
op?: ISequencedDocumentMessage,
|
|
1368
|
+
) {
|
|
1369
|
+
if (!this.localCollection) {
|
|
1370
|
+
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1371
|
+
}
|
|
1372
|
+
// The given interval is known to exist in the collection.
|
|
1373
|
+
this.localCollection.removeExistingInterval(interval);
|
|
1374
|
+
|
|
1375
|
+
if (interval) {
|
|
1376
|
+
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1377
|
+
if (local) {
|
|
1378
|
+
this.emitter.emit("delete", undefined, interval.serialize(), {
|
|
1379
|
+
localSeq: this.getNextLocalSeq(),
|
|
1380
|
+
});
|
|
1381
|
+
} else {
|
|
1382
|
+
if (this.onDeserialize) {
|
|
1383
|
+
this.onDeserialize(interval);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
this.emit("deleteInterval", interval, local, op);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* {@inheritdoc IIntervalCollection.removeIntervalById}
|
|
1393
|
+
*/
|
|
1394
|
+
public removeIntervalById(id: string): TInterval | undefined {
|
|
1395
|
+
if (!this.localCollection) {
|
|
1396
|
+
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1397
|
+
}
|
|
1398
|
+
const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1399
|
+
if (interval) {
|
|
1400
|
+
this.deleteExistingInterval(interval, true, undefined);
|
|
1401
|
+
}
|
|
1402
|
+
return interval;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* {@inheritdoc IIntervalCollection.changeProperties}
|
|
1407
|
+
* @deprecated - call change with the id and an object containing the new props values
|
|
1408
|
+
*/
|
|
1409
|
+
public changeProperties(id: string, props: PropertySet) {
|
|
1410
|
+
this.change(id, { props });
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* {@inheritdoc IIntervalCollection.change}
|
|
1415
|
+
* @deprecated - call change with the id and an object containing the new start, end, and/or props values
|
|
1416
|
+
*/
|
|
1417
|
+
public change(id: string, start: SequencePlace, end: SequencePlace): TInterval | undefined;
|
|
1418
|
+
/**
|
|
1419
|
+
* {@inheritdoc IIntervalCollection.change}
|
|
1420
|
+
*/
|
|
1421
|
+
public change(
|
|
1422
|
+
id: string,
|
|
1423
|
+
{ start, end, props }: { start?: SequencePlace; end?: SequencePlace; props?: PropertySet },
|
|
1424
|
+
): TInterval | undefined;
|
|
1425
|
+
public change(
|
|
1426
|
+
arg1: string,
|
|
1427
|
+
arg2: SequencePlace | { start?: SequencePlace; end?: SequencePlace; props?: PropertySet },
|
|
1428
|
+
arg3?: SequencePlace,
|
|
1429
|
+
): TInterval | undefined {
|
|
1430
|
+
const id: string = arg1;
|
|
1431
|
+
let start: SequencePlace | undefined;
|
|
1432
|
+
let end: SequencePlace | undefined;
|
|
1433
|
+
let props: PropertySet | undefined;
|
|
1434
|
+
if (isSequencePlace(arg2)) {
|
|
1435
|
+
start = arg2;
|
|
1436
|
+
end = arg3;
|
|
1437
|
+
} else {
|
|
1438
|
+
start = arg2.start;
|
|
1439
|
+
end = arg2.end;
|
|
1440
|
+
props = arg2.props;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (!this.localCollection) {
|
|
1444
|
+
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Force id to be a string.
|
|
1448
|
+
if (typeof id !== "string") {
|
|
1449
|
+
throw new UsageError("Change API requires an ID that is a string");
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Ensure that both start and end are defined or both are undefined.
|
|
1453
|
+
if ((start === undefined) !== (end === undefined)) {
|
|
1454
|
+
throw new UsageError(
|
|
1455
|
+
"Change API requires both start and end to be defined or undefined",
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// prevent the overwriting of an interval label, it should remain unchanged
|
|
1460
|
+
// once it has been inserted into the collection.
|
|
1461
|
+
if (props?.[reservedRangeLabelsKey] !== undefined) {
|
|
1462
|
+
throw new UsageError(
|
|
1463
|
+
"The label property should not be modified once inserted to the collection",
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const interval = this.getIntervalById(id);
|
|
1468
|
+
if (interval) {
|
|
1469
|
+
let deltaProps: PropertySet | undefined;
|
|
1470
|
+
let newInterval: TInterval | undefined;
|
|
1471
|
+
if (props !== undefined) {
|
|
1472
|
+
deltaProps = interval.addProperties(
|
|
1473
|
+
props,
|
|
1474
|
+
true,
|
|
1475
|
+
this.isCollaborating ? UnassignedSequenceNumber : UniversalSequenceNumber,
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
if (start !== undefined && end !== undefined) {
|
|
1479
|
+
newInterval = this.localCollection.changeInterval(interval, start, end);
|
|
1480
|
+
if (!this.isCollaborating && newInterval instanceof SequenceInterval) {
|
|
1481
|
+
setSlideOnRemove(newInterval.start);
|
|
1482
|
+
setSlideOnRemove(newInterval.end);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
const serializedInterval: SerializedIntervalDelta = interval.serialize();
|
|
1486
|
+
const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
|
|
1487
|
+
const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
|
|
1488
|
+
serializedInterval.start = startPos;
|
|
1489
|
+
serializedInterval.end = endPos;
|
|
1490
|
+
serializedInterval.startSide = startSide;
|
|
1491
|
+
serializedInterval.endSide = endSide;
|
|
1492
|
+
serializedInterval.stickiness = stickiness;
|
|
1493
|
+
// Emit a property bag containing the ID and the other (if any) properties changed
|
|
1494
|
+
serializedInterval.properties = {
|
|
1495
|
+
[reservedIntervalIdKey]: interval.getIntervalId(),
|
|
1496
|
+
...props,
|
|
1497
|
+
};
|
|
1498
|
+
const localSeq = this.getNextLocalSeq();
|
|
1499
|
+
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1500
|
+
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
1501
|
+
if (deltaProps !== undefined) {
|
|
1502
|
+
this.emit("propertyChanged", interval, deltaProps, true, undefined);
|
|
1503
|
+
}
|
|
1504
|
+
if (newInterval) {
|
|
1505
|
+
this.addPendingChange(id, serializedInterval);
|
|
1506
|
+
this.emitChange(newInterval, interval, true, false);
|
|
1507
|
+
}
|
|
1508
|
+
return newInterval;
|
|
1509
|
+
}
|
|
1510
|
+
// No interval to change
|
|
1511
|
+
return undefined;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
private get isCollaborating(): boolean {
|
|
1515
|
+
return this.client?.getCollabWindow().collaborating ?? false;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
private addPendingChange(id: string, serializedInterval: SerializedIntervalDelta) {
|
|
1519
|
+
if (!this.isCollaborating) {
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (serializedInterval.start !== undefined) {
|
|
1523
|
+
this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
|
|
1524
|
+
}
|
|
1525
|
+
if (serializedInterval.end !== undefined) {
|
|
1526
|
+
this.addPendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
private addPendingChangeHelper(
|
|
1531
|
+
id: string,
|
|
1532
|
+
pendingChanges: Map<string, SerializedIntervalDelta[]>,
|
|
1533
|
+
serializedInterval: SerializedIntervalDelta,
|
|
1534
|
+
) {
|
|
1535
|
+
let entries: SerializedIntervalDelta[] | undefined = pendingChanges.get(id);
|
|
1536
|
+
if (!entries) {
|
|
1537
|
+
entries = [];
|
|
1538
|
+
pendingChanges.set(id, entries);
|
|
1539
|
+
}
|
|
1540
|
+
entries.push(serializedInterval);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
private removePendingChange(serializedInterval: SerializedIntervalDelta) {
|
|
1544
|
+
// Change ops always have an ID.
|
|
1545
|
+
const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1546
|
+
if (serializedInterval.start !== undefined) {
|
|
1547
|
+
this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
|
|
1548
|
+
}
|
|
1549
|
+
if (serializedInterval.end !== undefined) {
|
|
1550
|
+
this.removePendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
private removePendingChangeHelper(
|
|
1555
|
+
id: string,
|
|
1556
|
+
pendingChanges: Map<string, SerializedIntervalDelta[]>,
|
|
1557
|
+
serializedInterval: SerializedIntervalDelta,
|
|
1558
|
+
) {
|
|
1559
|
+
const entries = pendingChanges.get(id);
|
|
1560
|
+
if (entries) {
|
|
1561
|
+
const pendingChange = entries.shift();
|
|
1562
|
+
if (entries.length === 0) {
|
|
1563
|
+
pendingChanges.delete(id);
|
|
1564
|
+
}
|
|
1565
|
+
if (
|
|
1566
|
+
pendingChange?.start !== serializedInterval.start ||
|
|
1567
|
+
pendingChange?.end !== serializedInterval.end
|
|
1568
|
+
) {
|
|
1569
|
+
throw new LoggingError("Mismatch in pending changes");
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
private hasPendingChangeStart(id: string) {
|
|
1575
|
+
const entries = this.pendingChangesStart.get(id);
|
|
1576
|
+
return entries && entries.length !== 0;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
private hasPendingChangeEnd(id: string) {
|
|
1580
|
+
const entries = this.pendingChangesEnd.get(id);
|
|
1581
|
+
return entries && entries.length !== 0;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/** @internal */
|
|
1585
|
+
public ackChange(
|
|
1586
|
+
serializedInterval: ISerializedInterval,
|
|
1587
|
+
local: boolean,
|
|
1588
|
+
op: ISequencedDocumentMessage,
|
|
1589
|
+
localOpMetadata: IMapMessageLocalMetadata | undefined,
|
|
1590
|
+
) {
|
|
1591
|
+
if (!this.localCollection) {
|
|
1592
|
+
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
if (local) {
|
|
1596
|
+
assert(
|
|
1597
|
+
localOpMetadata !== undefined,
|
|
1598
|
+
0x552 /* op metadata should be defined for local op */,
|
|
1599
|
+
);
|
|
1600
|
+
this.localSeqToSerializedInterval.delete(localOpMetadata?.localSeq);
|
|
1601
|
+
// This is an ack from the server. Remove the pending change.
|
|
1602
|
+
this.removePendingChange(serializedInterval);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// Note that the ID is in the property bag only to allow us to find the interval.
|
|
1606
|
+
// This API cannot change the ID, and writing to the ID property will result in an exception. So we
|
|
1607
|
+
// strip it out of the properties here.
|
|
1608
|
+
const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties ?? {};
|
|
1609
|
+
assert(id !== undefined, 0x3fe /* id must exist on the interval */);
|
|
1610
|
+
const interval: TInterval | undefined = this.getIntervalById(id);
|
|
1611
|
+
if (!interval) {
|
|
1612
|
+
// The interval has been removed locally; no-op.
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (local) {
|
|
1617
|
+
// Let the propertyManager prune its pending change-properties set.
|
|
1618
|
+
interval.propertyManager?.ackPendingProperties({
|
|
1619
|
+
type: MergeTreeDeltaType.ANNOTATE,
|
|
1620
|
+
props: serializedInterval.properties ?? {},
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
this.ackInterval(interval, op);
|
|
1624
|
+
} else {
|
|
1625
|
+
// If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
|
|
1626
|
+
// should be the winning change.
|
|
1627
|
+
let start: number | "start" | "end" | undefined;
|
|
1628
|
+
let end: number | "start" | "end" | undefined;
|
|
1629
|
+
// Track pending start/end independently of one another.
|
|
1630
|
+
if (!this.hasPendingChangeStart(id)) {
|
|
1631
|
+
start = serializedInterval.start;
|
|
1632
|
+
}
|
|
1633
|
+
if (!this.hasPendingChangeEnd(id)) {
|
|
1634
|
+
end = serializedInterval.end;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
let newInterval = interval;
|
|
1638
|
+
if (start !== undefined || end !== undefined) {
|
|
1639
|
+
// If changeInterval gives us a new interval, work with that one. Otherwise keep working with
|
|
1640
|
+
// the one we originally found in the tree.
|
|
1641
|
+
newInterval =
|
|
1642
|
+
this.localCollection.changeInterval(
|
|
1643
|
+
interval,
|
|
1644
|
+
toOptionalSequencePlace(start, serializedInterval.startSide),
|
|
1645
|
+
toOptionalSequencePlace(end, serializedInterval.endSide),
|
|
1646
|
+
op,
|
|
1647
|
+
) ?? interval;
|
|
1648
|
+
}
|
|
1649
|
+
const deltaProps = newInterval.addProperties(newProps, true, op.sequenceNumber);
|
|
1650
|
+
if (this.onDeserialize) {
|
|
1651
|
+
this.onDeserialize(newInterval);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (newInterval !== interval) {
|
|
1655
|
+
this.emitChange(newInterval, interval, local, false, op);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
const changedProperties = Object.keys(newProps).length > 0;
|
|
1659
|
+
if (changedProperties) {
|
|
1660
|
+
this.emit("propertyChanged", interval, deltaProps, local, op);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* {@inheritdoc IIntervalCollection.attachDeserializer}
|
|
1667
|
+
*/
|
|
1668
|
+
public attachDeserializer(onDeserialize: DeserializeCallback): void {
|
|
1669
|
+
// If no deserializer is specified can skip all processing work
|
|
1670
|
+
if (!onDeserialize) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// Start by storing the callbacks so that any subsequent modifications make use of them
|
|
1675
|
+
this.onDeserialize = onDeserialize;
|
|
1676
|
+
|
|
1677
|
+
// Trigger the async prepare work across all values in the collection
|
|
1678
|
+
if (this.attached) {
|
|
1679
|
+
this.map(onDeserialize);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* Returns new interval after rebasing. If undefined, the interval was
|
|
1685
|
+
* deleted as a result of rebasing. This can occur if the interval applies
|
|
1686
|
+
* to a range that no longer exists, and the interval was unable to slide.
|
|
1687
|
+
*
|
|
1688
|
+
* @internal
|
|
1689
|
+
*/
|
|
1690
|
+
public rebaseLocalInterval(
|
|
1691
|
+
opName: string,
|
|
1692
|
+
serializedInterval: SerializedIntervalDelta,
|
|
1693
|
+
localSeq: number,
|
|
1694
|
+
): SerializedIntervalDelta | undefined {
|
|
1695
|
+
if (!this.client) {
|
|
1696
|
+
// If there's no associated mergeTree client, the originally submitted op is still correct.
|
|
1697
|
+
return serializedInterval;
|
|
1698
|
+
}
|
|
1699
|
+
if (!this.attached) {
|
|
1700
|
+
throw new LoggingError("attachSequence must be called");
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
const { intervalType, properties, stickiness, startSide, endSide } = serializedInterval;
|
|
1704
|
+
|
|
1705
|
+
const { start: startRebased, end: endRebased } =
|
|
1706
|
+
this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
|
|
1707
|
+
|
|
1708
|
+
const intervalId = properties?.[reservedIntervalIdKey];
|
|
1709
|
+
const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(intervalId);
|
|
1710
|
+
|
|
1711
|
+
const rebased: SerializedIntervalDelta = {
|
|
1712
|
+
start: startRebased,
|
|
1713
|
+
end: endRebased,
|
|
1714
|
+
intervalType,
|
|
1715
|
+
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1716
|
+
properties,
|
|
1717
|
+
stickiness,
|
|
1718
|
+
startSide,
|
|
1719
|
+
endSide,
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
if (
|
|
1723
|
+
opName === "change" &&
|
|
1724
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when .hasPendingChangeStart returns false.
|
|
1725
|
+
(this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))
|
|
1726
|
+
) {
|
|
1727
|
+
this.removePendingChange(serializedInterval);
|
|
1728
|
+
this.addPendingChange(intervalId, rebased);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// if the interval slid off the string, rebase the op to be a noop and delete the interval.
|
|
1732
|
+
if (
|
|
1733
|
+
startRebased === DetachedReferencePosition ||
|
|
1734
|
+
endRebased === DetachedReferencePosition
|
|
1735
|
+
) {
|
|
1736
|
+
if (localInterval) {
|
|
1737
|
+
this.localCollection?.removeExistingInterval(localInterval);
|
|
1738
|
+
}
|
|
1739
|
+
return undefined;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (localInterval !== undefined) {
|
|
1743
|
+
// we know we must be using `SequenceInterval` because `this.client` exists
|
|
1744
|
+
assert(
|
|
1745
|
+
localInterval instanceof SequenceInterval,
|
|
1746
|
+
0x3a0 /* localInterval must be `SequenceInterval` when used with client */,
|
|
1747
|
+
);
|
|
1748
|
+
// The rebased op may place this interval's endpoints on different segments. Calling `changeInterval` here
|
|
1749
|
+
// updates the local client's state to be consistent with the emitted op.
|
|
1750
|
+
this.localCollection?.changeInterval(
|
|
1751
|
+
localInterval,
|
|
1752
|
+
toOptionalSequencePlace(startRebased, startSide),
|
|
1753
|
+
toOptionalSequencePlace(endRebased, endSide),
|
|
1754
|
+
undefined,
|
|
1755
|
+
localSeq,
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
return rebased;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
private getSlideToSegment(
|
|
1763
|
+
lref: LocalReferencePosition,
|
|
1764
|
+
): { segment: ISegment | undefined; offset: number | undefined } | undefined {
|
|
1765
|
+
if (!this.client) {
|
|
1766
|
+
throw new LoggingError("client does not exist");
|
|
1767
|
+
}
|
|
1768
|
+
const segoff = { segment: lref.getSegment(), offset: lref.getOffset() };
|
|
1769
|
+
if (segoff.segment?.localRefs?.has(lref) !== true) {
|
|
1770
|
+
return undefined;
|
|
1771
|
+
}
|
|
1772
|
+
const newSegoff = getSlideToSegoff(
|
|
1773
|
+
segoff,
|
|
1774
|
+
undefined,
|
|
1775
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1776
|
+
);
|
|
1777
|
+
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
1778
|
+
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
1779
|
+
? undefined
|
|
1780
|
+
: newSegoff;
|
|
1781
|
+
return value;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
private ackInterval(interval: TInterval, op: ISequencedDocumentMessage): void {
|
|
1785
|
+
// Only SequenceIntervals need potential sliding
|
|
1786
|
+
if (!(interval instanceof SequenceInterval)) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
if (
|
|
1791
|
+
!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove) &&
|
|
1792
|
+
!refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove)
|
|
1793
|
+
) {
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
const newStart = this.getSlideToSegment(interval.start);
|
|
1798
|
+
const newEnd = this.getSlideToSegment(interval.end);
|
|
1799
|
+
|
|
1800
|
+
const id = interval.properties[reservedIntervalIdKey];
|
|
1801
|
+
const hasPendingStartChange = this.hasPendingChangeStart(id);
|
|
1802
|
+
const hasPendingEndChange = this.hasPendingChangeEnd(id);
|
|
1803
|
+
|
|
1804
|
+
if (!hasPendingStartChange) {
|
|
1805
|
+
setSlideOnRemove(interval.start);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
if (!hasPendingEndChange) {
|
|
1809
|
+
setSlideOnRemove(interval.end);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
const needsStartUpdate = newStart !== undefined && !hasPendingStartChange;
|
|
1813
|
+
const needsEndUpdate = newEnd !== undefined && !hasPendingEndChange;
|
|
1814
|
+
|
|
1815
|
+
if (needsStartUpdate || needsEndUpdate) {
|
|
1816
|
+
if (!this.localCollection) {
|
|
1817
|
+
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// `interval`'s endpoints will get modified in-place, so clone it prior to doing so for event emission.
|
|
1821
|
+
const oldInterval = interval.clone() as TInterval & SequenceInterval;
|
|
1822
|
+
|
|
1823
|
+
// In this case, where we change the start or end of an interval,
|
|
1824
|
+
// it is necessary to remove and re-add the interval listeners.
|
|
1825
|
+
// This ensures that the correct listeners are added to the LocalReferencePosition.
|
|
1826
|
+
this.localCollection.removeExistingInterval(interval);
|
|
1827
|
+
if (!this.client) {
|
|
1828
|
+
throw new LoggingError("client does not exist");
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
if (needsStartUpdate) {
|
|
1832
|
+
const props = interval.start.properties;
|
|
1833
|
+
interval.start = createPositionReferenceFromSegoff(
|
|
1834
|
+
this.client,
|
|
1835
|
+
newStart,
|
|
1836
|
+
interval.start.refType,
|
|
1837
|
+
op,
|
|
1838
|
+
undefined,
|
|
1839
|
+
undefined,
|
|
1840
|
+
startReferenceSlidingPreference(interval.stickiness),
|
|
1841
|
+
startReferenceSlidingPreference(interval.stickiness) ===
|
|
1842
|
+
SlidingPreference.BACKWARD,
|
|
1843
|
+
);
|
|
1844
|
+
if (props) {
|
|
1845
|
+
interval.start.addProperties(props);
|
|
1846
|
+
}
|
|
1847
|
+
const oldSeg = oldInterval.start.getSegment();
|
|
1848
|
+
// remove and rebuild start interval as transient for event
|
|
1849
|
+
this.client.removeLocalReferencePosition(oldInterval.start);
|
|
1850
|
+
oldInterval.start.refType = ReferenceType.Transient;
|
|
1851
|
+
oldSeg?.localRefs?.addLocalRef(oldInterval.start, oldInterval.start.getOffset());
|
|
1852
|
+
}
|
|
1853
|
+
if (needsEndUpdate) {
|
|
1854
|
+
const props = interval.end.properties;
|
|
1855
|
+
interval.end = createPositionReferenceFromSegoff(
|
|
1856
|
+
this.client,
|
|
1857
|
+
newEnd,
|
|
1858
|
+
interval.end.refType,
|
|
1859
|
+
op,
|
|
1860
|
+
undefined,
|
|
1861
|
+
undefined,
|
|
1862
|
+
endReferenceSlidingPreference(interval.stickiness),
|
|
1863
|
+
endReferenceSlidingPreference(interval.stickiness) ===
|
|
1864
|
+
SlidingPreference.FORWARD,
|
|
1865
|
+
);
|
|
1866
|
+
if (props) {
|
|
1867
|
+
interval.end.addProperties(props);
|
|
1868
|
+
}
|
|
1869
|
+
// remove and rebuild end interval as transient for event
|
|
1870
|
+
const oldSeg = oldInterval.end.getSegment();
|
|
1871
|
+
this.client.removeLocalReferencePosition(oldInterval.end);
|
|
1872
|
+
oldInterval.end.refType = ReferenceType.Transient;
|
|
1873
|
+
oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
|
|
1874
|
+
}
|
|
1875
|
+
this.localCollection.add(interval);
|
|
1876
|
+
this.emitChange(interval, oldInterval as TInterval, true, true, op);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/** @internal */
|
|
1881
|
+
public ackAdd(
|
|
1882
|
+
serializedInterval: ISerializedInterval,
|
|
1883
|
+
local: boolean,
|
|
1884
|
+
op: ISequencedDocumentMessage,
|
|
1885
|
+
localOpMetadata: IMapMessageLocalMetadata | undefined,
|
|
1886
|
+
) {
|
|
1887
|
+
if (local) {
|
|
1888
|
+
assert(
|
|
1889
|
+
localOpMetadata !== undefined,
|
|
1890
|
+
0x553 /* op metadata should be defined for local op */,
|
|
1891
|
+
);
|
|
1892
|
+
this.localSeqToSerializedInterval.delete(localOpMetadata.localSeq);
|
|
1893
|
+
const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1894
|
+
const localInterval = this.getIntervalById(id);
|
|
1895
|
+
if (localInterval) {
|
|
1896
|
+
this.ackInterval(localInterval, op);
|
|
1897
|
+
}
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
if (!this.localCollection) {
|
|
1902
|
+
throw new LoggingError("attachSequence must be called");
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1906
|
+
|
|
1907
|
+
const interval: TInterval = this.localCollection.addInterval(
|
|
1908
|
+
toSequencePlace(serializedInterval.start, serializedInterval.startSide ?? Side.Before),
|
|
1909
|
+
toSequencePlace(serializedInterval.end, serializedInterval.endSide ?? Side.Before),
|
|
1910
|
+
serializedInterval.intervalType,
|
|
1911
|
+
serializedInterval.properties,
|
|
1912
|
+
op,
|
|
1913
|
+
);
|
|
1914
|
+
|
|
1915
|
+
if (interval) {
|
|
1916
|
+
if (this.onDeserialize) {
|
|
1917
|
+
this.onDeserialize(interval);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
this.emit("addInterval", interval, local, op);
|
|
1922
|
+
|
|
1923
|
+
return interval;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
/** @internal */
|
|
1927
|
+
public ackDelete(
|
|
1928
|
+
serializedInterval: ISerializedInterval,
|
|
1929
|
+
local: boolean,
|
|
1930
|
+
op: ISequencedDocumentMessage,
|
|
1931
|
+
): void {
|
|
1932
|
+
if (local) {
|
|
1933
|
+
// Local ops were applied when the message was created and there's no "pending delete"
|
|
1934
|
+
// state to book keep: remote operation application takes into account possibility of
|
|
1935
|
+
// locally deleted interval whenever a lookup happens.
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
if (!this.localCollection) {
|
|
1940
|
+
throw new LoggingError("attach must be called prior to deleting intervals");
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const id = this.localCollection.ensureSerializedId(serializedInterval);
|
|
1944
|
+
const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1945
|
+
if (interval) {
|
|
1946
|
+
this.deleteExistingInterval(interval, local, op);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/**
|
|
1951
|
+
* @internal
|
|
1952
|
+
*/
|
|
1953
|
+
public serializeInternal(): ISerializedIntervalCollectionV2 {
|
|
1954
|
+
if (!this.localCollection) {
|
|
1955
|
+
throw new LoggingError("attachSequence must be called");
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
return this.localCollection.serialize();
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* @returns an iterator over all intervals in this collection.
|
|
1963
|
+
*/
|
|
1964
|
+
public [Symbol.iterator](): IntervalCollectionIterator<TInterval> {
|
|
1965
|
+
const iterator = new IntervalCollectionIterator<TInterval>(this);
|
|
1966
|
+
return iterator;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* {@inheritdoc IIntervalCollection.CreateForwardIteratorWithStartPosition}
|
|
1971
|
+
*/
|
|
1972
|
+
public CreateForwardIteratorWithStartPosition(
|
|
1973
|
+
startPosition: number,
|
|
1974
|
+
): IntervalCollectionIterator<TInterval> {
|
|
1975
|
+
const iterator = new IntervalCollectionIterator<TInterval>(this, true, startPosition);
|
|
1976
|
+
return iterator;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithStartPosition}
|
|
1981
|
+
*/
|
|
1982
|
+
public CreateBackwardIteratorWithStartPosition(
|
|
1983
|
+
startPosition: number,
|
|
1984
|
+
): IntervalCollectionIterator<TInterval> {
|
|
1985
|
+
const iterator = new IntervalCollectionIterator<TInterval>(this, false, startPosition);
|
|
1986
|
+
return iterator;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* {@inheritdoc IIntervalCollection.CreateForwardIteratorWithEndPosition}
|
|
1991
|
+
*/
|
|
1992
|
+
public CreateForwardIteratorWithEndPosition(
|
|
1993
|
+
endPosition: number,
|
|
1994
|
+
): IntervalCollectionIterator<TInterval> {
|
|
1995
|
+
const iterator = new IntervalCollectionIterator<TInterval>(
|
|
1996
|
+
this,
|
|
1997
|
+
true,
|
|
1998
|
+
undefined,
|
|
1999
|
+
endPosition,
|
|
2000
|
+
);
|
|
2001
|
+
return iterator;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
/**
|
|
2005
|
+
* {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithEndPosition}
|
|
2006
|
+
*/
|
|
2007
|
+
public CreateBackwardIteratorWithEndPosition(
|
|
2008
|
+
endPosition: number,
|
|
2009
|
+
): IntervalCollectionIterator<TInterval> {
|
|
2010
|
+
const iterator = new IntervalCollectionIterator<TInterval>(
|
|
2011
|
+
this,
|
|
2012
|
+
false,
|
|
2013
|
+
undefined,
|
|
2014
|
+
endPosition,
|
|
2015
|
+
);
|
|
2016
|
+
return iterator;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* {@inheritdoc IIntervalCollection.gatherIterationResults}
|
|
2021
|
+
*/
|
|
2022
|
+
public gatherIterationResults(
|
|
2023
|
+
results: TInterval[],
|
|
2024
|
+
iteratesForward: boolean,
|
|
2025
|
+
start?: number,
|
|
2026
|
+
end?: number,
|
|
2027
|
+
) {
|
|
2028
|
+
if (!this.localCollection) {
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
this.localCollection.overlappingIntervalsIndex.gatherIterationResults(
|
|
2033
|
+
results,
|
|
2034
|
+
iteratesForward,
|
|
2035
|
+
start,
|
|
2036
|
+
end,
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* {@inheritdoc IIntervalCollection.findOverlappingIntervals}
|
|
2042
|
+
*/
|
|
2043
|
+
public findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[] {
|
|
2044
|
+
if (!this.localCollection) {
|
|
2045
|
+
throw new LoggingError("attachSequence must be called");
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
return this.localCollection.overlappingIntervalsIndex.findOverlappingIntervals(
|
|
2049
|
+
startPosition,
|
|
2050
|
+
endPosition,
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* {@inheritdoc IIntervalCollection.map}
|
|
2056
|
+
*/
|
|
2057
|
+
public map(fn: (interval: TInterval) => void) {
|
|
2058
|
+
if (!this.localCollection) {
|
|
2059
|
+
throw new LoggingError("attachSequence must be called");
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
for (const interval of this.localCollection.idIntervalIndex) {
|
|
2063
|
+
fn(interval);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* {@inheritdoc IIntervalCollection.previousInterval}
|
|
2069
|
+
*/
|
|
2070
|
+
public previousInterval(pos: number): TInterval | undefined {
|
|
2071
|
+
if (!this.localCollection) {
|
|
2072
|
+
throw new LoggingError("attachSequence must be called");
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
return this.localCollection.endIntervalIndex.previousInterval(pos);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* {@inheritdoc IIntervalCollection.nextInterval}
|
|
2080
|
+
*/
|
|
2081
|
+
public nextInterval(pos: number): TInterval | undefined {
|
|
2082
|
+
if (!this.localCollection) {
|
|
2083
|
+
throw new LoggingError("attachSequence must be called");
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
return this.localCollection.endIntervalIndex.nextInterval(pos);
|
|
2087
|
+
}
|
|
987
2088
|
}
|
|
988
2089
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
constructor(
|
|
996
|
-
collection: IntervalCollection<TInterval>,
|
|
997
|
-
iteratesForward: boolean = true,
|
|
998
|
-
start?: number,
|
|
999
|
-
end?: number) {
|
|
1000
|
-
this.results = [];
|
|
1001
|
-
this.index = 0;
|
|
1002
|
-
|
|
1003
|
-
collection.gatherIterationResults(this.results, iteratesForward, start, end);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
public next() {
|
|
1007
|
-
let _value: TInterval | undefined;
|
|
1008
|
-
let _done: boolean = true;
|
|
1009
|
-
|
|
1010
|
-
if (this.index < this.results.length) {
|
|
1011
|
-
_value = this.results[this.index++];
|
|
1012
|
-
_done = false;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
return {
|
|
1016
|
-
value: _value,
|
|
1017
|
-
done: _done,
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
2090
|
+
function setSlideOnRemove(lref: LocalReferencePosition) {
|
|
2091
|
+
let refType = lref.refType;
|
|
2092
|
+
refType = refType & ~ReferenceType.StayOnRemove;
|
|
2093
|
+
refType = refType | ReferenceType.SlideOnRemove;
|
|
2094
|
+
lref.refType = refType;
|
|
1020
2095
|
}
|
|
1021
2096
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
listener: (interval: TInterval, local: boolean, op: ISequencedDocumentMessage) => void);
|
|
1036
|
-
(event: "propertyChanged", listener: (interval: TInterval, propertyArgs: PropertySet) => void);
|
|
2097
|
+
/**
|
|
2098
|
+
* Information that identifies an interval within a `Sequence`.
|
|
2099
|
+
* @internal
|
|
2100
|
+
*/
|
|
2101
|
+
export interface IntervalLocator {
|
|
2102
|
+
/**
|
|
2103
|
+
* Label for the collection the interval is a part of
|
|
2104
|
+
*/
|
|
2105
|
+
label: string;
|
|
2106
|
+
/**
|
|
2107
|
+
* Interval within that collection
|
|
2108
|
+
*/
|
|
2109
|
+
interval: SequenceInterval;
|
|
1037
2110
|
}
|
|
1038
2111
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1055
|
-
private readonly requiresClient: boolean,
|
|
1056
|
-
private readonly emitter: IValueOpEmitter,
|
|
1057
|
-
serializedIntervals: ISerializedInterval[] | ISerializedIntervalCollectionV2,
|
|
1058
|
-
) {
|
|
1059
|
-
super();
|
|
1060
|
-
|
|
1061
|
-
if (Array.isArray(serializedIntervals)) {
|
|
1062
|
-
this.savedSerializedIntervals = serializedIntervals;
|
|
1063
|
-
} else {
|
|
1064
|
-
this.savedSerializedIntervals =
|
|
1065
|
-
serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
public attachGraph(client: Client, label: string) {
|
|
1070
|
-
if (this.attached) {
|
|
1071
|
-
throw new LoggingError("Only supports one Sequence attach");
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
if ((client === undefined) && (this.requiresClient)) {
|
|
1075
|
-
throw new LoggingError("Client required for this collection");
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// Instantiate the local interval collection based on the saved intervals
|
|
1079
|
-
this.client = client;
|
|
1080
|
-
this.localCollection = new LocalIntervalCollection<TInterval>(
|
|
1081
|
-
client,
|
|
1082
|
-
label,
|
|
1083
|
-
this.helpers,
|
|
1084
|
-
(interval) => this.emit("changeInterval", interval, true, undefined),
|
|
1085
|
-
);
|
|
1086
|
-
if (this.savedSerializedIntervals) {
|
|
1087
|
-
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
1088
|
-
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1089
|
-
this.localCollection.addInterval(
|
|
1090
|
-
serializedInterval.start,
|
|
1091
|
-
serializedInterval.end,
|
|
1092
|
-
serializedInterval.intervalType,
|
|
1093
|
-
serializedInterval.properties);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
this.savedSerializedIntervals = undefined;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* Gets the next local sequence number, modifying this client's collab window in doing so.
|
|
1101
|
-
*/
|
|
1102
|
-
private getNextLocalSeq(): number {
|
|
1103
|
-
return ++this.client.getCollabWindow().localSeq;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
public getIntervalById(id: string) {
|
|
1107
|
-
if (!this.attached) {
|
|
1108
|
-
throw new LoggingError("attach must be called before accessing intervals");
|
|
1109
|
-
}
|
|
1110
|
-
return this.localCollection.getIntervalById(id);
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
/**
|
|
1114
|
-
* Create a new interval and add it to the collection
|
|
1115
|
-
* @param start - interval start position
|
|
1116
|
-
* @param end - interval end position
|
|
1117
|
-
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
1118
|
-
* @param props - properties of the interval
|
|
1119
|
-
* @returns - the created interval
|
|
1120
|
-
*/
|
|
1121
|
-
public add(
|
|
1122
|
-
start: number,
|
|
1123
|
-
end: number,
|
|
1124
|
-
intervalType: IntervalType,
|
|
1125
|
-
props?: PropertySet,
|
|
1126
|
-
) {
|
|
1127
|
-
if (!this.attached) {
|
|
1128
|
-
throw new LoggingError("attach must be called prior to adding intervals");
|
|
1129
|
-
}
|
|
1130
|
-
if (intervalType & IntervalType.Transient) {
|
|
1131
|
-
throw new LoggingError("Can not add transient intervals");
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
const interval: TInterval = this.localCollection.addInterval(start, end, intervalType, props);
|
|
1135
|
-
|
|
1136
|
-
if (interval) {
|
|
1137
|
-
const serializedInterval = {
|
|
1138
|
-
end,
|
|
1139
|
-
intervalType,
|
|
1140
|
-
properties: interval.properties,
|
|
1141
|
-
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1142
|
-
start,
|
|
1143
|
-
};
|
|
1144
|
-
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1145
|
-
this.emitter.emit("add", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
this.emit("addInterval", interval, true, undefined);
|
|
1149
|
-
|
|
1150
|
-
return interval;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
private deleteExistingInterval(interval: TInterval, local: boolean, op?: ISequencedDocumentMessage) {
|
|
1154
|
-
// The given interval is known to exist in the collection.
|
|
1155
|
-
this.localCollection.removeExistingInterval(interval);
|
|
1156
|
-
|
|
1157
|
-
if (interval) {
|
|
1158
|
-
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1159
|
-
if (local) {
|
|
1160
|
-
this.emitter.emit(
|
|
1161
|
-
"delete",
|
|
1162
|
-
undefined,
|
|
1163
|
-
interval.serialize(this.client),
|
|
1164
|
-
{ localSeq: this.getNextLocalSeq() },
|
|
1165
|
-
);
|
|
1166
|
-
} else {
|
|
1167
|
-
if (this.onDeserialize) {
|
|
1168
|
-
this.onDeserialize(interval);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
this.emit("deleteInterval", interval, local, op);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
public removeIntervalById(id: string) {
|
|
1177
|
-
const interval = this.localCollection.getIntervalById(id);
|
|
1178
|
-
if (interval) {
|
|
1179
|
-
this.deleteExistingInterval(interval, true, undefined);
|
|
1180
|
-
}
|
|
1181
|
-
return interval;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
public changeProperties(id: string, props: PropertySet) {
|
|
1185
|
-
if (!this.attached) {
|
|
1186
|
-
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1187
|
-
}
|
|
1188
|
-
if (typeof (id) !== "string") {
|
|
1189
|
-
throw new LoggingError("Change API requires an ID that is a string");
|
|
1190
|
-
}
|
|
1191
|
-
if (!props) {
|
|
1192
|
-
throw new LoggingError("changeProperties should be called with a property set");
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
const interval = this.getIntervalById(id);
|
|
1196
|
-
if (interval) {
|
|
1197
|
-
// Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
|
|
1198
|
-
const deltaProps = interval.addProperties(props, true, UnassignedSequenceNumber);
|
|
1199
|
-
const serializedInterval: ISerializedInterval = interval.serialize(this.client);
|
|
1200
|
-
|
|
1201
|
-
// Emit a change op that will only change properties. Add the ID to
|
|
1202
|
-
// the property bag provided by the caller.
|
|
1203
|
-
serializedInterval.start = undefined as any;
|
|
1204
|
-
serializedInterval.end = undefined as any;
|
|
1205
|
-
|
|
1206
|
-
serializedInterval.properties = props;
|
|
1207
|
-
serializedInterval.properties[reservedIntervalIdKey] = interval.getIntervalId();
|
|
1208
|
-
this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
|
|
1209
|
-
this.emit("propertyChanged", interval, deltaProps);
|
|
1210
|
-
}
|
|
1211
|
-
this.emit("changeInterval", interval, true, undefined);
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
public change(id: string, start?: number, end?: number): TInterval | undefined {
|
|
1215
|
-
if (!this.attached) {
|
|
1216
|
-
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// Force id to be a string.
|
|
1220
|
-
if (typeof (id) !== "string") {
|
|
1221
|
-
throw new LoggingError("Change API requires an ID that is a string");
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
const interval = this.getIntervalById(id);
|
|
1225
|
-
if (interval) {
|
|
1226
|
-
const newInterval = this.localCollection.changeInterval(interval, start, end);
|
|
1227
|
-
const serializedInterval: ISerializedInterval = interval.serialize(this.client);
|
|
1228
|
-
serializedInterval.start = start;
|
|
1229
|
-
serializedInterval.end = end;
|
|
1230
|
-
// Emit a property bag containing only the ID, as we don't intend for this op to change any properties.
|
|
1231
|
-
serializedInterval.properties =
|
|
1232
|
-
{
|
|
1233
|
-
[reservedIntervalIdKey]: interval.getIntervalId(),
|
|
1234
|
-
};
|
|
1235
|
-
this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
|
|
1236
|
-
this.addPendingChange(id, serializedInterval);
|
|
1237
|
-
this.emit("changeInterval", newInterval, true, undefined);
|
|
1238
|
-
return newInterval;
|
|
1239
|
-
}
|
|
1240
|
-
// No interval to change
|
|
1241
|
-
return undefined;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
private addPendingChange(id: string, serializedInterval: ISerializedInterval) {
|
|
1245
|
-
if (serializedInterval.start !== undefined) {
|
|
1246
|
-
this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
|
|
1247
|
-
}
|
|
1248
|
-
if (serializedInterval.end !== undefined) {
|
|
1249
|
-
this.addPendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
private addPendingChangeHelper(
|
|
1254
|
-
id: string,
|
|
1255
|
-
pendingChanges: Map<string, ISerializedInterval[]>,
|
|
1256
|
-
serializedInterval: ISerializedInterval,
|
|
1257
|
-
) {
|
|
1258
|
-
let entries: ISerializedInterval[] | undefined = pendingChanges.get(id);
|
|
1259
|
-
if (!entries) {
|
|
1260
|
-
entries = [];
|
|
1261
|
-
pendingChanges.set(id, entries);
|
|
1262
|
-
}
|
|
1263
|
-
entries.push(serializedInterval);
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
private removePendingChange(serializedInterval: ISerializedInterval) {
|
|
1267
|
-
// Change ops always have an ID.
|
|
1268
|
-
const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1269
|
-
if (serializedInterval.start !== undefined) {
|
|
1270
|
-
this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
|
|
1271
|
-
}
|
|
1272
|
-
if (serializedInterval.end !== undefined) {
|
|
1273
|
-
this.removePendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
private removePendingChangeHelper(
|
|
1278
|
-
id: string,
|
|
1279
|
-
pendingChanges: Map<string, ISerializedInterval[]>,
|
|
1280
|
-
serializedInterval: ISerializedInterval,
|
|
1281
|
-
) {
|
|
1282
|
-
const entries = pendingChanges.get(id);
|
|
1283
|
-
if (entries) {
|
|
1284
|
-
const pendingChange = entries.shift();
|
|
1285
|
-
if (entries.length === 0) {
|
|
1286
|
-
pendingChanges.delete(id);
|
|
1287
|
-
}
|
|
1288
|
-
if (pendingChange?.start !== serializedInterval.start ||
|
|
1289
|
-
pendingChange?.end !== serializedInterval.end) {
|
|
1290
|
-
throw new LoggingError("Mismatch in pending changes");
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
private hasPendingChangeStart(id: string) {
|
|
1296
|
-
const entries = this.pendingChangesStart.get(id);
|
|
1297
|
-
return entries && entries.length !== 0;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
private hasPendingChangeEnd(id: string) {
|
|
1301
|
-
const entries = this.pendingChangesEnd.get(id);
|
|
1302
|
-
return entries && entries.length !== 0;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
/** @deprecated - use ackChange */
|
|
1306
|
-
public changeInterval(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
|
|
1307
|
-
return this.ackChange(serializedInterval, local, op);
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
/** @internal */
|
|
1311
|
-
public ackChange(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
|
|
1312
|
-
if (!this.attached) {
|
|
1313
|
-
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
let interval: TInterval | undefined;
|
|
1317
|
-
|
|
1318
|
-
if (local) {
|
|
1319
|
-
// This is an ack from the server. Remove the pending change.
|
|
1320
|
-
this.removePendingChange(serializedInterval);
|
|
1321
|
-
const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1322
|
-
interval = this.getIntervalById(id);
|
|
1323
|
-
if (interval) {
|
|
1324
|
-
// Let the propertyManager prune its pending change-properties set.
|
|
1325
|
-
interval.propertyManager?.ackPendingProperties(
|
|
1326
|
-
{
|
|
1327
|
-
type: MergeTreeDeltaType.ANNOTATE,
|
|
1328
|
-
props: serializedInterval.properties ?? {},
|
|
1329
|
-
});
|
|
1330
|
-
|
|
1331
|
-
this.ackInterval(interval, op);
|
|
1332
|
-
}
|
|
1333
|
-
} else {
|
|
1334
|
-
// If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
|
|
1335
|
-
// should be the winning change.
|
|
1336
|
-
// Note that the ID is in the property bag only to allow us to find the interval.
|
|
1337
|
-
// This API cannot change the ID, and writing to the ID property will result in an exception. So we
|
|
1338
|
-
// strip it out of the properties here.
|
|
1339
|
-
const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties;
|
|
1340
|
-
interval = this.getIntervalById(id);
|
|
1341
|
-
if (interval) {
|
|
1342
|
-
let start: number | undefined;
|
|
1343
|
-
let end: number | undefined;
|
|
1344
|
-
// Track pending start/end independently of one another.
|
|
1345
|
-
if (!this.hasPendingChangeStart(id)) {
|
|
1346
|
-
start = serializedInterval.start;
|
|
1347
|
-
}
|
|
1348
|
-
if (!this.hasPendingChangeEnd(id)) {
|
|
1349
|
-
end = serializedInterval.end;
|
|
1350
|
-
}
|
|
1351
|
-
if (start !== undefined || end !== undefined) {
|
|
1352
|
-
// If changeInterval gives us a new interval, work with that one. Otherwise keep working with
|
|
1353
|
-
// the one we originally found in the tree.
|
|
1354
|
-
interval = this.localCollection.changeInterval(interval, start, end, op) ?? interval;
|
|
1355
|
-
}
|
|
1356
|
-
const deltaProps = interval.addProperties(newProps, true, op.sequenceNumber);
|
|
1357
|
-
if (this.onDeserialize) {
|
|
1358
|
-
this.onDeserialize(interval);
|
|
1359
|
-
}
|
|
1360
|
-
this.emit("propertyChanged", interval, deltaProps);
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
if (interval) {
|
|
1364
|
-
this.emit("changeInterval", interval, local, op);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
public addConflictResolver(conflictResolver: IntervalConflictResolver<TInterval>): void {
|
|
1369
|
-
if (!this.attached) {
|
|
1370
|
-
throw new LoggingError("attachSequence must be called");
|
|
1371
|
-
}
|
|
1372
|
-
this.localCollection.addConflictResolver(conflictResolver);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
public attachDeserializer(onDeserialize: DeserializeCallback): void {
|
|
1376
|
-
// If no deserializer is specified can skip all processing work
|
|
1377
|
-
if (!onDeserialize) {
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// Start by storing the callbacks so that any subsequent modifications make use of them
|
|
1382
|
-
this.onDeserialize = onDeserialize;
|
|
1383
|
-
|
|
1384
|
-
// Trigger the async prepare work across all values in the collection
|
|
1385
|
-
this.localCollection.map((interval) => {
|
|
1386
|
-
onDeserialize(interval);
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
/** @internal */
|
|
1391
|
-
public rebaseLocalInterval(
|
|
1392
|
-
opName: string,
|
|
1393
|
-
serializedInterval: ISerializedInterval,
|
|
1394
|
-
localSeq: number,
|
|
1395
|
-
) {
|
|
1396
|
-
if (!this.attached) {
|
|
1397
|
-
throw new LoggingError("attachSequence must be called");
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
const { start, end, intervalType, properties, sequenceNumber } = serializedInterval;
|
|
1401
|
-
const startRebased = start === undefined ? undefined :
|
|
1402
|
-
this.client.rebasePosition(start, sequenceNumber, localSeq);
|
|
1403
|
-
const endRebased = end === undefined ? undefined :
|
|
1404
|
-
this.client.rebasePosition(end, sequenceNumber, localSeq);
|
|
1405
|
-
|
|
1406
|
-
const intervalId = properties?.[reservedIntervalIdKey];
|
|
1407
|
-
const rebased: ISerializedInterval = {
|
|
1408
|
-
start: startRebased,
|
|
1409
|
-
end: endRebased,
|
|
1410
|
-
intervalType,
|
|
1411
|
-
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1412
|
-
properties,
|
|
1413
|
-
};
|
|
1414
|
-
if (opName === "change" && (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
|
|
1415
|
-
this.removePendingChange(serializedInterval);
|
|
1416
|
-
this.addPendingChange(intervalId, rebased);
|
|
1417
|
-
}
|
|
1418
|
-
return rebased;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
private getSlideToSegment(lref: LocalReference) {
|
|
1422
|
-
const segoff = { segment: lref.segment, offset: lref.offset };
|
|
1423
|
-
const newSegoff = this.client.getSlideToSegment(segoff);
|
|
1424
|
-
const value: { segment: ISegment | undefined; offset: number | undefined; } | undefined
|
|
1425
|
-
= (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
|
|
1426
|
-
return value;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
private setSlideOnRemove(lref: LocalReference) {
|
|
1430
|
-
let refType = lref.refType;
|
|
1431
|
-
refType = refType & ~ReferenceType.StayOnRemove;
|
|
1432
|
-
refType = refType | ReferenceType.SlideOnRemove;
|
|
1433
|
-
lref.refType = refType;
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
|
|
1437
|
-
// in current usage, interval is always a SequenceInterval
|
|
1438
|
-
if (!(interval instanceof SequenceInterval)) {
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
if (!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove) &&
|
|
1443
|
-
!refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove)) {
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
const newStart = this.getSlideToSegment(interval.start);
|
|
1448
|
-
const newEnd = this.getSlideToSegment(interval.end);
|
|
1449
|
-
|
|
1450
|
-
const id = interval.properties[reservedIntervalIdKey];
|
|
1451
|
-
const hasPendingStartChange = this.hasPendingChangeStart(id);
|
|
1452
|
-
const hasPendingEndChange = this.hasPendingChangeEnd(id);
|
|
1453
|
-
|
|
1454
|
-
if (!hasPendingStartChange) {
|
|
1455
|
-
this.setSlideOnRemove(interval.start);
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
if (!hasPendingEndChange) {
|
|
1459
|
-
this.setSlideOnRemove(interval.end);
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
const needsStartUpdate = newStart !== undefined && !hasPendingStartChange;
|
|
1463
|
-
const needsEndUpdate = newEnd !== undefined && !hasPendingEndChange;
|
|
1464
|
-
|
|
1465
|
-
if (needsStartUpdate || needsEndUpdate) {
|
|
1466
|
-
// In this case, where we change the start or end of an interval,
|
|
1467
|
-
// it is necessary to remove and re-add the interval listeners.
|
|
1468
|
-
// This ensures that the correct listeners are added to the ReferencePosition.
|
|
1469
|
-
this.localCollection.removeExistingInterval(interval);
|
|
1470
|
-
|
|
1471
|
-
if (needsStartUpdate) {
|
|
1472
|
-
const props = interval.start.properties;
|
|
1473
|
-
this.client.removeLocalReferencePosition(interval.start);
|
|
1474
|
-
interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
|
|
1475
|
-
if (props) {
|
|
1476
|
-
interval.start.addProperties(props);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
if (needsEndUpdate) {
|
|
1480
|
-
const props = interval.end.properties;
|
|
1481
|
-
this.client.removeLocalReferencePosition(interval.end);
|
|
1482
|
-
interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
|
|
1483
|
-
if (props) {
|
|
1484
|
-
interval.end.addProperties(props);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
this.localCollection.add(interval);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
/** @deprecated - use ackAdd */
|
|
1492
|
-
public addInternal(
|
|
1493
|
-
serializedInterval: ISerializedInterval,
|
|
1494
|
-
local: boolean,
|
|
1495
|
-
op: ISequencedDocumentMessage) {
|
|
1496
|
-
return this.ackAdd(serializedInterval, local, op);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
/** @internal */
|
|
1500
|
-
public ackAdd(
|
|
1501
|
-
serializedInterval: ISerializedInterval,
|
|
1502
|
-
local: boolean,
|
|
1503
|
-
op: ISequencedDocumentMessage) {
|
|
1504
|
-
if (local) {
|
|
1505
|
-
const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1506
|
-
const localInterval = this.getIntervalById(id);
|
|
1507
|
-
if (localInterval) {
|
|
1508
|
-
this.ackInterval(localInterval, op);
|
|
1509
|
-
}
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
if (!this.attached) {
|
|
1514
|
-
throw new LoggingError("attachSequence must be called");
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1518
|
-
|
|
1519
|
-
const interval: TInterval = this.localCollection.addInterval(
|
|
1520
|
-
serializedInterval.start,
|
|
1521
|
-
serializedInterval.end,
|
|
1522
|
-
serializedInterval.intervalType,
|
|
1523
|
-
serializedInterval.properties,
|
|
1524
|
-
op);
|
|
1525
|
-
|
|
1526
|
-
if (interval) {
|
|
1527
|
-
if (this.onDeserialize) {
|
|
1528
|
-
this.onDeserialize(interval);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
this.emit("addInterval", interval, local, op);
|
|
1533
|
-
|
|
1534
|
-
return interval;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
/** @deprecated - use ackDelete */
|
|
1538
|
-
public deleteInterval(
|
|
1539
|
-
serializedInterval: ISerializedInterval,
|
|
1540
|
-
local: boolean,
|
|
1541
|
-
op: ISequencedDocumentMessage): void {
|
|
1542
|
-
return this.ackDelete(serializedInterval, local, op);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
/** @internal */
|
|
1546
|
-
public ackDelete(
|
|
1547
|
-
serializedInterval: ISerializedInterval,
|
|
1548
|
-
local: boolean,
|
|
1549
|
-
op: ISequencedDocumentMessage): void {
|
|
1550
|
-
if (local) {
|
|
1551
|
-
// Local ops were applied when the message was created and there's no "pending delete"
|
|
1552
|
-
// state to bookkeep: remote operation application takes into account possibility of
|
|
1553
|
-
// locally deleted interval whenever a lookup happens.
|
|
1554
|
-
return;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
if (!this.attached) {
|
|
1558
|
-
throw new LoggingError("attach must be called prior to deleting intervals");
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
const id = this.localCollection.ensureSerializedId(serializedInterval);
|
|
1562
|
-
const interval = this.localCollection.getIntervalById(id);
|
|
1563
|
-
if (interval) {
|
|
1564
|
-
this.deleteExistingInterval(interval, local, op);
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
/**
|
|
1569
|
-
* @internal
|
|
1570
|
-
*/
|
|
1571
|
-
public serializeInternal(): ISerializedIntervalCollectionV2 {
|
|
1572
|
-
if (!this.attached) {
|
|
1573
|
-
throw new LoggingError("attachSequence must be called");
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
return this.localCollection.serialize();
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
public [Symbol.iterator](): IntervalCollectionIterator<TInterval> {
|
|
1580
|
-
const iterator = new IntervalCollectionIterator<TInterval>(this);
|
|
1581
|
-
return iterator;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
public CreateForwardIteratorWithStartPosition(startPosition: number): IntervalCollectionIterator<TInterval> {
|
|
1585
|
-
const iterator = new IntervalCollectionIterator<TInterval>(this, true, startPosition);
|
|
1586
|
-
return iterator;
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
public CreateBackwardIteratorWithStartPosition(startPosition: number): IntervalCollectionIterator<TInterval> {
|
|
1590
|
-
const iterator = new IntervalCollectionIterator<TInterval>(this, false, startPosition);
|
|
1591
|
-
return iterator;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
public CreateForwardIteratorWithEndPosition(endPosition: number): IntervalCollectionIterator<TInterval> {
|
|
1595
|
-
const iterator = new IntervalCollectionIterator<TInterval>(this, true, undefined, endPosition);
|
|
1596
|
-
return iterator;
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
public CreateBackwardIteratorWithEndPosition(endPosition: number): IntervalCollectionIterator<TInterval> {
|
|
1600
|
-
const iterator = new IntervalCollectionIterator<TInterval>(this, false, undefined, endPosition);
|
|
1601
|
-
return iterator;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
public gatherIterationResults(
|
|
1605
|
-
results: TInterval[],
|
|
1606
|
-
iteratesForward: boolean,
|
|
1607
|
-
start?: number,
|
|
1608
|
-
end?: number) {
|
|
1609
|
-
if (!this.attached) {
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
this.localCollection.gatherIterationResults(results, iteratesForward, start, end);
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
public findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[] {
|
|
1617
|
-
if (!this.attached) {
|
|
1618
|
-
throw new LoggingError("attachSequence must be called");
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
return this.localCollection.findOverlappingIntervals(startPosition, endPosition);
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
public map(fn: (interval: TInterval) => void) {
|
|
1625
|
-
if (!this.attached) {
|
|
1626
|
-
throw new LoggingError("attachSequence must be called");
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
this.localCollection.map(fn);
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
public previousInterval(pos: number): TInterval {
|
|
1633
|
-
if (!this.attached) {
|
|
1634
|
-
throw new LoggingError("attachSequence must be called");
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
return this.localCollection.previousInterval(pos);
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
public nextInterval(pos: number): TInterval {
|
|
1641
|
-
if (!this.attached) {
|
|
1642
|
-
throw new LoggingError("attachSequence must be called");
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
return this.localCollection.nextInterval(pos);
|
|
1646
|
-
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Returns an object that can be used to find the interval a given LocalReferencePosition belongs to.
|
|
2114
|
+
* @returns undefined if the reference position is not the endpoint of any interval (e.g. it was created
|
|
2115
|
+
* on the merge tree directly by app code), otherwise an {@link IntervalLocator} for the interval this
|
|
2116
|
+
* endpoint is a part of.
|
|
2117
|
+
* @internal
|
|
2118
|
+
*/
|
|
2119
|
+
export function intervalLocatorFromEndpoint(
|
|
2120
|
+
potentialEndpoint: LocalReferencePosition,
|
|
2121
|
+
): IntervalLocator | undefined {
|
|
2122
|
+
const { interval, [reservedRangeLabelsKey]: collectionNameArray } =
|
|
2123
|
+
potentialEndpoint.properties ?? {};
|
|
2124
|
+
return interval && collectionNameArray?.length === 1
|
|
2125
|
+
? { label: collectionNameArray[0], interval }
|
|
2126
|
+
: undefined;
|
|
1647
2127
|
}
|