send some information about text effects to onevent so we can do better cursor tracking

This commit is contained in:
Charlotte Som 2025-03-05 13:11:59 +00:00
parent 916a5b46f3
commit 2263b23634
2 changed files with 40 additions and 12 deletions

View file

@ -8,9 +8,16 @@ const textarea = document.querySelector("textarea")!;
const pt = new PlainTextORDT();
let me: string | undefined = undefined;
pt.onevent = event => {
pt.onevent = (event, textIndex, affectedLength) => {
if (event.at[0] === me) return;
let start = textarea.selectionStart,
end = textarea.selectionEnd;
if (textIndex <= start) start += affectedLength;
if (textIndex <= end) end += affectedLength;
textarea.value = pt.render()[0];
textarea.setSelectionRange(start, end);
};
const socket = new WebSocket("/api/connect");

View file

@ -9,7 +9,11 @@ export type PlainTextOperation =
| { type: "delete" };
export class PlainTextORDT extends CausalTree<PlainTextOperation> {
onevent?: (op: CausalTreeOp<PlainTextOperation>) => void;
onevent?: (
op: CausalTreeOp<PlainTextOperation>,
textIndex: number,
affectedLength: number,
) => void;
// caches for insert op <=> text transformation,
// all these arrays should be the same length (SoA)
@ -21,12 +25,13 @@ export class PlainTextORDT extends CausalTree<PlainTextOperation> {
override apply(op: AnyCausalTreeOp<PlainTextOperation>): number {
const opIdx = super.apply(op);
op = this.operations[opIdx];
this.#applyCacheUpdate(op, opIdx);
this.onevent?.(op);
const [idx, len] = this.#applyCacheUpdate(op, opIdx);
this.onevent?.(op, idx, len);
return opIdx;
}
#applyCacheUpdate(op: CausalTreeOp<PlainTextOperation>, opIdx: number) {
#applyCacheUpdate(op: CausalTreeOp<PlainTextOperation>, opIdx: number): [number, number] {
for (let i = 0; i < this.opIndexCache.length; i++) {
if (this.opIndexCache[i] >= opIdx) {
this.opIndexCache[i] += 1;
@ -37,20 +42,36 @@ export class PlainTextORDT extends CausalTree<PlainTextOperation> {
const parentCacheIdx = this.timestamps.findIndex(it => it === parentTimestamp);
if (op.type === "insert") {
let idx = parentCacheIdx + 1;
for (; idx < this.timestamps.length; idx++) {
const curr = this.timestamps[idx];
let cacheIdx = parentCacheIdx + 1;
for (; cacheIdx < this.timestamps.length; cacheIdx++) {
const curr = this.timestamps[cacheIdx];
if (timestampCompare(curr, op.at) < 0) break;
}
this.sequences.splice(idx, 0, op.sequence);
this.timestamps.splice(idx, 0, op.at);
this.deleted.splice(idx, 0, false);
this.opIndexCache.splice(idx, 0, opIdx);
this.sequences.splice(cacheIdx, 0, op.sequence);
this.timestamps.splice(cacheIdx, 0, op.at);
this.deleted.splice(cacheIdx, 0, false);
this.opIndexCache.splice(cacheIdx, 0, opIdx);
let textIndex = 0;
for (let i = 0; i < cacheIdx; i++) {
if (this.deleted[i]) continue;
textIndex += this.sequences[i].length;
}
return [textIndex, op.sequence.length];
}
if (op.type === "delete" && parentCacheIdx !== -1) {
this.deleted[parentCacheIdx] = true;
let textIndex = 0;
for (let i = 0; i < parentCacheIdx; i++) {
if (this.deleted[i]) continue;
textIndex += this.sequences[i].length;
}
return [textIndex, -this.sequences[parentCacheIdx].length];
}
return [-1, 0];
}
findOpAtTextIndex(textIndex: number): number {