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,
applyUpdate,
encodeStateAsUpdate,
encodeStateVector
encodeStateVector,
UndoManager
} 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 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
/**

View File

@ -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)
}

View File

@ -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<GC|Item>} */ (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
}