Skip to content

Commit 80902a7

Browse files
authored
Merge pull request #623 from streamich/overlay-8
Overlay 8
2 parents dcf7391 + c5962d6 commit 80902a7

11 files changed

+493
-80
lines changed

src/json-crdt-extensions/peritext/editor/EditorSlices.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import {PersistedSlice} from '../slice/PersistedSlice';
12
import type {Peritext} from '../Peritext';
23
import type {SliceType} from '../slice/types';
34
import type {MarkerSlice} from '../slice/MarkerSlice';
45
import type {Slices} from '../slice/Slices';
56
import type {ITimestampStruct} from '../../../json-crdt-patch';
6-
import type {PersistedSlice} from '../slice/PersistedSlice';
77
import type {Cursor} from './Cursor';
88

99
export class EditorSlices<T = string> {
@@ -42,4 +42,8 @@ export class EditorSlices<T = string> {
4242
return marker;
4343
});
4444
}
45+
46+
public del(sliceOrId: PersistedSlice | ITimestampStruct): void {
47+
this.slices.del(sliceOrId instanceof PersistedSlice ? sliceOrId.id : sliceOrId);
48+
}
4549
}

src/json-crdt-extensions/peritext/overlay/MarkerOverlayPoint.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import {printTree} from 'tree-dump/lib/printTree';
22
import {OverlayPoint} from './OverlayPoint';
3+
import type {HeadlessNode2} from 'sonic-forest/lib/types2';
34
import type {SliceType} from '../slice/types';
45
import type {Anchor} from '../rga/constants';
56
import type {AbstractRga} from '../../../json-crdt/nodes/rga';
67
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
78
import type {MarkerSlice} from '../slice/MarkerSlice';
89

9-
export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> {
10+
export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> implements HeadlessNode2 {
1011
/**
11-
* Hash value of the preceding text contents, up until the next marker.
12+
* Hash value of the following text contents, up until the next marker.
1213
*/
1314
public textHash: number = 0;
1415

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

24-
/**
25-
* @todo Rename or access it directly.
26-
* @deprecated
27-
*/
28-
public markerHash(): number {
29-
return this.marker ? this.marker.hash : 0;
30-
}
31-
3225
public type(): SliceType {
3326
return this.marker && this.marker.type;
3427
}
@@ -57,4 +50,10 @@ export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> {
5750
]))
5851
);
5952
}
53+
54+
// ------------------------------------------------------------ HeadlessNode2
55+
56+
public p2: MarkerOverlayPoint<T> | undefined;
57+
public l2: MarkerOverlayPoint<T> | undefined;
58+
public r2: MarkerOverlayPoint<T> | undefined;
6059
}

src/json-crdt-extensions/peritext/overlay/Overlay.ts

Lines changed: 87 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {printTree} from 'tree-dump/lib/printTree';
22
import {printBinary} from 'tree-dump/lib/printBinary';
33
import {first, insertLeft, insertRight, last, next, prev, remove} from 'sonic-forest/lib/util';
4+
import {first2, insert2, next2, remove2} from 'sonic-forest/lib/util2';
45
import {splay} from 'sonic-forest/lib/splay/util';
56
import {Anchor} from '../rga/constants';
67
import {Point} from '../rga/Point';
@@ -19,6 +20,9 @@ import type {Printable} from 'tree-dump/lib/types';
1920
import type {MutableSlice, Slice} from '../slice/types';
2021
import type {Slices} from '../slice/Slices';
2122
import type {OverlayPair, OverlayTuple} from './types';
23+
import type {Comparator} from 'sonic-forest/lib/types';
24+
25+
const spatialComparator: Comparator<OverlayPoint> = (a: OverlayPoint, b: OverlayPoint) => a.cmpSpatial(b);
2226

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

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

113-
public find(predicate: (point: OverlayPoint<T>) => boolean): OverlayPoint<T> | undefined {
114-
let point = this.first();
115-
while (point) {
116-
if (predicate(point)) return point;
117-
point = next(point);
118-
}
119-
return;
120-
}
121-
118+
/** @todo Rename to `chunks()`. */
122119
public chunkSlices0(
123120
chunk: Chunk<T> | undefined,
124121
p1: Point<T>,
@@ -179,20 +176,17 @@ export class Overlay<T = string> implements Printable, Stateful {
179176
return new UndefEndIter(this.points0(after));
180177
}
181178

182-
public markers0(): UndefIterator<MarkerOverlayPoint<T>> {
183-
let curr = this.first();
179+
public markers0(after: undefined | MarkerOverlayPoint<T>): UndefIterator<MarkerOverlayPoint<T>> {
180+
let curr = after ? next2(after) : first2(this.root2);
184181
return () => {
185-
while (curr) {
186-
const ret = curr;
187-
if (curr) curr = next(curr);
188-
if (ret instanceof MarkerOverlayPoint) return ret;
189-
}
190-
return;
182+
const ret = curr;
183+
if (curr) curr = next2(curr);
184+
return ret;
191185
};
192186
}
193187

194188
public markers(): IterableIterator<MarkerOverlayPoint<T>> {
195-
return new UndefEndIter(this.markers0());
189+
return new UndefEndIter(this.markers0(undefined));
196190
}
197191

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

242+
/**
243+
* Finds the first point that satisfies the given predicate function.
244+
*
245+
* @param predicate Predicate function to find the point, returns true if the
246+
* point is found.
247+
* @returns The first point that satisfies the predicate, or undefined if no
248+
* point is found.
249+
*/
250+
public find(predicate: (point: OverlayPoint<T>) => boolean): OverlayPoint<T> | undefined {
251+
let point = this.first();
252+
while (point) {
253+
if (predicate(point)) return point;
254+
point = next(point);
255+
}
256+
return;
257+
}
258+
259+
/**
260+
* Finds all slices that are contained within the given range. A slice is
261+
* considered contained if its start and end points are within the range,
262+
* inclusive (uses {@link Range#contains} method to check containment).
263+
*
264+
* @param range The range to search for contained slices.
265+
* @returns A set of slices that are contained within the given range.
266+
*/
248267
public findContained(range: Range<T>): Set<Slice<T>> {
249268
const result = new Set<Slice<T>>();
250269
let point = this.getOrNextLower(range.start);
@@ -265,6 +284,14 @@ export class Overlay<T = string> implements Printable, Stateful {
265284
return result;
266285
}
267286

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

284-
public isBlockSplit(id: ITimestampStruct): boolean {
285-
const point = this.txt.point(id, Anchor.Before);
286-
const overlayPoint = this.getOrNextLower(point);
287-
return (
288-
overlayPoint instanceof MarkerOverlayPoint && overlayPoint.id.time === id.time && overlayPoint.id.sid === id.sid
289-
);
311+
/**
312+
* Returns `true` if the current character is a marker sentinel.
313+
*
314+
* @param id ID of the point to check.
315+
* @returns Whether the point is a marker point.
316+
*/
317+
public isMarker(id: ITimestampStruct): boolean {
318+
const p = this.txt.point(id, Anchor.Before);
319+
const op = this.getOrNextLower(p);
320+
return op instanceof MarkerOverlayPoint && op.id.time === id.time && op.id.sid === id.sid;
290321
}
291322

292323
// ----------------------------------------------------------------- Stateful
@@ -299,7 +330,12 @@ export class Overlay<T = string> implements Printable, Stateful {
299330
hash = this.refreshSlices(hash, txt.savedSlices);
300331
hash = this.refreshSlices(hash, txt.extraSlices);
301332
hash = this.refreshSlices(hash, txt.localSlices);
302-
if (!slicesOnly) this.computeSplitTextHashes();
333+
334+
// TODO: Move test hash calculation out of the overlay.
335+
if (!slicesOnly) {
336+
// hash = updateRga(hash, txt.str);
337+
hash = this.refreshTextSlices(hash);
338+
}
303339
return (this.hash = hash);
304340
}
305341

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

341377
private insSlice(slice: Slice<T>): [start: OverlayPoint<T>, end: OverlayPoint<T>] {
342-
// TODO: Test cases where the inserted slice is collapsed to one point.
343378
const x0 = slice.start;
344379
const x1 = slice.end;
345380
const [start, isStartNew] = this.upsertPoint(x0);
@@ -359,10 +394,7 @@ export class Overlay<T = string> implements Printable, Stateful {
359394
let curr: OverlayPoint<T> | undefined = start;
360395
do curr.addLayer(slice);
361396
while ((curr = next(curr)) && curr !== end);
362-
} else {
363-
// TODO: review if this is needed:
364-
start.addMarker(slice);
365-
}
397+
} else start.addMarker(slice);
366398
return [start, end];
367399
}
368400

@@ -408,6 +440,10 @@ export class Overlay<T = string> implements Printable, Stateful {
408440
* @returns Returns the existing point if it was already in the tree.
409441
*/
410442
private insPoint(point: OverlayPoint<T>): OverlayPoint<T> | undefined {
443+
if (point instanceof MarkerOverlayPoint) {
444+
this.root2 = insert2(this.root2, point, spatialComparator);
445+
// if (this.root2 !== point) this.root2 = splay2(this.root2!, point, 10);
446+
}
411447
let pivot = this.getOrNextLower(point);
412448
if (!pivot) pivot = first(this.root);
413449
if (!pivot) {
@@ -424,23 +460,23 @@ export class Overlay<T = string> implements Printable, Stateful {
424460
}
425461

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

430467
public leadingTextHash: number = 0;
431468

432-
protected computeSplitTextHashes(): void {
469+
protected refreshTextSlices(stateTotal: number): number {
433470
const txt = this.txt;
434471
const str = txt.str;
435472
const firstChunk = str.first();
436-
if (!firstChunk) return;
473+
if (!firstChunk) return stateTotal;
437474
let chunk: Chunk<T> | undefined = firstChunk;
438475
let marker: MarkerOverlayPoint<T> | undefined = undefined;
439-
let state: number = CONST.START_STATE;
440476
const i = this.tuples0(undefined);
477+
let state: number = CONST.START_STATE;
441478
for (let pair = i(); pair; pair = i()) {
442479
const [p1, p2] = pair;
443-
// TODO: need to incorporate slice attribute hash here?
444480
const id1 = p1.id;
445481
state = (state << 5) + state + (id1.sid >>> 0) + id1.time;
446482
let overlayPointHash = CONST.START_STATE;
@@ -450,15 +486,15 @@ export class Overlay<T = string> implements Printable, Stateful {
450486
(overlayPointHash << 5) + overlayPointHash + ((((id.sid >>> 0) + id.time) << 8) + (off << 4) + len);
451487
});
452488
state = updateNum(state, overlayPointHash);
453-
if (p1) {
454-
p1.hash = overlayPointHash;
455-
}
489+
p1.hash = overlayPointHash;
490+
stateTotal = updateNum(stateTotal, overlayPointHash);
456491
if (p2 instanceof MarkerOverlayPoint) {
457492
if (marker) {
458493
marker.textHash = state;
459494
} else {
460495
this.leadingTextHash = state;
461496
}
497+
stateTotal = updateNum(stateTotal, state);
462498
state = CONST.START_STATE;
463499
marker = p2;
464500
}
@@ -468,6 +504,7 @@ export class Overlay<T = string> implements Printable, Stateful {
468504
} else {
469505
this.leadingTextHash = state;
470506
}
507+
return stateTotal;
471508
}
472509

473510
// ---------------------------------------------------------------- Printable
@@ -482,9 +519,21 @@ export class Overlay<T = string> implements Printable, Stateful {
482519
])
483520
);
484521
};
522+
const printMarkerPoint = (tab: string, point: MarkerOverlayPoint<T>): string => {
523+
return (
524+
point.toString(tab) +
525+
printBinary(tab, [
526+
!point.l2 ? null : (tab) => printMarkerPoint(tab, point.l2!),
527+
!point.r2 ? null : (tab) => printMarkerPoint(tab, point.r2!),
528+
])
529+
);
530+
};
485531
return (
486532
`${this.constructor.name} #${this.hash.toString(36)}` +
487-
printTree(tab, [!this.root ? null : (tab) => printPoint(tab, this.root!)])
533+
printTree(tab, [
534+
!this.root ? null : (tab) => printPoint(tab, this.root!),
535+
!this.root2 ? null : (tab) => printMarkerPoint(tab, this.root2!),
536+
])
488537
);
489538
}
490539
}

src/json-crdt-extensions/peritext/overlay/OverlayPoint.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {OverlayRef, OverlayRefSliceEnd, OverlayRefSliceStart} from './refs';
44
import {printTree} from 'tree-dump/lib/printTree';
55
import type {MarkerSlice} from '../slice/MarkerSlice';
66
import type {HeadlessNode} from 'sonic-forest/lib/types';
7-
import type {Printable} from 'tree-dump/lib/types';
7+
import type {PrintChild, Printable} from 'tree-dump/lib/types';
88
import type {Slice} from '../slice/types';
99

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

@@ -96,10 +95,8 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
9695
* the state of the point. The markers are sorted by the slice ID.
9796
*
9897
* @param slice Slice to add to the marker list.
99-
* @deprecated This method might happen to be not necessary.
10098
*/
10199
public addMarker(slice: Slice<T>): void {
102-
/** @deprecated */
103100
const markers = this.markers;
104101
const length = markers.length;
105102
if (!length) {
@@ -131,10 +128,8 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
131128
* the text, even if the start and end of the slice are different.
132129
*
133130
* @param slice Slice to remove from the marker list.
134-
* @deprecated This method might happen to be not necessary.
135131
*/
136132
public removeMarker(slice: Slice<T>): void {
137-
/** @deprecated */
138133
const markers = this.markers;
139134
const length = markers.length;
140135
for (let i = 0; i < length; i++) {
@@ -218,13 +213,14 @@ export class OverlayPoint<T = string> extends Point<T> implements Printable, Hea
218213
const refs = lite ? '' : `, refs = ${this.refs.length}`;
219214
const header = this.toStringName(tab, lite) + refs;
220215
if (lite) return header;
221-
return (
222-
header +
223-
printTree(
224-
tab,
225-
this.layers.map((slice) => (tab) => slice.toString(tab)),
226-
)
227-
);
216+
const children: PrintChild[] = [];
217+
const layers = this.layers;
218+
const layerLength = layers.length;
219+
for (let i = 0; i < layerLength; i++) children.push((tab) => layers[i].toString(tab));
220+
const markers = this.markers;
221+
const markerLength = markers.length;
222+
for (let i = 0; i < markerLength; i++) children.push((tab) => markers[i].toString(tab));
223+
return header + printTree(tab, children);
228224
}
229225

230226
// ------------------------------------------------------------- HeadlessNode

0 commit comments

Comments
 (0)