UndoManager fixes

This commit is contained in:
Kevin Jahns 2019-06-24 23:04:53 +02:00
parent 952a9b2c41
commit e376b5d472
4 changed files with 70 additions and 27 deletions

View File

@ -42,5 +42,6 @@ export {
iterateDeletedStructs, iterateDeletedStructs,
applyUpdate, applyUpdate,
encodeStateAsUpdate, encodeStateAsUpdate,
encodeStateVector encodeStateVector,
UndoManager
} from './internals.js' } from './internals.js'

View File

@ -33,6 +33,31 @@ import * as maplib from 'lib0/map.js'
import * as set from 'lib0/set.js' import * as set from 'lib0/set.js'
import * as binary from 'lib0/binary.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. * 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) { if (leftItem.keep) {
rightItem.keep = true 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) // update left (do not set leftItem.rightOrigin as it will lead to problems when syncing)
leftItem.right = rightItem leftItem.right = rightItem
// update right // update right
@ -106,7 +134,7 @@ export const splitItem = (transaction, leftItem, diff) => {
*/ */
export const redoItem = (transaction, item, redoitems) => { export const redoItem = (transaction, item, redoitems) => {
if (item.redone !== null) { if (item.redone !== null) {
return item.redone return getItemCleanStart(transaction, transaction.doc.store, item.redone)
} }
let parentItem = item.parent._item let parentItem = item.parent._item
/** /**
@ -146,7 +174,7 @@ export const redoItem = (transaction, item, redoitems) => {
} }
if (parentItem !== null && parentItem.redone !== null) { if (parentItem !== null && parentItem.redone !== null) {
while (parentItem.redone !== null) { while (parentItem.redone !== null) {
parentItem = parentItem.redone parentItem = getItemCleanStart(transaction, transaction.doc.store, parentItem.redone)
} }
// find next cloned_redo items // find next cloned_redo items
while (left !== null) { while (left !== null) {
@ -156,7 +184,7 @@ export const redoItem = (transaction, item, redoitems) => {
let leftTrace = left let leftTrace = left
// trace redone until parent matches // trace redone until parent matches
while (leftTrace !== null && leftTrace.parent._item !== parentItem) { 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) { if (leftTrace !== null && leftTrace.parent._item === parentItem) {
left = leftTrace left = leftTrace
@ -171,7 +199,7 @@ export const redoItem = (transaction, item, redoitems) => {
let rightTrace = right let rightTrace = right
// trace redone until parent matches // trace redone until parent matches
while (rightTrace !== null && rightTrace.parent._item !== parentItem) { 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) { if (rightTrace !== null && rightTrace.parent._item === parentItem) {
right = rightTrace right = rightTrace
@ -188,7 +216,8 @@ export const redoItem = (transaction, item, redoitems) => {
item.parentSub, item.parentSub,
item.content.copy() item.content.copy()
) )
item.redone = redoneItem item.redone = redoneItem.id
keepItem(redoneItem)
redoneItem.integrate(transaction) redoneItem.integrate(transaction)
return redoneItem 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 * If this type's effect is reundone this type refers to the type that undid
* this operation. * this operation.
* @type {Item | null} * @type {ID | null}
*/ */
this.redone = null this.redone = null
/** /**

View File

@ -1,6 +1,5 @@
import { import {
getItem,
createID, createID,
writeID, writeID,
readID, readID,
@ -9,6 +8,7 @@ import {
findRootTypeKey, findRootTypeKey,
Item, Item,
ContentType, ContentType,
followRedone,
ID, Doc, AbstractType // eslint-disable-line ID, Doc, AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@ -222,19 +222,22 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
if (getState(store, rightID.client) <= rightID.clock) { if (getState(store, rightID.client) <= rightID.clock) {
return null return null
} }
const right = getItem(store, rightID) const res = followRedone(store, rightID)
const right = res.item
if (!(right instanceof Item)) { if (!(right instanceof Item)) {
return null 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 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 { } else {
if (tname !== null) { if (tname !== null) {
type = doc.get(tname) type = doc.get(tname)
@ -243,9 +246,9 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
// type does not exist yet // type does not exist yet
return null return null
} }
const struct = getItem(store, typeID) const { item } = followRedone(store, typeID)
if (struct instanceof Item && struct.content instanceof ContentType) { if (item instanceof Item && item.content instanceof ContentType) {
type = struct.content.type type = item.content.type
} else { } else {
// struct is garbage collected // struct is garbage collected
return null return null
@ -255,9 +258,6 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
} }
index = type._length index = type._length
} }
if (type._item !== null && type._item.deleted) {
return null
}
return createAbsolutePosition(type, index) return createAbsolutePosition(type, index)
} }

View File

@ -6,11 +6,14 @@ import {
redoItem, redoItem,
iterateStructs, iterateStructs,
isParentOf, isParentOf,
createID,
followRedone,
getItemCleanStart,
Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as time from 'lib0/time.js' import * as time from 'lib0/time.js'
import { Observable } from 'lib0/observable' import { Observable } from 'lib0/observable.js'
class StackItem { class StackItem {
/** /**
@ -59,17 +62,27 @@ const popStackItem = (undoManager, stack, eventType) => {
}) })
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(doc.clientID)) const structs = /** @type {Array<GC|Item>} */ (store.clients.get(doc.clientID))
iterateStructs(transaction, structs, stackItem.start, stackItem.len, struct => { 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))) { if (!struct.deleted && isParentOf(type, /** @type {Item} */ (struct))) {
struct.delete(transaction) struct.delete(transaction)
performedChange = true performedChange = true
} }
}) })
result = stackItem result = stackItem
if (result != null) {
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
}
} }
}, undoManager) }, undoManager)
if (result != null) {
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
}
return result return result
} }