Skip to content

Commit 45b1241

Browse files
committed
feat(json-crdt-extensions): 🎸 improve .move() method and fix .viewPos()
1 parent 2e48abf commit 45b1241

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class Point implements Pick<Stateful, 'refresh'>, Printable {
112112
*/
113113
public viewPos(): number {
114114
const pos = this.pos();
115-
if (pos < 0) return 0;
115+
if (pos < 0) return this.isStartOfStr() ? 0 : this.txt.str.length();
116116
return this.anchor === Anchor.Before ? pos : pos + 1;
117117
}
118118

@@ -313,14 +313,23 @@ export class Point implements Pick<Stateful, 'refresh'>, Printable {
313313
* and negative distances.
314314
*/
315315
public move(skip: number): void {
316-
// TODO: handle cases when cursor reaches ends of string, it should adjust anchor positions as well
317316
if (!skip) return;
317+
const anchor = this.anchor;
318+
if (anchor !== Anchor.After) this.refAfter();
318319
if (skip > 0) {
319320
const nextId = this.nextId(skip);
320-
if (nextId) this.id = nextId;
321+
if (!nextId) this.refEnd();
322+
else {
323+
this.id = nextId;
324+
if (anchor !== Anchor.After) this.refBefore();
325+
}
321326
} else {
322327
const prevId = this.prevId(-skip);
323-
if (prevId) this.id = prevId;
328+
if (!prevId) this.refStart();
329+
else {
330+
this.id = prevId;
331+
if (anchor !== Anchor.After) this.refBefore();
332+
}
324333
}
325334
}
326335

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

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,15 +820,96 @@ describe('.refAfter()', () => {
820820
});
821821

822822
describe('.move()', () => {
823-
test('can move forward', () => {
823+
test('smoke test', () => {
824+
const {peritext} = setupWithChunkedText();
825+
const p = peritext.pointAt(1, Anchor.After);
826+
expect(p.viewPos()).toBe(2);
827+
p.move(1);
828+
expect(p.viewPos()).toBe(3);
829+
p.move(2);
830+
expect(p.viewPos()).toBe(5);
831+
p.move(2);
832+
expect(p.viewPos()).toBe(7);
833+
p.move(-3);
834+
expect(p.viewPos()).toBe(4);
835+
p.move(-3);
836+
expect(p.viewPos()).toBe(1);
837+
p.move(-3);
838+
expect(p.viewPos()).toBe(0);
839+
});
840+
841+
test('can reach the end of str', () => {
842+
const {peritext} = setupWithChunkedText();
843+
const p = peritext.pointAt(0, Anchor.After);
844+
p.move(1);
845+
p.move(2);
846+
p.move(3);
847+
p.move(4);
848+
p.move(5);
849+
p.move(6);
850+
expect(p.isEndOfStr()).toBe(true);
851+
expect(p.viewPos()).toBe(9);
852+
expect(p.leftChar()!.view()).toBe('9');
853+
expect(p.anchor).toBe(Anchor.Before);
854+
});
855+
856+
test('can reach the start of str', () => {
857+
const {peritext} = setupWithChunkedText();
858+
const p = peritext.pointAt(8, Anchor.Before);
859+
p.move(-22);
860+
expect(p.isStartOfStr()).toBe(true);
861+
expect(p.viewPos()).toBe(0);
862+
expect(p.rightChar()!.view()).toBe('1');
863+
expect(p.anchor).toBe(Anchor.After);
864+
});
865+
866+
test('can move forward, when anchor = Before', () => {
824867
const {peritext, model} = setupWithChunkedText();
825868
model.api.str(['text']).del(4, 1);
826869
const txt = '12346789';
827870
for (let i = 0; i < txt.length - 1; i++) {
828871
const p = peritext.pointAt(i, Anchor.Before);
872+
expect(p.pos()).toBe(i);
829873
for (let j = i + 1; j < txt.length - 1; j++) {
830874
const p2 = p.clone();
831875
p2.move(j - i);
876+
expect(p2.pos()).toBe(j);
877+
expect(p2.anchor).toBe(Anchor.Before);
878+
expect(p2.rightChar()!.view()).toBe(txt[j]);
879+
}
880+
}
881+
});
882+
883+
test('can move forward, when anchor = After', () => {
884+
const {peritext, model} = setupWithChunkedText();
885+
model.api.str(['text']).del(4, 1);
886+
const txt = '12346789';
887+
for (let i = 0; i < txt.length - 1; i++) {
888+
const p = peritext.pointAt(i, Anchor.After);
889+
expect(p.pos()).toBe(i);
890+
expect(p.leftChar()!.view()).toBe(txt[i]);
891+
for (let j = i + 1; j < txt.length - 1; j++) {
892+
const p2 = p.clone();
893+
p2.move(j - i);
894+
expect(p2.pos()).toBe(j);
895+
expect(p2.anchor).toBe(Anchor.After);
896+
expect(p2.leftChar()!.view()).toBe(txt[j]);
897+
}
898+
}
899+
});
900+
901+
test('can move backward, when anchor = Before', () => {
902+
const {peritext, model} = setupWithChunkedText();
903+
model.api.str(['text']).del(4, 1);
904+
const txt = '12346789';
905+
for (let i = txt.length - 1; i > 0; i--) {
906+
const p = peritext.pointAt(i, Anchor.Before);
907+
expect(p.viewPos()).toBe(i);
908+
for (let j = i - 1; j > 0; j--) {
909+
const p2 = p.clone();
910+
p2.move(j - i);
911+
expect(p2.pos()).toBe(j);
912+
expect(p2.anchor).toBe(Anchor.Before);
832913
expect(p2.rightChar()!.view()).toBe(txt[j]);
833914
}
834915
}

0 commit comments

Comments
 (0)