Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0abc25e
feat(json-crdt-extensions): 🎸 add <kbd> inline annotation
streamich Nov 10, 2024
520389e
test(json-crdt-peritext-ui): 💍 make demo text larger
streamich Nov 10, 2024
464c9a6
fix(json-crdt-peritext-ui): 🐛 fixup caret rendering for spacious lines
streamich Nov 10, 2024
d348077
feat(json-crdt-peritext-ui): 🎸 improve selection and caret display
streamich Nov 10, 2024
0133eb8
fix(json-crdt-extensions): 🐛 on unite selection at current position, …
streamich Nov 10, 2024
2c1714b
fix(json-crdt-peritext-ui): 🐛 make sure collapsed cursors are proper …
streamich Nov 10, 2024
947b73d
feat(json-crdt-peritext-ui): 🎸 make anchors look flatter
streamich Nov 10, 2024
40cb826
feat(json-crdt-peritext-ui): 🎸 render score when typing
streamich Nov 10, 2024
4add638
feat(json-crdt-peritext-ui): 🎸 show score deltas and UT easter egg me…
streamich Nov 10, 2024
1f85886
feat(json-crdt-peritext-ui): 🎸 make +2 harder
streamich Nov 10, 2024
c37bf84
feat(json-crdt-peritext-ui): 🎸 tweak typing scoring parameters
streamich Nov 10, 2024
a4d77bd
feat(json-crdt-peritext-ui): 🎸 specify the "marker" event
streamich Nov 11, 2024
f779251
feat(json-crdt-peritext-ui): 🎸 implement initial "marker" event
streamich Nov 11, 2024
edfd647
feat(json-crdt-peritext-ui): 🎸 improve typing score display
streamich Nov 11, 2024
10bdf3d
feat(json-crdt-peritext-ui): 🎸 make score messages shake
streamich Nov 11, 2024
59ec4eb
feat(json-crdt-extensions): 🎸 improve deletion
streamich Nov 11, 2024
3a938fc
refactor(json-crdt-peritext-ui): 💡 separate caret score into its own …
streamich Nov 11, 2024
52001a9
feat(json-crdt-peritext-ui): 🎸 insert more type scoring phrases
streamich Nov 11, 2024
cdea38e
feat(json-crdt-extensions): 🎸 improve text insertion logic, especiall…
streamich Nov 11, 2024
397b57d
fix(json-crdt-peritext-ui): 🐛 correct types for event map
streamich Nov 11, 2024
3d7f58e
feat(json-crdt-peritext-ui): 🎸 do not show score on cursor movement
streamich Nov 11, 2024
071ec09
feat(json-crdt-peritext-ui): 🎸 keep score while actively moving cursor
streamich Nov 11, 2024
9fc675f
feat(json-crdt-peritext-ui): 🎸 keeps score while user performs actions
streamich Nov 11, 2024
02103b6
fix(json-crdt-extensions): 🐛 correctly delete when in range selection
streamich Nov 11, 2024
d9b3d4f
feat(json-crdt-extensions): 🎸 improve cursor collapsing logic
streamich Nov 11, 2024
ce233cc
feat(json-crdt-extensions): 🎸 improve block split insertion logic
streamich Nov 11, 2024
1e86004
perf(json-crdt-extensions): ⚡️ just cursor directly to insertion end
streamich Nov 11, 2024
23fc35d
feat(json-crdt-peritext-ui): 🎸 minor tweaks
streamich Nov 11, 2024
c125938
feat(json-crdt-extensions): 🎸 print human-readable block type
streamich Nov 11, 2024
573a2ec
feat(json-crdt-peritext-ui): 🎸 add ability to render different block …
streamich Nov 11, 2024
3700013
feat(json-crdt-extensions): 🎸 add ability to find current block marker
streamich Nov 11, 2024
cd8f771
feat(json-crdt-peritext-ui): 🎸 add initial block type switching funct…
streamich Nov 11, 2024
9b46a4d
test: 💍 fix linter errors
streamich Nov 11, 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
Original file line number Diff line number Diff line change
Expand Up @@ -220,17 +220,17 @@ runInlineSlicesTests('text with block split', (editor: Editor) => {
runInlineSlicesTests('text with deletes', (editor: Editor) => {
editor.insert('lmXXXnwYxyz');
editor.cursor.setAt(2, 3);
editor.cursor.del();
editor.del();
editor.cursor.setAt(3);
editor.insert('opqrstuv');
editor.cursor.setAt(12, 1);
editor.cursor.del();
editor.del();
editor.cursor.setAt(0);
editor.insert('ab1c3defghijk4444');
editor.cursor.setAt(2, 1);
editor.cursor.del();
editor.del();
editor.cursor.setAt(3, 1);
editor.cursor.del();
editor.del();
editor.cursor.setAt(11, 4);
editor.cursor.del();
editor.del();
});
14 changes: 7 additions & 7 deletions src/json-crdt-extensions/peritext/__tests__/Peritext.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const run = (setup: () => Kit) => {
const {peritext, model} = setup();
const {editor} = peritext;
expect(editor.cursor.isCollapsed()).toBe(true);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('hello world');
});
Expand All @@ -119,7 +119,7 @@ const run = (setup: () => Kit) => {
const {editor} = peritext;
editor.cursor.setAt(2, 3);
expect(editor.cursor.isCollapsed()).toBe(false);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('he world');
});
Expand All @@ -129,12 +129,12 @@ const run = (setup: () => Kit) => {
const {editor} = peritext;
peritext.editor.cursor.setAt(0, 1);
expect(editor.cursor.isCollapsed()).toBe(false);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('ello world');
editor.cursor.setAt(0, 1);
expect(editor.cursor.isCollapsed()).toBe(false);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('llo world');
});
Expand All @@ -144,12 +144,12 @@ const run = (setup: () => Kit) => {
const {editor} = peritext;
editor.cursor.setAt(peritext.str.length() - 1, 1);
expect(editor.cursor.isCollapsed()).toBe(false);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('hello worl');
peritext.editor.cursor.setAt(peritext.str.length() - 1, 1);
expect(editor.cursor.isCollapsed()).toBe(false);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('hello wor');
});
Expand All @@ -159,7 +159,7 @@ const run = (setup: () => Kit) => {
const {editor} = peritext;
editor.cursor.setAt(0, peritext.str.length());
expect(editor.cursor.isCollapsed()).toBe(false);
editor.cursor.collapse();
editor.collapseCursors();
expect(editor.cursor.isCollapsed()).toBe(true);
expect((model.view() as any).text).toBe('');
editor.insert('abc');
Expand Down
3 changes: 2 additions & 1 deletion src/json-crdt-extensions/peritext/block/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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 type {Path} from '@jsonjoy.com/json-pointer';
import type {Printable} from 'tree-dump';
import type {Peritext} from '../Peritext';
Expand Down Expand Up @@ -144,7 +145,7 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
}
protected toStringHeader(): string {
const hash = `#${this.hash.toString(36).slice(-4)}`;
const tag = `<${this.path.join('.')}>`;
const tag = this.path.map((step) => formatType(step)).join('.');
const header = `${this.toStringName()} ${hash} ${tag}`;
return header;
}
Expand Down
59 changes: 5 additions & 54 deletions src/json-crdt-extensions/peritext/editor/Cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,60 +71,11 @@ export class Cursor<T = string> extends PersistedSlice<T> {
}
}

/**
* Ensures there is no range selection. If user has selected a range,
* the contents is removed and the cursor is set at the start of the range as cursor.
*
* @todo If block boundaries are withing the range, remove the blocks.
* @todo Stress test this method.
*
* @returns Returns the cursor position after the operation.
*/
public collapse(): void {
const deleted = this.txt.delStr(this);
if (deleted) this.collapseToStart();
}

/**
* 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.
*/
public insert(text: string): void {
if (!text) return;
this.collapse();
const after = this.start.clone();
after.refAfter();
const textId = this.txt.ins(after.id, text);
const shift = text.length - 1;
this.setAfter(shift ? tick(textId, shift) : textId);
}

/**
* Deletes the given number of characters from the current caret position.
* Negative values delete backwards. If the cursor selects a range, the
* range is removed and the cursor is set at the start of the range.
*
* @param step Number of characters to delete. Negative values delete
* backwards.
*/
public del(step: number = -1): void {
if (!this.isCollapsed()) {
this.collapse();
return;
}
const point1 = this.start.clone();
const point2 = point1.clone();
if (step > 0) point2.step(1);
else if (step < 0) point1.step(-1);
else if (step === 0) {
point1.step(-1);
point2.step(1);
}
const txt = this.txt;
const range = txt.range(point1, point2);
txt.delStr(range);
point1.refAfter();
this.set(point1);
public collapseToStart(anchorSide: CursorAnchor = CursorAnchor.Start): void {
const start = this.start.clone();
start.refAfter();
const end = start.clone();
this.set(start, end, anchorSide);
}

// ---------------------------------------------------------------- Printable
Expand Down
91 changes: 64 additions & 27 deletions src/json-crdt-extensions/peritext/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import {UndefEndIter, type UndefIterator} from '../../../util/iterator';
import {PersistedSlice} from '../slice/PersistedSlice';
import {ValueSyncStore} from '../../../util/events/sync-store';
import {formatType} from '../slice/util';
import type {CommonSliceType} from '../slice';
import {CommonSliceType, type SliceType} from '../slice';
import type {ChunkSlice} from '../util/ChunkSlice';
import type {Peritext} from '../Peritext';
import type {Point} from '../rga/Point';
import type {Range} from '../rga/Range';
import type {CharIterator, CharPredicate, Position, TextRangeUnit} from './types';
import type {Printable} from 'tree-dump';
import {tick} from '../../../json-crdt-patch';

/**
* For inline boolean ("Overwrite") slices, both range endpoints should be
Expand Down Expand Up @@ -123,16 +124,46 @@ export class Editor<T = string> implements Printable {
for (let cursor: Cursor<T> | undefined, i = this.cursors0(); (cursor = i()); ) this.delCursor(cursor);
}

/**
* Ensures there is no range selection. If user has selected a range,
* the contents is removed and the cursor is set at the start of the range
* as caret.
*/
public collapseCursor(cursor: Cursor<T>): void {
this.delRange(cursor);
cursor.collapseToStart();
}

public collapseCursors(): void {
for (let i = this.cursors0(), cursor = i(); cursor; cursor = i()) this.collapseCursor(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.
*/
public insert0(cursor: Cursor<T>, text: string): void {
if (!text) return;
if (!cursor.isCollapsed()) this.delRange(cursor);
const after = cursor.start.clone();
after.refAfter();
const txt = this.txt;
const textId = txt.ins(after.id, text);
const shift = text.length - 1;
const point = txt.point(shift ? tick(textId, shift) : textId, Anchor.After);
cursor.set(point, point, CursorAnchor.Start);
}

/**
* Inserts text at the cursor positions and collapses cursors, if necessary.
* The applies any pending inline formatting to the inserted text.
*/
public insert(text: string): void {
if (!this.hasCursor()) this.addCursor();
for (let cursor: Cursor<T> | undefined, i = this.cursors0(); (cursor = i()); ) {
cursor.insert(text);
this.insert0(cursor, text);
const pending = this.pending.value;
if (pending.size) {
this.pending.next(new Map());
Expand All @@ -149,7 +180,16 @@ export class Editor<T = string> implements Printable {
* select a range, deletes the whole range.
*/
public del(step: number = -1): void {
this.forCursor((cursor) => cursor.del(step));
this.delete(step, 'char');
}

public delRange(range: Range<T>): void {
const txt = this.txt;
const overlay = txt.overlay;
const contained = overlay.findContained(range);
for (const slice of contained)
if (slice instanceof PersistedSlice && slice.behavior !== SliceBehavior.Cursor) slice.del();
txt.delStr(range);
}

/**
Expand All @@ -160,9 +200,10 @@ export class Editor<T = string> implements Printable {
* @param unit A unit of deletion: "char", "word", "line".
*/
public delete(step: number, unit: 'char' | 'word' | 'line'): void {
this.forCursor((cursor) => {
const txt = this.txt;
for (let i = this.cursors0(), cursor = i(); cursor; cursor = i()) {
if (!cursor.isCollapsed()) {
cursor.collapse();
this.collapseCursor(cursor);
return;
}
let point1 = cursor.start.clone();
Expand All @@ -173,12 +214,11 @@ export class Editor<T = string> implements Printable {
point1 = this.skip(point1, -1, unit);
point2 = this.skip(point2, 1, unit);
}
const txt = this.txt;
const range = txt.range(point1, point2);
txt.delStr(range);
this.delRange(range);
point1.refAfter();
cursor.set(point1);
});
}
}

// ----------------------------------------------------------------- movement
Expand Down Expand Up @@ -494,7 +534,7 @@ export class Editor<T = string> implements Printable {
public select(unit: TextRangeUnit): void {
this.forCursor((cursor) => {
const range = this.range(cursor.start, unit);
if (range) cursor.setRange(range);
if (range) cursor.set(range.start, range.end, CursorAnchor.Start);
else this.delCursors;
});
}
Expand All @@ -506,14 +546,6 @@ export class Editor<T = string> implements Printable {

// --------------------------------------------------------------- formatting

protected getSliceStore(slice: PersistedSlice<T>): EditorSlices<T> | undefined {
const sid = slice.id.sid;
if (sid === this.saved.slices.set.doc.clock.sid) return this.saved;
if (sid === this.extra.slices.set.doc.clock.sid) return this.extra;
if (sid === this.local.slices.set.doc.clock.sid) return this.local;
return;
}

protected toggleRangeExclFmt(
range: Range<T>,
type: CommonSliceType | string | number,
Expand All @@ -527,12 +559,7 @@ export class Editor<T = string> implements Printable {
const needToRemoveFormatting = complete.has(type);
makeRangeExtendable(range);
const contained = overlay.findContained(range);
for (const slice of contained) {
if (slice instanceof PersistedSlice && slice.type === type) {
const deletionStore = this.getSliceStore(slice);
if (deletionStore) deletionStore.del(slice.id);
}
}
for (const slice of contained) if (slice instanceof PersistedSlice && slice.type === type) slice.del();
if (needToRemoveFormatting) {
overlay.refresh();
const [complete2, partial2] = overlay.stat(range, 1e6);
Expand Down Expand Up @@ -584,10 +611,8 @@ export class Editor<T = string> implements Printable {
switch (slice.behavior) {
case SliceBehavior.One:
case SliceBehavior.Many:
case SliceBehavior.Erase: {
const deletionStore = this.getSliceStore(slice);
if (deletionStore) deletionStore.del(slice.id);
}
case SliceBehavior.Erase:
slice.del();
}
}
}
Expand All @@ -613,6 +638,18 @@ export class Editor<T = string> implements Printable {
}
}

public split(type?: SliceType, data?: unknown, slices: EditorSlices<T> = this.saved): void {
for (let i = this.cursors0(), cursor = i(); cursor; cursor = i()) {
this.collapseCursor(cursor);
if (type === void 0) {
// TODO: detect current block type
type = CommonSliceType.p;
}
slices.insMarker(type, data);
cursor.move(1);
}
}

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

public point(at: Position<T>): Point<T> {
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 @@ -35,7 +35,7 @@ export class EditorSlices<T = string> {

public insMarker(type: SliceType, data?: unknown, separator?: string): MarkerSlice<T>[] {
return this.insAtCursors((cursor) => {
cursor.collapse();
this.txt.editor.collapseCursor(cursor);
const after = cursor.start.clone();
after.refAfter();
const marker = this.slices.insMarkerAfter(after.id, type, data, separator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class MarkerOverlayPoint<T = string> extends OverlayPoint<T> implements H
// ---------------------------------------------------------------- Printable

public toStringName(): string {
return 'OverlayPoint';
return 'MarkerOverlayPoint';
}

public toStringHeader(tab: string, lite?: boolean): string {
Expand Down
Loading
Loading