Skip to content

Commit fc82cb6

Browse files
authored
Merge pull request #599 from streamich/peritext-overlay
Peritext `OverlayPoint`
2 parents a4caf8a + faf466f commit fc82cb6

File tree

5 files changed

+580
-0
lines changed

5 files changed

+580
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class Peritext implements Printable {
4949
*
5050
* @param pos Position of the character in the text.
5151
* @param anchor Whether the point should attach before or after a character.
52+
* Defaults to "before".
5253
* @returns The point.
5354
*/
5455
public pointAt(pos: number, anchor: Anchor = Anchor.Before): Point {
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import {Point} from '../rga/Point';
2+
import {compare} from '../../../json-crdt-patch/clock';
3+
import {OverlayRef, OverlayRefSliceEnd, OverlayRefSliceStart} from './refs';
4+
import {printTree} from 'sonic-forest/lib/print/printTree';
5+
import type {SplitSlice} from '../slice/SplitSlice';
6+
import type {HeadlessNode} from 'sonic-forest/lib/types';
7+
import type {Printable} from '../../../util/print/types';
8+
import type {Slice} from '../slice/types';
9+
10+
/**
11+
* A {@link Point} which is indexed in the {@link Overlay} tree. Represents
12+
* sparse locations in the string of the places where annotation slices start,
13+
* end, or are broken down by other intersecting slices.
14+
*/
15+
export class OverlayPoint extends Point implements Printable, HeadlessNode {
16+
/**
17+
* Hash of text contents until the next {@link OverlayPoint}. This field is
18+
* modified by the {@link Overlay} tree.
19+
*/
20+
public hash: number = 0;
21+
22+
// ------------------------------------------------------------------- layers
23+
24+
/**
25+
* Sorted list of layers, contains the interval from this point to the next
26+
* one. A *layer* is a part of a slice from the current point to the next one.
27+
* This interval can contain many layers, as the slices can be overlap.
28+
*/
29+
public readonly layers: Slice[] = [];
30+
31+
/**
32+
* Inserts a slice to the list of layers which contains the area from this
33+
* point until the next one. The operation is idempotent, so inserting the
34+
* same slice twice will not change the state of the point. The layers are
35+
* sorted by the slice ID.
36+
*
37+
* @param slice Slice to add to the layer list.
38+
*/
39+
public addLayer(slice: Slice): void {
40+
const layers = this.layers;
41+
const length = layers.length;
42+
if (!length) {
43+
layers.push(slice);
44+
return;
45+
}
46+
// We attempt to insert from the end of the list, as it is the most likely
47+
// scenario. And `.push()` is more efficient than `.unshift()`.
48+
const lastSlice = layers[length - 1];
49+
const sliceId = slice.id;
50+
const cmp = compare(lastSlice.id, sliceId);
51+
if (cmp < 0) {
52+
layers.push(slice);
53+
return;
54+
} else if (!cmp) return;
55+
for (let i = length - 2; i >= 0; i--) {
56+
const currSlice = layers[i];
57+
const cmp = compare(currSlice.id, sliceId);
58+
if (cmp < 0) {
59+
layers.splice(i + 1, 0, slice);
60+
return;
61+
} else if (!cmp) return;
62+
}
63+
layers.unshift(slice);
64+
}
65+
66+
/**
67+
* Removes a slice from the list of layers, which start from this overlay
68+
* point.
69+
*
70+
* @param slice Slice to remove from the layer list.
71+
*/
72+
public removeLayer(slice: Slice): void {
73+
const layers = this.layers;
74+
const length = layers.length;
75+
for (let i = 0; i < length; i++) {
76+
if (layers[i] === slice) {
77+
layers.splice(i, 1);
78+
return;
79+
}
80+
}
81+
}
82+
83+
// ------------------------------------------------------------------ markers
84+
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+
92+
/**
93+
* Inserts a slice to the list of markers which represent a single point in
94+
* the text, even if the start and end of the slice are different. The
95+
* operation is idempotent, so inserting the same slice twice will not change
96+
* the state of the point. The markers are sorted by the slice ID.
97+
*
98+
* @param slice Slice to add to the marker list.
99+
* @deprecated This method might happen to be not necessary.
100+
*/
101+
public addMarker(slice: Slice): void {
102+
/** @deprecated */
103+
const markers = this.markers;
104+
const length = markers.length;
105+
if (!length) {
106+
markers.push(slice);
107+
return;
108+
}
109+
// We attempt to insert from the end of the list, as it is the most likely
110+
// scenario. And `.push()` is more efficient than `.unshift()`.
111+
const lastSlice = markers[length - 1];
112+
const sliceId = slice.id;
113+
const cmp = compare(lastSlice.id, sliceId);
114+
if (cmp < 0) {
115+
markers.push(slice);
116+
return;
117+
} else if (!cmp) return;
118+
for (let i = length - 2; i >= 0; i--) {
119+
const currSlice = markers[i];
120+
const cmp = compare(currSlice.id, sliceId);
121+
if (cmp < 0) {
122+
markers.splice(i + 1, 0, slice);
123+
return;
124+
} else if (!cmp) return;
125+
}
126+
markers.unshift(slice);
127+
}
128+
129+
/**
130+
* Removes a slice from the list of markers, which represent a single point in
131+
* the text, even if the start and end of the slice are different.
132+
*
133+
* @param slice Slice to remove from the marker list.
134+
* @deprecated This method might happen to be not necessary.
135+
*/
136+
public removeMarker(slice: Slice): void {
137+
/** @deprecated */
138+
const markers = this.markers;
139+
const length = markers.length;
140+
for (let i = 0; i < length; i++) {
141+
if (markers[i] === slice) {
142+
markers.splice(i, 1);
143+
return;
144+
}
145+
}
146+
}
147+
148+
// --------------------------------------------------------------------- refs
149+
150+
/**
151+
* Sorted list of all references to rich-text constructs.
152+
*/
153+
public readonly refs: OverlayRef[] = [];
154+
155+
/**
156+
* Insert a reference to a marker.
157+
*
158+
* @param slice A marker (split slice).
159+
*/
160+
public addMarkerRef(slice: SplitSlice): void {
161+
this.refs.push(slice);
162+
this.addMarker(slice);
163+
}
164+
165+
/**
166+
* Insert a layer that starts at this point.
167+
*
168+
* @param slice A slice that starts at this point.
169+
*/
170+
public addLayerStartRef(slice: Slice): void {
171+
this.refs.push(new OverlayRefSliceStart(slice));
172+
this.addLayer(slice);
173+
}
174+
175+
/**
176+
* Insert a layer that ends at this point.
177+
*
178+
* @param slice A slice that ends at this point.
179+
*/
180+
public addLayerEndRef(slice: Slice): void {
181+
this.refs.push(new OverlayRefSliceEnd(slice));
182+
}
183+
184+
/**
185+
* Removes a reference to a marker or a slice, and remove the corresponding
186+
* layer or marker.
187+
*
188+
* @param slice A slice to remove the reference to.
189+
*/
190+
public removeRef(slice: Slice): void {
191+
const refs = this.refs;
192+
const length = refs.length;
193+
for (let i = 0; i < length; i++) {
194+
const ref = refs[i];
195+
if (ref === slice) {
196+
refs.splice(i, 1);
197+
this.removeMarker(slice);
198+
return;
199+
}
200+
if (
201+
(ref instanceof OverlayRefSliceStart && ref.slice === slice) ||
202+
(ref instanceof OverlayRefSliceEnd && ref.slice === slice)
203+
) {
204+
refs.splice(i, 1);
205+
this.removeLayer(slice);
206+
return;
207+
}
208+
}
209+
}
210+
211+
// ---------------------------------------------------------------- Printable
212+
213+
public toStringName(tab: string, lite?: boolean): string {
214+
return super.toString(tab, lite);
215+
}
216+
217+
public toString(tab: string = '', lite?: boolean): string {
218+
const refs = lite ? '' : `, refs = ${this.refs.length}`;
219+
const header = this.toStringName(tab, lite) + refs;
220+
if (lite) return header;
221+
return (
222+
header +
223+
printTree(
224+
tab,
225+
this.layers.map((slice) => (tab) => slice.toString(tab)),
226+
)
227+
);
228+
}
229+
230+
// ------------------------------------------------------------- HeadlessNode
231+
232+
public p: OverlayPoint | undefined = undefined;
233+
public l: OverlayPoint | undefined = undefined;
234+
public r: OverlayPoint | undefined = undefined;
235+
}

0 commit comments

Comments
 (0)