Skip to content

Commit 557d0b2

Browse files
committed
refactor(json-crdt-extensions): 💡 unify all iteration methods
1 parent 6b07e3a commit 557d0b2

File tree

7 files changed

+179
-193
lines changed

7 files changed

+179
-193
lines changed

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

Lines changed: 43 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import {compare, ITimestampStruct} from '../../../json-crdt-patch/clock';
1111
import {CONST, updateNum} from '../../../json-hash';
1212
import {MarkerSlice} from '../slice/MarkerSlice';
1313
import {Range} from '../rga/Range';
14-
import {UndefEndIter} from '../../../util/iterator';
14+
import {UndefEndIter, UndefIterator} from '../../../util/iterator';
1515
import type {Chunk} from '../../../json-crdt/nodes/rga';
1616
import type {Peritext} from '../Peritext';
1717
import type {Stateful} from '../types';
1818
import type {Printable} from 'tree-dump/lib/types';
1919
import type {MutableSlice, Slice} from '../slice/types';
2020
import type {Slices} from '../slice/Slices';
21+
import type {OverlayPair, OverlayTuple} from './types';
2122

2223
/**
2324
* Overlay is a tree structure that represents all the intersections of slices
@@ -29,7 +30,17 @@ import type {Slices} from '../slice/Slices';
2930
export class Overlay<T = string> implements Printable, Stateful {
3031
public root: OverlayPoint<T> | undefined = undefined;
3132

32-
constructor(protected readonly txt: Peritext<T>) {}
33+
/** A virtual absolute start point, used when the absolute start is missing. */
34+
private readonly START: OverlayPoint<T>;
35+
36+
/** A virtual absolute end point, used when the absolute end is missing. */
37+
private readonly END: OverlayPoint<T>;
38+
39+
constructor(protected readonly txt: Peritext<T>) {
40+
const id = txt.str.id;
41+
this.START = this.point(id, Anchor.After)
42+
this.END = this.point(id, Anchor.Before);
43+
}
3344

3445
private point(id: ITimestampStruct, anchor: Anchor): OverlayPoint<T> {
3546
return new OverlayPoint(this.txt.str, id, anchor);
@@ -47,34 +58,6 @@ export class Overlay<T = string> implements Printable, Stateful {
4758
return this.root ? last(this.root) : undefined;
4859
}
4960

50-
public iterator(): () => OverlayPoint<T> | undefined {
51-
return this.points(undefined);
52-
}
53-
54-
public entries(): IterableIterator<OverlayPoint<T>> {
55-
return new UndefEndIter(this.iterator());
56-
}
57-
58-
// [Symbol.iterator]() {
59-
// return this.entries();
60-
// }
61-
62-
public markerIterator(): () => MarkerOverlayPoint | undefined {
63-
let curr = this.first();
64-
return () => {
65-
while (curr) {
66-
const ret = curr;
67-
if (curr) curr = next(curr);
68-
if (ret instanceof MarkerOverlayPoint) return ret;
69-
}
70-
return;
71-
};
72-
}
73-
74-
public markers(): IterableIterator<OverlayPoint<T>> {
75-
return new UndefEndIter(this.iterator());
76-
}
77-
7861
/**
7962
* Retrieve overlay point or the previous one, measured in spacial dimension.
8063
*/
@@ -183,7 +166,7 @@ export class Overlay<T = string> implements Printable, Stateful {
183166
}) as Chunk<T>;
184167
}
185168

186-
public points(after: undefined | OverlayPoint<T>): () => OverlayPoint<T> | undefined {
169+
public points0(after: undefined | OverlayPoint<T>): UndefIterator<OverlayPoint<T>> {
187170
let curr = after ? next(after) : this.first();
188171
return () => {
189172
const ret = curr;
@@ -192,82 +175,55 @@ export class Overlay<T = string> implements Printable, Stateful {
192175
};
193176
}
194177

195-
/**
196-
* @todo Unify this with `.entries()`.
197-
* @deprecated
198-
*/
199-
public points0(
200-
start: undefined | OverlayPoint<T>,
201-
end: undefined | ((next: OverlayPoint<T>) => boolean),
202-
callback: (point: OverlayPoint<T>) => void,
203-
): void {
204-
const i = this.points(start);
205-
let point = i();
206-
while (point) {
207-
if (end && end(point)) return;
208-
callback(point);
209-
point = i();
210-
}
178+
public points(after?: undefined | OverlayPoint<T>): IterableIterator<OverlayPoint<T>> {
179+
return new UndefEndIter(this.points0(after));
211180
}
212181

213-
/** @deprecated */
214-
public points1(
215-
start: undefined | OverlayPoint<T>,
216-
end: undefined | ((next: OverlayPoint<T>) => boolean),
217-
callback: (p1: OverlayPoint<T>, p2: OverlayPoint<T>) => void,
218-
): void {
219-
let p1: OverlayPoint<T> | undefined;
220-
let p2: OverlayPoint<T> | undefined;
221-
this.points0(start, end, (point) => {
222-
if (p1) {
223-
p2 = point;
224-
callback(p1, p2);
225-
p1 = p2;
226-
} else {
227-
p1 = point;
182+
public markers0(): UndefIterator<MarkerOverlayPoint<T>> {
183+
let curr = this.first();
184+
return () => {
185+
while (curr) {
186+
const ret = curr;
187+
if (curr) curr = next(curr);
188+
if (ret instanceof MarkerOverlayPoint) return ret;
228189
}
229-
});
190+
return;
191+
};
192+
}
193+
194+
public markers(): IterableIterator<MarkerOverlayPoint<T>> {
195+
return new UndefEndIter(this.markers0());
230196
}
231197

232-
public pairs0(after: undefined | OverlayPoint<T>): () => undefined | [p1: OverlayPoint<T> | undefined, p2: OverlayPoint<T> | undefined] {
198+
public pairs0(after: undefined | OverlayPoint<T>): UndefIterator<OverlayPair<T>> {
233199
let p1: OverlayPoint<T> | undefined;
234200
let p2: OverlayPoint<T> | undefined;
235-
const iterator = this.points(after);
201+
const iterator = this.points0(after);
236202
return () => {
237203
p1 = p2;
238204
p2 = iterator();
239205
return (p1 || p2) ? [p1, p2] : undefined;
240206
};
241207
}
242208

243-
public pairs(after?: undefined | OverlayPoint<T>): IterableIterator<[p1: OverlayPoint<T> | undefined, p2: OverlayPoint<T> | undefined]> {
209+
public pairs(after?: undefined | OverlayPoint<T>): IterableIterator<OverlayPair<T>> {
244210
return new UndefEndIter(this.pairs0(after));
245211
}
246212

247-
public tuples0(after: undefined | OverlayPoint<T>): () => undefined | [p1: OverlayPoint<T>, p2: OverlayPoint<T>] {
213+
public tuples0(after: undefined | OverlayPoint<T>): UndefIterator<OverlayTuple<T>> {
248214
const iterator = this.pairs0(after);
249215
return () => {
250216
const pair = iterator();
251217
if (!pair) return;
252-
if (pair[0]) pair[0] = this.point(this.txt.str.id, Anchor.After);
253-
if (pair[1]) pair[1] = this.point(this.txt.str.id, Anchor.Before);
218+
if (pair[0] === undefined) pair[0] = this.START;
219+
if (pair[1] === undefined) pair[1] = this.END;
220+
return pair as OverlayTuple<T>;
254221
};
255222
}
256223

257-
// public tuples2(after: undefined | OverlayPoint<T>): () => undefined | [p1: OverlayPoint<T>, p2: OverlayPoint<T>] {
258-
// let p1: OverlayPoint<T> | undefined;
259-
// let p2: OverlayPoint<T> | undefined;
260-
// const iterator = this.points(after);
261-
// return () => {
262-
// const isFirst = !p1;
263-
// if (isFirst) {
264-
// p1 = iterator();
265-
// }
266-
// // p1 = p2;
267-
// // p2 = iterator();
268-
// // return p2 ? [p1, p2] : undefined;
269-
// };
270-
// }
224+
public tuples(after?: undefined | OverlayPoint<T>): IterableIterator<OverlayTuple<T>> {
225+
return new UndefEndIter(this.tuples0(after));
226+
}
271227

272228
public findContained(range: Range<T>): Set<Slice<T>> {
273229
const result = new Set<Slice<T>>();
@@ -461,7 +417,9 @@ export class Overlay<T = string> implements Printable, Stateful {
461417
let chunk: Chunk<T> | undefined = firstChunk;
462418
let marker: MarkerOverlayPoint<T> | undefined = undefined;
463419
let state: number = CONST.START_STATE;
464-
this.points1(undefined, undefined, (p1, p2) => {
420+
const i = this.tuples0(undefined);
421+
for (let pair = i(); pair; pair = i()) {
422+
const [p1, p2] = pair;
465423
// TODO: need to incorporate slice attribute hash here?
466424
const id1 = p1.id;
467425
state = (state << 5) + state + (id1.sid >>> 0) + id1.time;
@@ -484,7 +442,7 @@ export class Overlay<T = string> implements Printable, Stateful {
484442
state = CONST.START_STATE;
485443
marker = p2;
486444
}
487-
});
445+
}
488446
if ((marker as any) instanceof MarkerOverlayPoint) {
489447
(marker as any as MarkerOverlayPoint<T>).textHash = state;
490448
} else {

src/json-crdt-extensions/peritext/overlay/__tests__/Overlay.pointsx.spec.ts renamed to src/json-crdt-extensions/peritext/overlay/__tests__/Overlay.pairs.spec.ts

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
import {last, next} from 'sonic-forest/lib/util';
1+
import {next} from 'sonic-forest/lib/util';
22
import {Model} from '../../../../json-crdt/model';
33
import {Peritext} from '../../Peritext';
4-
import {Point} from '../../rga/Point';
5-
import {Anchor} from '../../rga/constants';
6-
import {SliceBehavior} from '../../slice/constants';
7-
import type {Overlay} from '../Overlay';
8-
import type {OverlayPoint} from '../OverlayPoint';
94

105
const setup = () => {
116
const model = Model.withLogicalClock();
@@ -34,84 +29,6 @@ const setupWithOverlay = () => {
3429
return res;
3530
};
3631

37-
describe('.points0()', () => {
38-
const getPoints = (overlay: Overlay, start?: OverlayPoint, end?: (next: OverlayPoint) => boolean) => {
39-
const points: OverlayPoint[] = [];
40-
overlay.points0(start, end, (point) => {
41-
points.push(point);
42-
});
43-
return points;
44-
};
45-
46-
describe('with overlay', () => {
47-
test('iterates through all points', () => {
48-
const {peritext} = setupWithOverlay();
49-
const overlay = peritext.overlay;
50-
const points = getPoints(overlay);
51-
expect(overlay.first()).not.toBe(undefined);
52-
expect(points.length).toBe(3);
53-
});
54-
55-
test('iterates through all points, when points anchored to the same anchor', () => {
56-
const {peritext, overlay} = setupWithOverlay();
57-
peritext.refresh();
58-
expect(getPoints(overlay).length).toBe(3);
59-
peritext.editor.cursor.setAt(2, 1);
60-
peritext.editor.saved.insStack('<b>');
61-
peritext.refresh();
62-
expect(getPoints(overlay).length).toBe(4);
63-
expect(overlay.first()).not.toBe(undefined);
64-
});
65-
66-
test('should not return virtual start point, if real start point exists', () => {
67-
const {peritext, overlay} = setup();
68-
peritext.editor.cursor.setAt(0);
69-
peritext.editor.saved.insMarker(['p'], '¶');
70-
peritext.refresh();
71-
const points = getPoints(overlay);
72-
expect(points.length).toBe(2);
73-
expect(overlay.first()).toBe(points[0]);
74-
});
75-
76-
test('should not return virtual end point, if real end point exists', () => {
77-
const {peritext, overlay} = setup();
78-
peritext.editor.cursor.setAt(0, peritext.strApi().view().length);
79-
peritext.editor.saved.insStack('bold');
80-
peritext.refresh();
81-
const points = getPoints(overlay);
82-
expect(points.length).toBe(2);
83-
expect(overlay.first()).toBe(points[0]);
84-
expect(last(overlay.root)).toBe(points[1]);
85-
});
86-
87-
test('can skip points from beginning', () => {
88-
const {overlay} = setupWithOverlay();
89-
overlay.refresh();
90-
const points1 = getPoints(overlay);
91-
expect(points1.length).toBe(3);
92-
const first = overlay.first()!;
93-
const points2 = getPoints(overlay, first);
94-
expect(points2.length).toBe(2);
95-
const second = next(first)!;
96-
const points3 = getPoints(overlay, second);
97-
expect(points3.length).toBe(1);
98-
const third = next(second);
99-
const points4 = getPoints(overlay, third);
100-
expect(points4.length).toBe(0);
101-
});
102-
103-
test('can skip the last real point', () => {
104-
const {overlay} = setupWithOverlay();
105-
overlay.refresh();
106-
expect(getPoints(overlay).length).toBe(3);
107-
const lastPoint = last(overlay.root!);
108-
const points1 = getPoints(overlay, undefined, (point) => point === lastPoint);
109-
expect(points1.length).toBe(2);
110-
});
111-
});
112-
});
113-
114-
11532
describe('.pairs()', () => {
11633
test('all adjacent pairs', () => {
11734
const {peritext} = setupWithOverlay();

0 commit comments

Comments
 (0)