Skip to content

Commit

Permalink
feat(json-crdt-extensions): 🎸 improve slice state refresh and print l…
Browse files Browse the repository at this point in the history
…ayout
  • Loading branch information
streamich committed Apr 19, 2024
1 parent 12e4129 commit c482e64
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 34 deletions.
6 changes: 0 additions & 6 deletions src/json-crdt-extensions/peritext/Peritext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,6 @@ export class Peritext implements Printable {
return slice;
}

// ---------------------------------------------------------------- Deletions

public delSlice(sliceId: ITimestampStruct): void {
this.slices.del(sliceId);
}

/** Select a single character before a point. */
public findCharBefore(point: Point): Range | undefined {
if (point.anchor === Anchor.After) {
Expand Down
27 changes: 16 additions & 11 deletions src/json-crdt-extensions/peritext/slice/Cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {printTree} from '../../../util/print/printTree';
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
import type {Peritext} from '../Peritext';
import type {Slice} from './types';
import {updateNum} from '../../../json-hash';

export class Cursor extends Range<string> implements Slice {
public readonly behavior = SliceBehavior.Overwrite;
Expand Down Expand Up @@ -80,8 +81,8 @@ export class Cursor extends Range<string> implements Slice {
return false;
}

public data(): unknown {
return 1;
public data() {
return undefined;
}

public move(move: number): void {
Expand All @@ -91,19 +92,23 @@ export class Cursor extends Range<string> implements Slice {
end.move(move);
}

public toString(tab: string = ''): string {
const text = JSON.stringify(this.text());
const focusIcon = this.anchorSide === CursorAnchor.Start ? '.→|' : '|←.';
const main = `${this.constructor.name} ${super.toString(tab + ' ', true)} ${focusIcon}`;
return main + printTree(tab, [() => text]);
}

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

public hash: number = 0;

public refresh(): number {
// TODO: implement this ...
return this.hash;
let state = super.refresh();
state = updateNum(state, this.anchorSide);
this.hash = state;
return state;
}

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

public toString(tab: string = ''): string {
const text = JSON.stringify(this.text());
const focusIcon = this.anchorSide === CursorAnchor.Start ? '.→|' : '|←.';
const main = `${this.constructor.name} ${super.toString(tab + ' ', true)} ${focusIcon}`;
return main + printTree(tab, [() => text]);
}
}
13 changes: 8 additions & 5 deletions src/json-crdt-extensions/peritext/slice/PersistedSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export class PersistedSlice<T = string> extends Range<T> implements Slice<T>, St
public behavior: SliceBehavior;
public type: SliceType;

public data(): unknown | undefined {
return this.tuple.get(4)?.view();
public data(): JsonNode | undefined {
return this.tuple.get(4);
}

public del(): boolean {
Expand Down Expand Up @@ -127,8 +127,11 @@ export class PersistedSlice<T = string> extends Range<T> implements Slice<T>, St
// ---------------------------------------------------------------- Printable

public toString(tab: string = ''): string {
const tagNode = this.tagNode();
const header = `${this.constructor.name} ${super.toString(tab)}`;
return header + printTree(tab, [!tagNode ? null : (tab) => tagNode.toString(tab)]);
const data = this.data();
const header = `${this.constructor.name} ${super.toString(tab)}, ${this.behavior}, ${JSON.stringify(this.type)}`;
return header + printTree(tab, [
// !data ? null : (tab) => JSON.stringify(data.view()).replace(/([\{\[\:])/g, '$1 ').replace(/([\}\]])/g, ' $1'),
!data ? null : (tab) => JSON.stringify(data.view()),
]);
}
}
2 changes: 1 addition & 1 deletion src/json-crdt-extensions/peritext/slice/Slices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class Slices implements Stateful, Printable {
api.apply();
}

public delMany(slices: Slice[]): void {
public delSlices(slices: Slice[]): void {
const api = this.txt.model.api;
const spans: ITimespanStruct[] = [];
const length = slices.length;
Expand Down
39 changes: 29 additions & 10 deletions src/json-crdt-extensions/peritext/slice/__tests__/Slices.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Anchor} from '../../rga/constants';
import {SliceBehavior} from '../constants';

const setup = () => {
const model = Model.withLogicalClock();
const model = Model.withLogicalClock(12345678);
model.api.root({
text: '',
slices: [],
Expand Down Expand Up @@ -36,7 +36,7 @@ describe('.ins()', () => {
expect(slice.end).toStrictEqual(range.end);
expect(slice.behavior).toBe(SliceBehavior.Stack);
expect(slice.type).toBe('b');
expect(slice.data()).toStrictEqual({bold: true});
expect(slice.data()!.view()).toStrictEqual({bold: true});
});

test('can insert two slices', () => {
Expand All @@ -48,8 +48,8 @@ describe('.ins()', () => {
const slice2 = editor.insertSlice('i', {italic: true});
peritext.refresh();
expect(peritext.slices.size()).toBe(2);
expect(slice1.data()).toStrictEqual({bold: true});
expect(slice2.data()).toStrictEqual({italic: true});
expect(slice1.data()!.view()).toStrictEqual({bold: true});
expect(slice2.data()!.view()).toStrictEqual({italic: true});
});

test('updates hash on slice insert', () => {
Expand Down Expand Up @@ -85,7 +85,7 @@ describe('.ins()', () => {
});

test('can store all different slice range and metadata combinations', () => {
const {peritext, model} = setup();
const {peritext} = setup();
const r1 = peritext.range(peritext.pointAt(4, Anchor.Before), peritext.pointAt(6, Anchor.After));
const r2 = peritext.range(peritext.pointAt(2, Anchor.After), peritext.pointAt(8, Anchor.After));
const r3 = peritext.range(peritext.pointAt(2, Anchor.After), peritext.pointAt(8, Anchor.Before));
Expand All @@ -98,12 +98,13 @@ describe('.ins()', () => {
for (const type of types) {
for (const data of datas) {
for (const behavior of behaviors) {
const {peritext, model} = setup();
const slice = peritext.slices.ins(range, behavior, type, data);
expect(slice.start.cmp(range.start)).toBe(0);
expect(slice.end.cmp(range.end)).toBe(0);
expect(slice.behavior).toBe(behavior);
expect(slice.type).toStrictEqual(type);
expect(slice.data()).toStrictEqual(data);
expect(slice.data()?.view()).toStrictEqual(data);
const buf = model.toBinary();
const model2 = Model.fromBinary(buf);
const peritext2 = new Peritext(model2, model2.api.str(['text']).node, model2.api.arr(['slices']).node);
Expand All @@ -113,32 +114,50 @@ describe('.ins()', () => {
expect(slice2.end.cmp(range.end)).toBe(0);
expect(slice2.behavior).toBe(behavior);
expect(slice2.type).toStrictEqual(type);
expect(slice2.data()).toStrictEqual(data);
expect(slice2.data()?.view()).toStrictEqual(data);
}
}
}
}
});
});

describe('deletes', () => {
describe('.del()', () => {
test('can delete a slice', () => {
const {peritext} = setup();
const {editor} = peritext;
editor.cursor.setAt(6, 5);
const slice1 = editor.insertSlice('b', {bold: true});
peritext.refresh();
const hash1 = peritext.slices.hash;
expect(peritext.slices.size()).toBe(1);
peritext.slices.del(slice1.id);
peritext.refresh();
const hash2 = peritext.slices.hash;
expect(peritext.slices.size()).toBe(0);
expect(hash1).not.toBe(hash2);
});
});

describe('.delSlices()', () => {
test('can delete a slice', () => {
const {peritext} = setup();
const {editor} = peritext;
editor.cursor.setAt(6, 5);
const slice1 = editor.insertSlice('b', {bold: true});
console.log(slice1 + '');
peritext.refresh();
const hash1 = peritext.slices.hash;
expect(peritext.slices.size()).toBe(1);
peritext.delSlice(slice1.id);
peritext.slices.delSlices([slice1]);
peritext.refresh();
const hash2 = peritext.slices.hash;
expect(peritext.slices.size()).toBe(0);
expect(hash1).not.toBe(hash2);
});
});

describe('tag changes', () => {
describe('.refresh()', () => {
test('recomputes hash on tag change', () => {
const {peritext} = setup();
const {editor} = peritext;
Expand Down
3 changes: 2 additions & 1 deletion src/json-crdt-extensions/peritext/slice/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {Range} from '../rga/Range';
import type {SliceType, Stateful} from '../types';
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
import type {SliceBehavior} from './constants';
import type {JsonNode} from '../../../json-crdt/nodes';

export interface Slice<T = string> extends Range<T>, Stateful {
/**
Expand All @@ -27,7 +28,7 @@ export interface Slice<T = string> extends Range<T>, Stateful {
* High-level user-defined metadata of the slice, which accompanies the slice
* type.
*/
data(): unknown;
data(): JsonNode | undefined;

/**
* Whether the slice is deleted.
Expand Down

0 comments on commit c482e64

Please sign in to comment.