@fluidframework/merge-tree 1.2.6 → 2.0.0-dev.1.3.0.96595

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.
Files changed (244) hide show
  1. package/.mocharc.js +12 -0
  2. package/README.md +2 -2
  3. package/dist/MergeTreeTextHelper.d.ts +23 -0
  4. package/dist/MergeTreeTextHelper.d.ts.map +1 -0
  5. package/dist/MergeTreeTextHelper.js +133 -0
  6. package/dist/MergeTreeTextHelper.js.map +1 -0
  7. package/dist/base.d.ts +2 -26
  8. package/dist/base.d.ts.map +1 -1
  9. package/dist/base.js.map +1 -1
  10. package/dist/client.d.ts +27 -16
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +81 -101
  13. package/dist/client.js.map +1 -1
  14. package/dist/collections/heap.d.ts +28 -0
  15. package/dist/collections/heap.d.ts.map +1 -0
  16. package/dist/collections/heap.js +65 -0
  17. package/dist/collections/heap.js.map +1 -0
  18. package/dist/collections/index.d.ts +11 -0
  19. package/dist/collections/index.d.ts.map +1 -0
  20. package/dist/collections/index.js +23 -0
  21. package/dist/collections/index.js.map +1 -0
  22. package/dist/collections/intervalTree.d.ts +60 -0
  23. package/dist/collections/intervalTree.d.ts.map +1 -0
  24. package/dist/collections/intervalTree.js +99 -0
  25. package/dist/collections/intervalTree.js.map +1 -0
  26. package/dist/collections/list.d.ts +39 -0
  27. package/dist/collections/list.d.ts.map +1 -0
  28. package/dist/collections/list.js +155 -0
  29. package/dist/collections/list.js.map +1 -0
  30. package/dist/collections/rbTree.d.ts +154 -0
  31. package/dist/collections/rbTree.d.ts.map +1 -0
  32. package/dist/{collections.js → collections/rbTree.js} +15 -478
  33. package/dist/collections/rbTree.js.map +1 -0
  34. package/dist/collections/stack.d.ts +16 -0
  35. package/dist/collections/stack.d.ts.map +1 -0
  36. package/dist/collections/stack.js +30 -0
  37. package/dist/collections/stack.js.map +1 -0
  38. package/dist/collections/tst.d.ts +55 -0
  39. package/dist/collections/tst.d.ts.map +1 -0
  40. package/dist/collections/tst.js +171 -0
  41. package/dist/collections/tst.js.map +1 -0
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -2
  45. package/dist/index.js.map +1 -1
  46. package/dist/localReference.d.ts +48 -99
  47. package/dist/localReference.d.ts.map +1 -1
  48. package/dist/localReference.js +132 -169
  49. package/dist/localReference.js.map +1 -1
  50. package/dist/mergeTree.d.ts +71 -302
  51. package/dist/mergeTree.d.ts.map +1 -1
  52. package/dist/mergeTree.js +395 -642
  53. package/dist/mergeTree.js.map +1 -1
  54. package/dist/mergeTreeDeltaCallback.d.ts +1 -1
  55. package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
  56. package/dist/mergeTreeDeltaCallback.js.map +1 -1
  57. package/dist/mergeTreeNodes.d.ts +344 -0
  58. package/dist/mergeTreeNodes.d.ts.map +1 -0
  59. package/dist/mergeTreeNodes.js +383 -0
  60. package/dist/mergeTreeNodes.js.map +1 -0
  61. package/dist/mergeTreeTracking.d.ts +1 -1
  62. package/dist/mergeTreeTracking.d.ts.map +1 -1
  63. package/dist/mergeTreeTracking.js.map +1 -1
  64. package/dist/opBuilder.d.ts +1 -1
  65. package/dist/opBuilder.d.ts.map +1 -1
  66. package/dist/opBuilder.js.map +1 -1
  67. package/dist/partialLengths.d.ts +188 -18
  68. package/dist/partialLengths.d.ts.map +1 -1
  69. package/dist/partialLengths.js +495 -253
  70. package/dist/partialLengths.js.map +1 -1
  71. package/dist/properties.d.ts.map +1 -1
  72. package/dist/properties.js.map +1 -1
  73. package/dist/referencePositions.d.ts +6 -26
  74. package/dist/referencePositions.d.ts.map +1 -1
  75. package/dist/referencePositions.js +3 -20
  76. package/dist/referencePositions.js.map +1 -1
  77. package/dist/segmentGroupCollection.d.ts +3 -1
  78. package/dist/segmentGroupCollection.d.ts.map +1 -1
  79. package/dist/segmentGroupCollection.js +14 -1
  80. package/dist/segmentGroupCollection.js.map +1 -1
  81. package/dist/segmentPropertiesManager.d.ts +10 -1
  82. package/dist/segmentPropertiesManager.d.ts.map +1 -1
  83. package/dist/segmentPropertiesManager.js +42 -13
  84. package/dist/segmentPropertiesManager.js.map +1 -1
  85. package/dist/snapshotChunks.d.ts +2 -1
  86. package/dist/snapshotChunks.d.ts.map +1 -1
  87. package/dist/snapshotChunks.js.map +1 -1
  88. package/dist/snapshotLoader.d.ts.map +1 -1
  89. package/dist/snapshotLoader.js.map +1 -1
  90. package/dist/snapshotV1.d.ts +1 -1
  91. package/dist/snapshotV1.d.ts.map +1 -1
  92. package/dist/snapshotV1.js +1 -1
  93. package/dist/snapshotV1.js.map +1 -1
  94. package/dist/snapshotlegacy.d.ts +5 -1
  95. package/dist/snapshotlegacy.d.ts.map +1 -1
  96. package/dist/snapshotlegacy.js +4 -0
  97. package/dist/snapshotlegacy.js.map +1 -1
  98. package/dist/sortedSegmentSet.d.ts +1 -1
  99. package/dist/sortedSegmentSet.d.ts.map +1 -1
  100. package/dist/sortedSegmentSet.js.map +1 -1
  101. package/dist/textSegment.d.ts +7 -7
  102. package/dist/textSegment.d.ts.map +1 -1
  103. package/dist/textSegment.js +3 -125
  104. package/dist/textSegment.js.map +1 -1
  105. package/{DEV.md → docs/DEV.md} +2 -2
  106. package/docs/Obliterate.md +639 -0
  107. package/{REFERENCEPOSITIONS.md → docs/REFERENCEPOSITIONS.md} +2 -2
  108. package/lib/MergeTreeTextHelper.d.ts +23 -0
  109. package/lib/MergeTreeTextHelper.d.ts.map +1 -0
  110. package/lib/MergeTreeTextHelper.js +129 -0
  111. package/lib/MergeTreeTextHelper.js.map +1 -0
  112. package/lib/base.d.ts +2 -26
  113. package/lib/base.d.ts.map +1 -1
  114. package/lib/base.js.map +1 -1
  115. package/lib/client.d.ts +27 -16
  116. package/lib/client.d.ts.map +1 -1
  117. package/lib/client.js +79 -99
  118. package/lib/client.js.map +1 -1
  119. package/lib/collections/heap.d.ts +28 -0
  120. package/lib/collections/heap.d.ts.map +1 -0
  121. package/lib/collections/heap.js +61 -0
  122. package/lib/collections/heap.js.map +1 -0
  123. package/lib/collections/index.d.ts +11 -0
  124. package/lib/collections/index.d.ts.map +1 -0
  125. package/lib/collections/index.js +11 -0
  126. package/lib/collections/index.js.map +1 -0
  127. package/lib/collections/intervalTree.d.ts +60 -0
  128. package/lib/collections/intervalTree.d.ts.map +1 -0
  129. package/lib/collections/intervalTree.js +94 -0
  130. package/lib/collections/intervalTree.js.map +1 -0
  131. package/lib/collections/list.d.ts +39 -0
  132. package/lib/collections/list.d.ts.map +1 -0
  133. package/lib/collections/list.js +149 -0
  134. package/lib/collections/list.js.map +1 -0
  135. package/lib/collections/rbTree.d.ts +154 -0
  136. package/lib/collections/rbTree.d.ts.map +1 -0
  137. package/lib/{collections.js → collections/rbTree.js} +14 -469
  138. package/lib/collections/rbTree.js.map +1 -0
  139. package/lib/collections/stack.d.ts +16 -0
  140. package/lib/collections/stack.d.ts.map +1 -0
  141. package/lib/collections/stack.js +26 -0
  142. package/lib/collections/stack.js.map +1 -0
  143. package/lib/collections/tst.d.ts +55 -0
  144. package/lib/collections/tst.d.ts.map +1 -0
  145. package/lib/collections/tst.js +167 -0
  146. package/lib/collections/tst.js.map +1 -0
  147. package/lib/index.d.ts +3 -1
  148. package/lib/index.d.ts.map +1 -1
  149. package/lib/index.js +3 -1
  150. package/lib/index.js.map +1 -1
  151. package/lib/localReference.d.ts +48 -99
  152. package/lib/localReference.d.ts.map +1 -1
  153. package/lib/localReference.js +132 -170
  154. package/lib/localReference.js.map +1 -1
  155. package/lib/mergeTree.d.ts +71 -302
  156. package/lib/mergeTree.d.ts.map +1 -1
  157. package/lib/mergeTree.js +371 -607
  158. package/lib/mergeTree.js.map +1 -1
  159. package/lib/mergeTreeDeltaCallback.d.ts +1 -1
  160. package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
  161. package/lib/mergeTreeDeltaCallback.js.map +1 -1
  162. package/lib/mergeTreeNodes.d.ts +344 -0
  163. package/lib/mergeTreeNodes.d.ts.map +1 -0
  164. package/lib/mergeTreeNodes.js +369 -0
  165. package/lib/mergeTreeNodes.js.map +1 -0
  166. package/lib/mergeTreeTracking.d.ts +1 -1
  167. package/lib/mergeTreeTracking.d.ts.map +1 -1
  168. package/lib/mergeTreeTracking.js.map +1 -1
  169. package/lib/opBuilder.d.ts +1 -1
  170. package/lib/opBuilder.d.ts.map +1 -1
  171. package/lib/opBuilder.js.map +1 -1
  172. package/lib/partialLengths.d.ts +188 -18
  173. package/lib/partialLengths.d.ts.map +1 -1
  174. package/lib/partialLengths.js +491 -249
  175. package/lib/partialLengths.js.map +1 -1
  176. package/lib/properties.d.ts.map +1 -1
  177. package/lib/properties.js.map +1 -1
  178. package/lib/referencePositions.d.ts +6 -26
  179. package/lib/referencePositions.d.ts.map +1 -1
  180. package/lib/referencePositions.js +3 -20
  181. package/lib/referencePositions.js.map +1 -1
  182. package/lib/segmentGroupCollection.d.ts +3 -1
  183. package/lib/segmentGroupCollection.d.ts.map +1 -1
  184. package/lib/segmentGroupCollection.js +14 -1
  185. package/lib/segmentGroupCollection.js.map +1 -1
  186. package/lib/segmentPropertiesManager.d.ts +10 -1
  187. package/lib/segmentPropertiesManager.d.ts.map +1 -1
  188. package/lib/segmentPropertiesManager.js +42 -13
  189. package/lib/segmentPropertiesManager.js.map +1 -1
  190. package/lib/snapshotChunks.d.ts +2 -1
  191. package/lib/snapshotChunks.d.ts.map +1 -1
  192. package/lib/snapshotChunks.js.map +1 -1
  193. package/lib/snapshotLoader.d.ts.map +1 -1
  194. package/lib/snapshotLoader.js.map +1 -1
  195. package/lib/snapshotV1.d.ts +1 -1
  196. package/lib/snapshotV1.d.ts.map +1 -1
  197. package/lib/snapshotV1.js +1 -1
  198. package/lib/snapshotV1.js.map +1 -1
  199. package/lib/snapshotlegacy.d.ts +5 -1
  200. package/lib/snapshotlegacy.d.ts.map +1 -1
  201. package/lib/snapshotlegacy.js +4 -0
  202. package/lib/snapshotlegacy.js.map +1 -1
  203. package/lib/sortedSegmentSet.d.ts +1 -1
  204. package/lib/sortedSegmentSet.d.ts.map +1 -1
  205. package/lib/sortedSegmentSet.js.map +1 -1
  206. package/lib/textSegment.d.ts +7 -7
  207. package/lib/textSegment.d.ts.map +1 -1
  208. package/lib/textSegment.js +1 -122
  209. package/lib/textSegment.js.map +1 -1
  210. package/package.json +99 -20
  211. package/src/MergeTreeTextHelper.ts +170 -0
  212. package/src/base.ts +2 -35
  213. package/src/client.ts +91 -111
  214. package/src/collections/heap.ts +75 -0
  215. package/src/collections/index.ts +11 -0
  216. package/src/collections/intervalTree.ts +146 -0
  217. package/src/collections/list.ts +165 -0
  218. package/src/{collections.ts → collections/rbTree.ts} +84 -563
  219. package/src/collections/stack.ts +27 -0
  220. package/src/collections/tst.ts +212 -0
  221. package/src/index.ts +8 -2
  222. package/src/localReference.ts +152 -203
  223. package/src/mergeTree.ts +578 -996
  224. package/src/mergeTreeDeltaCallback.ts +1 -1
  225. package/src/mergeTreeNodes.ts +752 -0
  226. package/src/mergeTreeTracking.ts +1 -1
  227. package/src/opBuilder.ts +1 -1
  228. package/src/partialLengths.ts +631 -258
  229. package/src/properties.ts +1 -0
  230. package/src/referencePositions.ts +10 -44
  231. package/src/segmentGroupCollection.ts +17 -2
  232. package/src/segmentPropertiesManager.ts +46 -12
  233. package/src/snapshotChunks.ts +2 -1
  234. package/src/snapshotLoader.ts +2 -1
  235. package/src/snapshotV1.ts +3 -3
  236. package/src/snapshotlegacy.ts +6 -2
  237. package/src/sortedSegmentSet.ts +1 -1
  238. package/src/textSegment.ts +10 -157
  239. package/dist/collections.d.ts +0 -197
  240. package/dist/collections.d.ts.map +0 -1
  241. package/dist/collections.js.map +0 -1
  242. package/lib/collections.d.ts +0 -197
  243. package/lib/collections.d.ts.map +0 -1
  244. package/lib/collections.js.map +0 -1
package/src/client.ts CHANGED
@@ -16,17 +16,17 @@ import { LoggingError } from "@fluidframework/telemetry-utils";
16
16
  import { IIntegerRange } from "./base";
17
17
  import { RedBlackTree } from "./collections";
18
18
  import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants";
19
- import { LocalReference, LocalReferencePosition } from "./localReference";
19
+ import { LocalReferencePosition } from "./localReference";
20
20
  import {
21
21
  CollaborationWindow,
22
22
  compareStrings,
23
23
  IConsensusInfo,
24
+ IMergeNode,
24
25
  ISegment,
25
26
  ISegmentAction,
26
27
  Marker,
27
- MergeTree,
28
28
  SegmentGroup,
29
- } from "./mergeTree";
29
+ } from "./mergeTreeNodes";
30
30
  import { MergeTreeDeltaCallback } from "./mergeTreeDeltaCallback";
31
31
  import {
32
32
  createAnnotateMarkerOp,
@@ -51,9 +51,11 @@ import {
51
51
  import { PropertySet } from "./properties";
52
52
  import { SnapshotLegacy } from "./snapshotlegacy";
53
53
  import { SnapshotLoader } from "./snapshotLoader";
54
- import { MergeTreeTextHelper } from "./textSegment";
54
+ import { IMergeTreeTextHelper } from "./textSegment";
55
55
  import { SnapshotV1 } from "./snapshotV1";
56
- import { ReferencePosition, RangeStackMap } from "./referencePositions";
56
+ import { ReferencePosition, RangeStackMap, DetachedReferencePosition } from "./referencePositions";
57
+ import { MergeTree } from "./mergeTree";
58
+ import { MergeTreeTextHelper } from "./MergeTreeTextHelper";
57
59
  import {
58
60
  IMergeTreeClientSequenceArgs,
59
61
  IMergeTreeDeltaOpArgs,
@@ -88,6 +90,10 @@ export class Client {
88
90
  this.mergeTree.mergeTreeMaintenanceCallback = callback;
89
91
  }
90
92
 
93
+ /**
94
+ * @deprecated for internal use only. public export will be removed.
95
+ * @internal
96
+ */
91
97
  protected readonly mergeTree: MergeTree;
92
98
 
93
99
  private readonly clientNameToIds = new RedBlackTree<string, number>(compareStrings);
@@ -167,11 +173,7 @@ export class Client {
167
173
  const annotateOp =
168
174
  createAnnotateMarkerOp(marker, props, combiningOp)!;
169
175
 
170
- if (this.applyAnnotateRangeOp({ op: annotateOp })) {
171
- return annotateOp;
172
- } else {
173
- return undefined;
174
- }
176
+ return this.applyAnnotateRangeOp({ op: annotateOp }) ? annotateOp : undefined;
175
177
  }
176
178
  /**
177
179
  * Annotates the range with the provided properties
@@ -241,7 +243,7 @@ export class Client {
241
243
  this.getCurrentSeq(),
242
244
  this.getClientId());
243
245
 
244
- if (pos === LocalReference.DetachedPosition) {
246
+ if (pos === DetachedReferencePosition) {
245
247
  return undefined;
246
248
  }
247
249
  const op = createInsertSegmentOp(
@@ -289,9 +291,17 @@ export class Client {
289
291
  * serializer which keeps track of all serialized handles.
290
292
  */
291
293
  public serializeGCData(handle: IFluidHandle, handleCollectingSerializer: IFluidSerializer): void {
294
+ let localInserts = 0;
295
+ let localRemoves = 0;
292
296
  this.mergeTree.walkAllSegments(
293
297
  this.mergeTree.root,
294
298
  (seg) => {
299
+ if (seg.seq === UnassignedSequenceNumber) {
300
+ localInserts++;
301
+ }
302
+ if (seg.removedSeq === UnassignedSequenceNumber) {
303
+ localRemoves++;
304
+ }
295
305
  // Only serialize segments that have not been removed.
296
306
  if (seg.removedSeq === undefined) {
297
307
  handleCollectingSerializer.stringify(
@@ -301,6 +311,10 @@ export class Client {
301
311
  return true;
302
312
  },
303
313
  );
314
+
315
+ if (localInserts > 0 || localRemoves > 0) {
316
+ this.logger.sendErrorEvent({ eventName: "LocalEditsInProcessGCData", localInserts, localRemoves });
317
+ }
304
318
  }
305
319
 
306
320
  public getCollabWindow(): CollaborationWindow {
@@ -312,30 +326,17 @@ export class Client {
312
326
  * does not exist in this merge tree
313
327
  * @param segment - The segment to get the position of
314
328
  */
315
- public getPosition(segment: ISegment): number {
329
+ public getPosition(segment: ISegment, localSeq?: number): number {
316
330
  if (segment?.parent === undefined) {
317
331
  return -1;
318
332
  }
319
- return this.mergeTree.getPosition(segment, this.getCurrentSeq(), this.getClientId());
320
- }
321
- /**
322
- * @deprecated - use createReferencePosition instead
323
- */
324
- public addLocalReference(lref: LocalReference) {
325
- return this.mergeTree.addLocalReference(lref);
326
- }
327
-
328
- /**
329
- * @deprecated - use removeReferencePosition instead
330
- */
331
- public removeLocalReference(lref: LocalReference) {
332
- return this.removeLocalReferencePosition(lref);
333
+ return this.mergeTree.getPosition(segment, this.getCurrentSeq(), this.getClientId(), localSeq);
333
334
  }
334
335
 
335
336
  public createLocalReferencePosition(
336
337
  segment: ISegment, offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined,
337
338
  ): LocalReferencePosition {
338
- return this.mergeTree.createLocalReferencePosition(segment, offset, refType, properties, this);
339
+ return this.mergeTree.createLocalReferencePosition(segment, offset ?? 0, refType, properties);
339
340
  }
340
341
 
341
342
  public removeLocalReferencePosition(lref: LocalReferencePosition) {
@@ -359,6 +360,13 @@ export class Client {
359
360
  return this.mergeTree.getMarkerFromId(id);
360
361
  }
361
362
 
363
+ /**
364
+ * Revert an op
365
+ */
366
+ public rollback?(op: any, localOpMetadata: unknown) {
367
+ this.mergeTree.rollback(op as IMergeTreeDeltaOp, localOpMetadata as SegmentGroup);
368
+ }
369
+
362
370
  /**
363
371
  * Performs the remove based on the provided op
364
372
  * @param opArgs - The ops args for the op
@@ -655,11 +663,7 @@ export class Client {
655
663
  return this.clientNameToIds.get(longClientId)!.data;
656
664
  }
657
665
  getLongClientId(shortClientId: number) {
658
- if (shortClientId >= 0) {
659
- return this.shortClientIdMap[shortClientId];
660
- } else {
661
- return "original";
662
- }
666
+ return shortClientId >= 0 ? this.shortClientIdMap[shortClientId] : "original";
663
667
  }
664
668
  addLongClientId(longClientId: string) {
665
669
  this.clientNameToIds.put(longClientId, this.shortClientIdMap.length);
@@ -677,36 +681,8 @@ export class Client {
677
681
  */
678
682
  protected findReconnectionPosition(segment: ISegment, localSeq: number) {
679
683
  assert(localSeq <= this.mergeTree.collabWindow.localSeq, 0x032 /* "localSeq greater than collab window" */);
680
- let segmentPosition = 0;
681
- /*
682
- Walk the segments up to the current segment, and calculate it's
683
- position taking into account local segments that were modified,
684
- after the current segment.
685
-
686
- TODO: Consider embedding this information into the tree for
687
- more efficient look up of pending segment positions.
688
- */
689
- this.mergeTree.walkAllSegments(this.mergeTree.root, (seg) => {
690
- // If we've found the desired segment, terminate the walk and return 'segmentPosition'.
691
- if (seg === segment) {
692
- return false;
693
- }
694
-
695
- // Otherwise, advance segmentPosition if the segment has been inserted and not removed
696
- // with respect to the given 'localSeq'.
697
- //
698
- // Note that all ACKed / remote ops are applied and we only need concern ourself with
699
- // determining if locally pending ops fall before/after the given 'localSeq'.
700
- if ((seg.localSeq === undefined || seg.localSeq <= localSeq) // Is inserted
701
- && (seg.removedSeq === undefined || seg.localRemovedSeq! > localSeq) // Not removed
702
- ) {
703
- segmentPosition += seg.cachedLength;
704
- }
705
-
706
- return true;
707
- });
708
-
709
- return segmentPosition;
684
+ const { currentSeq, clientId } = this.getCollabWindow();
685
+ return this.mergeTree.getPosition(segment, currentSeq, clientId, localSeq);
710
686
  }
711
687
 
712
688
  /**
@@ -716,6 +692,12 @@ export class Client {
716
692
  * If the position refers to a segment/offset that was removed by some operation between `seqNumberFrom` and
717
693
  * the current sequence number, the returned position will align with the position of a reference given
718
694
  * `SlideOnRemove` semantics.
695
+ *
696
+ * If a reference was initially given `StayOnRemove` semantics, with intent to later change to `SlideOnRemove`,
697
+ * that isn't equivalent, and so local bookkeeping needs to be updated.
698
+ *
699
+ * If the position has slid off and there is no nearest position (i.e. the
700
+ * tree is empty), returns `DetachedReferencePosition`
719
701
  */
720
702
  public rebasePosition(
721
703
  pos: number,
@@ -723,45 +705,31 @@ export class Client {
723
705
  localSeq: number,
724
706
  ): number {
725
707
  assert(localSeq <= this.mergeTree.collabWindow.localSeq, 0x300 /* localSeq greater than collab window */);
726
- let segment: ISegment | undefined;
727
- let posAccumulated = 0;
728
- let offset = pos;
729
- const isInsertedInView = (seg: ISegment) =>
730
- (seg.seq !== undefined && seg.seq !== UnassignedSequenceNumber && seg.seq <= seqNumberFrom)
731
- || (seg.localSeq !== undefined && seg.localSeq <= localSeq);
732
-
733
- const isRemovedFromView = ({ removedSeq, localRemovedSeq }: ISegment) =>
734
- (removedSeq !== undefined && removedSeq !== UnassignedSequenceNumber && removedSeq <= seqNumberFrom)
735
- || (localRemovedSeq !== undefined && localRemovedSeq <= localSeq);
736
-
737
- this.mergeTree.walkAllSegments(this.mergeTree.root, (seg) => {
738
- assert(seg.seq !== undefined || seg.localSeq !== undefined,
739
- 0x301 /* Either seq or localSeq should be defined */);
740
- segment = seg;
741
-
742
- if (isInsertedInView(seg) && !isRemovedFromView(seg)) {
743
- posAccumulated += seg.cachedLength;
744
- if (offset >= seg.cachedLength) {
745
- offset -= seg.cachedLength;
746
- }
708
+ const { clientId } = this.getCollabWindow();
709
+ let { segment, offset } = this.mergeTree.getContainingSegment(pos, seqNumberFrom, clientId, localSeq);
710
+ if (segment === undefined && offset === undefined) {
711
+ // getContainingSegment will only return non-removed segments. This means the position was past
712
+ // all non-removed segments in the tree, so we take the last one instead as an approximation.
713
+ let finalSegment: IMergeNode = this.mergeTree.root;
714
+ while (!finalSegment.isLeaf()) {
715
+ finalSegment = finalSegment.children[finalSegment.childCount - 1];
747
716
  }
717
+ segment = finalSegment;
718
+ offset = 0;
719
+ }
748
720
 
749
- // Keep going while we've yet to reach the segment at the desired position
750
- return posAccumulated <= pos;
751
- });
752
-
721
+ // if segment is undefined, it slid off the string
753
722
  assert(segment !== undefined, 0x302 /* No segment found */);
754
- const seqNumberTo = this.getCollabWindow().currentSeq;
755
- if ((segment.removedSeq !== undefined &&
756
- segment.removedSeq !== UnassignedSequenceNumber &&
757
- segment.removedSeq <= seqNumberTo)
758
- || (segment.localRemovedSeq !== undefined && segment.localRemovedSeq <= localSeq)) {
759
- // Segment that the position was in has been removed: null out offset.
760
- offset = 0;
723
+
724
+ const segoff = this.getSlideToSegment({ segment, offset }) ?? segment;
725
+
726
+ // case happens when rebasing op, but concurrently entire string has been deleted
727
+ if (segoff.segment === undefined || segoff.offset === undefined) {
728
+ return DetachedReferencePosition;
761
729
  }
762
730
 
763
- assert(0 <= offset && offset < segment.cachedLength, 0x303 /* Invalid offset */);
764
- return this.findReconnectionPosition(segment, localSeq) + offset;
731
+ assert(offset !== undefined && 0 <= offset && offset < segment.cachedLength, 0x303 /* Invalid offset */);
732
+ return this.findReconnectionPosition(segoff.segment, localSeq) + segoff.offset;
765
733
  }
766
734
 
767
735
  private resetPendingDeltaToOps(
@@ -776,7 +744,7 @@ export class Client {
776
744
  // We need to sort the segments by ordinal, as the segments are not sorted in the segment group.
777
745
  // The reason they need them sorted, as they have the same local sequence number and which means
778
746
  // farther segments will take into account nearer segments when calculating their position.
779
- // By sorting we ensure the nearer segment will be applied and sequenced before the father segments
747
+ // By sorting we ensure the nearer segment will be applied and sequenced before the farther segments
780
748
  // so their recalculated positions will be correct.
781
749
  for (const segment of segmentGroup.segments.sort((a, b) => a.ordinal < b.ordinal ? -1 : 1)) {
782
750
  const segmentSegGroup = segment.segmentGroups.dequeue();
@@ -791,7 +759,8 @@ export class Client {
791
759
  // if the segment has been removed, there's no need to send the annotate op
792
760
  // unless the remove was local, in which case the annotate must have come
793
761
  // before the remove
794
- if (segment.removedSeq === undefined || segment.localRemovedSeq !== undefined) {
762
+ if (segment.removedSeq === undefined ||
763
+ (segment.localRemovedSeq !== undefined && segment.removedSeq === UnassignedSequenceNumber)) {
795
764
  newOp = createAnnotateRangeOp(
796
765
  segmentPosition,
797
766
  segmentPosition + segment.cachedLength,
@@ -815,7 +784,7 @@ export class Client {
815
784
  break;
816
785
 
817
786
  case MergeTreeDeltaType.REMOVE:
818
- if (segment.localRemovedSeq !== undefined) {
787
+ if (segment.localRemovedSeq !== undefined && segment.removedSeq === UnassignedSequenceNumber) {
819
788
  newOp = createRemoveRangeOp(
820
789
  segmentPosition,
821
790
  segmentPosition + segment.cachedLength);
@@ -942,8 +911,8 @@ export class Client {
942
911
  }
943
912
 
944
913
  /**
945
- * Given an pending operation and segment group, regenerate the op, so it
946
- * can be resubmitted
914
+ * Given an pending operation and segment group, regenerate the op, so it
915
+ * can be resubmitted
947
916
  * @param resetOp - The op to reset
948
917
  * @param segmentGroup - The segment group associated with the op
949
918
  */
@@ -979,7 +948,7 @@ export class Client {
979
948
  return opList.length === 1 ? opList[0] : createGroupOp(...opList);
980
949
  }
981
950
 
982
- public createTextHelper() {
951
+ public createTextHelper(): IMergeTreeTextHelper {
983
952
  return new MergeTreeTextHelper(this.mergeTree);
984
953
  }
985
954
 
@@ -1028,18 +997,17 @@ export class Client {
1028
997
 
1029
998
  return loader.initialize(storage);
1030
999
  }
1031
-
1000
+ /**
1001
+ * @deprecated for internal use only. public export will be removed.
1002
+ * @internal
1003
+ */
1032
1004
  getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap {
1033
1005
  return this.mergeTree.getStackContext(startPos, this.getCollabWindow().clientId, rangeLabels);
1034
1006
  }
1035
1007
 
1036
1008
  private getLocalSequenceNumber() {
1037
1009
  const segWindow = this.getCollabWindow();
1038
- if (segWindow.collaborating) {
1039
- return UnassignedSequenceNumber;
1040
- } else {
1041
- return UniversalSequenceNumber;
1042
- }
1010
+ return segWindow.collaborating ? UnassignedSequenceNumber : UniversalSequenceNumber;
1043
1011
  }
1044
1012
  localTransaction(groupOp: IMergeTreeGroupMsg) {
1045
1013
  for (const op of groupOp.ops) {
@@ -1086,9 +1054,9 @@ export class Client {
1086
1054
  }
1087
1055
  }
1088
1056
 
1089
- getContainingSegment<T extends ISegment>(pos: number, op?: ISequencedDocumentMessage) {
1057
+ getContainingSegment<T extends ISegment>(pos: number, op?: ISequencedDocumentMessage, localSeq?: number) {
1090
1058
  const args = this.getClientSequenceArgsForMessage(op);
1091
- return this.mergeTree.getContainingSegment<T>(pos, args.referenceSequenceNumber, args.clientId);
1059
+ return this.mergeTree.getContainingSegment<T>(pos, args.referenceSequenceNumber, args.clientId, localSeq);
1092
1060
  }
1093
1061
 
1094
1062
  /**
@@ -1097,7 +1065,19 @@ export class Client {
1097
1065
  * @returns - segment and offset to slide the reference to
1098
1066
  */
1099
1067
  getSlideToSegment(segoff: { segment: ISegment | undefined; offset: number | undefined; }) {
1100
- return this.mergeTree._getSlideToSegment(segoff);
1068
+ if (segoff.segment === undefined) {
1069
+ return segoff;
1070
+ }
1071
+ const segment = this.mergeTree._getSlideToSegment(segoff.segment);
1072
+ if (segment === segoff.segment) {
1073
+ return segoff;
1074
+ }
1075
+ const offset =
1076
+ segment && segment.ordinal < segoff.segment.ordinal ? segment.cachedLength - 1 : 0;
1077
+ return {
1078
+ segment,
1079
+ offset,
1080
+ };
1101
1081
  }
1102
1082
 
1103
1083
  getPropertiesAtPosition(pos: number) {
@@ -0,0 +1,75 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * @deprecated for internal use only. public export will be removed.
8
+ * @internal
9
+ */
10
+ export interface Comparer<T> {
11
+ compare(a: T, b: T): number;
12
+ min: T;
13
+ }
14
+
15
+ /**
16
+ * @deprecated for internal use only. public export will be removed.
17
+ * @internal
18
+ */
19
+ export class Heap<T> {
20
+ private L: T[];
21
+ public count() {
22
+ return this.L.length - 1;
23
+ }
24
+ constructor(a: T[], public comp: Comparer<T>) {
25
+ this.L = [comp.min];
26
+ for (let i = 0, len = a.length; i < len; i++) {
27
+ this.add(a[i]);
28
+ }
29
+ }
30
+ public peek() {
31
+ return this.L[1];
32
+ }
33
+
34
+ public get() {
35
+ const x = this.L[1];
36
+ this.L[1] = this.L[this.count()];
37
+ this.L.pop();
38
+ this.fixDown(1);
39
+ return x;
40
+ }
41
+
42
+ public add(x: T) {
43
+ this.L.push(x);
44
+ this.fixup(this.count());
45
+ }
46
+
47
+ /* eslint-disable no-bitwise */
48
+ private fixup(k: number) {
49
+ let _k = k;
50
+ while (_k > 1 && (this.comp.compare(this.L[_k >> 1], this.L[_k]) > 0)) {
51
+ const tmp = this.L[_k >> 1];
52
+ this.L[_k >> 1] = this.L[_k];
53
+ this.L[_k] = tmp;
54
+ _k = _k >> 1;
55
+ }
56
+ }
57
+
58
+ private fixDown(k: number) {
59
+ let _k = k;
60
+ while ((_k << 1) <= (this.count())) {
61
+ let j = _k << 1;
62
+ if ((j < this.count()) && (this.comp.compare(this.L[j], this.L[j + 1]) > 0)) {
63
+ j++;
64
+ }
65
+ if (this.comp.compare(this.L[_k], this.L[j]) <= 0) {
66
+ break;
67
+ }
68
+ const tmp = this.L[_k];
69
+ this.L[_k] = this.L[j];
70
+ this.L[j] = tmp;
71
+ _k = j;
72
+ }
73
+ }
74
+ /* eslint-enable no-bitwise */
75
+ }
@@ -0,0 +1,11 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export * from "./heap";
7
+ export * from "./intervalTree";
8
+ export * from "./list";
9
+ export * from "./rbTree";
10
+ export * from "./stack";
11
+ export * from "./tst";
@@ -0,0 +1,146 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
7
+ import {
8
+ IIntegerRange,
9
+ } from "../base";
10
+ import { ConflictAction, IRBAugmentation, IRBMatcher, RBNode, RBNodeActions, RedBlackTree } from "./rbTree";
11
+
12
+ /**
13
+ * @deprecated for internal use only. public export will be removed.
14
+ * @internal
15
+ */
16
+ export interface AugmentedIntervalNode {
17
+ minmax: IInterval;
18
+ }
19
+
20
+ /**
21
+ * @deprecated for internal use only. public export will be removed.
22
+ * @internal
23
+ */
24
+ export const integerRangeToString = (range: IIntegerRange) => `[${range.start},${range.end})`;
25
+
26
+ /**
27
+ * @deprecated for internal use only. public export will be removed.
28
+ * @internal
29
+ */
30
+ export interface IInterval {
31
+ clone(): IInterval;
32
+ compare(b: IInterval): number;
33
+ compareStart(b: IInterval): number;
34
+ compareEnd(b: IInterval): number;
35
+ modify(
36
+ label: string,
37
+ start: number,
38
+ end: number,
39
+ op?: ISequencedDocumentMessage,
40
+ localSeq?: number
41
+ ): IInterval | undefined;
42
+ overlaps(b: IInterval): boolean;
43
+ union(b: IInterval): IInterval;
44
+ }
45
+
46
+ const intervalComparer = (a: IInterval, b: IInterval) => a.compare(b);
47
+ /**
48
+ * @deprecated for internal use only. public export will be removed.
49
+ * @internal
50
+ */
51
+ export type IntervalNode<T extends IInterval> = RBNode<T, AugmentedIntervalNode>;
52
+ /**
53
+ * @deprecated for internal use only. public export will be removed.
54
+ * @internal
55
+ */
56
+ export type IntervalConflictResolver<TInterval> = (a: TInterval, b: TInterval) => TInterval;
57
+
58
+ /**
59
+ * @deprecated for internal use only. public export will be removed.
60
+ * @internal
61
+ */
62
+ export class IntervalTree<T extends IInterval> implements IRBAugmentation<T, AugmentedIntervalNode>,
63
+ IRBMatcher<T, AugmentedIntervalNode> {
64
+ public intervals = new RedBlackTree<T, AugmentedIntervalNode>(intervalComparer, this);
65
+
66
+ public remove(x: T) {
67
+ this.intervals.remove(x);
68
+ }
69
+
70
+ public removeExisting(x: T) {
71
+ this.intervals.removeExisting(x);
72
+ }
73
+
74
+ public put(x: T, conflict?: IntervalConflictResolver<T>) {
75
+ let rbConflict: ConflictAction<T, AugmentedIntervalNode> | undefined;
76
+ if (conflict) {
77
+ rbConflict = (key: T, currentKey: T) => {
78
+ const ival = conflict(key, currentKey);
79
+ return {
80
+ key: ival,
81
+ };
82
+ };
83
+ }
84
+ this.intervals.put(x, { minmax: x.clone() }, rbConflict);
85
+ }
86
+
87
+ public map(fn: (x: T) => void) {
88
+ const actions: RBNodeActions<T, AugmentedIntervalNode> = {
89
+ infix: (node) => {
90
+ fn(node.key);
91
+ return true;
92
+ },
93
+ showStructure: true,
94
+ };
95
+ this.intervals.walk(actions);
96
+ }
97
+
98
+ public mapUntil(fn: (X: T) => boolean) {
99
+ const actions: RBNodeActions<T, AugmentedIntervalNode> = {
100
+ infix: (node) => {
101
+ return fn(node.key);
102
+ },
103
+ showStructure: true,
104
+ };
105
+ this.intervals.walk(actions);
106
+ }
107
+
108
+ public mapBackward(fn: (x: T) => void) {
109
+ const actions: RBNodeActions<T, AugmentedIntervalNode> = {
110
+ infix: (node) => {
111
+ fn(node.key);
112
+ return true;
113
+ },
114
+ showStructure: true,
115
+ };
116
+ this.intervals.walkBackward(actions);
117
+ }
118
+
119
+ // TODO: toString()
120
+ public match(x: T) {
121
+ return this.intervals.gather(x, this);
122
+ }
123
+
124
+ public matchNode(node: IntervalNode<T> | undefined, key: T) {
125
+ return !!node && node.key.overlaps(key);
126
+ }
127
+
128
+ public continueSubtree(node: IntervalNode<T> | undefined, key: T) {
129
+ return !!node && node.data.minmax.overlaps(key);
130
+ }
131
+
132
+ public update(node: IntervalNode<T>) {
133
+ if (node.left && node.right) {
134
+ node.data.minmax = node.key.union(
135
+ node.left.data.minmax.union(node.right.data.minmax));
136
+ } else {
137
+ if (node.left) {
138
+ node.data.minmax = node.key.union(node.left.data.minmax);
139
+ } else if (node.right) {
140
+ node.data.minmax = node.key.union(node.right.data.minmax);
141
+ } else {
142
+ node.data.minmax = node.key.clone();
143
+ }
144
+ }
145
+ }
146
+ }