Skip to content

Commit dcf1086

Browse files
authored
Merge pull request #581 from streamich/slices-5
Peritext `Slices` and `Cursor` class improvements
2 parents d14dd8e + 7c270aa commit dcf1086

22 files changed

+618
-227
lines changed

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

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
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';
1311

1412
/**
1513
* Context for a Peritext instance. Contains all the data and methods needed to
@@ -147,24 +145,6 @@ export class Peritext implements Printable {
147145
return textId;
148146
}
149147

150-
public insSlice(
151-
range: Range,
152-
behavior: SliceBehavior,
153-
type: SliceType,
154-
data?: unknown | ITimestampStruct,
155-
): PersistedSlice {
156-
// if (range.isCollapsed()) throw new Error('INVALID_RANGE');
157-
// TODO: If range is not collapsed, check if there are any visible characters in the range.
158-
const slice = this.slices.ins(range, behavior, type, data);
159-
return slice;
160-
}
161-
162-
// ---------------------------------------------------------------- Deletions
163-
164-
public delSlice(sliceId: ITimestampStruct): void {
165-
this.slices.del(sliceId);
166-
}
167-
168148
/** Select a single character before a point. */
169149
public findCharBefore(point: Point): Range | undefined {
170150
if (point.anchor === Anchor.After) {

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

Lines changed: 7 additions & 6 deletions
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 {
@@ -121,14 +122,14 @@ export class Editor implements Printable {
121122
}
122123

123124
public insertSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
124-
return this.txt.insSlice(this.cursor, SliceBehavior.Stack, type, data);
125+
return this.txt.slices.ins(this.cursor, SliceBehavior.Stack, type, data);
125126
}
126127

127128
public insertOverwriteSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
128-
return this.txt.insSlice(this.cursor, SliceBehavior.Overwrite, type, data);
129+
return this.txt.slices.ins(this.cursor, SliceBehavior.Overwrite, type, data);
129130
}
130131

131132
public insertEraseSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
132-
return this.txt.insSlice(this.cursor, SliceBehavior.Erase, type, data);
133+
return this.txt.slices.ins(this.cursor, SliceBehavior.Erase, type, data);
133134
}
134135
}

src/json-crdt-extensions/peritext/point/Point.ts renamed to src/json-crdt-extensions/peritext/rga/Point.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {compare, type ITimestampStruct, toDisplayString, equal, tick, containsId} from '../../../json-crdt-patch/clock';
2-
import {Anchor} from '../constants';
2+
import {Anchor} from './constants';
33
import {ChunkSlice} from '../util/ChunkSlice';
44
import {updateId} from '../../../json-crdt/hash';
55
import type {AbstractRga, Chunk} from '../../../json-crdt/nodes/rga';

src/json-crdt-extensions/peritext/slice/Range.ts renamed to src/json-crdt-extensions/peritext/rga/Range.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import {Point} from '../point/Point';
2-
import {Anchor} from '../constants';
1+
import {Point} from './Point';
2+
import {Anchor} from './constants';
3+
import {updateNum} from '../../../json-hash';
34
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
45
import type {Printable} from '../../../util/print/types';
56
import type {AbstractRga, Chunk} from '../../../json-crdt/nodes/rga';
7+
import type {Stateful} from '../types';
68

79
/**
810
* A range is a pair of points that represent a selection in the text. A range
911
* can be collapsed to a single point, then it is called a *marker*
1012
* (if it is stored in the text), or *caret* (if it is a cursor position).
1113
*/
12-
export class Range<T = string> implements Printable {
14+
export class Range<T = string> implements Pick<Stateful, 'refresh'>, Printable {
1315
/**
1416
* Creates a range from two points. The points are ordered so that the
1517
* start point is before or equal to the end point.
@@ -92,6 +94,14 @@ export class Range<T = string> implements Printable {
9294
return new Range(this.rga, this.start.clone(), this.end.clone());
9395
}
9496

97+
public cmp(range: Range<T>): -1 | 0 | 1 {
98+
return this.start.cmp(range.start) || this.end.cmp(range.end);
99+
}
100+
101+
public cmpSpatial(range: Range<T>): number {
102+
return this.start.cmpSpatial(range.start) || this.end.cmpSpatial(range.end);
103+
}
104+
95105
/**
96106
* Determines if the range is collapsed to a single point. Handles special
97107
* cases where the range is collapsed, but the points are not equal, for
@@ -206,6 +216,14 @@ export class Range<T = string> implements Printable {
206216
return result;
207217
}
208218

219+
// ----------------------------------------------------------------- Stateful
220+
221+
public refresh(): number {
222+
let state = this.start.refresh();
223+
state = updateNum(state, this.end.refresh());
224+
return state;
225+
}
226+
209227
// ---------------------------------------------------------------- Printable
210228

211229
public toString(tab: string = '', lite: boolean = true): string {

src/json-crdt-extensions/peritext/point/__tests__/Point.spec.ts renamed to src/json-crdt-extensions/peritext/rga/__tests__/Point.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Model} from '../../../../json-crdt/model';
22
import {Peritext} from '../../Peritext';
3-
import {Anchor} from '../../constants';
3+
import {Anchor} from '../constants';
44
import {tick} from '../../../../json-crdt-patch/clock';
55

66
const setup = () => {

src/json-crdt-extensions/peritext/slice/__tests__/Range.spec.ts renamed to src/json-crdt-extensions/peritext/rga/__tests__/Range.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Model} from '../../../../json-crdt/model';
22
import {Peritext} from '../../Peritext';
3-
import {Anchor} from '../../constants';
3+
import {Anchor} from '../constants';
44

55
const setup = (insert: (peritext: Peritext) => void = (peritext) => peritext.strApi().ins(0, 'Hello world!')) => {
66
const model = Model.withLogicalClock();
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const enum Anchor {
2+
Before = 0,
3+
After = 1,
4+
}
Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import {Point} from '../point/Point';
2-
import {Anchor, SliceBehavior, Tags} from '../constants';
3-
import {Range} from './Range';
1+
import {Point} from '../rga/Point';
2+
import {CursorAnchor, SliceBehavior, Tags} from './constants';
3+
import {Range} from '../rga/Range';
44
import {printTree} from '../../../util/print/printTree';
5+
import {updateNum} from '../../../json-hash';
56
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
67
import type {Peritext} from '../Peritext';
78
import type {Slice} from './types';
89

9-
export class Cursor extends Range<string> implements Slice {
10+
export class Cursor<T = string> extends Range<T> implements Slice<T> {
1011
public readonly behavior = SliceBehavior.Overwrite;
1112
public readonly type = Tags.Cursor;
1213

@@ -15,32 +16,30 @@ export class Cursor extends Range<string> implements Slice {
1516
* the end which does not move when user changes selection. The other
1617
* end is free to move, the moving end of the cursor is "focus". By default
1718
* "anchor" is the start of the cursor.
18-
*
19-
* @todo Create a custom enum for this, instead of using `Anchor`.
2019
*/
21-
public base: Anchor = Anchor.Before;
20+
public anchorSide: CursorAnchor = CursorAnchor.Start;
2221

2322
constructor(
2423
public readonly id: ITimestampStruct,
2524
protected readonly txt: Peritext,
26-
public start: Point,
27-
public end: Point,
25+
public start: Point<T>,
26+
public end: Point<T>,
2827
) {
29-
super(txt.str, start, end);
28+
super(txt.str as any, start, end);
3029
}
3130

32-
public anchor(): Point {
33-
return this.base === Anchor.Before ? this.start : this.end;
31+
public anchor(): Point<T> {
32+
return this.anchorSide === CursorAnchor.Start ? this.start : this.end;
3433
}
3534

36-
public focus(): Point {
37-
return this.base === Anchor.Before ? this.end : this.start;
35+
public focus(): Point<T> {
36+
return this.anchorSide === CursorAnchor.Start ? this.end : this.start;
3837
}
3938

40-
public set(start: Point, end?: Point, base: Anchor = Anchor.Before): void {
39+
public set(start: Point<T>, end?: Point<T>, base: CursorAnchor = CursorAnchor.Start): void {
4140
if (!end || end === start) end = start.clone();
4241
super.set(start, end);
43-
this.base = base;
42+
this.anchorSide = base;
4443
}
4544

4645
public setAt(start: number, length: number = 0): void {
@@ -51,7 +50,7 @@ export class Cursor extends Range<string> implements Slice {
5150
len = -len;
5251
}
5352
super.setAt(at, len);
54-
this.base = length < 0 ? Anchor.After : Anchor.Before;
53+
this.anchorSide = length < 0 ? CursorAnchor.End : CursorAnchor.Start;
5554
}
5655

5756
/**
@@ -60,30 +59,25 @@ export class Cursor extends Range<string> implements Slice {
6059
* @param point Point to set the edge to.
6160
* @param edge 0 for "focus", 1 for "anchor."
6261
*/
63-
public setEdge(point: Point, edge: 0 | 1 = 0): void {
62+
public setEdge(point: Point<T>, edge: 0 | 1 = 0): void {
6463
if (this.start === this.end) this.end = this.end.clone();
6564
let anchor = this.anchor();
6665
let focus = this.focus();
6766
if (edge === 0) focus = point;
6867
else anchor = point;
6968
if (focus.cmpSpatial(anchor) < 0) {
70-
this.base = Anchor.After;
69+
this.anchorSide = CursorAnchor.End;
7170
this.start = focus;
7271
this.end = anchor;
7372
} else {
74-
this.base = Anchor.Before;
73+
this.anchorSide = CursorAnchor.Start;
7574
this.start = anchor;
7675
this.end = focus;
7776
}
7877
}
7978

80-
/** @todo Maybe move it to another interface? */
81-
public del(): boolean {
82-
return false;
83-
}
84-
85-
public data(): unknown {
86-
return 1;
79+
public data() {
80+
return undefined;
8781
}
8882

8983
public move(move: number): void {
@@ -93,19 +87,23 @@ export class Cursor extends Range<string> implements Slice {
9387
end.move(move);
9488
}
9589

96-
public toString(tab: string = ''): string {
97-
const text = JSON.stringify(this.text());
98-
const focusIcon = this.base === Anchor.Before ? '.⇨|' : '|⇦.';
99-
const main = `${this.constructor.name} ${super.toString(tab + ' ', true)} ${focusIcon}`;
100-
return main + printTree(tab, [() => text]);
101-
}
102-
10390
// ----------------------------------------------------------------- Stateful
10491

10592
public hash: number = 0;
10693

10794
public refresh(): number {
108-
// TODO: implement this ...
109-
return this.hash;
95+
let state = super.refresh();
96+
state = updateNum(state, this.anchorSide);
97+
this.hash = state;
98+
return state;
99+
}
100+
101+
// ---------------------------------------------------------------- Printable
102+
103+
public toString(tab: string = ''): string {
104+
const text = JSON.stringify(this.text());
105+
const focusIcon = this.anchorSide === CursorAnchor.Start ? '.→|' : '|←.';
106+
const main = `${this.constructor.name} ${super.toString(tab + ' ', true)} ${focusIcon}`;
107+
return main + printTree(tab, [() => text]);
110108
}
111109
}

0 commit comments

Comments
 (0)