Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/json-crdt-extensions/peritext/editor/EditorSlices.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {PersistedSlice} from '../slice/PersistedSlice';
import type {Peritext} from '../Peritext';
import type {SliceType} from '../slice/types';
import type {MarkerSlice} from '../slice/MarkerSlice';
import type {Slices} from '../slice/Slices';
import type {ITimestampStruct} from '../../../json-crdt-patch';
import type {PersistedSlice} from '../slice/PersistedSlice';
import type {Cursor} from './Cursor';

export class EditorSlices<T = string> {
Expand Down Expand Up @@ -42,4 +42,8 @@ export class EditorSlices<T = string> {
return marker;
});
}

public del(sliceOrId: PersistedSlice | ITimestampStruct): void {
this.slices.del(sliceOrId instanceof PersistedSlice ? sliceOrId.id : sliceOrId);
}
}
19 changes: 9 additions & 10 deletions src/json-crdt-extensions/peritext/overlay/MarkerOverlayPoint.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {printTree} from 'tree-dump/lib/printTree';
import {OverlayPoint} from './OverlayPoint';
import type {HeadlessNode2} from 'sonic-forest/lib/types2';
import type {SliceType} from '../slice/types';
import type {Anchor} from '../rga/constants';
import type {AbstractRga} from '../../../json-crdt/nodes/rga';
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
import type {MarkerSlice} from '../slice/MarkerSlice';

export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> {
export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> implements HeadlessNode2 {
/**
* Hash value of the preceding text contents, up until the next marker.
* Hash value of the following text contents, up until the next marker.
*/
public textHash: number = 0;

Expand All @@ -21,14 +22,6 @@ export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> {
super(rga, id, anchor);
}

/**
* @todo Rename or access it directly.
* @deprecated
*/
public markerHash(): number {
return this.marker ? this.marker.hash : 0;
}

public type(): SliceType {
return this.marker && this.marker.type;
}
Expand Down Expand Up @@ -57,4 +50,10 @@ export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> {
]))
);
}

// ------------------------------------------------------------ HeadlessNode2

public p2: MarkerOverlayPoint<T> | undefined;
public l2: MarkerOverlayPoint<T> | undefined;
public r2: MarkerOverlayPoint<T> | undefined;
}
125 changes: 87 additions & 38 deletions src/json-crdt-extensions/peritext/overlay/Overlay.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {printTree} from 'tree-dump/lib/printTree';
import {printBinary} from 'tree-dump/lib/printBinary';
import {first, insertLeft, insertRight, last, next, prev, remove} from 'sonic-forest/lib/util';
import {first2, insert2, next2, remove2} from 'sonic-forest/lib/util2';
import {splay} from 'sonic-forest/lib/splay/util';
import {Anchor} from '../rga/constants';
import {Point} from '../rga/Point';
Expand All @@ -19,6 +20,9 @@ import type {Printable} from 'tree-dump/lib/types';
import type {MutableSlice, Slice} from '../slice/types';
import type {Slices} from '../slice/Slices';
import type {OverlayPair, OverlayTuple} from './types';
import type {Comparator} from 'sonic-forest/lib/types';

const spatialComparator: Comparator<OverlayPoint> = (a: OverlayPoint, b: OverlayPoint) => a.cmpSpatial(b);

/**
* Overlay is a tree structure that represents all the intersections of slices
Expand All @@ -29,6 +33,7 @@ import type {OverlayPair, OverlayTuple} from './types';
*/
export class Overlay<T = string> implements Printable, Stateful {
public root: OverlayPoint<T> | undefined = undefined;
public root2: MarkerOverlayPoint<T> | undefined = undefined;

/** A virtual absolute start point, used when the absolute start is missing. */
public readonly START: OverlayPoint<T>;
Expand Down Expand Up @@ -110,15 +115,7 @@ export class Overlay<T = string> implements Printable, Stateful {
return result;
}

public find(predicate: (point: OverlayPoint<T>) => boolean): OverlayPoint<T> | undefined {
let point = this.first();
while (point) {
if (predicate(point)) return point;
point = next(point);
}
return;
}

/** @todo Rename to `chunks()`. */
public chunkSlices0(
chunk: Chunk<T> | undefined,
p1: Point<T>,
Expand Down Expand Up @@ -179,20 +176,17 @@ export class Overlay<T = string> implements Printable, Stateful {
return new UndefEndIter(this.points0(after));
}

public markers0(): UndefIterator<MarkerOverlayPoint<T>> {
let curr = this.first();
public markers0(after: undefined | MarkerOverlayPoint<T>): UndefIterator<MarkerOverlayPoint<T>> {
let curr = after ? next2(after) : first2(this.root2);
return () => {
while (curr) {
const ret = curr;
if (curr) curr = next(curr);
if (ret instanceof MarkerOverlayPoint) return ret;
}
return;
const ret = curr;
if (curr) curr = next2(curr);
return ret;
};
}

public markers(): IterableIterator<MarkerOverlayPoint<T>> {
return new UndefEndIter(this.markers0());
return new UndefEndIter(this.markers0(undefined));
}

public pairs0(after: undefined | OverlayPoint<T>): UndefIterator<OverlayPair<T>> {
Expand Down Expand Up @@ -245,6 +239,31 @@ export class Overlay<T = string> implements Printable, Stateful {
return new UndefEndIter(this.tuples0(after));
}

/**
* Finds the first point that satisfies the given predicate function.
*
* @param predicate Predicate function to find the point, returns true if the
* point is found.
* @returns The first point that satisfies the predicate, or undefined if no
* point is found.
*/
public find(predicate: (point: OverlayPoint<T>) => boolean): OverlayPoint<T> | undefined {
let point = this.first();
while (point) {
if (predicate(point)) return point;
point = next(point);
}
return;
}

/**
* Finds all slices that are contained within the given range. A slice is
* considered contained if its start and end points are within the range,
* inclusive (uses {@link Range#contains} method to check containment).
*
* @param range The range to search for contained slices.
* @returns A set of slices that are contained within the given range.
*/
public findContained(range: Range<T>): Set<Slice<T>> {
const result = new Set<Slice<T>>();
let point = this.getOrNextLower(range.start);
Expand All @@ -265,6 +284,14 @@ export class Overlay<T = string> implements Printable, Stateful {
return result;
}

/**
* Finds all slices that overlap with the given range. A slice is considered
* overlapping if its start or end point is within the range, inclusive
* (uses {@link Range#containsPoint} method to check overlap).
*
* @param range The range to search for overlapping slices.
* @returns A set of slices that overlap with the given range.
*/
public findOverlapping(range: Range<T>): Set<Slice<T>> {
const result = new Set<Slice<T>>();
let point = this.getOrNextLower(range.start);
Expand All @@ -281,12 +308,16 @@ export class Overlay<T = string> implements Printable, Stateful {
return result;
}

public isBlockSplit(id: ITimestampStruct): boolean {
const point = this.txt.point(id, Anchor.Before);
const overlayPoint = this.getOrNextLower(point);
return (
overlayPoint instanceof MarkerOverlayPoint && overlayPoint.id.time === id.time && overlayPoint.id.sid === id.sid
);
/**
* Returns `true` if the current character is a marker sentinel.
*
* @param id ID of the point to check.
* @returns Whether the point is a marker point.
*/
public isMarker(id: ITimestampStruct): boolean {
const p = this.txt.point(id, Anchor.Before);
const op = this.getOrNextLower(p);
return op instanceof MarkerOverlayPoint && op.id.time === id.time && op.id.sid === id.sid;
}

// ----------------------------------------------------------------- Stateful
Expand All @@ -299,7 +330,12 @@ export class Overlay<T = string> implements Printable, Stateful {
hash = this.refreshSlices(hash, txt.savedSlices);
hash = this.refreshSlices(hash, txt.extraSlices);
hash = this.refreshSlices(hash, txt.localSlices);
if (!slicesOnly) this.computeSplitTextHashes();

// TODO: Move test hash calculation out of the overlay.
if (!slicesOnly) {
// hash = updateRga(hash, txt.str);
hash = this.refreshTextSlices(hash);
}
return (this.hash = hash);
}

Expand Down Expand Up @@ -339,7 +375,6 @@ export class Overlay<T = string> implements Printable, Stateful {
}

private insSlice(slice: Slice<T>): [start: OverlayPoint<T>, end: OverlayPoint<T>] {
// TODO: Test cases where the inserted slice is collapsed to one point.
const x0 = slice.start;
const x1 = slice.end;
const [start, isStartNew] = this.upsertPoint(x0);
Expand All @@ -359,10 +394,7 @@ export class Overlay<T = string> implements Printable, Stateful {
let curr: OverlayPoint<T> | undefined = start;
do curr.addLayer(slice);
while ((curr = next(curr)) && curr !== end);
} else {
// TODO: review if this is needed:
start.addMarker(slice);
}
} else start.addMarker(slice);
return [start, end];
}

Expand Down Expand Up @@ -408,6 +440,10 @@ export class Overlay<T = string> implements Printable, Stateful {
* @returns Returns the existing point if it was already in the tree.
*/
private insPoint(point: OverlayPoint<T>): OverlayPoint<T> | undefined {
if (point instanceof MarkerOverlayPoint) {
this.root2 = insert2(this.root2, point, spatialComparator);
// if (this.root2 !== point) this.root2 = splay2(this.root2!, point, 10);
}
let pivot = this.getOrNextLower(point);
if (!pivot) pivot = first(this.root);
if (!pivot) {
Expand All @@ -424,23 +460,23 @@ export class Overlay<T = string> implements Printable, Stateful {
}

private delPoint(point: OverlayPoint<T>): void {
if (point instanceof MarkerOverlayPoint) this.root2 = remove2(this.root2, point);
this.root = remove(this.root, point);
}

public leadingTextHash: number = 0;

protected computeSplitTextHashes(): void {
protected refreshTextSlices(stateTotal: number): number {
const txt = this.txt;
const str = txt.str;
const firstChunk = str.first();
if (!firstChunk) return;
if (!firstChunk) return stateTotal;
let chunk: Chunk<T> | undefined = firstChunk;
let marker: MarkerOverlayPoint<T> | undefined = undefined;
let state: number = CONST.START_STATE;
const i = this.tuples0(undefined);
let state: number = CONST.START_STATE;
for (let pair = i(); pair; pair = i()) {
const [p1, p2] = pair;
// TODO: need to incorporate slice attribute hash here?
const id1 = p1.id;
state = (state << 5) + state + (id1.sid >>> 0) + id1.time;
let overlayPointHash = CONST.START_STATE;
Expand All @@ -450,15 +486,15 @@ export class Overlay<T = string> implements Printable, Stateful {
(overlayPointHash << 5) + overlayPointHash + ((((id.sid >>> 0) + id.time) << 8) + (off << 4) + len);
});
state = updateNum(state, overlayPointHash);
if (p1) {
p1.hash = overlayPointHash;
}
p1.hash = overlayPointHash;
stateTotal = updateNum(stateTotal, overlayPointHash);
if (p2 instanceof MarkerOverlayPoint) {
if (marker) {
marker.textHash = state;
} else {
this.leadingTextHash = state;
}
stateTotal = updateNum(stateTotal, state);
state = CONST.START_STATE;
marker = p2;
}
Expand All @@ -468,6 +504,7 @@ export class Overlay<T = string> implements Printable, Stateful {
} else {
this.leadingTextHash = state;
}
return stateTotal;
}

// ---------------------------------------------------------------- Printable
Expand All @@ -482,9 +519,21 @@ export class Overlay<T = string> implements Printable, Stateful {
])
);
};
const printMarkerPoint = (tab: string, point: MarkerOverlayPoint<T>): string => {
return (
point.toString(tab) +
printBinary(tab, [
!point.l2 ? null : (tab) => printMarkerPoint(tab, point.l2!),
!point.r2 ? null : (tab) => printMarkerPoint(tab, point.r2!),
])
);
};
return (
`${this.constructor.name} #${this.hash.toString(36)}` +
printTree(tab, [!this.root ? null : (tab) => printPoint(tab, this.root!)])
printTree(tab, [
!this.root ? null : (tab) => printPoint(tab, this.root!),
!this.root2 ? null : (tab) => printMarkerPoint(tab, this.root2!),
])
);
}
}
22 changes: 9 additions & 13 deletions src/json-crdt-extensions/peritext/overlay/OverlayPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {OverlayRef, OverlayRefSliceEnd, OverlayRefSliceStart} from './refs';
import {printTree} from 'tree-dump/lib/printTree';
import type {MarkerSlice} from '../slice/MarkerSlice';
import type {HeadlessNode} from 'sonic-forest/lib/types';
import type {Printable} from 'tree-dump/lib/types';
import type {PrintChild, Printable} from 'tree-dump/lib/types';
import type {Slice} from '../slice/types';

/**
Expand Down Expand Up @@ -85,7 +85,6 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
/**
* Collapsed slices - markers (block splits), which represent a single point
* in the text, even if the start and end of the slice are different.
* @deprecated This field might happen to be not necessary.
*/
public readonly markers: Slice<T>[] = [];

Expand All @@ -96,10 +95,8 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
* the state of the point. The markers are sorted by the slice ID.
*
* @param slice Slice to add to the marker list.
* @deprecated This method might happen to be not necessary.
*/
public addMarker(slice: Slice<T>): void {
/** @deprecated */
const markers = this.markers;
const length = markers.length;
if (!length) {
Expand Down Expand Up @@ -131,10 +128,8 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
* the text, even if the start and end of the slice are different.
*
* @param slice Slice to remove from the marker list.
* @deprecated This method might happen to be not necessary.
*/
public removeMarker(slice: Slice<T>): void {
/** @deprecated */
const markers = this.markers;
const length = markers.length;
for (let i = 0; i < length; i++) {
Expand Down Expand Up @@ -218,13 +213,14 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
const refs = lite ? '' : `, refs = ${this.refs.length}`;
const header = this.toStringName(tab, lite) + refs;
if (lite) return header;
return (
header +
printTree(
tab,
this.layers.map((slice) => (tab) => slice.toString(tab)),
)
);
const children: PrintChild[] = [];
const layers = this.layers;
const layerLength = layers.length;
for (let i = 0; i < layerLength; i++) children.push((tab) => layers[i].toString(tab));
const markers = this.markers;
const markerLength = markers.length;
for (let i = 0; i < markerLength; i++) children.push((tab) => markers[i].toString(tab));
return header + printTree(tab, children);
}

// ------------------------------------------------------------- HeadlessNode
Expand Down
Loading