Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 0 additions & 4 deletions src/json-crdt-extensions/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@ export const enum ExtensionId {
peritext = 2,
quill = 3,
}

export const enum Chars {
BlockSplitSentinel = '\n',
}
88 changes: 73 additions & 15 deletions src/json-crdt-extensions/peritext/Peritext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import {printTree} from 'sonic-forest/lib/print/printTree';
import {Anchor} from './rga/constants';
import {Point} from './rga/Point';
import {Range} from './rga/Range';
import {Editor} from './editor/Editor';
import {printTree} from '../../util/print/printTree';
import {ArrNode, StrNode} from '../../json-crdt/nodes';
import {Slices} from './slice/Slices';
import {type ITimestampStruct} from '../../json-crdt-patch/clock';
import {Overlay} from './overlay/Overlay';
import {Chars} from './constants';
import {interval} from '../../json-crdt-patch/clock';
import {CONST, updateNum} from '../../json-hash';
import type {ITimestampStruct} from '../../json-crdt-patch/clock';
import type {Model} from '../../json-crdt/model';
import type {Printable} from '../../util/print/types';
import type {StringChunk} from './util/types';
import type {SliceType} from './types';
import type {MarkerSlice} from './slice/MarkerSlice';

/**
* Context for a Peritext instance. Contains all the data and methods needed to
Expand All @@ -16,6 +23,7 @@ import type {Printable} from '../../util/print/types';
export class Peritext implements Printable {
public readonly slices: Slices;
public readonly editor: Editor;
public readonly overlay = new Overlay(this);

constructor(
public readonly model: Model,
Expand All @@ -30,7 +38,30 @@ export class Peritext implements Printable {
return this.model.api.wrap(this.str);
}

// ------------------------------------------------------------------- Points
/** @todo Find a better place for this function. */
public firstVisChunk(): StringChunk | undefined {
const str = this.str;
let curr = str.first();
if (!curr) return;
while (curr.del) {
curr = str.next(curr);
if (!curr) return;
}
return curr;
}

/** Select a single character before a point. */
public findCharBefore(point: Point): Range | undefined {
if (point.anchor === Anchor.After) {
const chunk = point.chunk();
if (chunk && !chunk.del) return this.range(this.point(point.id, Anchor.Before), point);
}
const id = point.prevId();
if (!id) return;
return this.range(this.point(id, Anchor.Before), this.point(id, Anchor.After));
}

// ------------------------------------------------------------------- points

/**
* Creates a point at a character ID.
Expand Down Expand Up @@ -81,7 +112,7 @@ export class Peritext implements Printable {
return this.point(this.str.id, Anchor.Before);
}

// ------------------------------------------------------------------- Ranges
// ------------------------------------------------------------------- ranges

/**
* Creates a range from two points. The points can be in any order.
Expand Down Expand Up @@ -117,7 +148,7 @@ export class Peritext implements Printable {
return Range.at(this.str, start, length);
}

// --------------------------------------------------------------- Insertions
// --------------------------------------------------------------------- text

/**
* Insert plain text at a view position in the text.
Expand Down Expand Up @@ -146,15 +177,37 @@ export class Peritext implements Printable {
return textId;
}

/** Select a single character before a point. */
public findCharBefore(point: Point): Range | undefined {
if (point.anchor === Anchor.After) {
const chunk = point.chunk();
if (chunk && !chunk.del) return this.range(this.point(point.id, Anchor.Before), point);
}
const id = point.prevId();
if (!id) return;
return this.range(this.point(id, Anchor.Before), this.point(id, Anchor.After));
// ------------------------------------------------------------------ markers

public insMarker(
after: ITimestampStruct,
type: SliceType,
data?: unknown,
char: string = Chars.BlockSplitSentinel,
): MarkerSlice {
const api = this.model.api;
const builder = api.builder;
const str = this.str;
/**
* We skip one clock cycle to prevent Block-wise RGA from merging adjacent
* characters. We want the marker chunk to always be its own distinct chunk.
*/
builder.nop(1);
const textId = builder.insStr(str.id, after, char[0]);
const point = this.point(textId, Anchor.Before);
const range = this.range(point, point);
return this.slices.insMarker(range, type, data);
}

/** @todo This can probably use .del() */
public delMarker(split: MarkerSlice): void {
const str = this.str;
const api = this.model.api;
const builder = api.builder;
const strChunk = split.start.chunk();
if (strChunk) builder.del(str.id, [interval(strChunk.id, 0, 1)]);
builder.del(this.slices.set.id, [interval(split.id, 0, 1)]);
api.apply();
}

// ---------------------------------------------------------------- Printable
Expand All @@ -169,6 +222,8 @@ export class Peritext implements Printable {
(tab) => this.str.toString(tab),
nl,
(tab) => this.slices.toString(tab),
nl,
(tab) => this.overlay.toString(tab),
])
);
}
Expand All @@ -178,6 +233,9 @@ export class Peritext implements Printable {
public hash: number = 0;

public refresh(): number {
return this.slices.refresh();
let state: number = CONST.START_STATE;
this.overlay.refresh();
state = updateNum(state, this.overlay.hash);
return (this.hash = state);
}
}
3 changes: 3 additions & 0 deletions src/json-crdt-extensions/peritext/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const enum Chars {
BlockSplitSentinel = '\n',
}
7 changes: 7 additions & 0 deletions src/json-crdt-extensions/peritext/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import {Anchor} from '../rga/constants';
import {SliceBehavior} from '../slice/constants';
import {tick, type ITimestampStruct} from '../../../json-crdt-patch/clock';
import {PersistedSlice} from '../slice/PersistedSlice';
import {Chars} from '../constants';
import type {Range} from '../rga/Range';
import type {Peritext} from '../Peritext';
import type {Printable} from '../../../util/print/types';
import type {Point} from '../rga/Point';
import type {SliceType} from '../types';
import type {MarkerSlice} from '../slice/MarkerSlice';

export class Editor implements Printable {
/**
Expand Down Expand Up @@ -132,4 +134,9 @@ export class Editor implements Printable {
public insertEraseSlice(type: SliceType, data?: unknown | ITimestampStruct): PersistedSlice {
return this.txt.slices.ins(this.cursor, SliceBehavior.Erase, type, data);
}

public insMarker(type: SliceType, data?: unknown): MarkerSlice {
const after = this.collapseSelection();
return this.txt.insMarker(after, type, data, Chars.BlockSplitSentinel);
}
}
Loading