/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import type { LocalReferencePosition } from "./localReference.js"; import type { ISegment } from "./mergeTreeNodes.js"; import { SortedSegmentSet } from "./sortedSegmentSet.js"; /** * @legacy @beta */ export type Trackable = ISegment | LocalReferencePosition; /** * @legacy @beta */ export interface ITrackingGroup { tracked: readonly Trackable[]; size: number; has(trackable: Trackable): boolean; link(trackable: Trackable): void; unlink(trackable: Trackable): boolean; } /** * @legacy @beta */ export class TrackingGroup implements ITrackingGroup { private readonly trackedSet: SortedSegmentSet; constructor() { this.trackedSet = new SortedSegmentSet(); } public get tracked(): readonly Trackable[] { return this.trackedSet.items; } public get size(): number { return this.trackedSet.size; } public has(trackable: Trackable): boolean { return this.trackedSet.has(trackable); } public link(trackable: Trackable): void { if (!this.trackedSet.has(trackable)) { this.trackedSet.addOrUpdate(trackable); trackable.trackingCollection.link(this); } } public unlink(trackable: Trackable): boolean { if (this.trackedSet.remove(trackable)) { trackable.trackingCollection.unlink(this); return true; } return false; } } /** * Tracking group backed by an unordered set. Lookup, insertion, and deletion are O(1) */ export class UnorderedTrackingGroup implements ITrackingGroup { private readonly trackedSet: Set; constructor() { this.trackedSet = new Set(); } public get tracked(): readonly Trackable[] { return [...this.trackedSet]; } public get size(): number { return this.trackedSet.size; } public has(trackable: Trackable): boolean { return this.trackedSet.has(trackable); } public link(trackable: Trackable): void { if (!this.trackedSet.has(trackable)) { this.trackedSet.add(trackable); trackable.trackingCollection.link(this); } } public unlink(trackable: Trackable): boolean { if (this.trackedSet.delete(trackable)) { trackable.trackingCollection.unlink(this); return true; } return false; } } /** * A collection of {@link ITrackingGroup}. * @legacy @beta */ export class TrackingGroupCollection { private readonly _trackingGroups: Set; public get trackingGroups(): Set { // Cast here is necessary to avoid a breaking change to // `TrackingGroupCollection`. Ideally we could just return // `Set` return this._trackingGroups as Set; } constructor(private readonly trackable: Trackable) { this._trackingGroups = new Set(); } public link(trackingGroup: ITrackingGroup): void { if (trackingGroup) { if (!this._trackingGroups.has(trackingGroup)) { this._trackingGroups.add(trackingGroup); } if (!trackingGroup.has(this.trackable)) { trackingGroup.link(this.trackable); } } } public unlink(trackingGroup: ITrackingGroup): boolean { if (this._trackingGroups.has(trackingGroup)) { if (trackingGroup.has(this.trackable)) { trackingGroup.unlink(this.trackable); } this._trackingGroups.delete(trackingGroup); return true; } return false; } public copyTo(trackable: Trackable): void { for (const sg of this._trackingGroups) { trackable.trackingCollection.link(sg); } } public get empty(): boolean { return this._trackingGroups.size === 0; } public matches(trackingCollection: TrackingGroupCollection): boolean { if ( // eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- TODO: ADO#58520 Code owners should verify if this code change is safe and make it if so or update this comment otherwise !trackingCollection || this._trackingGroups.size !== trackingCollection._trackingGroups.size ) { return false; } for (const tg of this._trackingGroups.values()) { if (!trackingCollection._trackingGroups.has(tg)) { return false; } } return true; } }