@fluidframework/sequence 2.0.0-dev.4.1.0.148229 → 2.0.0-dev.4.3.0.157531
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +28 -6
- package/dist/intervalCollection.d.ts +96 -30
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +164 -123
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalTree.d.ts +8 -1
- package/dist/intervalTree.d.ts.map +1 -1
- package/dist/intervalTree.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/intervalCollection.d.ts +96 -30
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +163 -121
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalTree.d.ts +8 -1
- package/lib/intervalTree.d.ts.map +1 -1
- package/lib/intervalTree.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +21 -31
- package/src/intervalCollection.ts +230 -145
- package/src/intervalTree.ts +8 -1
- package/src/packageVersion.ts +1 -1
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
addProperties,
|
|
13
13
|
Client,
|
|
14
14
|
compareReferencePositions,
|
|
15
|
-
ConflictAction,
|
|
16
15
|
createMap,
|
|
17
16
|
ICombiningOp,
|
|
18
17
|
ISegment,
|
|
@@ -78,9 +77,9 @@ export interface ISerializedInterval {
|
|
|
78
77
|
* At the time of writing, it's not plumbed through to the reconnect/rebase code, however, which does need it.
|
|
79
78
|
*/
|
|
80
79
|
sequenceNumber: number;
|
|
81
|
-
/** Start position of the interval
|
|
80
|
+
/** Start position of the interval */
|
|
82
81
|
start: number;
|
|
83
|
-
/** End position of the interval
|
|
82
|
+
/** End position of the interval */
|
|
84
83
|
end: number;
|
|
85
84
|
/** Interval type to create */
|
|
86
85
|
intervalType: IntervalType;
|
|
@@ -224,11 +223,9 @@ export class Interval implements ISerializableInterval {
|
|
|
224
223
|
/**
|
|
225
224
|
* {@inheritDoc ISerializableInterval.getIntervalId}
|
|
226
225
|
*/
|
|
227
|
-
public getIntervalId(): string
|
|
226
|
+
public getIntervalId(): string {
|
|
228
227
|
const id = this.properties?.[reservedIntervalIdKey];
|
|
229
|
-
|
|
230
|
-
return undefined;
|
|
231
|
-
}
|
|
228
|
+
assert(id !== undefined, 0x5e1 /* interval ID should not be undefined */);
|
|
232
229
|
return `${id}`;
|
|
233
230
|
}
|
|
234
231
|
|
|
@@ -400,6 +397,22 @@ export class Interval implements ISerializableInterval {
|
|
|
400
397
|
* Interval impelmentation whose ends are associated with positions in a mutatable sequence.
|
|
401
398
|
* As such, when content is inserted into the middle of the interval, the interval expands to
|
|
402
399
|
* include that content.
|
|
400
|
+
*
|
|
401
|
+
* @remarks - The endpoint's position should be treated exclusively to get reasonable behavior--i.e.
|
|
402
|
+
* an interval referring to "hello" in "hello world" should have a start position of 0 and an end
|
|
403
|
+
* position of 5.
|
|
404
|
+
*
|
|
405
|
+
* To see why, consider what happens if "llo wor" is removed from the string to make "held".
|
|
406
|
+
* The interval's startpoint remains on the "h" (it isn't altered), but the interval's endpoint
|
|
407
|
+
* slides forward to the next unremoved position, which is the "l" in "held".
|
|
408
|
+
* Users would generally expect the interval to now refer to "he" (as it is the subset of content
|
|
409
|
+
* remaining after the removal), hence the "l" should be excluded.
|
|
410
|
+
* If the interval endpoint was treated inclusively, the interval would now refer to "hel", which
|
|
411
|
+
* is undesirable.
|
|
412
|
+
*
|
|
413
|
+
* Since the end of an interval is treated exclusively but cannot be greater than or equal to the
|
|
414
|
+
* length of the associated sequence, application models which leverage interval collections should
|
|
415
|
+
* consider inserting a marker at the end of the sequence to represent the end of the content.
|
|
403
416
|
*/
|
|
404
417
|
export class SequenceInterval implements ISerializableInterval {
|
|
405
418
|
/**
|
|
@@ -556,11 +569,9 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
556
569
|
/**
|
|
557
570
|
* {@inheritDoc ISerializableInterval.getIntervalId}
|
|
558
571
|
*/
|
|
559
|
-
public getIntervalId(): string
|
|
572
|
+
public getIntervalId(): string {
|
|
560
573
|
const id = this.properties?.[reservedIntervalIdKey];
|
|
561
|
-
|
|
562
|
-
return undefined;
|
|
563
|
-
}
|
|
574
|
+
assert(id !== undefined, 0x5e2 /* interval ID should not be undefined */);
|
|
564
575
|
return `${id}`;
|
|
565
576
|
}
|
|
566
577
|
|
|
@@ -593,7 +604,6 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
593
604
|
|
|
594
605
|
/**
|
|
595
606
|
* @returns whether this interval overlaps two numerical positions.
|
|
596
|
-
* @remarks - this is currently strict overlap, which doesn't align with the endpoint treatment of`.overlaps()`
|
|
597
607
|
*/
|
|
598
608
|
public overlapsPos(bstart: number, bend: number) {
|
|
599
609
|
const startPos = this.client.localReferencePositionToPosition(this.start);
|
|
@@ -679,6 +689,7 @@ function createPositionReferenceFromSegoff(
|
|
|
679
689
|
refType: ReferenceType,
|
|
680
690
|
op?: ISequencedDocumentMessage,
|
|
681
691
|
localSeq?: number,
|
|
692
|
+
fromSnapshot?: boolean,
|
|
682
693
|
): LocalReferencePosition {
|
|
683
694
|
if (segoff.segment) {
|
|
684
695
|
const ref = client.createLocalReferencePosition(
|
|
@@ -695,7 +706,12 @@ function createPositionReferenceFromSegoff(
|
|
|
695
706
|
// - References coming from a remote client (location may have been concurrently removed)
|
|
696
707
|
// - References being rebased to a new sequence number
|
|
697
708
|
// (segment they originally referred to may have been removed with no suitable replacement)
|
|
698
|
-
if (
|
|
709
|
+
if (
|
|
710
|
+
!op &&
|
|
711
|
+
!localSeq &&
|
|
712
|
+
!fromSnapshot &&
|
|
713
|
+
!refTypeIncludesFlag(refType, ReferenceType.Transient)
|
|
714
|
+
) {
|
|
699
715
|
throw new UsageError("Non-transient references need segment");
|
|
700
716
|
}
|
|
701
717
|
|
|
@@ -728,7 +744,7 @@ function createPositionReference(
|
|
|
728
744
|
);
|
|
729
745
|
segoff = client.getContainingSegment(pos, undefined, localSeq);
|
|
730
746
|
}
|
|
731
|
-
return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq);
|
|
747
|
+
return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot);
|
|
732
748
|
}
|
|
733
749
|
|
|
734
750
|
export function createSequenceInterval(
|
|
@@ -774,97 +790,54 @@ export function createSequenceInterval(
|
|
|
774
790
|
return ival;
|
|
775
791
|
}
|
|
776
792
|
|
|
777
|
-
export function
|
|
778
|
-
a.addPropertySet(b.properties);
|
|
779
|
-
return a;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
export function createIntervalIndex(conflict?: IntervalConflictResolver<Interval>) {
|
|
793
|
+
export function createIntervalIndex() {
|
|
783
794
|
const helpers: IIntervalHelpers<Interval> = {
|
|
784
795
|
compareEnds: compareIntervalEnds,
|
|
785
796
|
create: createInterval,
|
|
786
797
|
};
|
|
787
798
|
const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
|
|
788
|
-
if (conflict) {
|
|
789
|
-
lc.addConflictResolver(conflict);
|
|
790
|
-
} else {
|
|
791
|
-
lc.addConflictResolver(defaultIntervalConflictResolver);
|
|
792
|
-
}
|
|
793
799
|
return lc;
|
|
794
800
|
}
|
|
795
801
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
+
/**
|
|
803
|
+
* Collection of intervals.
|
|
804
|
+
*
|
|
805
|
+
* Implementers of this interface will typically implement additional APIs to support efficiently querying a collection
|
|
806
|
+
* of intervals in some manner, for example:
|
|
807
|
+
* - "find all intervals with start endpoint between these two points"
|
|
808
|
+
* - "find all intervals which overlap this range"
|
|
809
|
+
* etc.
|
|
810
|
+
*/
|
|
811
|
+
export interface IntervalIndex<TInterval extends ISerializableInterval> {
|
|
812
|
+
/**
|
|
813
|
+
* Adds an interval to the index.
|
|
814
|
+
* @remarks - Application code should never need to invoke this method on their index for production scenarios:
|
|
815
|
+
* Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
|
|
816
|
+
*/
|
|
817
|
+
add(interval: TInterval): void;
|
|
802
818
|
|
|
803
|
-
|
|
819
|
+
/**
|
|
820
|
+
* Removes an interval from the index.
|
|
821
|
+
* @remarks - Application code should never need to invoke this method on their index for production scenarios:
|
|
822
|
+
* Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
|
|
823
|
+
*/
|
|
824
|
+
remove(interval: TInterval): void;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
|
|
828
|
+
implements IntervalIndex<TInterval>
|
|
829
|
+
{
|
|
830
|
+
private readonly intervalTree = new IntervalTree<TInterval>();
|
|
804
831
|
|
|
805
832
|
constructor(
|
|
806
833
|
private readonly client: Client,
|
|
807
|
-
private readonly label: string,
|
|
808
834
|
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
809
|
-
|
|
810
|
-
private readonly onPositionChange?: (
|
|
811
|
-
interval: TInterval,
|
|
812
|
-
previousInterval: TInterval,
|
|
813
|
-
) => void,
|
|
814
|
-
) {
|
|
815
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
816
|
-
this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
public addConflictResolver(conflictResolver: IntervalConflictResolver<TInterval>) {
|
|
820
|
-
this.conflictResolver = conflictResolver;
|
|
821
|
-
this.endConflictResolver = (key: TInterval, currentKey: TInterval) => {
|
|
822
|
-
const ival = conflictResolver(key, currentKey);
|
|
823
|
-
return {
|
|
824
|
-
data: ival,
|
|
825
|
-
key: ival,
|
|
826
|
-
};
|
|
827
|
-
};
|
|
828
|
-
}
|
|
835
|
+
) {}
|
|
829
836
|
|
|
830
837
|
public map(fn: (interval: TInterval) => void) {
|
|
831
838
|
this.intervalTree.map(fn);
|
|
832
839
|
}
|
|
833
840
|
|
|
834
|
-
public createLegacyId(start: number, end: number): string {
|
|
835
|
-
// Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
|
|
836
|
-
// without ID's.
|
|
837
|
-
return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
* Validates that a serialized interval has the ID property. Creates an ID
|
|
842
|
-
* if one does not already exist
|
|
843
|
-
*
|
|
844
|
-
* @param serializedInterval - The interval to be checked
|
|
845
|
-
* @returns The interval's existing or newly created id
|
|
846
|
-
*/
|
|
847
|
-
public ensureSerializedId(serializedInterval: ISerializedInterval): string {
|
|
848
|
-
let id: string | undefined = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
849
|
-
if (id === undefined) {
|
|
850
|
-
// An interval came over the wire without an ID, so create a non-unique one based on start/end.
|
|
851
|
-
// This will allow all clients to refer to this interval consistently.
|
|
852
|
-
id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
|
|
853
|
-
const newProps = {
|
|
854
|
-
[reservedIntervalIdKey]: id,
|
|
855
|
-
};
|
|
856
|
-
serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
|
|
857
|
-
}
|
|
858
|
-
// Make the ID immutable for safety's sake.
|
|
859
|
-
Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
|
|
860
|
-
configurable: false,
|
|
861
|
-
enumerable: true,
|
|
862
|
-
writable: false,
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
return id;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
841
|
public mapUntil(fn: (interval: TInterval) => boolean) {
|
|
869
842
|
this.intervalTree.mapUntil(fn);
|
|
870
843
|
}
|
|
@@ -953,7 +926,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
953
926
|
|
|
954
927
|
/**
|
|
955
928
|
* @returns an array of all intervals contained in this collection that overlap the range
|
|
956
|
-
* `[startPosition, endPosition
|
|
929
|
+
* `[startPosition, endPosition)`.
|
|
957
930
|
*/
|
|
958
931
|
public findOverlappingIntervals(startPosition: number, endPosition: number) {
|
|
959
932
|
if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
|
|
@@ -971,6 +944,61 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
971
944
|
return overlappingIntervalNodes.map((node) => node.key);
|
|
972
945
|
}
|
|
973
946
|
|
|
947
|
+
public remove(interval: TInterval) {
|
|
948
|
+
this.intervalTree.removeExisting(interval);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
public add(interval: TInterval) {
|
|
952
|
+
this.intervalTree.put(interval);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
class IdIntervalIndex<TInterval extends ISerializableInterval>
|
|
957
|
+
implements IntervalIndex<TInterval>, Iterable<TInterval>
|
|
958
|
+
{
|
|
959
|
+
private readonly intervalIdMap: Map<string, TInterval> = new Map();
|
|
960
|
+
|
|
961
|
+
public add(interval: TInterval) {
|
|
962
|
+
const id = interval.getIntervalId();
|
|
963
|
+
assert(
|
|
964
|
+
id !== undefined,
|
|
965
|
+
0x2c0 /* "ID must be created before adding interval to collection" */,
|
|
966
|
+
);
|
|
967
|
+
// Make the ID immutable.
|
|
968
|
+
Object.defineProperty(interval.properties, reservedIntervalIdKey, {
|
|
969
|
+
configurable: false,
|
|
970
|
+
enumerable: true,
|
|
971
|
+
writable: false,
|
|
972
|
+
});
|
|
973
|
+
this.intervalIdMap.set(id, interval);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
public remove(interval: TInterval) {
|
|
977
|
+
const id = interval.getIntervalId();
|
|
978
|
+
assert(id !== undefined, 0x311 /* expected id to exist on interval */);
|
|
979
|
+
this.intervalIdMap.delete(id);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
public getIntervalById(id: string) {
|
|
983
|
+
return this.intervalIdMap.get(id);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
public [Symbol.iterator]() {
|
|
987
|
+
return this.intervalIdMap.values();
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
class EndpointIndex<TInterval extends ISerializableInterval> implements IntervalIndex<TInterval> {
|
|
992
|
+
private readonly endIntervalTree: RedBlackTree<TInterval, TInterval>;
|
|
993
|
+
|
|
994
|
+
constructor(
|
|
995
|
+
private readonly client: Client,
|
|
996
|
+
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
997
|
+
) {
|
|
998
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
999
|
+
this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
974
1002
|
public previousInterval(pos: number) {
|
|
975
1003
|
const transientInterval = this.helpers.create(
|
|
976
1004
|
"transient",
|
|
@@ -999,32 +1027,85 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
999
1027
|
}
|
|
1000
1028
|
}
|
|
1001
1029
|
|
|
1002
|
-
public
|
|
1003
|
-
|
|
1004
|
-
"transient",
|
|
1005
|
-
startPosition,
|
|
1006
|
-
endPosition,
|
|
1007
|
-
this.client,
|
|
1008
|
-
IntervalType.Transient,
|
|
1009
|
-
);
|
|
1010
|
-
this.intervalTree.remove(transientInterval);
|
|
1011
|
-
this.endIntervalTree.remove(transientInterval);
|
|
1012
|
-
return transientInterval;
|
|
1030
|
+
public add(interval: TInterval): void {
|
|
1031
|
+
this.endIntervalTree.put(interval, interval);
|
|
1013
1032
|
}
|
|
1014
1033
|
|
|
1015
|
-
|
|
1016
|
-
this.intervalTree.removeExisting(interval);
|
|
1034
|
+
public remove(interval: TInterval): void {
|
|
1017
1035
|
this.endIntervalTree.remove(interval);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1018
1038
|
|
|
1019
|
-
|
|
1039
|
+
export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
1040
|
+
private static readonly legacyIdPrefix = "legacy";
|
|
1041
|
+
public readonly overlappingIntervalsIndex: OverlappingIntervalsIndex<TInterval>;
|
|
1042
|
+
public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
|
|
1043
|
+
public readonly endIntervalIndex: EndpointIndex<TInterval>;
|
|
1044
|
+
private readonly indexes: IntervalIndex<TInterval>[];
|
|
1020
1045
|
|
|
1021
|
-
|
|
1046
|
+
constructor(
|
|
1047
|
+
private readonly client: Client,
|
|
1048
|
+
private readonly label: string,
|
|
1049
|
+
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
1050
|
+
/** Callback invoked each time one of the endpoints of an interval slides. */
|
|
1051
|
+
private readonly onPositionChange?: (
|
|
1052
|
+
interval: TInterval,
|
|
1053
|
+
previousInterval: TInterval,
|
|
1054
|
+
) => void,
|
|
1055
|
+
) {
|
|
1056
|
+
this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
|
|
1057
|
+
this.idIntervalIndex = new IdIntervalIndex();
|
|
1058
|
+
this.endIntervalIndex = new EndpointIndex(client, helpers);
|
|
1059
|
+
this.indexes = [
|
|
1060
|
+
this.overlappingIntervalsIndex,
|
|
1061
|
+
this.idIntervalIndex,
|
|
1062
|
+
this.endIntervalIndex,
|
|
1063
|
+
];
|
|
1064
|
+
}
|
|
1022
1065
|
|
|
1023
|
-
|
|
1066
|
+
public createLegacyId(start: number, end: number): string {
|
|
1067
|
+
// Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
|
|
1068
|
+
// without ID's.
|
|
1069
|
+
return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Validates that a serialized interval has the ID property. Creates an ID
|
|
1074
|
+
* if one does not already exist
|
|
1075
|
+
*
|
|
1076
|
+
* @param serializedInterval - The interval to be checked
|
|
1077
|
+
* @returns The interval's existing or newly created id
|
|
1078
|
+
*/
|
|
1079
|
+
public ensureSerializedId(serializedInterval: ISerializedInterval): string {
|
|
1080
|
+
let id: string | undefined = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1081
|
+
if (id === undefined) {
|
|
1082
|
+
// Back-compat: 0.39 and earlier did not have IDs on intervals. If an interval from such a client
|
|
1083
|
+
// comes over the wire, create a non-unique one based on start/end.
|
|
1084
|
+
// This will allow all clients to refer to this interval consistently.
|
|
1085
|
+
id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
|
|
1086
|
+
const newProps = {
|
|
1087
|
+
[reservedIntervalIdKey]: id,
|
|
1088
|
+
};
|
|
1089
|
+
serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
|
|
1090
|
+
}
|
|
1091
|
+
// Make the ID immutable for safety's sake.
|
|
1092
|
+
Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
|
|
1093
|
+
configurable: false,
|
|
1094
|
+
enumerable: true,
|
|
1095
|
+
writable: false,
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
return id;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
private removeIntervalFromIndexes(interval: TInterval) {
|
|
1102
|
+
for (const index of this.indexes) {
|
|
1103
|
+
index.remove(interval);
|
|
1104
|
+
}
|
|
1024
1105
|
}
|
|
1025
1106
|
|
|
1026
1107
|
public removeExistingInterval(interval: TInterval) {
|
|
1027
|
-
this.
|
|
1108
|
+
this.removeIntervalFromIndexes(interval);
|
|
1028
1109
|
this.removeIntervalListeners(interval);
|
|
1029
1110
|
}
|
|
1030
1111
|
|
|
@@ -1066,33 +1147,18 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1066
1147
|
}
|
|
1067
1148
|
}
|
|
1068
1149
|
|
|
1069
|
-
private
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
0x2c0 /* "ID must be created before adding interval to collection" */,
|
|
1074
|
-
);
|
|
1075
|
-
// Make the ID immutable.
|
|
1076
|
-
Object.defineProperty(interval.properties, reservedIntervalIdKey, {
|
|
1077
|
-
configurable: false,
|
|
1078
|
-
enumerable: true,
|
|
1079
|
-
writable: false,
|
|
1080
|
-
});
|
|
1081
|
-
this.intervalTree.put(interval, this.conflictResolver);
|
|
1082
|
-
this.endIntervalTree.put(interval, interval, this.endConflictResolver);
|
|
1083
|
-
this.intervalIdMap.set(id, interval);
|
|
1150
|
+
private addIntervalToIndexes(interval: TInterval) {
|
|
1151
|
+
for (const index of this.indexes) {
|
|
1152
|
+
index.add(interval);
|
|
1153
|
+
}
|
|
1084
1154
|
}
|
|
1085
1155
|
|
|
1086
1156
|
public add(interval: TInterval): void {
|
|
1087
1157
|
this.linkEndpointsToInterval(interval);
|
|
1088
|
-
this.
|
|
1158
|
+
this.addIntervalToIndexes(interval);
|
|
1089
1159
|
this.addIntervalListeners(interval);
|
|
1090
1160
|
}
|
|
1091
1161
|
|
|
1092
|
-
public getIntervalById(id: string) {
|
|
1093
|
-
return this.intervalIdMap.get(id);
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
1162
|
public changeInterval(
|
|
1097
1163
|
interval: TInterval,
|
|
1098
1164
|
start: number | undefined,
|
|
@@ -1111,10 +1177,11 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1111
1177
|
}
|
|
1112
1178
|
|
|
1113
1179
|
public serialize(): ISerializedIntervalCollectionV2 {
|
|
1114
|
-
const intervals = this.intervalTree.intervals.keys();
|
|
1115
1180
|
return {
|
|
1116
1181
|
label: this.label,
|
|
1117
|
-
intervals:
|
|
1182
|
+
intervals: Array.from(this.idIntervalIndex, (interval) =>
|
|
1183
|
+
compressInterval(interval.serialize()),
|
|
1184
|
+
),
|
|
1118
1185
|
version: 2,
|
|
1119
1186
|
};
|
|
1120
1187
|
}
|
|
@@ -1147,7 +1214,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1147
1214
|
previousInterval = interval.clone() as TInterval & SequenceInterval;
|
|
1148
1215
|
previousInterval.start = cloneRef(previousInterval.start);
|
|
1149
1216
|
previousInterval.end = cloneRef(previousInterval.end);
|
|
1150
|
-
this.
|
|
1217
|
+
this.removeIntervalFromIndexes(interval);
|
|
1151
1218
|
}
|
|
1152
1219
|
},
|
|
1153
1220
|
() => {
|
|
@@ -1157,7 +1224,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1157
1224
|
);
|
|
1158
1225
|
pendingChanges--;
|
|
1159
1226
|
if (pendingChanges === 0) {
|
|
1160
|
-
this.
|
|
1227
|
+
this.addIntervalToIndexes(interval);
|
|
1161
1228
|
this.onPositionChange?.(interval, previousInterval);
|
|
1162
1229
|
previousInterval = undefined;
|
|
1163
1230
|
}
|
|
@@ -1629,16 +1696,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1629
1696
|
if (!this.localCollection) {
|
|
1630
1697
|
throw new LoggingError("attach must be called before accessing intervals");
|
|
1631
1698
|
}
|
|
1632
|
-
return this.localCollection.getIntervalById(id);
|
|
1699
|
+
return this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1633
1700
|
}
|
|
1634
1701
|
|
|
1635
1702
|
/**
|
|
1636
1703
|
* Creates a new interval and add it to the collection.
|
|
1637
|
-
* @param start - interval start position
|
|
1638
|
-
* @param end - interval end position
|
|
1704
|
+
* @param start - interval start position (inclusive)
|
|
1705
|
+
* @param end - interval end position (exclusive)
|
|
1639
1706
|
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
1640
1707
|
* @param props - properties of the interval
|
|
1641
1708
|
* @returns - the created interval
|
|
1709
|
+
* @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
|
|
1710
|
+
* with how the current half-open behavior is represented.
|
|
1642
1711
|
*/
|
|
1643
1712
|
public add(
|
|
1644
1713
|
start: number,
|
|
@@ -1715,7 +1784,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1715
1784
|
if (!this.localCollection) {
|
|
1716
1785
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1717
1786
|
}
|
|
1718
|
-
const interval = this.localCollection.getIntervalById(id);
|
|
1787
|
+
const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1719
1788
|
if (interval) {
|
|
1720
1789
|
this.deleteExistingInterval(interval, true, undefined);
|
|
1721
1790
|
}
|
|
@@ -1939,11 +2008,17 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1939
2008
|
}
|
|
1940
2009
|
}
|
|
1941
2010
|
|
|
1942
|
-
|
|
2011
|
+
/**
|
|
2012
|
+
* @deprecated - This functionality was useful when adding two intervals at the same start/end positions resulted
|
|
2013
|
+
* in a conflict. This is no longer the case (as of PR#6407), as interval collections support multiple intervals
|
|
2014
|
+
* at the same location and gives each interval a unique id.
|
|
2015
|
+
*
|
|
2016
|
+
* As such, the conflict resolver is never invoked and unnecessary. This API will be removed in an upcoming release.
|
|
2017
|
+
*/
|
|
2018
|
+
public addConflictResolver(_: IntervalConflictResolver<TInterval>): void {
|
|
1943
2019
|
if (!this.localCollection) {
|
|
1944
2020
|
throw new LoggingError("attachSequence must be called");
|
|
1945
2021
|
}
|
|
1946
|
-
this.localCollection.addConflictResolver(conflictResolver);
|
|
1947
2022
|
}
|
|
1948
2023
|
|
|
1949
2024
|
public attachDeserializer(onDeserialize: DeserializeCallback): void {
|
|
@@ -1956,9 +2031,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1956
2031
|
this.onDeserialize = onDeserialize;
|
|
1957
2032
|
|
|
1958
2033
|
// Trigger the async prepare work across all values in the collection
|
|
1959
|
-
this.
|
|
1960
|
-
onDeserialize
|
|
1961
|
-
}
|
|
2034
|
+
if (this.attached) {
|
|
2035
|
+
this.map(onDeserialize);
|
|
2036
|
+
}
|
|
1962
2037
|
}
|
|
1963
2038
|
|
|
1964
2039
|
/**
|
|
@@ -1987,7 +2062,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
1987
2062
|
this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
|
|
1988
2063
|
|
|
1989
2064
|
const intervalId = properties?.[reservedIntervalIdKey];
|
|
1990
|
-
const localInterval = this.localCollection?.getIntervalById(intervalId);
|
|
2065
|
+
const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(intervalId);
|
|
1991
2066
|
|
|
1992
2067
|
const rebased: SerializedIntervalDelta = {
|
|
1993
2068
|
start: startRebased,
|
|
@@ -2209,7 +2284,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2209
2284
|
}
|
|
2210
2285
|
|
|
2211
2286
|
const id = this.localCollection.ensureSerializedId(serializedInterval);
|
|
2212
|
-
const interval = this.localCollection.getIntervalById(id);
|
|
2287
|
+
const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
2213
2288
|
if (interval) {
|
|
2214
2289
|
this.deleteExistingInterval(interval, local, op);
|
|
2215
2290
|
}
|
|
@@ -2302,7 +2377,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2302
2377
|
return;
|
|
2303
2378
|
}
|
|
2304
2379
|
|
|
2305
|
-
this.localCollection.gatherIterationResults(
|
|
2380
|
+
this.localCollection.overlappingIntervalsIndex.gatherIterationResults(
|
|
2381
|
+
results,
|
|
2382
|
+
iteratesForward,
|
|
2383
|
+
start,
|
|
2384
|
+
end,
|
|
2385
|
+
);
|
|
2306
2386
|
}
|
|
2307
2387
|
|
|
2308
2388
|
/**
|
|
@@ -2314,7 +2394,10 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2314
2394
|
throw new LoggingError("attachSequence must be called");
|
|
2315
2395
|
}
|
|
2316
2396
|
|
|
2317
|
-
return this.localCollection.findOverlappingIntervals(
|
|
2397
|
+
return this.localCollection.overlappingIntervalsIndex.findOverlappingIntervals(
|
|
2398
|
+
startPosition,
|
|
2399
|
+
endPosition,
|
|
2400
|
+
);
|
|
2318
2401
|
}
|
|
2319
2402
|
|
|
2320
2403
|
/**
|
|
@@ -2325,7 +2408,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2325
2408
|
throw new LoggingError("attachSequence must be called");
|
|
2326
2409
|
}
|
|
2327
2410
|
|
|
2328
|
-
this.localCollection.
|
|
2411
|
+
for (const interval of this.localCollection.idIntervalIndex) {
|
|
2412
|
+
fn(interval);
|
|
2413
|
+
}
|
|
2329
2414
|
}
|
|
2330
2415
|
|
|
2331
2416
|
public previousInterval(pos: number): TInterval | undefined {
|
|
@@ -2333,7 +2418,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2333
2418
|
throw new LoggingError("attachSequence must be called");
|
|
2334
2419
|
}
|
|
2335
2420
|
|
|
2336
|
-
return this.localCollection.previousInterval(pos);
|
|
2421
|
+
return this.localCollection.endIntervalIndex.previousInterval(pos);
|
|
2337
2422
|
}
|
|
2338
2423
|
|
|
2339
2424
|
public nextInterval(pos: number): TInterval | undefined {
|
|
@@ -2341,7 +2426,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
|
|
|
2341
2426
|
throw new LoggingError("attachSequence must be called");
|
|
2342
2427
|
}
|
|
2343
2428
|
|
|
2344
|
-
return this.localCollection.nextInterval(pos);
|
|
2429
|
+
return this.localCollection.endIntervalIndex.nextInterval(pos);
|
|
2345
2430
|
}
|
|
2346
2431
|
}
|
|
2347
2432
|
|
package/src/intervalTree.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface IInterval {
|
|
|
61
61
|
): IInterval | undefined;
|
|
62
62
|
/**
|
|
63
63
|
* @returns whether this interval overlaps with `b`.
|
|
64
|
-
*
|
|
64
|
+
* Intervals are considered to overlap if their intersection is non-empty.
|
|
65
65
|
*/
|
|
66
66
|
overlaps(b: IInterval): boolean;
|
|
67
67
|
/**
|
|
@@ -77,6 +77,13 @@ const intervalComparer = (a: IInterval, b: IInterval) => a.compare(b);
|
|
|
77
77
|
|
|
78
78
|
export type IntervalNode<T extends IInterval> = RBNode<T, AugmentedIntervalNode>;
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated - This functionality was useful when adding two intervals at the same start/end positions resulted
|
|
82
|
+
* in a conflict. This is no longer the case (as of PR#6407), as interval collections support multiple intervals
|
|
83
|
+
* at the same location and gives each interval a unique id.
|
|
84
|
+
*
|
|
85
|
+
* As such, conflict resolvers are never invoked and unnecessary. They will be removed in an upcoming release.
|
|
86
|
+
*/
|
|
80
87
|
export type IntervalConflictResolver<TInterval> = (a: TInterval, b: TInterval) => TInterval;
|
|
81
88
|
|
|
82
89
|
export class IntervalTree<T extends IInterval>
|
package/src/packageVersion.ts
CHANGED