UNPKG

loro-codemirror

Version:
158 lines (157 loc) 5.31 kB
import { EditorSelection, StateEffect, StateField, } from "@codemirror/state"; import { EditorView, ViewUpdate } from "@codemirror/view"; import { Cursor, LoroDoc, LoroText, UndoManager, } from "loro-crdt"; import { loroSyncAnnotation } from "./sync.js"; export const undoEffect = StateEffect.define(); export const redoEffect = StateEffect.define(); export const undoManagerStateField = StateField.define({ create(state) { return undefined; }, update(value, transaction) { for (const effect of transaction.effects) { if (effect.is(undoEffect)) { if (value && value.canUndo()) { value.undo(); } } else if (effect.is(redoEffect)) { if (value && value.canRedo()) { value.redo(); } } } return value; }, }); export class UndoPluginValue { constructor(view, doc, undoManager, getTextFromDoc) { this.view = view; this.doc = doc; this.undoManager = undoManager; this.getTextFromDoc = getTextFromDoc; this.lastSelection = { anchor: undefined, head: undefined, }; this.sub = doc.subscribe((e) => { if (e.origin !== "undo") return; let changes = []; let pos = 0; for (let { diff, target } of e.events) { const text = this.getTextFromDoc(this.doc); // Skip if the event is not a text event if (diff.type !== "text") return; // Skip if the event is not for the current document if (target !== text.id) return; const textDiff = diff.diff; for (const delta of textDiff) { if (delta.insert) { changes.push({ from: pos, to: pos, insert: delta.insert, }); } else if (delta.delete) { changes.push({ from: pos, to: pos + delta.delete, }); pos += delta.delete; } else if (delta.retain != null) { pos += delta.retain; } } this.view.dispatch({ changes, annotations: [loroSyncAnnotation.of("undo")], }); } }); this.undoManager.setOnPop((isUndo, value, counterRange) => { var _a, _b; const anchor = (_a = value.cursors[0]) !== null && _a !== void 0 ? _a : undefined; const head = (_b = value.cursors[1]) !== null && _b !== void 0 ? _b : undefined; if (!anchor) return; setTimeout(() => { const anchorPos = this.doc.getCursorPos(anchor).offset; const headPos = head ? this.doc.getCursorPos(head).offset : anchorPos; const selection = EditorSelection.single(anchorPos, headPos); this.view.dispatch({ selection, effects: [EditorView.scrollIntoView(selection.ranges[0])], }); }, 0); }); this.undoManager.setOnPush((isUndo, counterRange) => { const cursors = []; let selection = this.lastSelection; if (!isUndo) { const stateSelection = this.view.state.selection.main; selection.anchor = this.getTextFromDoc(this.doc).getCursor(stateSelection.anchor); selection.head = this.getTextFromDoc(this.doc).getCursor(stateSelection.head); } if (selection.anchor) { cursors.push(selection.anchor); } if (selection.head) { cursors.push(selection.head); } return { value: null, cursors, }; }); } update(update) { if (update.selectionSet) { this.lastSelection = { anchor: this.getTextFromDoc(this.doc).getCursor(update.state.selection.main.anchor), head: this.getTextFromDoc(this.doc).getCursor(update.state.selection.main.head), }; } } destroy() { var _a; (_a = this.sub) === null || _a === void 0 ? void 0 : _a.call(this); this.sub = undefined; } } export const undo = (view) => { view.dispatch({ effects: [undoEffect.of(null)], }); return true; }; export const redo = (view) => { view.dispatch({ effects: [redoEffect.of(null)], }); return true; }; export const undoKeyMap = [ { key: "Mod-z", run: undo, preventDefault: true, }, { key: "Mod-y", mac: "Mod-Shift-z", run: redo, preventDefault: true, }, { key: "Mod-Shift-z", run: redo, preventDefault: true, }, ];