Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Peritext fragment #785

Merged
merged 20 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b155ae3
feat(json-crdt-extensions): 🎸 add more block elements
streamich Nov 15, 2024
7bf4f93
perf(json-crdt-extensions): ⚡️ do not pre-compute chunk slices
streamich Nov 15, 2024
340919a
feat(json-crdt-extensions): 🎸 add first implementation of Fragment
streamich Nov 15, 2024
5d90876
fix(json-crdt-peritext-ui): 🐛 correct React warnings
streamich Nov 15, 2024
3d2ef77
feat(json-crdt-extensions): 🎸 improve Fragment code setup
streamich Nov 15, 2024
efd088a
feat(json-crdt-extensions): 🎸 cap all blocks to their range
streamich Nov 15, 2024
e8ee319
feat(json-crdt-extensions): 🎸 clamp Inline elements inside a Fragment
streamich Nov 15, 2024
1b57de2
test(json-crdt-extensions): 💍 improve fragment tests
streamich Nov 16, 2024
6530aaf
test: 💍 fix up all tests
streamich Nov 16, 2024
02b9581
feat(json-crdt-extensions): 🎸 create constant enum for slice types
streamich Nov 16, 2024
bdf4deb
feat(json-crdt-extensions): 🎸 add JSON markup language utilities
streamich Nov 16, 2024
cec7865
feat(json-crdt-extensions): 🎸 add JSON ML walk method
streamich Nov 16, 2024
f75284d
feat(json-crdt-extensions): 🎸 setup export methods
streamich Nov 16, 2024
88b93e4
refactor: 💡 cleanup JSON ML walker
streamich Nov 16, 2024
4e42f46
chore: 🤖 minor tweaks
streamich Nov 16, 2024
44df515
chore(json-crdt-extensions): 🤖 update comment
streamich Nov 22, 2024
df01d81
Merge remote-tracking branch 'origin/peritext-fragment' into peritext…
streamich Nov 22, 2024
5c1d753
style: 💄 fix linter warnings
streamich Nov 25, 2024
932375b
style: 💄 run formatter
streamich Nov 25, 2024
d0b09de
chore: 🤖 fix all build issues
streamich Nov 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading