Skip to content

Commit

Permalink
Merge pull request #785 from streamich/peritext-fragment
Browse files Browse the repository at this point in the history
Peritext fragment
  • Loading branch information
streamich authored Nov 25, 2024
2 parents bdab7c8 + d0b09de commit 052b3c3
Show file tree
Hide file tree
Showing 34 changed files with 560 additions and 253 deletions.
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"noExplicitAny": "off",
"useIsArray": "off",
"noAssignInExpressions": "off",
"noConfusingLabels": "off"
"noConfusingLabels": "off",
"noConfusingVoidType": "off"
},
"complexity": {
"noStaticOnlyClass": "off",
Expand Down
10 changes: 7 additions & 3 deletions src/json-crdt-extensions/peritext/Peritext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {CONST, updateNum} from '../../json-hash';
import {SESSION} from '../../json-crdt-patch/constants';
import {s} from '../../json-crdt-patch';
import {ExtraSlices} from './slice/ExtraSlices';
import {Blocks} from './block/Blocks';
import {Fragment} from './block/Fragment';
import {updateRga} from '../../json-crdt/hash';
import type {ITimestampStruct} from '../../json-crdt-patch/clock';
import type {Printable} from 'tree-dump/lib/types';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class Peritext<T = string> implements Printable {

public readonly editor: Editor<T>;
public readonly overlay = new Overlay<T>(this);
public readonly blocks: Blocks;
public readonly blocks: Fragment;

/**
* Creates a new Peritext context.
Expand Down Expand Up @@ -86,7 +86,7 @@ export class Peritext<T = string> implements Printable {
});
this.localSlices = new LocalSlices(this, localSlicesModel.root.node().get(0)!);
this.editor = new Editor<T>(this);
this.blocks = new Blocks(this as Peritext);
this.blocks = new Fragment(this as Peritext, this.pointAbsStart() as Point, this.pointAbsEnd() as Point);
}

public strApi(): StrApi {
Expand Down Expand Up @@ -219,6 +219,10 @@ export class Peritext<T = string> implements Printable {
return this.range(start, end);
}

public fragment(range: Range): Fragment {
return new Fragment(this as Peritext, range.start, range.end);
}

// ---------------------------------------------------------- text (& slices)

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {InlineAttrStartPoint, InlineAttrContained} from '../block/Inline';
import {CommonSliceType} from '../slice/constants';
import {SliceTypeName} from '../slice/constants';
import {setupKit} from './setup';

const setup = () => {
Expand All @@ -24,7 +24,7 @@ test('cursor at the start of string and slice annotation at the start of string'
expect(inline1.text()).toBe('');
expect(inline2.text()).toBe('a');
expect(inline3.text()).toBe('b');
expect(inline1.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrStartPoint);
expect(inline1.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrStartPoint);
expect(inline2.attr().bold[0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr()).toEqual({});
});
Expand All @@ -48,7 +48,7 @@ test('cursor walking over character marked as bold', () => {
expect(inline2.text()).toBe('a');
expect(inline3.text()).toBe('b');
expect(inline2.attr().bold[0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrStartPoint);
expect(inline3.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrStartPoint);
// expect(inline2.attr()).toEqual({bold: [[void 0], InlineAttrPos.Contained]});
// expect(inline3.attr()).toEqual({
// [SliceTypes.Cursor]: [[[CursorAnchor.Start, void 0]], InlineAttrPos.Collapsed],
Expand Down Expand Up @@ -78,7 +78,7 @@ test('cursor walking over character marked as bold and one more', () => {
expect(inline2.attr().bold[0]).toBeInstanceOf(InlineAttrContained);
// expect(inline2.attr()).toEqual({bold: [1, InlineAttrPos.Contained]});
expect(inline3.attr()).toEqual({});
expect(inline4.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrStartPoint);
expect(inline4.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrStartPoint);
// expect(inline4.attr()).toEqual({
// [SliceTypes.Cursor]: [[[CursorAnchor.Start, void 0]], InlineAttrPos.Collapsed],
// });
Expand All @@ -94,7 +94,7 @@ test('cursor can move across block boundary forwards', () => {
expect(peritext.blocks.root.children.length).toBe(2);
expect([...peritext.blocks.root.children[0].texts()].length).toBe(1);
expect([...peritext.blocks.root.children[0].texts()][0].text()).toBe('a');
expect([...peritext.blocks.root.children[0].texts()][0].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(
expect([...peritext.blocks.root.children[0].texts()][0].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(
InlineAttrStartPoint,
);

Expand All @@ -105,7 +105,7 @@ test('cursor can move across block boundary forwards', () => {
expect([...peritext.blocks.root.children[0].texts()][0].text()).toBe('a');
expect([...peritext.blocks.root.children[0].texts()][0].attr()).toEqual({});
expect([...peritext.blocks.root.children[0].texts()][1].text()).toBe('');
expect([...peritext.blocks.root.children[0].texts()][1].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(
expect([...peritext.blocks.root.children[0].texts()][1].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(
InlineAttrStartPoint,
);
expect([...peritext.blocks.root.children[1].texts()].length).toBe(1);
Expand All @@ -121,7 +121,7 @@ test('cursor can move across block boundary forwards', () => {
expect([...peritext.blocks.root.children[1].texts()][0].text()).toBe('');
expect([...peritext.blocks.root.children[1].texts()][0].attr()).toEqual({});
expect([...peritext.blocks.root.children[1].texts()][1].text()).toBe('b');
expect([...peritext.blocks.root.children[1].texts()][1].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(
expect([...peritext.blocks.root.children[1].texts()][1].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(
InlineAttrStartPoint,
);
editor.cursor.move(1);
Expand All @@ -134,7 +134,7 @@ test('cursor can move across block boundary forwards', () => {
expect([...peritext.blocks.root.children[1].texts()][0].text()).toBe('b');
expect([...peritext.blocks.root.children[1].texts()][0].attr()).toEqual({});
expect([...peritext.blocks.root.children[1].texts()][1].text()).toBe('');
expect([...peritext.blocks.root.children[1].texts()][1].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(
expect([...peritext.blocks.root.children[1].texts()][1].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(
InlineAttrStartPoint,
);
});
22 changes: 11 additions & 11 deletions src/json-crdt-extensions/peritext/__tests__/Peritext.tree.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {InlineAttrContained, InlineAttrEnd, InlineAttrPassing, InlineAttrStart} from '../block/Inline';
import type {LeafBlock} from '../block/LeafBlock';
import {CommonSliceType} from '../slice/constants';
import {SliceTypeName} from '../slice/constants';
import {type Kit, setupHelloWorldKit, setupHelloWorldWithFewEditsKit} from './setup';

const run = (setup: () => Kit) => {
Expand Down Expand Up @@ -79,10 +79,10 @@ const run = (setup: () => Kit) => {
expect(inline3.attr().bold[0].slice.data()).toBe(undefined);
expect(inline3.attr().italic[0]).toBeInstanceOf(InlineAttrStart);
expect(inline3.attr().italic[0].slice.data()).toBe(undefined);
expect(inline3.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrStart);
expect(inline3.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrStart);
expect(inline4.attr().italic[0]).toBeInstanceOf(InlineAttrEnd);
expect(inline4.attr().italic[0].slice.data()).toBe(undefined);
expect(inline4.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrEnd);
expect(inline4.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrEnd);
expect(inline5.attr()).toEqual({});
});

Expand All @@ -102,7 +102,7 @@ const run = (setup: () => Kit) => {
expect(inline2.attr().bold[0].slice.data()).toBe(undefined);
expect(inline2.attr().italic[0]).toBeInstanceOf(InlineAttrContained);
expect(inline2.attr().italic[0].slice.data()).toBe(undefined);
expect(inline2.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrContained);
expect(inline2.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr()).toEqual({});
});

Expand All @@ -124,10 +124,10 @@ const run = (setup: () => Kit) => {
expect(inline2.attr().bold[0].slice.data()).toBe(undefined);
expect(inline2.attr().italic[0]).toBeInstanceOf(InlineAttrStart);
expect(inline2.attr().italic[0].slice.data()).toBe(undefined);
expect(inline2.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrStart);
expect(inline2.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrStart);
expect(inline3.attr().italic[0]).toBeInstanceOf(InlineAttrEnd);
expect(inline3.attr().italic[0].slice.data()).toBe(undefined);
expect(inline3.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrEnd);
expect(inline3.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrEnd);
expect(inline4.attr()).toEqual({});
});

Expand All @@ -151,7 +151,7 @@ const run = (setup: () => Kit) => {
expect(inline3.attr().bold[0].slice.data()).toBe(undefined);
expect(inline3.attr().italic[0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr().italic[0].slice.data()).toBe(undefined);
expect(inline3.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrContained);
expect(inline4.attr()).toEqual({});
});

Expand All @@ -173,7 +173,7 @@ const run = (setup: () => Kit) => {
expect(inline2.attr().bold[0].slice.data()).toBe(undefined);
expect(inline3.attr().italic[0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr().italic[0].slice.data()).toBe(undefined);
expect(inline3.attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrContained);
expect(inline3.attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrContained);
expect(inline4.attr()).toEqual({});
});
});
Expand Down Expand Up @@ -268,13 +268,13 @@ const run = (setup: () => Kit) => {
expect([...block1.texts()].length).toBe(2);
expect([...block1.texts()][0].attr()).toEqual({});
expect([...block1.texts()][1].attr().bold[0]).toBeInstanceOf(InlineAttrStart);
expect([...block1.texts()][1].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrStart);
expect([...block1.texts()][1].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrStart);
expect([...block2.texts()].length).toBe(1);
expect([...block2.texts()][0].attr().bold[0]).toBeInstanceOf(InlineAttrPassing);
expect([...block2.texts()][0].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrPassing);
expect([...block2.texts()][0].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrPassing);
expect([...block3.texts()].length).toBe(2);
expect([...block3.texts()][0].attr().bold[0]).toBeInstanceOf(InlineAttrEnd);
expect([...block3.texts()][0].attr()[CommonSliceType.Cursor][0]).toBeInstanceOf(InlineAttrEnd);
expect([...block3.texts()][0].attr()[SliceTypeName.Cursor][0]).toBeInstanceOf(InlineAttrEnd);
expect([...block3.texts()][1].attr()).toEqual({});
});
});
Expand Down
30 changes: 15 additions & 15 deletions src/json-crdt-extensions/peritext/__tests__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,19 @@ export const runAlphabetKitTestSuite = (runTestSuite: (getKit: () => Kit) => voi
describe('basic alphabet', () => {
runTestSuite(setupAlphabetKit);
});
describe('alphabet with two chunks', () => {
runTestSuite(setupAlphabetWithTwoChunksKit);
});
describe('alphabet with chunk split', () => {
runTestSuite(setupAlphabetChunkSplitKit);
});
describe('alphabet with deletes', () => {
runTestSuite(setupAlphabetWithDeletesKit);
});
describe('alphabet written in reverse', () => {
runTestSuite(setupAlphabetWrittenInReverse);
});
describe('alphabet written in reverse with deletes', () => {
runTestSuite(setupAlphabetWrittenInReverseWithDeletes);
});
// describe('alphabet with two chunks', () => {
// runTestSuite(setupAlphabetWithTwoChunksKit);
// });
// describe('alphabet with chunk split', () => {
// runTestSuite(setupAlphabetChunkSplitKit);
// });
// describe('alphabet with deletes', () => {
// runTestSuite(setupAlphabetWithDeletesKit);
// });
// describe('alphabet written in reverse', () => {
// runTestSuite(setupAlphabetWrittenInReverse);
// });
// describe('alphabet written in reverse with deletes', () => {
// runTestSuite(setupAlphabetWrittenInReverseWithDeletes);
// });
};
50 changes: 39 additions & 11 deletions src/json-crdt-extensions/peritext/block/Block.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import {printTree} from 'tree-dump/lib/printTree';
import {CONST, updateJson, updateNum} from '../../../json-hash';
import {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint';
import type {OverlayPoint} from '../overlay/OverlayPoint';
import {UndefEndIter, type UndefIterator} from '../../../util/iterator';
import {Inline} from './Inline';
import {formatType} from '../slice/util';
import {Range} from '../rga/Range';
import type {Point} from '../rga/Point';
import type {OverlayPoint} from '../overlay/OverlayPoint';
import type {Path} from '@jsonjoy.com/json-pointer';
import type {Printable} from 'tree-dump';
import type {Peritext} from '../Peritext';
import type {Stateful} from '../types';
import type {OverlayTuple} from '../overlay/types';
import type {JsonMlNode} from '../../../json-ml';

export interface IBlock {
readonly path: Path;
Expand All @@ -18,7 +21,7 @@ export interface IBlock {

type T = string;

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

public children: Block[] = [];
Expand All @@ -27,7 +30,11 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
public readonly txt: Peritext,
public readonly path: Path,
public readonly marker: MarkerOverlayPoint | undefined,
) {}
public start: Point,
public end: Point,
) {
super(txt.str, start, end);
}

/**
* @returns Stable unique identifier within a list of blocks. Used for React
Expand Down Expand Up @@ -98,9 +105,24 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
public texts0(): UndefIterator<Inline> {
const txt = this.txt;
const iterator = this.tuples0();
const blockStart = this.start;
const blockEnd = this.end;
let isFirst = true;
let next = iterator();
return () => {
const pair = iterator();
return pair && Inline.create(txt, pair[0], pair[1]);
const pair = next;
next = iterator();
if (!pair) return;
const [p1, p2] = pair;
let start: Point = p1;
let end: Point = p2;
if (isFirst) {
isFirst = false;
if (blockStart.cmpSpatial(p1) > 0) start = blockStart;
}
const isLast = !next;
if (isLast) if (blockEnd.cmpSpatial(p2) < 0) end = blockEnd;
return new Inline(txt, p1, p2, start, end);
};
}

Expand All @@ -111,14 +133,20 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
public text(): string {
let str = '';
const iterator = this.texts0();
let text = iterator();
while (text) {
str += text.text();
text = iterator();
let inline = iterator();
while (inline) {
str += inline.text();
inline = iterator();
}
return str;
}

// ------------------------------------------------------------------- export

toJsonMl(): JsonMlNode {
throw new Error('not implemented');
}

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

public hash: number = 0;
Expand All @@ -140,13 +168,13 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {

// ---------------------------------------------------------------- Printable

protected toStringName(): string {
public toStringName(): string {
return 'Block';
}
protected toStringHeader(): string {
const hash = `#${this.hash.toString(36).slice(-4)}`;
const tag = this.path.map((step) => formatType(step)).join('.');
const header = `${this.toStringName()} ${hash} ${tag}`;
const header = `${super.toString('', true)} ${hash} ${tag} `;
return header;
}

Expand Down
Loading

0 comments on commit 052b3c3

Please sign in to comment.