@fluidframework/merge-tree 0.53.0 → 0.54.2

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/src/mergeTree.ts CHANGED
@@ -4,7 +4,9 @@
4
4
  */
5
5
 
6
6
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
7
- /* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-shadow, max-len, no-bitwise */
7
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
8
+ /* eslint-disable @typescript-eslint/no-shadow */
9
+ /* eslint-disable no-bitwise */
8
10
 
9
11
  import { assert, Trace } from "@fluidframework/common-utils";
10
12
  import { IIntegerRange } from "./base";
@@ -165,14 +167,28 @@ export interface ISegmentChanges {
165
167
 
166
168
  export interface BlockAction<TClientData> {
167
169
  // eslint-disable-next-line @typescript-eslint/prefer-function-type
168
- (block: IMergeBlock, pos: number, refSeq: number, clientId: number, start: number | undefined, end: number | undefined,
169
- accum: TClientData): boolean;
170
+ (
171
+ block: IMergeBlock,
172
+ pos: number,
173
+ refSeq: number,
174
+ clientId: number,
175
+ start: number | undefined,
176
+ end: number | undefined,
177
+ accum: TClientData,
178
+ ): boolean;
170
179
  }
171
180
 
172
181
  export interface NodeAction<TClientData> {
173
182
  // eslint-disable-next-line @typescript-eslint/prefer-function-type
174
- (node: IMergeNode, pos: number, refSeq: number, clientId: number, start: number | undefined, end: number | undefined,
175
- clientData: TClientData): boolean;
183
+ (
184
+ node: IMergeNode,
185
+ pos: number,
186
+ refSeq: number,
187
+ clientId: number,
188
+ start: number | undefined,
189
+ end: number | undefined,
190
+ clientData: TClientData,
191
+ ): boolean;
176
192
  }
177
193
 
178
194
  export interface IncrementalSegmentAction<TContext> {
@@ -374,20 +390,20 @@ export function ordinalToArray(ord: string) {
374
390
  // `MaxNodesInBlock`. (i.e., `MaxNodesInBlock` contains 1 extra slot for temporary storage to
375
391
  // facilitate splits.)
376
392
  export const MaxNodesInBlock = 8;
393
+ const traceOrdinals = false;
377
394
 
378
395
  export class MergeBlock extends MergeNode implements IMergeBlock {
379
- static traceOrdinals = false;
380
- children: IMergeNode[];
381
- constructor(public childCount: number) {
396
+ public children: IMergeNode[];
397
+ public constructor(public childCount: number) {
382
398
  super();
383
399
  this.children = new Array<IMergeNode>(MaxNodesInBlock);
384
400
  }
385
401
 
386
- hierBlock(): HierMergeBlock | undefined {
402
+ public hierBlock(): HierMergeBlock | undefined {
387
403
  return undefined;
388
404
  }
389
405
 
390
- setOrdinal(child: IMergeNode, index: number) {
406
+ public setOrdinal(child: IMergeNode, index: number) {
391
407
  let childCount = this.childCount;
392
408
  if (childCount === 8) {
393
409
  childCount = 7;
@@ -403,18 +419,23 @@ export class MergeBlock extends MergeNode implements IMergeBlock {
403
419
  localOrdinal = prevOrdCode + ordinalWidth;
404
420
  }
405
421
  child.ordinal = this.ordinal + String.fromCharCode(localOrdinal);
406
- if (MergeBlock.traceOrdinals) {
422
+ if (traceOrdinals) {
423
+ // eslint-disable-next-line max-len
407
424
  console.log(`so: prnt chld prev ${ordinalToArray(this.ordinal)} ${ordinalToArray(child.ordinal)} ${(index > 0) ? ordinalToArray(this.children[index - 1].ordinal) : "NA"}`);
408
425
  }
409
426
  assert(child.ordinal.length === (this.ordinal.length + 1), 0x041 /* "Unexpected child ordinal length!" */);
410
427
  if (index > 0) {
411
- assert(child.ordinal > this.children[index - 1].ordinal, 0x042 /* "Child ordinal <= previous sibling ordinal!" */);
428
+ assert(
429
+ child.ordinal > this.children[index - 1].ordinal,
430
+ 0x042, /* "Child ordinal <= previous sibling ordinal!" */
431
+ );
432
+ // eslint-disable-next-line max-len
412
433
  // console.log(`${ordinalToArray(this.ordinal)} ${ordinalToArray(child.ordinal)} ${ordinalToArray(this.children[index - 1].ordinal)}`);
413
434
  // console.log(`ord width ${ordinalWidth}`);
414
435
  }
415
436
  }
416
437
 
417
- assignChild(child: IMergeNode, index: number, updateOrdinal = true) {
438
+ public assignChild(child: IMergeNode, index: number, updateOrdinal = true) {
418
439
  child.parent = this;
419
440
  child.index = index;
420
441
  if (updateOrdinal) {
@@ -425,9 +446,9 @@ export class MergeBlock extends MergeNode implements IMergeBlock {
425
446
  }
426
447
 
427
448
  class HierMergeBlock extends MergeBlock implements IMergeBlock {
428
- rightmostTiles: MapLike<ReferencePosition>;
429
- leftmostTiles: MapLike<ReferencePosition>;
430
- rangeStacks: MapLike<Stack<ReferencePosition>>;
449
+ public rightmostTiles: MapLike<ReferencePosition>;
450
+ public leftmostTiles: MapLike<ReferencePosition>;
451
+ public rangeStacks: MapLike<Stack<ReferencePosition>>;
431
452
 
432
453
  constructor(childCount: number) {
433
454
  super(childCount);
@@ -436,16 +457,16 @@ class HierMergeBlock extends MergeBlock implements IMergeBlock {
436
457
  this.rangeStacks = createMap<Stack<ReferencePosition>>();
437
458
  }
438
459
 
439
- addNodeReferences(mergeTree: MergeTree, node: IMergeNode) {
460
+ public addNodeReferences(mergeTree: MergeTree, node: IMergeNode) {
440
461
  addNodeReferences(mergeTree, node, this.rightmostTiles, this.leftmostTiles,
441
462
  this.rangeStacks);
442
463
  }
443
464
 
444
- hierBlock() {
465
+ public hierBlock() {
445
466
  return this;
446
467
  }
447
468
 
448
- hierToString(indentCount: number) {
469
+ public hierToString(indentCount: number) {
449
470
  let strbuf = "";
450
471
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
451
472
  for (const key in this.rangeStacks) {
@@ -469,42 +490,45 @@ function nodeTotalLength(mergeTree: MergeTree, node: IMergeNode) {
469
490
  }
470
491
 
471
492
  export abstract class BaseSegment extends MergeNode implements ISegment {
472
- constructor() {
473
- super();
474
- }
475
493
  public clientId: number = LocalClientId;
476
494
  public seq: number = UniversalSequenceNumber;
477
- removedSeq?: number;
478
- removedClientId?: number;
479
- removedClientOverlap?: number[];
480
- readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
481
- readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this);
482
- propertyManager?: PropertiesManager;
483
- properties?: PropertySet;
484
- localRefs?: LocalReferenceCollection;
485
- abstract readonly type: string;
486
- localSeq?: number;
487
- localRemovedSeq?: number;
488
-
489
- addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow) {
495
+ public removedSeq?: number;
496
+ public removedClientId?: number;
497
+ public removedClientOverlap?: number[];
498
+ public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
499
+ public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this);
500
+ public propertyManager?: PropertiesManager;
501
+ public properties?: PropertySet;
502
+ public localRefs?: LocalReferenceCollection;
503
+ public abstract readonly type: string;
504
+ public localSeq?: number;
505
+ public localRemovedSeq?: number;
506
+
507
+ public addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow) {
490
508
  if (!this.propertyManager) {
491
509
  this.propertyManager = new PropertiesManager();
492
510
  }
493
511
  if (!this.properties) {
494
512
  this.properties = createMap<any>();
495
513
  }
496
- return this.propertyManager.addProperties(this.properties, newProps, op, seq, collabWindow && collabWindow.collaborating);
514
+ return this.propertyManager.addProperties(
515
+ this.properties,
516
+ newProps,
517
+ op,
518
+ seq,
519
+ collabWindow && collabWindow.collaborating,
520
+ );
497
521
  }
498
522
 
499
- hasProperty(key: string): boolean {
523
+ public hasProperty(key: string): boolean {
500
524
  return !!this.properties && (this.properties[key] !== undefined);
501
525
  }
502
526
 
503
- isLeaf() {
527
+ public isLeaf() {
504
528
  return true;
505
529
  }
506
530
 
507
- cloneInto(b: ISegment) {
531
+ protected cloneInto(b: ISegment) {
508
532
  b.clientId = this.clientId;
509
533
  // TODO: deep clone properties
510
534
  b.properties = clone(this.properties);
@@ -514,17 +538,17 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
514
538
  b.seq = this.seq;
515
539
  }
516
540
 
517
- canAppend(segment: ISegment): boolean {
541
+ public canAppend(segment: ISegment): boolean {
518
542
  return false;
519
543
  }
520
544
 
521
- addSerializedProps(jseg: IJSONSegment) {
545
+ protected addSerializedProps(jseg: IJSONSegment) {
522
546
  if (this.properties) {
523
547
  jseg.props = this.properties;
524
548
  }
525
549
  }
526
550
 
527
- abstract toJSONObject(): any;
551
+ public abstract toJSONObject(): any;
528
552
 
529
553
  public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs, mergeTree: MergeTree): boolean {
530
554
  const currentSegmentGroup = this.segmentGroups.dequeue();
@@ -542,7 +566,8 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
542
566
  return true;
543
567
 
544
568
  case MergeTreeDeltaType.REMOVE:
545
- const removalInfo = mergeTree.getRemovalInfo(this);
569
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
570
+ const removalInfo: IRemovalInfo = this;
546
571
  assert(!!removalInfo, 0x046 /* "On remove ack, missing removal info!" */);
547
572
  assert(!!removalInfo.removedSeq, 0x047 /* "On remove ack, missing removed sequence number!" */);
548
573
  this.localRemovedSeq = undefined;
@@ -597,13 +622,17 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
597
622
  if (this.propertyManager) {
598
623
  if (this.properties) {
599
624
  other.propertyManager = new PropertiesManager();
600
- other.properties = this.propertyManager.copyTo(this.properties, other.properties, other.propertyManager);
625
+ other.properties = this.propertyManager.copyTo(
626
+ this.properties,
627
+ other.properties,
628
+ other.propertyManager,
629
+ );
601
630
  }
602
631
  }
603
632
  }
604
633
 
605
- abstract clone(): ISegment;
606
- abstract append(segment: ISegment): void;
634
+ public abstract clone(): ISegment;
635
+ public abstract append(segment: ISegment): void;
607
636
  protected abstract createSplitSegmentAt(pos: number): BaseSegment | undefined;
608
637
  }
609
638
 
@@ -612,11 +641,13 @@ export const reservedRangeLabelsKey = "referenceRangeLabels";
612
641
  export const reservedMarkerIdKey = "markerId";
613
642
  export const reservedMarkerSimpleTypeKey = "markerSimpleType";
614
643
 
615
- export const refGetTileLabels = (refPos: ReferencePosition) => (refPos.refType & ReferenceType.Tile) &&
616
- refPos.properties ? refPos.properties[reservedTileLabelsKey] as string[] : undefined;
644
+ export const refGetTileLabels = (refPos: ReferencePosition) =>
645
+ (refPos.refType & ReferenceType.Tile)
646
+ && refPos.properties ? refPos.properties[reservedTileLabelsKey] as string[] : undefined;
617
647
 
618
- export const refGetRangeLabels = (refPos: ReferencePosition) => (refPos.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) &&
619
- refPos.properties ? refPos.properties[reservedRangeLabelsKey] as string[] : undefined;
648
+ export const refGetRangeLabels = (refPos: ReferencePosition) =>
649
+ (refPos.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd))
650
+ && refPos.properties ? refPos.properties[reservedRangeLabelsKey] as string[] : undefined;
620
651
 
621
652
  export function refHasTileLabel(refPos: ReferencePosition, label: string) {
622
653
  const tileLabels = refPos.getTileLabels();
@@ -1025,63 +1056,51 @@ export type LocalReferenceMapper = (id: string) => LocalReference;
1025
1056
 
1026
1057
  // Represents a sequence of text segments
1027
1058
  export class MergeTree {
1028
- // Maximum length of text segment to be considered to be merged with other segment.
1029
- // Maximum segment length is at least 2x of it (not taking into account initial segment creation).
1030
- // The bigger it is, the more expensive it is to break segment into sub-segments (on edits)
1031
- // The smaller it is, the more segments we have in snapshots (and in memory) - it's more expensive to load snapshots.
1032
- // Small number also makes ReplayTool produce false positives ("same" snapshots have slightly different binary representations).
1033
- // More measurements needs to be done, but it's very likely the right spot is somewhere between 1K-2K mark.
1034
- // That said, we also break segments on newline and there are very few segments that are longer than 256 because of it.
1035
- // must be an even number
1036
- static TextSegmentGranularity = 256;
1037
-
1038
- static zamboniSegmentsMaxCount = 2;
1039
- static options = {
1059
+ private static readonly zamboniSegmentsMaxCount = 2;
1060
+ public static readonly options = {
1040
1061
  incrementalUpdate: true,
1041
1062
  insertAfterRemovedSegs: true,
1042
1063
  measureOrdinalTime: true,
1043
1064
  measureWindowTime: true,
1044
1065
  zamboniSegments: true,
1045
1066
  };
1046
- static traceAppend = false;
1047
- static traceZRemove = false;
1048
- static traceOrdinals = false;
1049
- static traceGatherText = false;
1050
- static diagInsertTie = false;
1051
- static skipLeftShift = true;
1052
- static diagOverlappingRemove = false;
1053
- static traceTraversal = false;
1054
- static traceIncrTraversal = false;
1055
- static initBlockUpdateActions: BlockUpdateActions;
1056
- static theUnfinishedNode = <IMergeBlock>{ childCount: -1 };
1067
+ private static readonly traceAppend = false;
1068
+ private static readonly traceZRemove = false;
1069
+ private static readonly traceOrdinals = false;
1070
+ public static readonly traceGatherText = false;
1071
+ private static readonly diagInsertTie = false;
1072
+ public static readonly diagOverlappingRemove = false;
1073
+ private static readonly traceTraversal = false;
1074
+ private static readonly traceIncrTraversal = false;
1075
+ private static readonly initBlockUpdateActions: BlockUpdateActions;
1076
+ private static readonly theUnfinishedNode = <IMergeBlock>{ childCount: -1 };
1057
1077
  // WARNING:
1058
1078
  // Setting blockUpdateMarkers to false will result in eventual consistency issues
1059
1079
  // for property updates on markers when loading from snapshots
1060
- static readonly blockUpdateMarkers = true;
1080
+ private static readonly blockUpdateMarkers = true;
1061
1081
 
1062
- windowTime = 0;
1063
- packTime = 0;
1064
- ordTime = 0;
1065
- maxOrdTime = 0;
1082
+ private windowTime = 0;
1083
+ private packTime = 0;
1084
+ private ordTime = 0;
1085
+ private maxOrdTime = 0;
1066
1086
 
1067
1087
  root: IMergeBlock;
1068
- blockUpdateActions: BlockUpdateActions;
1069
- collabWindow = new CollaborationWindow();
1070
- pendingSegments: List<SegmentGroup> | undefined;
1071
- segmentsToScour: Heap<LRUSegment> | undefined;
1072
- // TODO: change this to ES6 map; add remove on segment remove
1088
+ private readonly blockUpdateActions: BlockUpdateActions = MergeTree.initBlockUpdateActions;
1089
+ public readonly collabWindow = new CollaborationWindow();
1090
+ public pendingSegments: List<SegmentGroup> | undefined;
1091
+ private segmentsToScour: Heap<LRUSegment> | undefined;
1092
+ // TODO: add remove on segment remove
1073
1093
  // for now assume only markers have ids and so point directly at the Segment
1074
1094
  // if we need to have pointers to non-markers, we can change to point at local refs
1075
- idToSegment = createMap<ISegment>();
1076
- minSeqListeners: Heap<MinListener> | undefined;
1095
+ private readonly idToSegment = new Map<string, ISegment>();
1096
+ private minSeqListeners: Heap<MinListener> | undefined;
1077
1097
  // For diagnostics
1078
- getLongClientId?: (id: number) => string;
1079
- mergeTreeDeltaCallback?: MergeTreeDeltaCallback;
1080
- mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback;
1098
+ public getLongClientId?: (id: number) => string;
1099
+ public mergeTreeDeltaCallback?: MergeTreeDeltaCallback;
1100
+ public mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback;
1081
1101
 
1082
1102
  // TODO: make and use interface describing options
1083
- constructor(public options?: PropertySet) {
1084
- this.blockUpdateActions = MergeTree.initBlockUpdateActions;
1103
+ public constructor(public options?: PropertySet) {
1085
1104
  this.root = this.makeBlock(0);
1086
1105
  }
1087
1106
 
@@ -1096,13 +1115,13 @@ export class MergeTree {
1096
1115
  return block;
1097
1116
  }
1098
1117
 
1099
- clone() {
1118
+ public clone() {
1100
1119
  const b = new MergeTree(this.options);
1101
1120
  // For now assume that b will not collaborate
1102
1121
  b.root = b.blockClone(this.root);
1103
1122
  }
1104
1123
 
1105
- blockClone(block: IMergeBlock, segments?: ISegment[]) {
1124
+ public blockClone(block: IMergeBlock, segments?: ISegment[]) {
1106
1125
  const bBlock = this.makeBlock(block.childCount);
1107
1126
  for (let i = 0; i < block.childCount; i++) {
1108
1127
  const child = block.children[i];
@@ -1126,8 +1145,8 @@ export class MergeTree {
1126
1145
  return b;
1127
1146
  }
1128
1147
 
1129
- localNetLength(segment: ISegment) {
1130
- const removalInfo = this.getRemovalInfo(segment);
1148
+ public localNetLength(segment: ISegment) {
1149
+ const removalInfo: IRemovalInfo = segment;
1131
1150
  if (removalInfo.removedSeq !== undefined) {
1132
1151
  return 0;
1133
1152
  } else {
@@ -1136,8 +1155,8 @@ export class MergeTree {
1136
1155
  }
1137
1156
 
1138
1157
  // TODO: remove id when segment removed
1139
- mapIdToSegment(id: string, segment: ISegment) {
1140
- this.idToSegment[id] = segment;
1158
+ public mapIdToSegment(id: string, segment: ISegment) {
1159
+ this.idToSegment.set(id, segment);
1141
1160
  }
1142
1161
 
1143
1162
  private addNode(block: IMergeBlock, node: IMergeNode) {
@@ -1146,7 +1165,8 @@ export class MergeTree {
1146
1165
  return index;
1147
1166
  }
1148
1167
 
1149
- reloadFromSegments(segments: ISegment[]) {
1168
+ /* eslint-disable max-len */
1169
+ public reloadFromSegments(segments: ISegment[]) {
1150
1170
  // This code assumes that a later call to `startCollaboration()` will initialize partial lengths.
1151
1171
  assert(!this.collabWindow.collaborating, 0x049 /* "Trying to reload from segments while collaborating!" */);
1152
1172
 
@@ -1202,9 +1222,10 @@ export class MergeTree {
1202
1222
  console.log(`reload time ${elapsedMicroseconds(clockStart)}`);
1203
1223
  }
1204
1224
  }
1225
+ /* eslint-enable max-len */
1205
1226
 
1206
1227
  // For now assume min starts at zero
1207
- startCollaboration(localClientId: number, minSeq: number, currentSeq: number) {
1228
+ public startCollaboration(localClientId: number, minSeq: number, currentSeq: number) {
1208
1229
  this.collabWindow.clientId = localClientId;
1209
1230
  this.collabWindow.minSeq = minSeq;
1210
1231
  this.collabWindow.collaborating = true;
@@ -1252,11 +1273,11 @@ export class MergeTree {
1252
1273
  holdNodes.push(segment);
1253
1274
  } else {
1254
1275
  if (MergeTree.traceZRemove) {
1255
- // eslint-disable-next-line @typescript-eslint/dot-notation
1276
+ // eslint-disable-next-line @typescript-eslint/dot-notation, max-len
1256
1277
  console.log(`${this.getLongClientId!(this.collabWindow.clientId)}: Zremove ${segment["text"]}; cli ${this.getLongClientId!(segment.clientId)}`);
1257
1278
  }
1258
1279
 
1259
- // Notify maintenance event observers that the segment is being unlinked from the MergeTree.
1280
+ // Notify maintenance event observers that the segment is being unlinked from the MergeTree
1260
1281
  if (this.mergeTreeMaintenanceCallback) {
1261
1282
  this.mergeTreeMaintenanceCallback({
1262
1283
  operation: MergeTreeMaintenanceType.UNLINK,
@@ -1278,7 +1299,7 @@ export class MergeTree {
1278
1299
 
1279
1300
  if (canAppend) {
1280
1301
  if (MergeTree.traceAppend) {
1281
- // eslint-disable-next-line @typescript-eslint/dot-notation
1302
+ // eslint-disable-next-line @typescript-eslint/dot-notation, max-len
1282
1303
  console.log(`${this.getLongClientId!(this.collabWindow.clientId)}: append ${prevSegment!["text"]} + ${segment["text"]}; cli ${this.getLongClientId!(prevSegment!.clientId)} + cli ${this.getLongClientId!(segment.clientId)}`);
1283
1304
  }
1284
1305
  prevSegment!.append(segment);
@@ -1427,13 +1448,20 @@ export class MergeTree {
1427
1448
  }
1428
1449
  }
1429
1450
 
1430
- getCollabWindow() {
1451
+ public getCollabWindow() {
1431
1452
  return this.collabWindow;
1432
1453
  }
1433
1454
 
1434
- getStats() {
1455
+ public getStats() {
1435
1456
  const nodeGetStats = (block: IMergeBlock): MergeTreeStats => {
1436
- const stats: MergeTreeStats = { maxHeight: 0, nodeCount: 0, leafCount: 0, removedLeafCount: 0, liveCount: 0, histo: [] };
1457
+ const stats: MergeTreeStats = {
1458
+ maxHeight: 0,
1459
+ nodeCount: 0,
1460
+ leafCount: 0,
1461
+ removedLeafCount: 0,
1462
+ liveCount: 0,
1463
+ histo: [],
1464
+ };
1437
1465
  for (let k = 0; k < MaxNodesInBlock; k++) {
1438
1466
  stats.histo[k] = 0;
1439
1467
  }
@@ -1476,11 +1504,11 @@ export class MergeTree {
1476
1504
  return rootStats;
1477
1505
  }
1478
1506
 
1479
- findHistorialPosition(pos: number, fromSeq: number, toSeq: number, clientId: number) {
1507
+ public findHistorialPosition(pos: number, fromSeq: number, toSeq: number, clientId: number) {
1480
1508
  return this.findHistorialPositionFromClient(pos, fromSeq, toSeq, clientId);
1481
1509
  }
1482
1510
 
1483
- findHistorialPositionFromClient(pos: number, fromSeq: number, toSeq: number, clientId: number) {
1511
+ private findHistorialPositionFromClient(pos: number, fromSeq: number, toSeq: number, clientId: number) {
1484
1512
  assert(fromSeq < toSeq, 0x04a /* "Invalid range for historical position search!" */);
1485
1513
  if (pos < this.getLength(fromSeq, clientId)) {
1486
1514
  assert(toSeq <= this.collabWindow.currentSeq,
@@ -1498,7 +1526,13 @@ export class MergeTree {
1498
1526
  }
1499
1527
  }
1500
1528
 
1501
- findHistorialRangeFromClient(rangeStart: number, rangeEnd: number, fromSeq: number, toSeq: number, clientId: number) {
1529
+ public findHistorialRangeFromClient(
1530
+ rangeStart: number,
1531
+ rangeEnd: number,
1532
+ fromSeq: number,
1533
+ toSeq: number,
1534
+ clientId: number,
1535
+ ) {
1502
1536
  const ranges: IIntegerRange[] = [];
1503
1537
  const recordRange = (
1504
1538
  segment: ISegment,
@@ -1525,11 +1559,11 @@ export class MergeTree {
1525
1559
  return ranges;
1526
1560
  }
1527
1561
 
1528
- findHistorialRange(rangeStart: number, rangeEnd: number, fromSeq: number, toSeq: number, clientId: number) {
1562
+ public findHistorialRange(rangeStart: number, rangeEnd: number, fromSeq: number, toSeq: number, clientId: number) {
1529
1563
  return this.findHistorialRangeFromClient(rangeStart, rangeEnd, fromSeq, toSeq, clientId);
1530
1564
  }
1531
1565
 
1532
- getLength(refSeq: number, clientId: number) {
1566
+ public getLength(refSeq: number, clientId: number) {
1533
1567
  return this.blockLength(this.root, refSeq, clientId);
1534
1568
  }
1535
1569
 
@@ -1538,7 +1572,7 @@ export class MergeTree {
1538
1572
  */
1539
1573
  public get length() { return this.root.cachedLength; }
1540
1574
 
1541
- getPosition(node: MergeNode, refSeq: number, clientId: number) {
1575
+ public getPosition(node: MergeNode, refSeq: number, clientId: number) {
1542
1576
  let totalOffset = 0;
1543
1577
  let parent = node.parent;
1544
1578
  let prevParent: IMergeBlock | undefined;
@@ -1557,26 +1591,7 @@ export class MergeTree {
1557
1591
  return totalOffset;
1558
1592
  }
1559
1593
 
1560
- cloneSegments(refSeq: number, clientId: number, start = 0, end?: number) {
1561
- let _end = end;
1562
- const gatherSegment = (
1563
- segment: ISegment, pos: number, refSeq: number, clientId: number, start: number,
1564
- end: number, accumSegments: SegmentAccumulator) => {
1565
- accumSegments.segments.push(segment.clone());
1566
- return true;
1567
- };
1568
-
1569
- if (_end === undefined) {
1570
- _end = this.blockLength(this.root, refSeq, clientId);
1571
- }
1572
- const accum: SegmentAccumulator = {
1573
- segments: [],
1574
- };
1575
- this.mapRange<SegmentAccumulator>({ leaf: gatherSegment }, refSeq, clientId, accum, start, _end);
1576
- return accum.segments;
1577
- }
1578
-
1579
- getContainingSegment<T extends ISegment>(pos: number, refSeq: number, clientId: number) {
1594
+ public getContainingSegment<T extends ISegment>(pos: number, refSeq: number, clientId: number) {
1580
1595
  let segment: T | undefined;
1581
1596
  let offset: number | undefined;
1582
1597
 
@@ -1597,10 +1612,6 @@ export class MergeTree {
1597
1612
  }
1598
1613
  }
1599
1614
 
1600
- getRemovalInfo(segment: ISegment) {
1601
- return segment as IRemovalInfo;
1602
- }
1603
-
1604
1615
  private nodeLength(node: IMergeNode, refSeq: number, clientId: number) {
1605
1616
  if ((!this.collabWindow.collaborating) || (this.collabWindow.clientId === clientId)) {
1606
1617
  // Local client sees all segments, even when collaborating
@@ -1615,7 +1626,7 @@ export class MergeTree {
1615
1626
  return node.partialLengths!.getPartialLength(refSeq, clientId);
1616
1627
  } else {
1617
1628
  const segment = node;
1618
- const removalInfo = this.getRemovalInfo(segment);
1629
+ const removalInfo: IRemovalInfo = segment;
1619
1630
 
1620
1631
  if(removalInfo.removedSeq !== undefined
1621
1632
  && removalInfo.removedSeq !== UnassignedSequenceNumber
@@ -1629,8 +1640,11 @@ export class MergeTree {
1629
1640
  ((segment.seq !== UnassignedSequenceNumber) && (segment.seq! <= refSeq)))) {
1630
1641
  // Segment happened by reference sequence number or segment from requesting client
1631
1642
  if (removalInfo.removedSeq !== undefined) {
1632
- if ((removalInfo.removedClientId === clientId) ||
1633
- (removalInfo.removedClientOverlap && (removalInfo.removedClientOverlap.includes(clientId)))) {
1643
+ if (
1644
+ removalInfo.removedClientId === clientId
1645
+ || (removalInfo.removedClientOverlap
1646
+ && removalInfo.removedClientOverlap.includes(clientId))
1647
+ ) {
1634
1648
  return 0;
1635
1649
  } else {
1636
1650
  return segment.cachedLength;
@@ -1653,7 +1667,7 @@ export class MergeTree {
1653
1667
  }
1654
1668
  }
1655
1669
 
1656
- addMinSeqListener(minRequired: number, onMinGE: (minSeq: number) => void) {
1670
+ public addMinSeqListener(minRequired: number, onMinGE: (minSeq: number) => void) {
1657
1671
  if (!this.minSeqListeners) {
1658
1672
  this.minSeqListeners = new Heap<MinListener>([],
1659
1673
  minListenerComparer);
@@ -1671,8 +1685,11 @@ export class MergeTree {
1671
1685
  }
1672
1686
  }
1673
1687
 
1674
- setMinSeq(minSeq: number) {
1675
- assert(minSeq <= this.collabWindow.currentSeq, 0x04e /* "Trying to set minSeq above currentSeq of collab window!" */);
1688
+ public setMinSeq(minSeq: number) {
1689
+ assert(
1690
+ minSeq <= this.collabWindow.currentSeq,
1691
+ 0x04e, /* "Trying to set minSeq above currentSeq of collab window!" */
1692
+ );
1676
1693
 
1677
1694
  // Only move forward
1678
1695
  assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
@@ -1698,7 +1715,7 @@ export class MergeTree {
1698
1715
  return LocalReference.DetachedPosition;
1699
1716
  }
1700
1717
 
1701
- getStackContext(startPos: number, clientId: number, rangeLabels: string[]) {
1718
+ public getStackContext(startPos: number, clientId: number, rangeLabels: string[]) {
1702
1719
  const searchInfo: IMarkerSearchRangeInfo = {
1703
1720
  mergeTree: this,
1704
1721
  stacks: createMap<Stack<Marker>>(),
@@ -1711,7 +1728,7 @@ export class MergeTree {
1711
1728
  }
1712
1729
 
1713
1730
  // TODO: filter function
1714
- findTile(startPos: number, clientId: number, tileLabel: string, posPrecedesTile = true) {
1731
+ public findTile(startPos: number, clientId: number, tileLabel: string, posPrecedesTile = true) {
1715
1732
  const searchInfo: IReferenceSearchInfo = {
1716
1733
  mergeTree: this,
1717
1734
  posPrecedesTile,
@@ -1758,7 +1775,10 @@ export class MergeTree {
1758
1775
  for (let childIndex = 0; childIndex < block.childCount; childIndex++) {
1759
1776
  const child = children[childIndex];
1760
1777
  const len = this.nodeLength(child, refSeq, clientId) ?? 0;
1761
- if (((!contains) && (_pos < len)) || (contains && contains(child, _pos, refSeq, clientId, undefined, undefined, clientData))) {
1778
+ if (
1779
+ (!contains && _pos < len)
1780
+ || (contains && contains(child, _pos, refSeq, clientId, undefined, undefined, clientData))
1781
+ ) {
1762
1782
  // Found entry containing pos
1763
1783
  if (!child.isLeaf()) {
1764
1784
  return this.searchBlock(child, _pos, _segpos, refSeq, clientId, actions, clientData);
@@ -1842,7 +1862,7 @@ export class MergeTree {
1842
1862
  * Assign sequence number to existing segment; update partial lengths to reflect the change
1843
1863
  * @param seq - sequence number given by server to pending segment
1844
1864
  */
1845
- ackPendingSegment(opArgs: IMergeTreeDeltaOpArgs, verboseOps = false) {
1865
+ public ackPendingSegment(opArgs: IMergeTreeDeltaOpArgs, verboseOps = false) {
1846
1866
  const seq = opArgs.sequencedMessage!.sequenceNumber;
1847
1867
  const pendingSegmentGroup = this.pendingSegments!.dequeue();
1848
1868
  const nodesToUpdate: IMergeBlock[] = [];
@@ -1897,8 +1917,8 @@ export class MergeTree {
1897
1917
  }
1898
1918
 
1899
1919
  // TODO: error checking
1900
- getMarkerFromId(id: string) {
1901
- return this.idToSegment[id];
1920
+ public getMarkerFromId(id: string) {
1921
+ return this.idToSegment.get(id);
1902
1922
  }
1903
1923
 
1904
1924
  /**
@@ -1908,7 +1928,7 @@ export class MergeTree {
1908
1928
  * @param refseq - The reference sequence number at which to compute the position.
1909
1929
  * @param clientId - The client id with which to compute the position.
1910
1930
  */
1911
- posFromRelativePos(
1931
+ public posFromRelativePos(
1912
1932
  relativePos: IRelativePosition,
1913
1933
  refseq = this.collabWindow.currentSeq,
1914
1934
  clientId = this.collabWindow.clientId) {
@@ -1972,7 +1992,11 @@ export class MergeTree {
1972
1992
  }
1973
1993
  }
1974
1994
 
1975
- public insertAtReferencePosition(referencePosition: ReferencePosition, insertSegment: ISegment, opArgs: IMergeTreeDeltaOpArgs): void {
1995
+ public insertAtReferencePosition(
1996
+ referencePosition: ReferencePosition,
1997
+ insertSegment: ISegment,
1998
+ opArgs: IMergeTreeDeltaOpArgs,
1999
+ ): void {
1976
2000
  if (insertSegment.cachedLength === 0) {
1977
2001
  return;
1978
2002
  }
@@ -2124,11 +2148,19 @@ export class MergeTree {
2124
2148
  block.assignChild(child, childIndex, false);
2125
2149
  }
2126
2150
 
2127
- private blockInsert<T extends ISegment>(pos: number, refSeq: number, clientId: number, seq: number, localSeq: number | undefined, newSegments: T[]) {
2151
+ private blockInsert<T extends ISegment>(
2152
+ pos: number,
2153
+ refSeq: number,
2154
+ clientId: number,
2155
+ seq: number,
2156
+ localSeq: number | undefined,
2157
+ newSegments: T[],
2158
+ ) {
2128
2159
  let segIsLocal = false;
2129
2160
  const checkSegmentIsLocal = (segment: ISegment, pos: number, refSeq: number, clientId: number) => {
2130
2161
  if (segment.seq === UnassignedSequenceNumber) {
2131
2162
  if (MergeTree.diagInsertTie) {
2163
+ // eslint-disable-next-line max-len
2132
2164
  console.log(`@cli ${glc(this, this.collabWindow.clientId)}: promoting continue due to seq ${segment.seq} text ${segment.toString()} ref ${refSeq}`);
2133
2165
  }
2134
2166
  segIsLocal = true;
@@ -2141,6 +2173,7 @@ export class MergeTree {
2141
2173
  segIsLocal = false;
2142
2174
  this.rightExcursion(node, checkSegmentIsLocal);
2143
2175
  if (MergeTree.diagInsertTie && segIsLocal) {
2176
+ // eslint-disable-next-line max-len
2144
2177
  console.log(`@cli ${glc(this, this.collabWindow.clientId)}: attempting continue with seq ${seq} ref ${refSeq} `);
2145
2178
  }
2146
2179
  return segIsLocal;
@@ -2364,6 +2397,7 @@ export class MergeTree {
2364
2397
  return undefined;
2365
2398
  } else if (splitNode === MergeTree.theUnfinishedNode) {
2366
2399
  if (MergeTree.traceTraversal) {
2400
+ // eslint-disable-next-line max-len
2367
2401
  console.log(`@cli ${glc(this, this.collabWindow.clientId)} unfinished bus pos ${_pos} len ${len}`);
2368
2402
  }
2369
2403
  _pos -= len; // Act as if shifted segment
@@ -2404,6 +2438,7 @@ export class MergeTree {
2404
2438
  }
2405
2439
  if (MergeTree.traceTraversal) {
2406
2440
  if ((!found) && (_pos > 0)) {
2441
+ // eslint-disable-next-line max-len
2407
2442
  console.log(`inserting walk fell through pos ${_pos} len: ${this.blockLength(this.root, refSeq, clientId)}`);
2408
2443
  }
2409
2444
  }
@@ -2483,6 +2518,7 @@ export class MergeTree {
2483
2518
  if (i > 0) {
2484
2519
  if (child.ordinal <= block.children[i - 1].ordinal) {
2485
2520
  console.log("node sib integrity issue");
2521
+ // eslint-disable-next-line max-len
2486
2522
  console.log(`??: prnt chld prev ${ordinalToArray(block.ordinal)} ${ordinalToArray(child.ordinal)} ${(i > 0) ? ordinalToArray(block.children[i - 1].ordinal) : "NA"}`);
2487
2523
  }
2488
2524
  }
@@ -2541,7 +2577,7 @@ export class MergeTree {
2541
2577
  * @param seq - The sequence number of the annotate operation
2542
2578
  * @param opArgs - The op args for the annotate op. this is passed to the merge tree callback if there is one
2543
2579
  */
2544
- annotateRange(
2580
+ public annotateRange(
2545
2581
  start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined, refSeq: number,
2546
2582
  clientId: number, seq: number, opArgs: IMergeTreeDeltaOpArgs) {
2547
2583
  this.ensureIntervalBoundary(start, refSeq, clientId);
@@ -2583,7 +2619,15 @@ export class MergeTree {
2583
2619
  }
2584
2620
  }
2585
2621
 
2586
- markRangeRemoved(start: number, end: number, refSeq: number, clientId: number, seq: number, overwrite = false, opArgs: IMergeTreeDeltaOpArgs) {
2622
+ public markRangeRemoved(
2623
+ start: number,
2624
+ end: number,
2625
+ refSeq: number,
2626
+ clientId: number,
2627
+ seq: number,
2628
+ overwrite = false,
2629
+ opArgs: IMergeTreeDeltaOpArgs,
2630
+ ) {
2587
2631
  let _overwrite = overwrite;
2588
2632
  this.ensureIntervalBoundary(start, refSeq, clientId);
2589
2633
  this.ensureIntervalBoundary(end, refSeq, clientId);
@@ -2592,9 +2636,10 @@ export class MergeTree {
2592
2636
  const savedLocalRefs: LocalReferenceCollection[] = [];
2593
2637
  const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
2594
2638
  const markRemoved = (segment: ISegment, pos: number, start: number, end: number) => {
2595
- const removalInfo = this.getRemovalInfo(segment);
2639
+ const removalInfo: IRemovalInfo = segment;
2596
2640
  if (removalInfo.removedSeq !== undefined) {
2597
2641
  if (MergeTree.diagOverlappingRemove) {
2642
+ // eslint-disable-next-line max-len
2598
2643
  console.log(`yump @seq ${seq} cli ${glc(this, this.collabWindow.clientId)}: overlaps deleted segment ${removalInfo.removedSeq} text '${segment.toString()}'`);
2599
2644
  }
2600
2645
  _overwrite = true;
@@ -2622,8 +2667,8 @@ export class MergeTree {
2622
2667
  // Save segment so can assign removed sequence number when acked by server
2623
2668
  if (this.collabWindow.collaborating) {
2624
2669
  // Use removal information
2625
- const removalInfo = this.getRemovalInfo(segment);
2626
- if ((removalInfo.removedSeq === UnassignedSequenceNumber) && (clientId === this.collabWindow.clientId)) {
2670
+ const removalInfo: IRemovalInfo = segment;
2671
+ if (removalInfo.removedSeq === UnassignedSequenceNumber && clientId === this.collabWindow.clientId) {
2627
2672
  segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
2628
2673
  } else {
2629
2674
  if (MergeTree.options.zamboniSegments) {
@@ -2701,7 +2746,7 @@ export class MergeTree {
2701
2746
  }
2702
2747
  }
2703
2748
 
2704
- removeLocalReference(segment: ISegment, lref: LocalReference) {
2749
+ public removeLocalReference(segment: ISegment, lref: LocalReference) {
2705
2750
  if (segment.localRefs) {
2706
2751
  const removedRef = segment.localRefs.removeLocalRef(lref);
2707
2752
  if (removedRef) {
@@ -2711,7 +2756,7 @@ export class MergeTree {
2711
2756
  }
2712
2757
  }
2713
2758
 
2714
- addLocalReference(lref: LocalReference) {
2759
+ public addLocalReference(lref: LocalReference) {
2715
2760
  const segment = lref.segment!;
2716
2761
  let localRefs = segment.localRefs;
2717
2762
  if (!localRefs) {
@@ -2744,7 +2789,12 @@ export class MergeTree {
2744
2789
  block.cachedLength = len;
2745
2790
  }
2746
2791
 
2747
- private blockUpdatePathLengths(startBlock: IMergeBlock | undefined, seq: number, clientId: number, newStructure = false) {
2792
+ private blockUpdatePathLengths(
2793
+ startBlock: IMergeBlock | undefined,
2794
+ seq: number,
2795
+ clientId: number,
2796
+ newStructure = false,
2797
+ ) {
2748
2798
  let block: IMergeBlock | undefined = startBlock;
2749
2799
  while (block !== undefined) {
2750
2800
  if (newStructure) {
@@ -2758,8 +2808,16 @@ export class MergeTree {
2758
2808
 
2759
2809
  private blockUpdateLength(node: IMergeBlock, seq: number, clientId: number) {
2760
2810
  this.blockUpdate(node);
2761
- if (this.collabWindow.collaborating && (seq !== UnassignedSequenceNumber) && (seq !== TreeMaintenanceSequenceNumber)) {
2762
- if (node.partialLengths !== undefined && MergeTree.options.incrementalUpdate && clientId !== NonCollabClient) {
2811
+ if (
2812
+ this.collabWindow.collaborating
2813
+ && seq !== UnassignedSequenceNumber
2814
+ && seq !== TreeMaintenanceSequenceNumber
2815
+ ) {
2816
+ if (
2817
+ node.partialLengths !== undefined
2818
+ && MergeTree.options.incrementalUpdate
2819
+ && clientId !== NonCollabClient
2820
+ ) {
2763
2821
  node.partialLengths.update(this, node, seq, clientId, this.collabWindow);
2764
2822
  } else {
2765
2823
  node.partialLengths = PartialSequenceLengths.combine(this, node, this.collabWindow);
@@ -2767,12 +2825,25 @@ export class MergeTree {
2767
2825
  }
2768
2826
  }
2769
2827
 
2770
- map<TClientData>(actions: SegmentActions<TClientData>, refSeq: number, clientId: number, accum: TClientData) {
2828
+ public map<TClientData>(
2829
+ actions: SegmentActions<TClientData>,
2830
+ refSeq: number,
2831
+ clientId: number,
2832
+ accum: TClientData,
2833
+ ) {
2771
2834
  // TODO: optimize to avoid comparisons
2772
2835
  this.nodeMap(this.root, actions, 0, refSeq, clientId, accum);
2773
2836
  }
2774
2837
 
2775
- mapRange<TClientData>(actions: SegmentActions<TClientData>, refSeq: number, clientId: number, accum: TClientData, start?: number, end?: number, splitRange: boolean = false) {
2838
+ public mapRange<TClientData>(
2839
+ actions: SegmentActions<TClientData>,
2840
+ refSeq: number,
2841
+ clientId: number,
2842
+ accum: TClientData,
2843
+ start?: number,
2844
+ end?: number,
2845
+ splitRange: boolean = false,
2846
+ ) {
2776
2847
  if (splitRange) {
2777
2848
  if (start) {
2778
2849
  this.ensureIntervalBoundary(start, refSeq, clientId);
@@ -2784,9 +2855,10 @@ export class MergeTree {
2784
2855
  this.nodeMap(this.root, actions, 0, refSeq, clientId, accum, start, end);
2785
2856
  }
2786
2857
 
2787
- nodeToString(block: IMergeBlock, strbuf: string, indentCount = 0) {
2858
+ public nodeToString(block: IMergeBlock, strbuf: string, indentCount = 0) {
2788
2859
  let _strbuf = strbuf;
2789
2860
  _strbuf += internedSpaces(indentCount);
2861
+ // eslint-disable-next-line max-len
2790
2862
  _strbuf += `Node (len ${block.cachedLength}) p len (${block.parent ? block.parent.cachedLength : 0}) ord ${ordinalToArray(block.ordinal)} with ${block.childCount} segs:\n`;
2791
2863
  if (MergeTree.blockUpdateMarkers) {
2792
2864
  _strbuf += internedSpaces(indentCount);
@@ -2804,8 +2876,9 @@ export class MergeTree {
2804
2876
  } else {
2805
2877
  const segment = child;
2806
2878
  _strbuf += internedSpaces(indentCount + 4);
2879
+ // eslint-disable-next-line max-len
2807
2880
  _strbuf += `cli: ${glc(this, segment.clientId)} seq: ${segment.seq} ord: ${ordinalToArray(segment.ordinal)}`;
2808
- const removalInfo = this.getRemovalInfo(segment);
2881
+ const removalInfo: IRemovalInfo = segment;
2809
2882
  if (removalInfo.removedSeq !== undefined) {
2810
2883
  _strbuf += ` rcli: ${glc(this, removalInfo.removedClientId!)} rseq: ${removalInfo.removedSeq}`;
2811
2884
  }
@@ -2818,7 +2891,7 @@ export class MergeTree {
2818
2891
  return _strbuf;
2819
2892
  }
2820
2893
 
2821
- toString() {
2894
+ public toString() {
2822
2895
  return this.nodeToString(this.root, "", 0);
2823
2896
  }
2824
2897
 
@@ -2846,7 +2919,7 @@ export class MergeTree {
2846
2919
  const len = this.nodeLength(child, state.refSeq, state.clientId) ?? 0;
2847
2920
  if (MergeTree.traceIncrTraversal) {
2848
2921
  if (child.isLeaf()) {
2849
- // eslint-disable-next-line @typescript-eslint/dot-notation
2922
+ // eslint-disable-next-line @typescript-eslint/dot-notation, max-len
2850
2923
  console.log(`considering (r ${state.refSeq} c ${glc(this, state.clientId)}) seg with text ${child["text"]} len ${len} seq ${child.seq} rseq ${child.removedSeq} cli ${glc(this, child.clientId)}`);
2851
2924
  }
2852
2925
  }
@@ -2913,6 +2986,7 @@ export class MergeTree {
2913
2986
  segInfo += ` rcli: ${glc(this, segment.removedClientId!)} rseq: ${segment.removedSeq}`;
2914
2987
  }
2915
2988
  }
2989
+ // eslint-disable-next-line max-len
2916
2990
  console.log(`@tcli ${glc(this, this.collabWindow.clientId)}: map len: ${len} start: ${_start} end: ${_end} ${segInfo}`);
2917
2991
  }
2918
2992
  if (go && (_end > 0) && (len > 0) && (_start < len)) {
@@ -2949,7 +3023,7 @@ export class MergeTree {
2949
3023
 
2950
3024
  // Invokes the leaf action for all segments. Note that *all* segments are visited
2951
3025
  // regardless of if they would be visible to the current `clientId` and `refSeq`.
2952
- walkAllSegments<TClientData>(
3026
+ public walkAllSegments<TClientData>(
2953
3027
  block: IMergeBlock,
2954
3028
  action: (segment: ISegment, accum?: TClientData) => boolean,
2955
3029
  accum?: TClientData,