Skip to content

Commit fc27be3

Browse files
authored
Merge pull request #620 from streamich/overlay-6
Improve iteration methods through `Overlay`
2 parents d846a5c + bd738ad commit fc27be3

16 files changed

+739
-155
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ const setup = () => {
1919
test('can insert markers', () => {
2020
const {peritext} = setup();
2121
const {editor} = peritext;
22-
expect(size(peritext.overlay.root)).toBe(0);
22+
expect([...peritext.overlay].length).toBe(0);
2323
editor.cursor.setAt(0);
24+
peritext.refresh();
25+
expect([...peritext.overlay].length).toBe(1);
2426
editor.insMarker(['p'], '<p>');
2527
peritext.refresh();
26-
expect(size(peritext.overlay.root)).toBe(1);
28+
expect(size(peritext.overlay.root)).toBe(2);
2729
editor.cursor.setAt(9);
2830
editor.insMarker(['p'], '<p>');
2931
peritext.refresh();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {s} from '../../../json-crdt-patch';
2+
import {ModelWithExt, ext} from '../../ModelWithExt';
3+
4+
/**
5+
* Creates a Peritext instance with text "0123456789", with single-char and
6+
* block-wise chunks, as well as with plenty of tombstones.
7+
*/
8+
export const setupNumbersWithTombstones = () => {
9+
const schema = s.obj({
10+
text: ext.peritext.new('1234'),
11+
});
12+
const model = ModelWithExt.create(schema);
13+
const str = model.s.text.toExt().text();
14+
str.ins(1, '234');
15+
str.ins(2, '345');
16+
str.ins(3, '456');
17+
str.ins(4, '567');
18+
str.ins(5, '678');
19+
str.ins(6, '789');
20+
str.del(7, 1);
21+
str.del(8, 1);
22+
str.ins(0, '0');
23+
str.del(1, 4);
24+
str.del(2, 1);
25+
str.ins(1, '1');
26+
str.del(0, 1);
27+
str.ins(0, '0');
28+
str.ins(2, '234');
29+
str.del(4, 7);
30+
str.del(4, 2);
31+
str.del(7, 3);
32+
str.ins(6, '6789');
33+
str.del(7, 2);
34+
str.ins(7, '78');
35+
str.del(10, 2);
36+
str.del(2, 3);
37+
str.ins(2, '234');
38+
if (str.view() !== '0123456789') throw new Error('Invalid text');
39+
const api = model.api;
40+
const peritextApi = model.s.text.toExt();
41+
const peritext = peritextApi.txt;
42+
const editor = peritextApi.editor;
43+
return {
44+
schema,
45+
model,
46+
api,
47+
peritextApi,
48+
peritext,
49+
editor,
50+
};
51+
};

src/json-crdt-extensions/peritext/constants.ts

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ export const enum Chars {
66
BlockSplitSentinel = '\n',
77
}
88

9+
export const enum Position {
10+
/**
11+
* Specifies the absolute start of the text, i.e. the position before the
12+
* first character. In model space it is defined as string ID and "after"
13+
* anchor.
14+
*/
15+
AbsStart = -1,
16+
17+
/**
18+
* Specifies the absolute end of the text, i.e. the position after the last
19+
* character. In model space it is defined as string ID and "before" anchor.
20+
*/
21+
AbsEnd = 9007199254740991, // Number.MAX_SAFE_INTEGER
22+
}
23+
924
export const MNEMONIC = ExtensionName[ExtensionId.peritext];
1025

1126
export const SCHEMA = (text: string) =>

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,25 @@ export class Editor<T = string> {
7777
return true;
7878
}
7979

80+
/** @deprecated use `.saved.insStack` */
8081
public insStackSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice<T> {
8182
const range = this.cursor.range();
8283
return this.txt.savedSlices.ins(range, SliceBehavior.Stack, type, data);
8384
}
8485

86+
/** @deprecated use `.saved.insOverwrite` */
8587
public insOverwriteSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice<T> {
8688
const range = this.cursor.range();
8789
return this.txt.savedSlices.ins(range, SliceBehavior.Overwrite, type, data);
8890
}
8991

92+
/** @deprecated use `.saved.insErase` */
9093
public insEraseSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice<T> {
9194
const range = this.cursor.range();
9295
return this.txt.savedSlices.ins(range, SliceBehavior.Erase, type, data);
9396
}
9497

95-
/** @deprecated */
98+
/** @deprecated use `.saved.insMarker` */
9699
public insMarker(type: SliceType, data?: unknown): MarkerSlice<T> {
97100
return this.saved.insMarker(type, data, Chars.BlockSplitSentinel)[0];
98101
}

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

+27-6
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,44 @@ import type {Peritext} from '../Peritext';
22
import type {SliceType} from '../slice/types';
33
import type {MarkerSlice} from '../slice/MarkerSlice';
44
import type {Slices} from '../slice/Slices';
5+
import type {ITimestampStruct} from '../../../json-crdt-patch';
6+
import type {PersistedSlice} from '../slice/PersistedSlice';
7+
import type {Cursor} from './Cursor';
58

69
export class EditorSlices<T = string> {
710
constructor(
811
protected readonly txt: Peritext<T>,
912
protected readonly slices: Slices<T>,
1013
) {}
1114

15+
protected insAtCursors<S extends PersistedSlice<T>>(callback: (cursor: Cursor<T>) => S): S[] {
16+
const slices: S[] = [];
17+
this.txt.editor.cursors((cursor) => {
18+
const slice = callback(cursor);
19+
slices.push(slice);
20+
});
21+
return slices;
22+
}
23+
24+
public insStack(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice<T>[] {
25+
return this.insAtCursors((cursor) => this.slices.insStack(cursor.range(), type, data));
26+
}
27+
28+
public insOverwrite(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice<T>[] {
29+
return this.insAtCursors((cursor) => this.slices.insOverwrite(cursor.range(), type, data));
30+
}
31+
32+
public insErase(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice<T>[] {
33+
return this.insAtCursors((cursor) => this.slices.insErase(cursor.range(), type, data));
34+
}
35+
1236
public insMarker(type: SliceType, data?: unknown, separator?: string): MarkerSlice<T>[] {
13-
const {txt, slices} = this;
14-
const markers: MarkerSlice<T>[] = [];
15-
txt.editor.cursors((cursor) => {
37+
return this.insAtCursors((cursor) => {
1638
cursor.collapse();
1739
const after = cursor.start.clone();
1840
after.refAfter();
19-
const marker = slices.insMarkerAfter(after.id, type, data, separator);
20-
markers.push(marker);
41+
const marker = this.slices.insMarkerAfter(after.id, type, data, separator);
42+
return marker;
2143
});
22-
return markers;
2344
}
2445
}

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> {
4646
}
4747

4848
public toString(tab: string = '', lite?: boolean): string {
49-
return super.toString(tab, lite) + (lite ? '' : printTree(tab, [(tab) => this.marker.toString(tab)]));
49+
return (
50+
this.toStringName(tab, lite) +
51+
(lite
52+
? ''
53+
: printTree(tab, [
54+
(tab) => this.marker.toString(tab),
55+
...this.layers.map((slice) => (tab: string) => slice.toString(tab)),
56+
...this.markers.map((slice) => (tab: string) => slice.toString(tab)),
57+
]))
58+
);
5059
}
5160
}

0 commit comments

Comments
 (0)