Skip to content

Commit 30caaa4

Browse files
committed
refactor(json-crdt-extensions): 💡 improve range collapsing logic
1 parent ad95b7f commit 30caaa4

File tree

4 files changed

+63
-30
lines changed

4 files changed

+63
-30
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class Peritext implements Printable {
4141
* @param anchor Whether the point should be before or after the character.
4242
* @returns The point.
4343
*/
44-
public point(id: ITimestampStruct, anchor: Anchor = Anchor.After): Point {
44+
public point(id: ITimestampStruct = this.str.id, anchor: Anchor = Anchor.After): Point {
4545
return new Point(this, id, anchor);
4646
}
4747

@@ -68,7 +68,7 @@ export class Peritext implements Printable {
6868
*
6969
* @returns A point at the start of the text.
7070
*/
71-
public pointAtStart(): Point {
71+
public pointAbsStart(): Point {
7272
return this.point(this.str.id, Anchor.After);
7373
}
7474

@@ -78,7 +78,7 @@ export class Peritext implements Printable {
7878
*
7979
* @returns A point at the end of the text.
8080
*/
81-
public pointAtEnd(): Point {
81+
public pointAbsEnd(): Point {
8282
return this.point(this.str.id, Anchor.Before);
8383
}
8484

@@ -118,7 +118,7 @@ export class Peritext implements Printable {
118118
if (!length) {
119119
const startId = !start ? str.id : str.find(start - 1) || str.id;
120120
const point = this.point(startId, Anchor.After);
121-
return this.range(point, point);
121+
return this.range(point, point.clone());
122122
}
123123
const startId = str.find(start) || str.id;
124124
const endId = str.find(start + length - 1) || startId;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,6 @@ describe('.refAfter()', () => {
10191019
});
10201020
});
10211021

1022-
10231022
describe('.refVisible()', () => {
10241023
test('skips deleted chars, attaches to visible char', () => {
10251024
const {peritext} = setupWithChunkedText();

src/json-crdt-extensions/peritext/slice/Range.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import type {Printable} from '../../../util/print/types';
77

88
/**
99
* A range is a pair of points that represent a selection in the text. A range
10-
* can be collapsed to a single point, at which point it is called a *marker*,
11-
* if it is stored in the text, or *caret*, if it is a cursor position.
10+
* can be collapsed to a single point, then it is called a *marker*
11+
* (if it is stored in the text), or *caret* (if it is a cursor position).
1212
*/
1313
export class Range implements Printable {
1414
/**
@@ -52,24 +52,13 @@ export class Range implements Printable {
5252
* @returns True if the range is collapsed to a single point.
5353
*/
5454
public isCollapsed(): boolean {
55-
const start = this.start;
56-
const end = this.end;
57-
if (start === end) return true;
58-
const pos1 = start.pos();
59-
const pos2 = end.pos();
60-
if (pos1 === pos2) {
61-
if (start.anchor === end.anchor) return true;
62-
if (start.anchor === Anchor.After) return true;
63-
else {
64-
const chunk = start.chunk();
65-
if (chunk && chunk.del) {
66-
// TODO: Revisit where is the best place for this normalization.
67-
// this.start = this.end.clone();
68-
return true;
69-
}
70-
}
71-
}
72-
return false;
55+
const {start, end} = this;
56+
if (start.compareSpatial(end) === 0) return true;
57+
const start2 = start.clone();
58+
const end2 = end.clone();
59+
start2.refAfter();
60+
end2.refAfter();
61+
return start2.compare(end2) === 0;
7362
}
7463

7564
/**
@@ -92,7 +81,12 @@ export class Range implements Printable {
9281
this.start = this.end.clone();
9382
}
9483

95-
public viewRange(): [at: number, len: number] {
84+
/**
85+
* Returns the range in the view coordinates as a position and length.
86+
*
87+
* @returns The range as a view position and length.
88+
*/
89+
public views(): [at: number, len: number] {
9690
const start = this.start.viewPos();
9791
const end = this.end.viewPos();
9892
return [start, end - start];
@@ -108,6 +102,7 @@ export class Range implements Printable {
108102
}
109103

110104
public setAt(start: number, length: number = 0): void {
105+
// TODO: move implementation to here
111106
const range = this.txt.rangeAt(start, length);
112107
this.setRange(range);
113108
}

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

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {Model} from '../../../../json-crdt/model';
22
import {Peritext} from '../../Peritext';
33
import {Anchor} from '../../constants';
4-
import {Editor} from '../../editor/Editor';
54

65
const setup = (insert: (peritext: Peritext) => void = (peritext) => peritext.strApi().ins(0, 'Hello world!')) => {
76
const model = Model.withLogicalClock();
@@ -74,15 +73,15 @@ describe('.isCollapsed()', () => {
7473
describe('when range is collapsed', () => {
7574
test('returns true at the beginning of string', () => {
7675
const {peritext} = setup();
77-
const point = peritext.pointAtStart();
76+
const point = peritext.pointAbsStart();
7877
const range = peritext.range(point, point);
7978
const isCollapsed = range.isCollapsed();
8079
expect(isCollapsed).toBe(true);
8180
});
8281

8382
test('returns true at the end of string', () => {
8483
const {peritext} = setup();
85-
const point = peritext.pointAtEnd();
84+
const point = peritext.pointAbsEnd();
8685
const range = peritext.range(point, point);
8786
const isCollapsed = range.isCollapsed();
8887
expect(isCollapsed).toBe(true);
@@ -117,7 +116,7 @@ describe('.isCollapsed()', () => {
117116
describe('when first character is deleted', () => {
118117
test('returns true at the beginning of string', () => {
119118
const {peritext} = setupEvenDeleted();
120-
const point = peritext.pointAtStart();
119+
const point = peritext.pointAbsStart();
121120
const range = peritext.range(point, point);
122121
const isCollapsed = range.isCollapsed();
123122
expect(isCollapsed).toBe(true);
@@ -130,7 +129,9 @@ describe('.isCollapsed()', () => {
130129
const isCollapsed = range.isCollapsed();
131130
expect(isCollapsed).toBe(true);
132131
});
132+
});
133133

134+
describe('when characters are deleted', () => {
134135
test('returns true when in the middle of deleted characters', () => {
135136
const {peritext} = setupEvenDeleted();
136137
const range = peritext.rangeAt(2, 1);
@@ -146,10 +147,48 @@ describe('.isCollapsed()', () => {
146147
peritext.strApi().del(0, 5);
147148
expect(range.isCollapsed()).toBe(true);
148149
});
150+
151+
test('when all text is selected', () => {
152+
const {peritext} = setupEvenDeleted();
153+
const range = peritext.range(peritext.pointAbsStart(), peritext.pointAbsEnd());
154+
expect(range.isCollapsed()).toBe(false);
155+
peritext.strApi().del(0, 5);
156+
expect(range.isCollapsed()).toBe(true);
157+
});
149158
});
150159
});
151160
});
152161

162+
describe('.collapseToStart()', () => {
163+
test('collapses range to start', () => {
164+
const {peritext} = setup();
165+
const range = peritext.rangeAt(2, 3);
166+
range.collapseToStart();
167+
expect(range.isCollapsed()).toBe(true);
168+
expect(range.start.rightChar()?.view()).toBe('l');
169+
expect(range.end.rightChar()?.view()).toBe('l');
170+
});
171+
});
172+
173+
describe('.collapseToEnd()', () => {
174+
test('collapses range to end', () => {
175+
const {peritext} = setup();
176+
const range = peritext.rangeAt(2, 3);
177+
range.collapseToEnd();
178+
expect(range.isCollapsed()).toBe(true);
179+
expect(range.start.leftChar()?.view()).toBe('o');
180+
expect(range.end.leftChar()?.view()).toBe('o');
181+
});
182+
});
183+
184+
describe('.view()', () => {
185+
test('returns correct view', () => {
186+
const {peritext} = setup();
187+
const range = peritext.rangeAt(2, 3);
188+
expect(range.views()).toEqual([2, 3]);
189+
});
190+
});
191+
153192
describe('.contains()', () => {
154193
test('returns true if slice is contained', () => {
155194
const {peritext} = setup();

0 commit comments

Comments
 (0)