Skip to content

Commit 7f7d40a

Browse files
authored
Merge pull request #610 from streamich/overlay-3
Setup the `Overlay` class
2 parents c10ccce + c9c5ae4 commit 7f7d40a

File tree

13 files changed

+613
-54
lines changed

13 files changed

+613
-54
lines changed

src/json-crdt-extensions/constants.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,3 @@ export const enum ExtensionId {
44
peritext = 2,
55
quill = 3,
66
}
7-
8-
export const enum Chars {
9-
BlockSplitSentinel = '\n',
10-
}

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

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
import {printTree} from 'sonic-forest/lib/print/printTree';
12
import {Anchor} from './rga/constants';
23
import {Point} from './rga/Point';
34
import {Range} from './rga/Range';
45
import {Editor} from './editor/Editor';
5-
import {printTree} from '../../util/print/printTree';
66
import {ArrNode, StrNode} from '../../json-crdt/nodes';
77
import {Slices} from './slice/Slices';
8-
import {type ITimestampStruct} from '../../json-crdt-patch/clock';
8+
import {Overlay} from './overlay/Overlay';
9+
import {Chars} from './constants';
10+
import {interval} from '../../json-crdt-patch/clock';
11+
import {CONST, updateNum} from '../../json-hash';
12+
import type {ITimestampStruct} from '../../json-crdt-patch/clock';
913
import type {Model} from '../../json-crdt/model';
1014
import type {Printable} from '../../util/print/types';
15+
import type {StringChunk} from './util/types';
16+
import type {SliceType} from './types';
17+
import type {MarkerSlice} from './slice/MarkerSlice';
1118

1219
/**
1320
* Context for a Peritext instance. Contains all the data and methods needed to
@@ -16,6 +23,7 @@ import type {Printable} from '../../util/print/types';
1623
export class Peritext implements Printable {
1724
public readonly slices: Slices;
1825
public readonly editor: Editor;
26+
public readonly overlay = new Overlay(this);
1927

2028
constructor(
2129
public readonly model: Model,
@@ -30,7 +38,30 @@ export class Peritext implements Printable {
3038
return this.model.api.wrap(this.str);
3139
}
3240

33-
// ------------------------------------------------------------------- Points
41+
/** @todo Find a better place for this function. */
42+
public firstVisChunk(): StringChunk | undefined {
43+
const str = this.str;
44+
let curr = str.first();
45+
if (!curr) return;
46+
while (curr.del) {
47+
curr = str.next(curr);
48+
if (!curr) return;
49+
}
50+
return curr;
51+
}
52+
53+
/** Select a single character before a point. */
54+
public findCharBefore(point: Point): Range | undefined {
55+
if (point.anchor === Anchor.After) {
56+
const chunk = point.chunk();
57+
if (chunk && !chunk.del) return this.range(this.point(point.id, Anchor.Before), point);
58+
}
59+
const id = point.prevId();
60+
if (!id) return;
61+
return this.range(this.point(id, Anchor.Before), this.point(id, Anchor.After));
62+
}
63+
64+
// ------------------------------------------------------------------- points
3465

3566
/**
3667
* Creates a point at a character ID.
@@ -81,7 +112,7 @@ export class Peritext implements Printable {
81112
return this.point(this.str.id, Anchor.Before);
82113
}
83114

84-
// ------------------------------------------------------------------- Ranges
115+
// ------------------------------------------------------------------- ranges
85116

86117
/**
87118
* Creates a range from two points. The points can be in any order.
@@ -117,7 +148,7 @@ export class Peritext implements Printable {
117148
return Range.at(this.str, start, length);
118149
}
119150

120-
// --------------------------------------------------------------- Insertions
151+
// --------------------------------------------------------------------- text
121152

122153
/**
123154
* Insert plain text at a view position in the text.
@@ -146,15 +177,37 @@ export class Peritext implements Printable {
146177
return textId;
147178
}
148179

149-
/** Select a single character before a point. */
150-
public findCharBefore(point: Point): Range | undefined {
151-
if (point.anchor === Anchor.After) {
152-
const chunk = point.chunk();
153-
if (chunk && !chunk.del) return this.range(this.point(point.id, Anchor.Before), point);
154-
}
155-
const id = point.prevId();
156-
if (!id) return;
157-
return this.range(this.point(id, Anchor.Before), this.point(id, Anchor.After));
180+
// ------------------------------------------------------------------ markers
181+
182+
public insMarker(
183+
after: ITimestampStruct,
184+
type: SliceType,
185+
data?: unknown,
186+
char: string = Chars.BlockSplitSentinel,
187+
): MarkerSlice {
188+
const api = this.model.api;
189+
const builder = api.builder;
190+
const str = this.str;
191+
/**
192+
* We skip one clock cycle to prevent Block-wise RGA from merging adjacent
193+
* characters. We want the marker chunk to always be its own distinct chunk.
194+
*/
195+
builder.nop(1);
196+
const textId = builder.insStr(str.id, after, char[0]);
197+
const point = this.point(textId, Anchor.Before);
198+
const range = this.range(point, point);
199+
return this.slices.insMarker(range, type, data);
200+
}
201+
202+
/** @todo This can probably use .del() */
203+
public delMarker(split: MarkerSlice): void {
204+
const str = this.str;
205+
const api = this.model.api;
206+
const builder = api.builder;
207+
const strChunk = split.start.chunk();
208+
if (strChunk) builder.del(str.id, [interval(strChunk.id, 0, 1)]);
209+
builder.del(this.slices.set.id, [interval(split.id, 0, 1)]);
210+
api.apply();
158211
}
159212

160213
// ---------------------------------------------------------------- Printable
@@ -169,6 +222,8 @@ export class Peritext implements Printable {
169222
(tab) => this.str.toString(tab),
170223
nl,
171224
(tab) => this.slices.toString(tab),
225+
nl,
226+
(tab) => this.overlay.toString(tab),
172227
])
173228
);
174229
}
@@ -178,6 +233,9 @@ export class Peritext implements Printable {
178233
public hash: number = 0;
179234

180235
public refresh(): number {
181-
return this.slices.refresh();
236+
let state: number = CONST.START_STATE;
237+
this.overlay.refresh();
238+
state = updateNum(state, this.overlay.hash);
239+
return (this.hash = state);
182240
}
183241
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const enum Chars {
2+
BlockSplitSentinel = '\n',
3+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {Anchor} from '../rga/constants';
33
import {SliceBehavior} from '../slice/constants';
44
import {tick, type ITimestampStruct} from '../../../json-crdt-patch/clock';
55
import {PersistedSlice} from '../slice/PersistedSlice';
6+
import {Chars} from '../constants';
67
import type {Range} from '../rga/Range';
78
import type {Peritext} from '../Peritext';
89
import type {Printable} from '../../../util/print/types';
910
import type {Point} from '../rga/Point';
1011
import type {SliceType} from '../types';
12+
import type {MarkerSlice} from '../slice/MarkerSlice';
1113

1214
export class Editor implements Printable {
1315
/**
@@ -132,4 +134,9 @@ export class Editor implements Printable {
132134
public insertEraseSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
133135
return this.txt.slices.ins(this.cursor, SliceBehavior.Erase, type, data);
134136
}
137+
138+
public insMarker(type: SliceType, data?: unknown): MarkerSlice {
139+
const after = this.collapseSelection();
140+
return this.txt.insMarker(after, type, data, Chars.BlockSplitSentinel);
141+
}
135142
}

0 commit comments

Comments
 (0)