diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index 91972316..dcacbb3a 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -55,11 +55,19 @@ export const useSearchMarker = (tr, yarray, index, f) => { searchMarker.push(fsm) } const diff = fsm.index - index + // @todo create fresh marker if diff > index if (diff > 0) { fsm.backward(tr, diff) } else { fsm.forward(tr, -diff) } + // @todo remove this tests + const otherTesting = new ListIterator(yarray) + otherTesting.forward(tr, index) + if (otherTesting.nextItem !== fsm.nextItem || otherTesting.index !== fsm.index || otherTesting.reachedEnd !== fsm.reachedEnd) { + throw new Error('udtirane') + } + const result = f(fsm) if (fsm.reachedEnd) { fsm.reachedEnd = false @@ -93,7 +101,7 @@ export const useSearchMarker = (tr, yarray, index, f) => { * @param {Array} searchMarker * @param {number} index * @param {number} len If insertion, len is positive. If deletion, len is negative. - * @param {ListIterator} origSearchMarker Do not update this searchmarker because it is the one we used to manipulate. + * @param {ListIterator|null} origSearchMarker Do not update this searchmarker because it is the one we used to manipulate. @todo !=null for improved perf in ytext */ export const updateMarkerChanges = (searchMarker, index, len, origSearchMarker) => { for (let i = searchMarker.length - 1; i >= 0; i--) { diff --git a/src/types/YText.js b/src/types/YText.js index 76b66de6..e327a640 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -27,6 +27,7 @@ import { updateMarkerChanges, ContentType, useSearchMarker, + findIndexCleanStart, ListIterator, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line } from '../internals.js' @@ -127,9 +128,27 @@ const findPosition = (transaction, parent, index) => { const currentAttributes = new Map() if (parent._searchMarker) { return useSearchMarker(transaction, parent, index, listIter => { + let left, right + if (listIter.rel > 0) { + // must exist because rel > 0 + const nextItem = /** @type {Item} */ (listIter.nextItem) + if (listIter.rel === nextItem.length) { + left = nextItem + right = left.right + } else { + const structs = /** @type {Array} */ (transaction.doc.store.clients.get(nextItem.id.client)) + const after = /** @type {Item} */ (structs[findIndexCleanStart(transaction, structs, nextItem.id.clock + listIter.rel)]) + listIter.nextItem = after + listIter.rel = 0 + left = listIter.left + right = listIter.right + } + } else { + left = listIter.left + right = listIter.right + } // @todo this should simply split if .rel > 0 - const pos = new ItemTextListPosition(listIter.left, listIter.right, listIter.index, currentAttributes) - return findNextPosition(transaction, pos, index - listIter.index + listIter.rel) + return new ItemTextListPosition(left, right, index, currentAttributes) }) } else { const pos = new ItemTextListPosition(null, parent._start, 0, currentAttributes) @@ -266,7 +285,7 @@ const insertText = (transaction, parent, currPos, text, attributes) => { const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : (text instanceof AbstractType ? new ContentType(text) : new ContentEmbed(text)) let { left, right, index } = currPos if (parent._searchMarker) { - updateMarkerChanges(transaction, parent._searchMarker, currPos.index, content.getLength()) + updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength(), null) } right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content) right.integrate(transaction, 0) @@ -471,7 +490,7 @@ const deleteText = (transaction, currPos, length) => { } const parent = /** @type {AbstractType} */ (/** @type {Item} */ (currPos.left || currPos.right).parent) if (parent._searchMarker) { - updateMarkerChanges(transaction, parent._searchMarker, currPos.index, -startLength + length) + updateMarkerChanges(parent._searchMarker, currPos.index, -startLength + length, null) } return currPos } diff --git a/src/utils/ListIterator.js b/src/utils/ListIterator.js index 08d28e15..d3bdf562 100644 --- a/src/utils/ListIterator.js +++ b/src/utils/ListIterator.js @@ -162,13 +162,18 @@ export class ListIterator { /** * @param {Transaction} tr * @param {number} len + * @return {ListIterator} */ backward (tr, len) { if (this.index - len < 0) { throw lengthExceeded } - let item = this.nextItem && this.nextItem.left this.index -= len + if (this.rel > len) { + this.rel -= len + return this + } + let item = this.nextItem && this.nextItem.left if (this.rel) { len -= this.rel this.rel = 0 @@ -177,8 +182,10 @@ export class ListIterator { if (item.countable && !item.deleted && item.moved === this.currMove) { len -= item.length if (len < 0) { - this.rel = item.length + len + this.rel = -len len = 0 + } + if (len === 0) { break } } else if (item.content.constructor === ContentMove && item.moved === this.currMove) { @@ -201,8 +208,7 @@ export class ListIterator { } item = item.left } - this.index -= len - this.nextItem = item && item.right + this.nextItem = item return this } diff --git a/tests/testHelper.js b/tests/testHelper.js index 4df1d11e..542abe39 100644 --- a/tests/testHelper.js +++ b/tests/testHelper.js @@ -373,6 +373,29 @@ export const compare = users => { t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])) compareDS(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store)) compareStructStores(users[i].store, users[i + 1].store) + // test list-iterator + { + const user = users[0] + user.transact(tr => { + const type = user.getArray('array') + Y.useSearchMarker(tr, type, type.length, walker => { + for (let i = type.length; i >= 0; i--) { + const otherWalker = new Y.ListIterator(type) + otherWalker.forward(tr, walker.index) + otherWalker.forward(tr, 0) + walker.forward(tr, 0) + t.assert(walker.index === i) + t.assert(walker.left === otherWalker.left) + t.assert(walker.right === otherWalker.right) + t.assert(walker.nextItem === otherWalker.nextItem) + t.assert(walker.reachedEnd === otherWalker.reachedEnd) + if (i > 0) { + walker.backward(tr, 1) + } + } + }) + }) + } } users.map(u => u.destroy()) }