Skip to content

Commit 02c58ab

Browse files
committed
Merge branch 'master' into next
2 parents 02e9935 + dcf1086 commit 02c58ab

25 files changed

+2116
-932
lines changed

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

+93-39
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import {Anchor, SliceBehavior} from './constants';
2-
import {Point} from './point/Point';
3-
import {Range} from './slice/Range';
1+
import {Anchor} from './rga/constants';
2+
import {Point} from './rga/Point';
3+
import {Range} from './rga/Range';
44
import {Editor} from './editor/Editor';
55
import {printTree} from '../../util/print/printTree';
66
import {ArrNode, StrNode} from '../../json-crdt/nodes';
77
import {Slices} from './slice/Slices';
88
import {type ITimestampStruct} from '../../json-crdt-patch/clock';
99
import type {Model} from '../../json-crdt/model';
1010
import type {Printable} from '../../util/print/types';
11-
import type {SliceType} from './types';
12-
import type {PersistedSlice} from './slice/PersistedSlice';
13-
import {CONST} from '../../json-hash';
1411

12+
/**
13+
* Context for a Peritext instance. Contains all the data and methods needed to
14+
* interact with the text.
15+
*/
1516
export class Peritext implements Printable {
1617
public readonly slices: Slices;
1718
public readonly editor: Editor;
@@ -25,48 +26,117 @@ export class Peritext implements Printable {
2526
this.editor = new Editor(this);
2627
}
2728

28-
public point(id: ITimestampStruct, anchor: Anchor = Anchor.After): Point {
29-
return new Point(this, id, anchor);
29+
public strApi() {
30+
return this.model.api.wrap(this.str);
3031
}
3132

33+
// ------------------------------------------------------------------- Points
34+
35+
/**
36+
* Creates a point at a character ID.
37+
*
38+
* @param id Character ID to which the point should be attached.
39+
* @param anchor Whether the point should be before or after the character.
40+
* @returns The point.
41+
*/
42+
public point(id: ITimestampStruct = this.str.id, anchor: Anchor = Anchor.After): Point {
43+
return new Point(this.str, id, anchor);
44+
}
45+
46+
/**
47+
* Creates a point at a view position in the text. The `pos` argument specifies
48+
* the position of the character, not the gap between characters.
49+
*
50+
* @param pos Position of the character in the text.
51+
* @param anchor Whether the point should attach before or after a character.
52+
* @returns The point.
53+
*/
3254
public pointAt(pos: number, anchor: Anchor = Anchor.Before): Point {
55+
// TODO: Provide ability to attach to the beginning of the text?
56+
// TODO: Provide ability to attach to the end of the text?
3357
const str = this.str;
3458
const id = str.find(pos);
3559
if (!id) return this.point(str.id, Anchor.After);
3660
return this.point(id, anchor);
3761
}
3862

39-
public pointAtStart(): Point {
63+
/**
64+
* Creates a point which is attached to the start of the text, before the
65+
* first character.
66+
*
67+
* @returns A point at the start of the text.
68+
*/
69+
public pointAbsStart(): Point {
4070
return this.point(this.str.id, Anchor.After);
4171
}
4272

43-
public pointAtEnd(): Point {
73+
/**
74+
* Creates a point which is attached to the end of the text, after the last
75+
* character.
76+
*
77+
* @returns A point at the end of the text.
78+
*/
79+
public pointAbsEnd(): Point {
4480
return this.point(this.str.id, Anchor.Before);
4581
}
4682

83+
// ------------------------------------------------------------------- Ranges
84+
85+
/**
86+
* Creates a range from two points. The points can be in any order.
87+
*
88+
* @param p1 Point
89+
* @param p2 Point
90+
* @returns A range with points in correct order.
91+
*/
92+
public rangeFromPoints(p1: Point, p2: Point): Range {
93+
return Range.from(this.str, p1, p2);
94+
}
95+
96+
/**
97+
* Creates a range from two points, the points have to be in the correct order.
98+
*
99+
* @param start Start point of the range, must be before or equal to end.
100+
* @param end End point of the range, must be after or equal to start.
101+
* @returns A range with the given start and end points.
102+
*/
47103
public range(start: Point, end: Point): Range {
48-
return new Range(this, start, end);
104+
return new Range(this.str, start, end);
49105
}
50106

107+
/**
108+
* A convenience method for creating a range from a view position and a length.
109+
* See {@link Range.at} for more information.
110+
*
111+
* @param start Position in the text.
112+
* @param length Length of the range.
113+
* @returns A range from the given position with the given length.
114+
*/
51115
public rangeAt(start: number, length: number = 0): Range {
52-
const str = this.str;
53-
if (!length) {
54-
const startId = !start ? str.id : str.find(start - 1) || str.id;
55-
const point = this.point(startId, Anchor.After);
56-
return this.range(point, point);
57-
}
58-
const startId = str.find(start) || str.id;
59-
const endId = str.find(start + length - 1) || startId;
60-
const startEndpoint = this.point(startId, Anchor.Before);
61-
const endEndpoint = this.point(endId, Anchor.After);
62-
return this.range(startEndpoint, endEndpoint);
116+
return Range.at(this.str, start, length);
63117
}
64118

119+
// --------------------------------------------------------------- Insertions
120+
121+
/**
122+
* Insert plain text at a view position in the text.
123+
*
124+
* @param pos View position in the text.
125+
* @param text Text to insert.
126+
*/
65127
public insAt(pos: number, text: string): void {
66-
const str = this.model.api.wrap(this.str);
128+
const str = this.strApi();
67129
str.ins(pos, text);
68130
}
69131

132+
/**
133+
* Insert plain text after a character referenced by its ID and return the
134+
* ID of the insertion operation.
135+
*
136+
* @param after Character ID after which the text should be inserted.
137+
* @param text Text to insert.
138+
* @returns ID of the insertion operation.
139+
*/
70140
public ins(after: ITimestampStruct, text: string): ITimestampStruct {
71141
if (!text) throw new Error('NO_TEXT');
72142
const api = this.model.api;
@@ -75,22 +145,6 @@ export class Peritext implements Printable {
75145
return textId;
76146
}
77147

78-
public insSlice(
79-
range: Range,
80-
behavior: SliceBehavior,
81-
type: SliceType,
82-
data?: unknown | ITimestampStruct,
83-
): PersistedSlice {
84-
// if (range.isCollapsed()) throw new Error('INVALID_RANGE');
85-
// TODO: If range is not collapsed, check if there are any visible characters in the range.
86-
const slice = this.slices.ins(range, behavior, type, data);
87-
return slice;
88-
}
89-
90-
public delSlice(sliceId: ITimestampStruct): void {
91-
this.slices.del(sliceId);
92-
}
93-
94148
/** Select a single character before a point. */
95149
public findCharBefore(point: Point): Range | undefined {
96150
if (point.anchor === Anchor.After) {

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import {Cursor} from '../slice/Cursor';
2-
import {Anchor, SliceBehavior} from '../constants';
2+
import {Anchor} from '../rga/constants';
3+
import {SliceBehavior} from '../slice/constants';
34
import {tick, type ITimestampStruct} from '../../../json-crdt-patch/clock';
45
import {PersistedSlice} from '../slice/PersistedSlice';
5-
import type {Range} from '../slice/Range';
6+
import type {Range} from '../rga/Range';
67
import type {Peritext} from '../Peritext';
78
import type {Printable} from '../../../util/print/types';
8-
import type {Point} from '../point/Point';
9+
import type {Point} from '../rga/Point';
910
import type {SliceType} from '../types';
1011

1112
export class Editor implements Printable {
@@ -54,8 +55,8 @@ export class Editor implements Printable {
5455
const api = model.api;
5556
api.builder.del(str.id, range);
5657
api.apply();
57-
if (start.anchor === Anchor.After) cursor.setCaret(start.id);
58-
else cursor.setCaret(start.prevId() || str.id);
58+
if (start.anchor === Anchor.After) cursor.setAfter(start.id);
59+
else cursor.setAfter(start.prevId() || str.id);
5960
}
6061
return cursor.start.id;
6162
}
@@ -68,7 +69,8 @@ export class Editor implements Printable {
6869
if (!text) return;
6970
const after = this.collapseSelection();
7071
const textId = this.txt.ins(after, text);
71-
this.cursor.setCaret(textId, text.length - 1);
72+
const shift = text.length - 1;
73+
this.cursor.setAfter(shift ? tick(textId, shift) : textId);
7274
}
7375

7476
/**
@@ -120,6 +122,14 @@ export class Editor implements Printable {
120122
}
121123

122124
public insertSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
123-
return this.txt.insSlice(this.cursor, SliceBehavior.Stack, type, data);
125+
return this.txt.slices.ins(this.cursor, SliceBehavior.Stack, type, data);
126+
}
127+
128+
public insertOverwriteSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
129+
return this.txt.slices.ins(this.cursor, SliceBehavior.Overwrite, type, data);
130+
}
131+
132+
public insertEraseSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
133+
return this.txt.slices.ins(this.cursor, SliceBehavior.Erase, type, data);
124134
}
125135
}

0 commit comments

Comments
 (0)