From e376b5d47221d3864d51b0c1bfd0a24a61911f11 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Mon, 24 Jun 2019 23:04:53 +0200 Subject: [PATCH] UndoManager fixes --- src/index.js | 3 ++- src/structs/Item.js | 41 ++++++++++++++++++++++++++++++----- src/utils/RelativePosition.js | 32 +++++++++++++-------------- src/utils/UndoManager.js | 21 ++++++++++++++---- 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/index.js b/src/index.js index 8df553ed..73d4e76c 100644 --- a/src/index.js +++ b/src/index.js @@ -42,5 +42,6 @@ export { iterateDeletedStructs, applyUpdate, encodeStateAsUpdate, - encodeStateVector + encodeStateVector, + UndoManager } from './internals.js' diff --git a/src/structs/Item.js b/src/structs/Item.js index 9ca3db07..9d7e46fe 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -33,6 +33,31 @@ import * as maplib from 'lib0/map.js' import * as set from 'lib0/set.js' import * as binary from 'lib0/binary.js' +/** + * @param {StructStore} store + * @param {ID} id + * @return {{item:Item, diff:number}} + */ +export const followRedone = (store, id) => { + /** + * @type {ID|null} + */ + let nextID = id + let diff = 0 + let item + do { + if (diff > 0) { + nextID = createID(nextID.client, nextID.clock + diff) + } + item = getItem(store, nextID) + diff = nextID.clock - item.id.clock + nextID = item.redone + } while (nextID !== null) + return { + item, diff + } +} + /** * Make sure that neither item nor any of its parents is ever deleted. * @@ -77,6 +102,9 @@ export const splitItem = (transaction, leftItem, diff) => { if (leftItem.keep) { rightItem.keep = true } + if (leftItem.redone !== null) { + rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff) + } // update left (do not set leftItem.rightOrigin as it will lead to problems when syncing) leftItem.right = rightItem // update right @@ -106,7 +134,7 @@ export const splitItem = (transaction, leftItem, diff) => { */ export const redoItem = (transaction, item, redoitems) => { if (item.redone !== null) { - return item.redone + return getItemCleanStart(transaction, transaction.doc.store, item.redone) } let parentItem = item.parent._item /** @@ -146,7 +174,7 @@ export const redoItem = (transaction, item, redoitems) => { } if (parentItem !== null && parentItem.redone !== null) { while (parentItem.redone !== null) { - parentItem = parentItem.redone + parentItem = getItemCleanStart(transaction, transaction.doc.store, parentItem.redone) } // find next cloned_redo items while (left !== null) { @@ -156,7 +184,7 @@ export const redoItem = (transaction, item, redoitems) => { let leftTrace = left // trace redone until parent matches while (leftTrace !== null && leftTrace.parent._item !== parentItem) { - leftTrace = leftTrace.redone + leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, transaction.doc.store, leftTrace.redone) } if (leftTrace !== null && leftTrace.parent._item === parentItem) { left = leftTrace @@ -171,7 +199,7 @@ export const redoItem = (transaction, item, redoitems) => { let rightTrace = right // trace redone until parent matches while (rightTrace !== null && rightTrace.parent._item !== parentItem) { - rightTrace = rightTrace.redone + rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, transaction.doc.store, rightTrace.redone) } if (rightTrace !== null && rightTrace.parent._item === parentItem) { right = rightTrace @@ -188,7 +216,8 @@ export const redoItem = (transaction, item, redoitems) => { item.parentSub, item.content.copy() ) - item.redone = redoneItem + item.redone = redoneItem.id + keepItem(redoneItem) redoneItem.integrate(transaction) return redoneItem } @@ -254,7 +283,7 @@ export class Item extends AbstractStruct { /** * If this type's effect is reundone this type refers to the type that undid * this operation. - * @type {Item | null} + * @type {ID | null} */ this.redone = null /** diff --git a/src/utils/RelativePosition.js b/src/utils/RelativePosition.js index 24f30b96..d2f7eb10 100644 --- a/src/utils/RelativePosition.js +++ b/src/utils/RelativePosition.js @@ -1,6 +1,5 @@ import { - getItem, createID, writeID, readID, @@ -9,6 +8,7 @@ import { findRootTypeKey, Item, ContentType, + followRedone, ID, Doc, AbstractType // eslint-disable-line } from '../internals.js' @@ -222,19 +222,22 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { if (getState(store, rightID.client) <= rightID.clock) { return null } - const right = getItem(store, rightID) + const res = followRedone(store, rightID) + const right = res.item if (!(right instanceof Item)) { return null } - index = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock - let n = right.left - while (n !== null) { - if (!n.deleted && n.countable) { - index += n.length - } - n = n.left - } type = right.parent + if (type._item !== null && !type._item.deleted) { + index = right.deleted || !right.countable ? 0 : res.diff + let n = right.left + while (n !== null) { + if (!n.deleted && n.countable) { + index += n.length + } + n = n.left + } + } } else { if (tname !== null) { type = doc.get(tname) @@ -243,9 +246,9 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { // type does not exist yet return null } - const struct = getItem(store, typeID) - if (struct instanceof Item && struct.content instanceof ContentType) { - type = struct.content.type + const { item } = followRedone(store, typeID) + if (item instanceof Item && item.content instanceof ContentType) { + type = item.content.type } else { // struct is garbage collected return null @@ -255,9 +258,6 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { } index = type._length } - if (type._item !== null && type._item.deleted) { - return null - } return createAbsolutePosition(type, index) } diff --git a/src/utils/UndoManager.js b/src/utils/UndoManager.js index 54d6611d..737028ab 100644 --- a/src/utils/UndoManager.js +++ b/src/utils/UndoManager.js @@ -6,11 +6,14 @@ import { redoItem, iterateStructs, isParentOf, + createID, + followRedone, + getItemCleanStart, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line } from '../internals.js' import * as time from 'lib0/time.js' -import { Observable } from 'lib0/observable' +import { Observable } from 'lib0/observable.js' class StackItem { /** @@ -59,17 +62,27 @@ const popStackItem = (undoManager, stack, eventType) => { }) const structs = /** @type {Array} */ (store.clients.get(doc.clientID)) iterateStructs(transaction, structs, stackItem.start, stackItem.len, struct => { + if (struct instanceof Item && struct.redone !== null) { + let { item, diff } = followRedone(store, struct.id) + if (diff > 0) { + item = getItemCleanStart(transaction, store, struct.id) + } + if (item.length > stackItem.len) { + getItemCleanStart(transaction, store, createID(item.id.client, item.id.clock + stackItem.len)) + } + struct = item + } if (!struct.deleted && isParentOf(type, /** @type {Item} */ (struct))) { struct.delete(transaction) performedChange = true } }) result = stackItem + if (result != null) { + undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager]) + } } }, undoManager) - if (result != null) { - undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager]) - } return result }