Catrope has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/82786


Change subject: Rewrite MetaList.onTransact
......................................................................

Rewrite MetaList.onTransact

TODO more detailed commit summary

Change-Id: I899d2b3c94c2cfa55823879bca95456750f64382
---
M modules/ve/dm/ve.dm.MetaItem.js
M modules/ve/dm/ve.dm.MetaList.js
2 files changed, 163 insertions(+), 102 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/86/82786/1

diff --git a/modules/ve/dm/ve.dm.MetaItem.js b/modules/ve/dm/ve.dm.MetaItem.js
index 86a60ce..91657c9 100644
--- a/modules/ve/dm/ve.dm.MetaItem.js
+++ b/modules/ve/dm/ve.dm.MetaItem.js
@@ -129,39 +129,6 @@
 };
 
 /**
- * Queue up a change to the item's offset and index.
- * @param {number} offset New offset
- * @param {number} index New index
- */
-ve.dm.MetaItem.prototype.setMove = function ( offset, index ) {
-       this.move = {
-               'offset': offset,
-               'index': index
-       };
-};
-
-/**
- * Whether or not a move is pending.
- * @returns {boolean} A move is pending
- */
-ve.dm.MetaItem.prototype.isMovePending = function () {
-       return this.move !== null;
-};
-
-/**
- * Apply the pending move and clear it.
- * @throws No move pending
- */
-ve.dm.MetaItem.prototype.applyMove = function () {
-       if ( this.move === null ) {
-               throw new Error( 'No move pending' );
-       }
-       this.setOffset( this.move.offset );
-       this.setIndex( this.move.index );
-       this.move = null;
-};
-
-/**
  * Attach this item to a MetaList.
  * @param {ve.dm.MetaList} list Parent list to attach to
  * @param {number} offset Offset of this item in the parent list's document
@@ -187,3 +154,11 @@
                this.index = null;
        }
 };
+
+/**
+ * Check whether this item is attached to a MetaList.
+ * @returns {boolean} Whether item is attached
+ */
+ve.dm.MetaItem.prototype.isAttached = function () {
+       return this.list !== null;
+};
diff --git a/modules/ve/dm/ve.dm.MetaList.js b/modules/ve/dm/ve.dm.MetaList.js
index 78e8af4..cc4be52 100644
--- a/modules/ve/dm/ve.dm.MetaList.js
+++ b/modules/ve/dm/ve.dm.MetaList.js
@@ -80,98 +80,184 @@
  * @emits remove
  */
 ve.dm.MetaList.prototype.onTransact = function ( tx, reversed ) {
-       var i, j, k, ilen, jlen, klen, ins, rm, retain, itemIndex, item,
-               offset = 0, newOffset = 0, index = 0, ops = tx.getOperations(),
-               insertedItems = [], removedItems = [];
-       // Look for replaceMetadata operations in the transaction and 
insert/remove items as appropriate
-       // This requires we also inspect retain, replace and replaceMetadata 
operations in order to
-       // track the offset and index. We track the pre-transaction offset, we 
need to do that in
-       // order to remove items correctly. This also means inserted items are 
initially at the wrong
-       // offset, but we translate it later.
+       var i, ilen, j, jlen, k, klen, item, ins, rm, insMeta, rmMeta, 
retainMeta,
+               numItems = this.items.length,
+               itemIndex = 0, // Current index into this.items
+               offset = 0, // Current pre-transaction offset
+               newOffset = 0, // Current post-transaction offset
+               index = 0, // Current pre-transaction index
+               newIndex = 0, // Current post-transaction index
+               // Array of items that should appear in this.items after we're 
done. This includes newly
+               // inserted items as well as existing items that aren't being 
removed.
+               // [ { item: ve.dm.MetaItem, offset: offset to move to, index: 
index to move to } ]
+               newItems = [],
+               removedItems = [], // Array of items that should be removed 
from this.items
+               events = [], // Array of events that we should emit when we're 
done
+               ops = tx.getOperations();
+
+       // Go through the transaction operations and plan out where to add, 
remove and move items. We
+       // don't actually touch this.items yet, otherwise we 1) get it out of 
order which breaks
+       // findItem() and 2) lose information about what the pre-transaction 
state of this.items was.
        for ( i = 0, ilen = ops.length; i < ilen; i++ ) {
                switch ( ops[i].type ) {
                        case 'retain':
+                               // Advance itemIndex through the retain and 
update items we encounter along the way
+                               for ( ;
+                                       itemIndex < numItems && 
this.items[itemIndex].offset < offset + ops[i].length;
+                                       itemIndex++
+                               ) {
+                                       // Plan to move this item to the 
post-transaction offset and index
+                                       newItems.push( {
+                                               'item': this.items[itemIndex],
+                                               'offset': 
this.items[itemIndex].offset + newOffset - offset,
+                                               'index': 
this.items[itemIndex].offset === offset ?
+                                                       // Adjust index for 
insertions or removals that happened at this offset
+                                                       newIndex - index + 
this.items[itemIndex].index :
+                                                       // Offset is retained 
over completely, don't adjust index
+                                                       
this.items[itemIndex].index } );
+                               }
+
                                offset += ops[i].length;
                                newOffset += ops[i].length;
                                index = 0;
+                               newIndex = 0;
                                break;
+
+                       case 'retainMetadata':
+                               // Advance itemIndex through the retain and 
update items we encounter along the way
+                               for ( ;
+                                       itemIndex < numItems && 
this.items[itemIndex].offset === offset &&
+                                               this.items[itemIndex].index < 
index + ops[i].length;
+                                       itemIndex++
+                               ) {
+                                       newItems.push( {
+                                               'item': this.items[itemIndex],
+                                               'offset': newOffset,
+                                               'index': 
this.items[itemIndex].index + newIndex - index
+                                       } );
+                               }
+
+                               index += ops[i].length;
+                               newIndex += ops[i].length;
+                               break;
+
                        case 'replace':
-                               // if we have metadata replace info we can 
calculate the new
-                               // offset and index directly
-                               ins = reversed ? ops[i].removeMetadata : 
ops[i].insertMetadata;
-                               rm = reversed ? ops[i].insertMetadata : 
ops[i].removeMetadata;
-                               retain = ops[i].retainMetadata || 0;
-                               if ( rm !== undefined ) {
-                                       // find the first itemIndex - the rest 
should be in order after it
-                                       for ( j = 0, jlen = rm.length; j < 
jlen; j++ ) {
-                                               if ( rm[j] !== undefined ) {
-                                                       itemIndex = 
this.findItem( offset + retain + j, 0 );
-                                                       break;
-                                               }
+                               ins = reversed ? ops[i].remove : ops[i].insert;
+                               rm = reversed ? ops[i].insert : ops[i].remove;
+                               if ( ops[i].retainMetadata !== undefined ) {
+                                       insMeta = reversed ? 
ops[i].removeMetadata : ops[i].insertMetadata;
+                                       rmMeta = reversed ? 
ops[i].insertMetadata : ops[i].removeMetadata;
+                                       retainMeta = ops[i].retainMetadata;
+
+                                       // Process removed metadata
+                                       for ( ;
+                                               itemIndex < numItems &&
+                                                       
this.items[itemIndex].offset < offset + retainMeta + rmMeta.length;
+                                               itemIndex++
+                                       ) {
+                                               removedItems.push( 
this.items[itemIndex] );
                                        }
-                                       // iterate through all the inserted 
metaItems
-                                       for ( j = 0, jlen = ins.length; j < 
jlen; j++ ) {
-                                               item = ins[j];
-                                               if ( item !== undefined ) {
-                                                       for ( k = 0, klen = 
item.length; k < klen; k++ ) {
-                                                               // Queue up the 
move for later so we don't break the metaItem ordering
-                                                               
this.items[itemIndex].setMove( newOffset + retain + j, k );
-                                                               itemIndex++;
+
+                                       // Process inserted metadata
+                                       for ( j = 0, jlen = insMeta.length; j < 
jlen; j++ ) {
+                                               if ( insMeta[j] ) {
+                                                       for ( k = 0, klen = 
insMeta[j].length; k < klen; k++ ) {
+                                                               item = 
ve.dm.metaItemFactory.createFromElement( insMeta[j][k] );
+                                                               newItems.push( {
+                                                                       'item': 
item,
+                                                                       
'offset': newOffset + retainMeta + j,
+                                                                       
'index': k
+                                                               } );
                                                        }
                                                }
                                        }
+                               } else {
+                                       // No metadata handling specified, 
which means we just have to deal with offset
+                                       // adjustments, same as a retain
+                                       for ( ;
+                                                       itemIndex < numItems &&
+                                                               
this.items[itemIndex].offset < offset + rm.length;
+                                                       itemIndex++
+                                       ) {
+                                               newItems.push( {
+                                                       'item': 
this.items[itemIndex],
+                                                       'offset': 
this.items[itemIndex].offset + newOffset - offset,
+                                                       'index': 
this.items[itemIndex].index
+                                               } );
+                                       }
                                }
-                               offset += reversed ? ops[i].insert.length : 
ops[i].remove.length;
-                               newOffset += reversed ? ops[i].remove.length : 
ops[i].insert.length;
-                               index = 0;
+
+                               offset += rm.length;
+                               newOffset += ins.length;
                                break;
-                       case 'retainMetadata':
-                               index += ops[i].length;
-                               break;
+
                        case 'replaceMetadata':
-                               ins = reversed ? ops[i].remove : ops[i].insert;
-                               rm = reversed ? ops[i].insert : ops[i].remove;
-                               for ( j = 0, jlen = rm.length; j < jlen; j++ ) {
-                                       item = this.deleteRemovedItem( offset, 
index + j );
-                                       removedItems.push( { 'item': item, 
'offset': offset, 'index': index } );
+                               insMeta = reversed ? ops[i].remove : 
ops[i].insert;
+                               rmMeta = reversed ? ops[i].insert : 
ops[i].remove;
+
+                               // Process removed items
+                               for ( ;
+                                       itemIndex < numItems && 
this.items[itemIndex].offset === offset &&
+                                               this.items[itemIndex].index < 
index + rmMeta.length;
+                                       itemIndex++
+                               ) {
+                                       removedItems.push( 
this.items[itemIndex] );
                                }
-                               for ( j = 0, jlen = ins.length; j < jlen; j++ ) 
{
-                                       item = 
ve.dm.metaItemFactory.createFromElement( ins[j] );
-                                       // offset and index are 
pre-transaction, but we'll fix them later
-                                       this.addInsertedItem( offset, index + 
j, item );
-                                       insertedItems.push( { 'item': item } );
+
+                               // Process inserted items
+                               for ( j = 0, jlen = insMeta.length; j < jlen; 
j++ ) {
+                                       item = 
ve.dm.metaItemFactory.createFromElement( insMeta[j] );
+                                       newItems.push( { 'item': item, 
'offset': newOffset, 'index': newIndex + j } );
                                }
-                               index += rm.length;
+
+                               index += rmMeta.length;
+                               newIndex += insMeta.length;
                                break;
                }
        }
-
-       // Translate the offsets of all items, and reindex them too
-       // Reindexing is simple because the above ensures the items are already 
in the right order
-       offset = -1;
-       index = 0;
-       for ( i = 0, ilen = this.items.length; i < ilen; i++ ) {
-               if ( this.items[i].isMovePending() ) {
-                       // move was calculated from metadata replace info, just 
apply it
-                       this.items[i].applyMove();
-               } else {
-                       // otherwise calculate the new offset from the 
transaction
-                       this.items[i].setOffset( tx.translateOffset( 
this.items[i].getOffset(), reversed ) );
-                       if ( this.items[i].getOffset() === offset ) {
-                               index++;
-                       } else {
-                               index = 0;
-                       }
-                       this.items[i].setIndex( index );
-               }
-               offset = this.items[i].getOffset();
+       // Update the remaining items that the transaction didn't touch or 
retain over
+       for ( ; itemIndex < numItems; itemIndex++ ) {
+               newItems.push( {
+                       'item': this.items[itemIndex],
+                       'offset': this.items[itemIndex].offset + newOffset - 
offset,
+                       'index': this.items[itemIndex].offset === offset ?
+                               newIndex - index + this.items[itemIndex].index :
+                               this.items[itemIndex].index
+               } );
        }
 
-       for ( i = 0, ilen = insertedItems.length; i < ilen; i++ ) {
-               this.emit( 'insert', insertedItems[i].item );
-       }
+       // Process the changes, and queue up events. We emit the events at the 
end when the MetaList
+       // is back in a consistent state
+
+       // Remove removed items
        for ( i = 0, ilen = removedItems.length; i < ilen; i++ ) {
-               this.emit( 'remove', removedItems[i].item, 
removedItems[i].offset, removedItems[i].index );
+               this.deleteRemovedItem( removedItems[i].offset, 
removedItems[i].index );
+               events.push( [
+                       'remove', removedItems[i].item, removedItems[i].offset, 
removedItems[i].index
+               ] );
+       }
+
+       // Move moved items (these appear as inserted items that are already 
attached)
+       for ( i = 0, ilen = newItems.length; i < ilen; i++ ) {
+               if ( newItems[i].item.isAttached() ) {
+                       if ( newItems[i].offset !== newItems[i].item.offset || 
newItems[i].index !== newItems[i].item.index ) {
+                               this.deleteRemovedItem( 
newItems[i].item.offset, newItems[i].item.index );
+                               this.addInsertedItem( newItems[i].offset, 
newItems[i].index, newItems[i].item );
+                       }
+               }
+       }
+
+       // Insert new items
+       for ( i = 0, ilen = newItems.length; i < ilen; i++ ) {
+               if ( !newItems[i].item.isAttached() ) {
+                       this.addInsertedItem( newItems[i].offset, 
newItems[i].index, newItems[i].item );
+                       events.push( [ 'insert', newItems[i].item ] );
+               }
+       }
+
+       // Emit events
+       for ( i = 0, ilen = events.length; i < ilen; i++ ) {
+               this.emit.apply( this, events[i] );
        }
 };
 

-- 
To view, visit https://gerrit.wikimedia.org/r/82786
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I899d2b3c94c2cfa55823879bca95456750f64382
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to