fix circlic move-loops

This commit is contained in:
Kevin Jahns
2022-04-04 15:35:23 +02:00
parent b32f88cd40
commit e8ecc8f74b
4 changed files with 93 additions and 6 deletions

View File

@@ -44,13 +44,13 @@ export const getMovedCoords = (moved, tr) => {
}
/**
* @param {Transaction} tr
* @param {ContentMove} moved
* @param {Item} movedItem
* @param {Set<Item>} trackedMovedItems
* @param {Transaction} tr
* @return {boolean} true if there is a loop
*/
export const findMoveLoop = (moved, movedItem, trackedMovedItems, tr) => {
export const findMoveLoop = (tr, moved, movedItem, trackedMovedItems) => {
if (trackedMovedItems.has(movedItem)) {
return true
}
@@ -60,10 +60,13 @@ export const findMoveLoop = (moved, movedItem, trackedMovedItems, tr) => {
*/
let { start, end } = getMovedCoords(moved, tr)
while (start !== end && start != null) {
if (start.deleted && start.moved === movedItem && start.content.constructor === ContentMove) {
if (findMoveLoop(start.content, start, trackedMovedItems, tr)) {
return true
}
if (
!start.deleted &&
start.moved === movedItem &&
start.content.constructor === ContentMove &&
findMoveLoop(tr, start.content, start, trackedMovedItems)
) {
return true
}
start = start.right
}
@@ -171,6 +174,10 @@ export class ContentMove {
transaction.prevMoved.set(start, prevMove)
}
start.moved = item
if (!start.deleted && start.content.constructor === ContentMove && findMoveLoop(transaction, start.content, start, new Set([item]))) {
item.deleteAsCleanup(transaction)
return
}
} else if (currMoved != null) {
/** @type {ContentMove} */ (currMoved.content).overrides.add(item)
}

View File

@@ -119,6 +119,7 @@ export const splitItem = (transaction, leftItem, diff) => {
}
leftItem.length = diff
if (leftItem.moved) {
rightItem.moved = leftItem.moved
const m = transaction.prevMoved.get(leftItem)
if (m) {
transaction.prevMoved.set(rightItem, m)
@@ -534,6 +535,24 @@ export class Item extends AbstractStruct {
if (this.parentSub === null && this.countable && !this.deleted) {
/** @type {AbstractType<any>} */ (this.parent)._length += this.length
}
// check if this item is in a moved range
if ((this.left && this.left.moved) || (this.right && this.right.moved)) {
const leftMoved = this.left && this.left.moved && /** @type {ContentMove} */ (this.left.moved.content)
const rightMoved = this.right && this.right.moved && /** @type {ContentMove} */ (this.right.moved.content)
if (leftMoved === rightMoved) {
this.moved = /** @type {Item} */ (this.left).moved
} else if (
(leftMoved != null && !leftMoved.isCollapsed()) ||
(rightMoved != null && !rightMoved.isCollapsed())
) {
// We know that this item is on the edge of a moved range.
// @todo Instead, we could check to which moved-range this item belongs
// This approach (reintegration) is pretty expensive in some scenarios
leftMoved && leftMoved.integrate(transaction, /** @type {any} */ (this.left).moved)
rightMoved && rightMoved.integrate(transaction, /** @type {any} */ (this.right).moved)
}
}
addStruct(transaction.doc.store, this)
this.content.integrate(transaction, this)
// add parent to transaction.changed
@@ -644,6 +663,19 @@ export class Item extends AbstractStruct {
}
}
/**
* Similar to `this.delete(tr)`, but additionally ensures
* that the deleted range is broadcasted using a different
* origin/source in a separate update event, so that
* the providers don't filter this message.
*
* @param {Transaction} transaction
*/
deleteAsCleanup (transaction) {
this.delete(transaction)
addToDeleteSet(transaction.cleanupDeletions, this.id.client, this.id.clock, this.length)
}
/**
* @param {StructStore} store
* @param {boolean} parentGCd