Skip to content

Commit 8a23776

Browse files
committed
feat(json-crdt-extensions): 🎸 improve OverlayPoint ref operations
1 parent 7aea094 commit 8a23776

File tree

2 files changed

+150
-52
lines changed

2 files changed

+150
-52
lines changed

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

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Point} from '../rga/Point';
22
import {compare} from '../../../json-crdt-patch/clock';
33
import {OverlayRef, OverlayRefSliceEnd, OverlayRefSliceStart} from './refs';
44
import {printTree} from 'sonic-forest/lib/print/printTree';
5+
import type {SplitSlice} from '../slice/SplitSlice';
56
import type {HeadlessNode} from 'sonic-forest/lib/types';
67
import type {Printable} from '../../../util/print/types';
78
import type {Slice} from '../slice/types';
@@ -13,9 +14,12 @@ import type {Slice} from '../slice/types';
1314
*/
1415
export class OverlayPoint extends Point implements Printable, HeadlessNode {
1516
/**
16-
* Sorted list of all references to rich-text constructs.
17+
* Hash of text contents until the next {@link OverlayPoint}. This field is
18+
* modified by the {@link Overlay} tree.
1719
*/
18-
public readonly refs: OverlayRef[] = [];
20+
public hash: number = 0;
21+
22+
// ------------------------------------------------------------------- layers
1923

2024
/**
2125
* Sorted list of layers, contains the interval from this point to the next
@@ -24,38 +28,6 @@ export class OverlayPoint extends Point implements Printable, HeadlessNode {
2428
*/
2529
public readonly layers: Slice[] = [];
2630

27-
/**
28-
* Collapsed slices - markers (block splits), which represent a single point
29-
* in the text, even if the start and end of the slice are different.
30-
*/
31-
public readonly markers: Slice[] = [];
32-
33-
/**
34-
* Hash of text contents until the next {@link OverlayPoint}. This field is
35-
* modified by the {@link Overlay} tree.
36-
*/
37-
public hash: number = 0;
38-
39-
public removeSlice(slice: Slice): void {
40-
const refs = this.refs;
41-
const length = refs.length;
42-
for (let i = 0; i < length; i++) {
43-
const ref = refs[i];
44-
if (
45-
ref === slice ||
46-
(ref instanceof OverlayRefSliceStart && ref.slice === slice) ||
47-
(ref instanceof OverlayRefSliceEnd && ref.slice === slice)
48-
) {
49-
refs.splice(i, 1);
50-
break;
51-
}
52-
}
53-
this.removeLayer(slice);
54-
this.removeMarker(slice);
55-
}
56-
57-
// ------------------------------------------------------------------- layers
58-
5931
/**
6032
* Inserts a slice to the list of layers which contains the area from this
6133
* point until the next one. The operation is idempotent, so inserting the
@@ -110,58 +82,108 @@ export class OverlayPoint extends Point implements Printable, HeadlessNode {
11082

11183
// ------------------------------------------------------------------ markers
11284

85+
/**
86+
* Collapsed slices - markers (block splits), which represent a single point
87+
* in the text, even if the start and end of the slice are different.
88+
* @deprecated This field might happen to be not necessary.
89+
*/
90+
public readonly markers: Slice[] = [];
91+
11392
/**
11493
* Inserts a slice to the list of markers which represent a single point in
11594
* the text, even if the start and end of the slice are different. The
11695
* operation is idempotent, so inserting the same slice twice will not change
11796
* the state of the point. The markers are sorted by the slice ID.
11897
*
11998
* @param slice Slice to add to the marker list.
99+
* @deprecated This method might happen to be not necessary.
120100
*/
121101
public addMarker(slice: Slice): void {
122-
const points = this.markers;
123-
const length = points.length;
102+
/** @deprecated */
103+
const markers = this.markers;
104+
const length = markers.length;
124105
if (!length) {
125-
points.push(slice);
106+
markers.push(slice);
126107
return;
127108
}
128109
// We attempt to insert from the end of the list, as it is the most likely
129110
// scenario. And `.push()` is more efficient than `.unshift()`.
130-
const lastSlice = points[length - 1];
111+
const lastSlice = markers[length - 1];
131112
const sliceId = slice.id;
132113
const cmp = compare(lastSlice.id, sliceId);
133114
if (cmp < 0) {
134-
points.push(slice);
115+
markers.push(slice);
135116
return;
136117
} else if (!cmp) return;
137118
for (let i = length - 2; i >= 0; i--) {
138-
const currSlice = points[i];
119+
const currSlice = markers[i];
139120
const cmp = compare(currSlice.id, sliceId);
140121
if (cmp < 0) {
141-
points.splice(i + 1, 0, slice);
122+
markers.splice(i + 1, 0, slice);
142123
return;
143124
} else if (!cmp) return;
144125
}
145-
points.unshift(slice);
126+
markers.unshift(slice);
146127
}
147128

148129
/**
149130
* Removes a slice from the list of markers, which represent a single point in
150131
* the text, even if the start and end of the slice are different.
151132
*
152133
* @param slice Slice to remove from the marker list.
134+
* @deprecated This method might happen to be not necessary.
153135
*/
154136
public removeMarker(slice: Slice): void {
155-
const points = this.markers;
156-
const length = points.length;
137+
/** @deprecated */
138+
const markers = this.markers;
139+
const length = markers.length;
157140
for (let i = 0; i < length; i++) {
158-
if (points[i] === slice) {
159-
points.splice(i, 1);
141+
if (markers[i] === slice) {
142+
markers.splice(i, 1);
160143
return;
161144
}
162145
}
163146
}
164147

148+
// --------------------------------------------------------------------- refs
149+
150+
/**
151+
* Sorted list of all references to rich-text constructs.
152+
*/
153+
public readonly refs: OverlayRef[] = [];
154+
155+
public addMarkerRef(slice: SplitSlice): void {
156+
this.refs.push(slice);
157+
this.addMarker(slice);
158+
}
159+
160+
public addLayerStartRef(slice: Slice): void {
161+
this.refs.push(new OverlayRefSliceStart(slice));
162+
this.addLayer(slice);
163+
}
164+
165+
public addLayerEndRef(slice: Slice): void {
166+
this.refs.push(new OverlayRefSliceEnd(slice));
167+
}
168+
169+
public removeRef(slice: Slice): void {
170+
const refs = this.refs;
171+
const length = refs.length;
172+
for (let i = 0; i < length; i++) {
173+
const ref = refs[i];
174+
if (
175+
ref === slice ||
176+
(ref instanceof OverlayRefSliceStart && ref.slice === slice) ||
177+
(ref instanceof OverlayRefSliceEnd && ref.slice === slice)
178+
) {
179+
refs.splice(i, 1);
180+
break;
181+
}
182+
}
183+
this.removeLayer(slice);
184+
this.removeMarker(slice);
185+
}
186+
165187
// ---------------------------------------------------------------- Printable
166188

167189
public toStringName(tab: string, lite?: boolean): string {

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

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Point} from '../../rga/Point';
22
import {setup} from '../../slice/__tests__/setup';
33
import {OverlayPoint} from '../OverlayPoint';
4+
import {OverlayRefSliceEnd, OverlayRefSliceStart} from '../refs';
45

56
const setupOverlayPoint = () => {
67
const deps = setup();
@@ -196,9 +197,9 @@ describe('markers', () => {
196197

197198
test('can add tree markers by appending them', () => {
198199
const {peritext, getPoint} = setupOverlayPoint();
199-
const marker1 = peritext.slices.insSplit(peritext.rangeAt(6, 0), '<p>');
200-
const marker2 = peritext.slices.insSplit(peritext.rangeAt(6, 0), '<p>');
201-
const marker3 = peritext.slices.insSplit(peritext.rangeAt(6, 0), '<p>');
200+
const marker1 = peritext.slices.insSplit(peritext.rangeAt(6, 1), '<p>');
201+
const marker2 = peritext.slices.insSplit(peritext.rangeAt(6, 2), '<p>');
202+
const marker3 = peritext.slices.insSplit(peritext.rangeAt(6, 3), '<p>');
202203
const point = getPoint(marker2.start);
203204
point.addMarker(marker1);
204205
point.addMarker(marker2);
@@ -210,9 +211,9 @@ describe('markers', () => {
210211

211212
test('can remove markers', () => {
212213
const {peritext, getPoint} = setupOverlayPoint();
213-
const marker1 = peritext.slices.insSplit(peritext.rangeAt(6, 0), '<p>');
214-
const marker2 = peritext.slices.insSplit(peritext.rangeAt(6, 0), '<p>');
215-
const marker3 = peritext.slices.insSplit(peritext.rangeAt(6, 0), '<p>');
214+
const marker1 = peritext.slices.insSplit(peritext.rangeAt(6, 1), '<p>');
215+
const marker2 = peritext.slices.insSplit(peritext.rangeAt(6, 1), '<p>');
216+
const marker3 = peritext.slices.insSplit(peritext.rangeAt(6, 2), '<p>');
216217
const point = getPoint(marker1.start);
217218
point.addMarker(marker2);
218219
point.addMarker(marker1);
@@ -232,3 +233,78 @@ describe('markers', () => {
232233
expect(point.markers.length).toBe(0);
233234
});
234235
});
236+
237+
describe('refs', () => {
238+
test('can add marker ref', () => {
239+
const {peritext, getPoint} = setupOverlayPoint();
240+
const marker = peritext.slices.insSplit(peritext.rangeAt(10, 1), '<p>');
241+
const point = getPoint(marker.start);
242+
expect(point.markers.length).toBe(0);
243+
expect(point.refs.length).toBe(0);
244+
point.addMarkerRef(marker);
245+
expect(point.markers.length).toBe(1);
246+
expect(point.refs.length).toBe(1);
247+
expect(point.markers[0]).toBe(marker);
248+
expect(point.refs[0]).toBe(marker);
249+
});
250+
251+
test('can add layer ref (start)', () => {
252+
const {peritext, getPoint} = setupOverlayPoint();
253+
const slice = peritext.slices.insErase(peritext.rangeAt(0, 4), 123);
254+
const point = getPoint(slice.start);
255+
expect(point.layers.length).toBe(0);
256+
expect(point.refs.length).toBe(0);
257+
point.addLayerStartRef(slice);
258+
expect(point.layers.length).toBe(1);
259+
expect(point.refs.length).toBe(1);
260+
expect(point.layers[0]).toBe(slice);
261+
expect((point.refs[0] as OverlayRefSliceStart).slice).toBe(slice);
262+
});
263+
264+
test('can add layer ref (end)', () => {
265+
const {peritext, getPoint} = setupOverlayPoint();
266+
const slice = peritext.slices.insErase(peritext.rangeAt(0, 4), 123);
267+
const point = getPoint(slice.end);
268+
expect(point.layers.length).toBe(0);
269+
expect(point.refs.length).toBe(0);
270+
point.addLayerEndRef(slice);
271+
expect(point.layers.length).toBe(0);
272+
expect(point.refs.length).toBe(1);
273+
expect((point.refs[0] as OverlayRefSliceEnd).slice).toBe(slice);
274+
});
275+
276+
test('can add marker and layer start', () => {
277+
const {peritext, getPoint} = setupOverlayPoint();
278+
const marker = peritext.slices.insSplit(peritext.rangeAt(10, 1), '<p>');
279+
const slice = peritext.slices.insErase(peritext.rangeAt(10, 4), 123);
280+
const point = getPoint(slice.end);
281+
expect(point.layers.length).toBe(0);
282+
expect(point.markers.length).toBe(0);
283+
expect(point.refs.length).toBe(0);
284+
point.addMarkerRef(marker);
285+
point.addLayerStartRef(slice);
286+
expect(point.layers.length).toBe(1);
287+
expect(point.markers.length).toBe(1);
288+
expect(point.refs.length).toBe(2);
289+
});
290+
291+
test('can remove marker and layer', () => {
292+
const {peritext, getPoint} = setupOverlayPoint();
293+
const marker = peritext.slices.insSplit(peritext.rangeAt(10, 1), '<p>');
294+
const slice = peritext.slices.insErase(peritext.rangeAt(10, 4), 123);
295+
const point = getPoint(slice.end);
296+
point.addMarkerRef(marker);
297+
point.addLayerStartRef(slice);
298+
expect(point.layers.length).toBe(1);
299+
expect(point.markers.length).toBe(1);
300+
expect(point.refs.length).toBe(2);
301+
point.removeRef(slice);
302+
expect(point.layers.length).toBe(0);
303+
expect(point.markers.length).toBe(1);
304+
expect(point.refs.length).toBe(1);
305+
point.removeRef(marker);
306+
expect(point.layers.length).toBe(0);
307+
expect(point.markers.length).toBe(0);
308+
expect(point.refs.length).toBe(0);
309+
});
310+
});

0 commit comments

Comments
 (0)