made simple one-time move work

This commit is contained in:
Kevin Jahns
2021-12-06 13:23:01 +01:00
parent d314c3e1a6
commit fc5e36158f
7 changed files with 258 additions and 102 deletions

View File

@@ -11,7 +11,7 @@ import {
ContentType,
ContentDoc,
Doc,
ID, AbstractContent, ContentMove, Transaction, Item, AbstractType // eslint-disable-line
RelativePosition, ID, AbstractContent, ContentMove, Transaction, Item, AbstractType // eslint-disable-line
} from '../internals.js'
const lengthExceeded = error.create('Length exceeded!')
@@ -122,8 +122,8 @@ export class ListIterator {
len += this.rel
this.rel = 0
}
while (item && !this.reachedEnd && (len > 0 || (len === 0 && (!item.countable || item.deleted)))) {
if (item.countable && !item.deleted && item.moved === this.currMove) {
while (item && !this.reachedEnd && (len > 0 || (len === 0 && (!item.countable || item.deleted || item === this.currMoveEnd)))) {
if (item.countable && !item.deleted && item.moved === this.currMove && len > 0) {
len -= item.length
if (len < 0) {
this.rel = item.length + len
@@ -228,10 +228,10 @@ export class ListIterator {
_slice (tr, len, value, slice, concat) {
this.index += len
while (len > 0 && !this.reachedEnd) {
while (this.nextItem && this.nextItem.countable && !this.reachedEnd && len > 0) {
if (!this.nextItem.deleted) {
while (this.nextItem && this.nextItem.countable && !this.reachedEnd && len > 0 && this.nextItem !== this.currMoveEnd) {
if (!this.nextItem.deleted && this.nextItem.moved === this.currMove) {
const item = this.nextItem
const slicedContent = slice(this.nextItem.content, this.rel, len)
const slicedContent = slice(item.content, this.rel, len)
len -= slicedContent.length
value = concat(value, slicedContent)
if (item.length !== slicedContent.length) {
@@ -268,18 +268,16 @@ export class ListIterator {
const sm = this.type._searchMarker
let item = this.nextItem
while (len > 0 && !this.reachedEnd) {
while (item && item.countable && !this.reachedEnd && len > 0) {
if (!item.deleted) {
if (this.rel > 0) {
item = getItemCleanStart(tr, createID(item.id.client, item.id.clock + this.rel))
this.rel = 0
}
if (len < item.length) {
getItemCleanStart(tr, createID(item.id.client, item.id.clock + len))
}
len -= item.length
item.delete(tr)
while (item && !item.deleted && item.countable && !this.reachedEnd && len > 0) {
if (this.rel > 0) {
item = getItemCleanStart(tr, createID(item.id.client, item.id.clock + this.rel))
this.rel = 0
}
if (len < item.length) {
getItemCleanStart(tr, createID(item.id.client, item.id.clock + len))
}
len -= item.length
item.delete(tr)
if (item.right) {
item = item.right
} else {
@@ -300,12 +298,8 @@ export class ListIterator {
/**
* @param {Transaction} tr
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} content
*/
insertArrayValue (tr, content) {
/**
* @type {Item | null}
*/
_splitRel (tr) {
if (this.rel > 0) {
/**
* @type {ID}
@@ -314,6 +308,14 @@ export class ListIterator {
this.nextItem = getItemCleanStart(tr, createID(itemid.client, itemid.clock + this.rel))
this.rel = 0
}
}
/**
* @param {Transaction} tr
* @param {Array<AbstractContent>} content
*/
insertContents (tr, content) {
this._splitRel(tr)
const sm = this.type._searchMarker
const parent = this.type
const store = tr.doc.store
@@ -327,18 +329,55 @@ export class ListIterator {
* @type {Item | null}
*/
let left = this.left
content.forEach(c => {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, c)
left.integrate(tr, 0)
})
if (right === null && left !== null) {
this.nextItem = left
this.reachedEnd = true
} else {
this.nextItem = right
}
if (sm) {
updateMarkerChanges(sm, this.index, content.length, this)
}
}
/**
* @param {Transaction} tr
* @param {RelativePosition} start
* @param {RelativePosition} end
*/
insertMove (tr, start, end) {
this.insertContents(tr, [new ContentMove(start, end, 1)]) // @todo adjust priority
// @todo is there a better alrogirthm to update searchmarkers? We could simply remove the markers that are in the updated range.
// Also note that searchmarkers are updated in insertContents as well.
const sm = this.type._searchMarker
if (sm) sm.length = 0
}
/**
* @param {Transaction} tr
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} values
*/
insertArrayValue (tr, values) {
this._splitRel(tr)
/**
* @type {Array<AbstractContent>}
*/
const contents = []
/**
* @type {Array<Object|Array<any>|number|null>}
*/
let jsonContent = []
const packJsonContent = () => {
if (jsonContent.length > 0) {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent))
left.integrate(tr, 0)
contents.push(new ContentAny(jsonContent))
jsonContent = []
}
}
content.forEach(c => {
values.forEach(c => {
if (c === null) {
jsonContent.push(c)
} else {
@@ -355,17 +394,14 @@ export class ListIterator {
switch (c.constructor) {
case Uint8Array:
case ArrayBuffer:
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
left.integrate(tr, 0)
contents.push(new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
break
case Doc:
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
left.integrate(tr, 0)
contents.push(new ContentDoc(/** @type {Doc} */ (c)))
break
default:
if (c instanceof AbstractType) {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
left.integrate(tr, 0)
contents.push(new ContentType(c))
} else {
throw new Error('Unexpected content type in insert operation')
}
@@ -374,16 +410,8 @@ export class ListIterator {
}
})
packJsonContent()
if (right === null && left !== null) {
this.nextItem = left
this.reachedEnd = true
} else {
this.nextItem = right
}
if (sm) {
updateMarkerChanges(sm, this.index, content.length, this)
}
this.index += content.length
this.insertContents(tr, contents)
this.index += values.length
}
/**

View File

@@ -1,7 +1,8 @@
import {
isDeleted,
Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
getMovedCoords,
ContentMove, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
} from '../internals.js'
import * as set from 'lib0/set'
@@ -153,62 +154,105 @@ export class YEvent {
get changes () {
let changes = this._changes
if (changes === null) {
const target = this.target
const added = set.create()
const deleted = set.create()
/**
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
*/
const delta = []
changes = {
added,
deleted,
delta,
keys: this.keys
}
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
if (changed.has(null)) {
this.transaction.doc.transact(tr => {
const target = this.target
const added = set.create()
const deleted = set.create()
/**
* @type {any}
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
*/
let lastOp = null
const packOp = () => {
if (lastOp) {
delta.push(lastOp)
}
const delta = []
changes = {
added,
deleted,
delta,
keys: this.keys
}
for (let item = target._start; item !== null; item = item.right) {
if (item.deleted) {
if (this.deletes(item) && !this.adds(item)) {
if (lastOp === null || lastOp.delete === undefined) {
packOp()
lastOp = { delete: 0 }
}
lastOp.delete += item.length
deleted.add(item)
} // else nop
} else {
if (this.adds(item)) {
if (lastOp === null || lastOp.insert === undefined) {
packOp()
lastOp = { insert: [] }
}
lastOp.insert = lastOp.insert.concat(item.content.getContent())
added.add(item)
} else {
if (lastOp === null || lastOp.retain === undefined) {
packOp()
lastOp = { retain: 0 }
}
lastOp.retain += item.length
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
if (changed.has(null)) {
/**
* @type {Array<{ end: Item | null, move: Item | null, isNew : boolean }>}
*/
const movedStack = []
/**
* @type {Item | null}
*/
let currMove = null
/**
* @type {boolean}
*/
let currMoveIsNew = false
/**
* @type {Item | null}
*/
let currMoveEnd = null
/**
* @type {any}
*/
let lastOp = null
const packOp = () => {
if (lastOp) {
delta.push(lastOp)
}
}
for (let item = target._start; item !== null;) {
if (item === currMoveEnd) {
item = currMove
const { end, move, isNew } = movedStack.pop() || { end: null, move: null, isNew: false }
currMoveIsNew = isNew
currMoveEnd = end
currMove = move
} else if (item.content.constructor === ContentMove) {
if (item.moved === currMove) {
movedStack.push({ end: currMoveEnd, move: currMove, isNew: currMoveIsNew })
const { start, end } = getMovedCoords(item.content, tr)
currMove = item
currMoveEnd = end
currMoveIsNew = this.adds(item)
item = start
continue // do not move to item.right
}
} else if (item.moved !== currMove) {
if (!currMoveIsNew && item.countable && !this.adds(item)) {
if (lastOp === null || lastOp.delete === undefined) {
packOp()
lastOp = { delete: 0 }
}
lastOp.delete += item.length
}
} else if (item.deleted) {
if (!currMoveIsNew && this.deletes(item) && !this.adds(item)) {
if (lastOp === null || lastOp.delete === undefined) {
packOp()
lastOp = { delete: 0 }
}
lastOp.delete += item.length
deleted.add(item)
}
} else {
if (currMoveIsNew || this.adds(item)) {
if (lastOp === null || lastOp.insert === undefined) {
packOp()
lastOp = { insert: [] }
}
lastOp.insert = lastOp.insert.concat(item.content.getContent())
added.add(item)
} else {
if (lastOp === null || lastOp.retain === undefined) {
packOp()
lastOp = { retain: 0 }
}
lastOp.retain += item.length
}
}
item = /** @type {Item} */ (item).right
}
if (lastOp !== null && lastOp.retain === undefined) {
packOp()
}
}
if (lastOp !== null && lastOp.retain === undefined) {
packOp()
}
}
this._changes = changes
this._changes = changes
})
}
return /** @type {any} */ (changes)
}