Skip to content

Commit

Permalink
feat(json-crdt-extensions): 🎸 add iteration over block points
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 7, 2024
1 parent 067fed6 commit 19dcc68
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 3 deletions.
30 changes: 29 additions & 1 deletion src/json-crdt-extensions/peritext/block/Block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {printTree} from 'tree-dump/lib/printTree';
import {CONST, updateJson, updateNum} from '../../../json-hash';
import {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint';
import {OverlayPoint} from '../overlay/OverlayPoint';
import {UndefEndIter, type UndefIterator} from '../../../util/iterator';
import type {Path} from '../../../json-pointer';
import type {Printable} from 'tree-dump';
import type {Peritext} from '../Peritext';
Expand All @@ -11,6 +13,8 @@ export interface IBlock {
readonly parent: IBlock | null;
}

type T = string;

export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
public parent: Block | null = null;

Expand All @@ -19,7 +23,6 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
constructor(
public readonly txt: Peritext,
public readonly path: Path,
/** @todo rename this */
public readonly marker: MarkerOverlayPoint | undefined,
) {}

Expand All @@ -43,6 +46,31 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
return this.marker?.data() as Attr | undefined;
}

/**
* Iterate through all overlay points of this block, until the next marker
* (regardless if that marker is a child or not).
*/
public points0(): UndefIterator<OverlayPoint<T>> {
const txt = this.txt;
const overlay = txt.overlay;
const iterator = overlay.points0(this.marker);
let closed = false;
return () => {
if (closed) return;
const point = iterator();
if (!point) return;
if (point instanceof MarkerOverlayPoint) {
closed = true;
return;
}
return point;
};
}

public points(): IterableIterator<OverlayPoint<T>> {
return new UndefEndIter(this.points0());
}

// ----------------------------------------------------------------- Stateful

public hash: number = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {setupHelloWorldKit} from '../../__tests__/setup';
import {OverlayPoint} from '../../overlay/OverlayPoint';

describe('points', () => {
test('no iteration in empty document', () => {
const {peritext} = setupHelloWorldKit();
peritext.refresh();
const blocks = peritext.blocks;
const block = blocks.root.children[0]!;
const iterator = block.points0();
expect(iterator()).toBe(undefined);
});

test('returns all overlay points in single block document', () => {
const {peritext} = setupHelloWorldKit();
peritext.editor.cursor.setAt(3, 3);
peritext.editor.saved.insStack('bold');
peritext.refresh();
const block = peritext.blocks.root.children[0]!;
const iterator = block.points0();
const point1 = iterator();
const point2 = iterator();
const point3 = iterator();
expect(point1).toBeInstanceOf(OverlayPoint);
expect(point2).toBeInstanceOf(OverlayPoint);
expect(point3).toBe(undefined);
});

test('returns only points within that block, in two-block document', () => {
const {peritext} = setupHelloWorldKit();
peritext.editor.cursor.setAt(1, 2);
peritext.editor.saved.insStack('bold');
peritext.editor.cursor.setAt(7, 2);
peritext.editor.saved.insStack('italic');
peritext.editor.cursor.setAt(8, 2);
peritext.editor.saved.insStack('underline');
peritext.editor.cursor.setAt(6);
peritext.editor.saved.insMarker('p');
peritext.editor.delCursors();
peritext.refresh();
expect(peritext.blocks.root.children.length).toBe(2);
const block1 = peritext.blocks.root.children[0]!;
const block2 = peritext.blocks.root.children[1]!;
const points1 = [...block1.points()];
const points2 = [...block2.points()];
expect(points1.length).toBe(2);
expect(points2.length).toBe(4);
});
});
15 changes: 15 additions & 0 deletions src/json-crdt-extensions/peritext/block/__tests__/Blocks.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import {setupHelloWorldKit} from '../../__tests__/setup';
import {MarkerOverlayPoint} from '../../overlay/MarkerOverlayPoint';
import {Block} from '../Block';
import {LeafBlock} from '../LeafBlock';

test('can construct block representation of a document without markers', () => {
const {peritext} = setupHelloWorldKit();
peritext.refresh();
const blocks = peritext.blocks;
expect(blocks.root.children.length).toBe(1);
const block = blocks.root.children[0];
expect(block instanceof LeafBlock).toBe(true);
expect(block.path).toEqual([0]);
expect(block.marker).toBe(undefined);
});

test('can construct a two-paragraph document', () => {
const {peritext} = setupHelloWorldKit();
peritext.editor.cursor.setAt(6);
Expand All @@ -11,8 +23,11 @@ test('can construct a two-paragraph document', () => {
const paragraph1 = blocks.root.children[0];
const paragraph2 = blocks.root.children[1];
expect(blocks.root instanceof Block).toBe(true);
expect(blocks.root.children.length).toBe(2);
expect(paragraph1 instanceof LeafBlock).toBe(true);
expect(paragraph2 instanceof LeafBlock).toBe(true);
expect(paragraph1.path).toEqual([0]);
expect(paragraph2.path).toEqual(['p']);
expect(paragraph1.marker).toBe(undefined);
expect(paragraph2.marker instanceof MarkerOverlayPoint).toBe(true);
});
2 changes: 1 addition & 1 deletion src/json-crdt-extensions/peritext/editor/Cursor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ITimestampStruct, tick} from '../../../json-crdt-patch';
import {tick} from '../../../json-crdt-patch';
import {Anchor} from '../rga/constants';
import {Point} from '../rga/Point';
import {CursorAnchor} from '../slice/constants';
Expand Down
10 changes: 10 additions & 0 deletions src/json-crdt-extensions/peritext/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export class Editor<T = string> {
this.local = new EditorSlices(txt, txt.localSlices);
}

// ------------------------------------------------------------------ cursors

public firstCursor(): Cursor<T> | undefined {
const iterator = this.txt.localSlices.iterator0();
let cursor = iterator();
Expand Down Expand Up @@ -57,6 +59,12 @@ export class Editor<T = string> {
});
}

public delCursors(): void {
this.cursors((cursor) => this.local.del(cursor));
}

// ------------------------------------------------------------- text editing

/**
* Insert inline text at current cursor position. If cursor selects a range,
* the range is removed and the text is inserted at the start of the range.
Expand All @@ -73,6 +81,8 @@ export class Editor<T = string> {
this.cursors((cursor) => cursor.delBwd());
}

// ------------------------------------------------------------------ various

/** @todo Add main impl details of this to `Cursor`, but here ensure there is only one cursor. */
public selectAll(): boolean {
const range = this.txt.rangeAll();
Expand Down
2 changes: 1 addition & 1 deletion src/json-crdt-extensions/peritext/editor/EditorSlices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class EditorSlices<T = string> {
});
}

public del(sliceOrId: PersistedSlice | ITimestampStruct): void {
public del(sliceOrId: PersistedSlice<T> | ITimestampStruct): void {
this.slices.del(sliceOrId instanceof PersistedSlice ? sliceOrId.id : sliceOrId);
}
}

0 comments on commit 19dcc68

Please sign in to comment.