jenkins-bot has submitted this change and it was merged.

Change subject: Remove no-insertion metadata corner case from 
`ve.dm.Transaction.pushReplace()`.
......................................................................


Remove no-insertion metadata corner case from `ve.dm.Transaction.pushReplace()`.

This version pushes a `replaceMetadata` operation after a `replace` to
fixup trailing metadata if there is no inserted region and the removed
region includes metadata.  This avoids a corner case where the
size of the metadata arrays inserted/removed in `Transaction.pushReplace()`
do not match the size of the data arrays inserted/removed.

We remove the now-unused `Document.getMetadataReplace()` method.

We also adjust `MetaList.onTransact()` so that it continues to work
properly when the number of metadata entries in `replace.insert` is
not the same as the number of metadata entries in `replace.remove`.

Change-Id: I1d600405b855ca1cb569853bb885b0752df47173
---
M modules/ve/dm/ve.dm.Document.js
M modules/ve/dm/ve.dm.MetaList.js
M modules/ve/dm/ve.dm.Transaction.js
M modules/ve/test/dm/ve.dm.Document.test.js
M modules/ve/test/dm/ve.dm.Transaction.test.js
5 files changed, 120 insertions(+), 93 deletions(-)

Approvals:
  Catrope: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/modules/ve/dm/ve.dm.Document.js b/modules/ve/dm/ve.dm.Document.js
index 739f850..daa30ac 100644
--- a/modules/ve/dm/ve.dm.Document.js
+++ b/modules/ve/dm/ve.dm.Document.js
@@ -334,46 +334,6 @@
 };
 
 /**
- * Get the metadata replace operation required to keep data & metadata in sync 
after a splice
- *
- * @method
- * @param {number} offset Data offset to start at
- * @param {number} remove Number of elements being removed
- * @param {Array} insert Element data being inserted
- * @returns {Object} Metadata replace operation to keep data & metadata in sync
- */
-ve.dm.Document.prototype.getMetadataReplace = function ( offset, remove, 
insert ) {
-       var removeMetadata, insertMetadata, replace = {}, extend;
-       if ( remove > 0 ) {
-               // if we are removing anything we need to collapse the metadata
-               if ( insert.length > 0 ) {
-                       // if there's at least one element inserted, collapse 
the metadata
-                       // onto the first cursor position.
-                       extend = 0;
-               } else {
-                       // Make the collapsed region is 1 larger than the 
deleted region, so we
-                       // can put the collapsed metadata on the element 
immediately
-                       // following the deleted region.
-                       extend = 1;
-               }
-               removeMetadata = this.getMetadata( new ve.Range( offset, offset 
+ remove + extend ) );
-               // check removeMetadata is non-empty
-               if ( !ve.compare( removeMetadata, new Array( 
removeMetadata.length ) ) ) {
-                       insertMetadata = ve.dm.MetaLinearData.static.merge( 
removeMetadata );
-                       // pad out the end of `insertMetadata` so it is
-                       // `insert.length + extend` elements long.  (if 
`extend` is 1, then
-                       // `insert.length` is 0 and thus we don't need to pad 
further.)
-                       if ( extend === 0 ) {
-                               ve.batchSplice( insertMetadata, 1, 0, new 
Array( insert.length - 1 ) );
-                       }
-                       replace.remove = removeMetadata;
-                       replace.insert = insertMetadata;
-               }
-       }
-       return replace;
-};
-
-/**
  * Splice metadata into and/or out of the linear model.
  *
  * `this.metadata` will be updated accordingly.
diff --git a/modules/ve/dm/ve.dm.MetaList.js b/modules/ve/dm/ve.dm.MetaList.js
index 934b2db..4096de4 100644
--- a/modules/ve/dm/ve.dm.MetaList.js
+++ b/modules/ve/dm/ve.dm.MetaList.js
@@ -100,7 +100,32 @@
                                // offset and index directly
                                ins = reversed ? ops[i].removeMetadata : 
ops[i].insertMetadata;
                                rm = reversed ? ops[i].insertMetadata : 
ops[i].removeMetadata;
-                               if ( rm !== undefined ) {
+                               if ( rm === undefined ) {
+                                       // no impact on metadata.
+                                       /* jshint noempty: false */
+                               } else if ( ins.length === 0 ) {
+                                       for ( j = 0, jlen = rm.length; j < 
jlen; j++ ) {
+                                               if ( rm[j] !== undefined ) {
+                                                       for ( k = 0, klen = 
rm[j].length; k < klen; k++ ) {
+                                                               item = 
this.deleteRemovedItem( offset + j, k );
+                                                               
removedItems.push( { 'item': item, 'offset': offset + j, 'index': k } );
+                                                       }
+                                               }
+                                       }
+                               } else if ( rm.length === 0 ) {
+                                       itemIndex = -Math.pow(2, 53); // INT_MIN
+                                       for ( j = 0, jlen = ins.length; j < 
jlen; j++ ) {
+                                               if ( ins[j] !== undefined ) {
+                                                       for ( k = 0, klen = 
ins[j].length; k < klen; k++ ) {
+                                                               item = 
ve.dm.metaItemFactory.createFromElement( ins[j][k] );
+                                                               // offset is 
pre-transaction, but we'll fix it up w/ setMove
+                                                               
this.addInsertedItem( offset, itemIndex++, item );
+                                                               item.setMove( 
newOffset + j, k );
+                                                               
insertedItems.push( { 'item': item } );
+                                                       }
+                                               }
+                                       }
+                               } else {
                                        // 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 ) {
diff --git a/modules/ve/dm/ve.dm.Transaction.js 
b/modules/ve/dm/ve.dm.Transaction.js
index 761c036..6719110 100644
--- a/modules/ve/dm/ve.dm.Transaction.js
+++ b/modules/ve/dm/ve.dm.Transaction.js
@@ -840,6 +840,10 @@
  * Adds a replace op to remove the desired range and, where required, splices 
in retain ops
  * to prevent the deletion of internal data.
  *
+ * An extra `replaceMetadata` operation might be pushed at the end if the
+ * affected region contains metadata; see
+ * {@link ve.dm.Transaction#pushReplace} for details.
+ *
  * @param {ve.dm.Document} doc Document
  * @param {number} removeStart Offset to start removing from
  * @param {number} removeEnd Offset to remove to
@@ -869,7 +873,17 @@
 };
 
 /**
- * Add a replace operation, keeping metadata in sync if required
+ * Add a replace operation, keeping metadata in sync if required.
+ *
+ * Note that metadata attached to removed content is moved so that it
+ * attaches just before the inserted content.  If there is
+ * metadata attached to the removed content but there is no inserted
+ * content, then an extra `replaceMetadata` operation is pushed in order
+ * to properly insert the merged metadata before the character immediately
+ * after the removed content. (Note that there is an extra metadata element
+ * after the final data element; if the removed region is at the very end of
+ * the document, the inserted `replaceMetadata` operation targets this
+ * final metadata element.)
  *
  * @method
  * @param {ve.dm.Document} doc Document model
@@ -885,18 +899,44 @@
 
        var op, end = this.operations.length - 1,
                lastOp = end >= 0 ? this.operations[end] : null,
-               remove = doc.getData( new ve.Range( offset, offset + 
removeLength ) ),
-               metadataReplace = doc.getMetadataReplace( offset, removeLength, 
insert ),
-               removeMetadata = metadataReplace.remove,
-               insertMetadata = metadataReplace.insert;
+               penultOp = end >= 1 ? this.operations[ end - 1 ] : null,
+               range = new ve.Range( offset, offset + removeLength ),
+               remove = doc.getData( range ),
+               removeMetadata = doc.getMetadata( range ),
+               insertMetadata, extraMetadata;
+
+       if ( !ve.compare( removeMetadata, new Array( removeMetadata.length ) ) 
) {
+               // if we are removing a range which includes metadata, we need 
to
+               // collapse it.  If there's nothing to insert, we also need to 
add
+               // an extra `replaceMetadata` operation later in order to 
insert the
+               // collapsed metadata.
+               insertMetadata = ve.dm.MetaLinearData.static.merge( 
removeMetadata );
+               if ( insert.length === 0 ) {
+                       extraMetadata = insertMetadata[0];
+                       insertMetadata = [];
+               } else {
+                       // pad out at end so insert metadata is the same length 
as insert data
+                       ve.batchSplice( insertMetadata, 1, 0, new Array( 
insert.length - 1 ) );
+               }
+       }
 
        // simple replaces can be combined
        // (but don't do this if there is metadata to be removed and the 
previous
        // replace had a non-zero insertion, because that would shift the 
metadata
        // location.)
        if (
+               lastOp && lastOp.type === 'replaceMetadata' &&
+               lastOp.insert.length > 0 && lastOp.remove.length === 0 &&
+               penultOp && penultOp.type === 'replace' &&
+               penultOp.insert.length === 0 /* this is always true */
+       ) {
+               this.operations.pop();
+               lastOp = penultOp;
+               /* fall through */
+       }
+       if (
                lastOp && lastOp.type === 'replace' &&
-               !( lastOp.insert.length > 0 && removeMetadata !== undefined )
+               !( lastOp.insert.length > 0 && insertMetadata !== undefined )
        ) {
                lastOp = this.operations.pop();
                this.lengthDifference -= lastOp.insert.length - 
lastOp.remove.length;
@@ -906,18 +946,29 @@
                        lastOp.remove.length + removeLength,
                        lastOp.insert.concat( insert )
                );
-       } else {
-               op = {
-                       'type': 'replace',
-                       'remove': remove,
-                       'insert': insert
-               };
-               if ( removeMetadata !== undefined ) {
-                       op.removeMetadata = removeMetadata;
-                       op.insertMetadata = insertMetadata;
-               }
-               this.operations.push( op );
-               this.lengthDifference += insert.length - remove.length;
+               return;
+       }
+
+       if ( lastOp && lastOp.type === 'replaceMetadata' ) {
+               // `replace` operates on the metadata at the given offset; the 
transaction
+               // touches the same region twice if `replace` follows a 
`replaceMetadata`
+               // without a `retain` in between.
+               throw new Error( 'replace after replaceMetadata not allowed' );
+       }
+
+       op = {
+               'type': 'replace',
+               'remove': remove,
+               'insert': insert
+       };
+       if ( insertMetadata !== undefined ) {
+               op.removeMetadata = removeMetadata;
+               op.insertMetadata = insertMetadata;
+       }
+       this.operations.push( op );
+       this.lengthDifference += insert.length - remove.length;
+       if ( extraMetadata !== undefined ) {
+               this.pushReplaceMetadata( [], extraMetadata );
        }
 };
 
diff --git a/modules/ve/test/dm/ve.dm.Document.test.js 
b/modules/ve/test/dm/ve.dm.Document.test.js
index 1a387f7..0efe710 100644
--- a/modules/ve/test/dm/ve.dm.Document.test.js
+++ b/modules/ve/test/dm/ve.dm.Document.test.js
@@ -112,32 +112,6 @@
        }
 } );
 
-QUnit.test( 'getMetadataReplace', 3, function ( assert ) {
-       var replace, expectedReplace,
-               doc = ve.dm.example.createExampleDocument( 'withMeta' );
-
-       replace = doc.getMetadataReplace( 10, 1, [] );
-       expectedReplace = {
-               'remove': doc.getMetadata().slice( 10, 12 ),
-               'insert': ve.dm.MetaLinearData.static.merge( 
doc.getMetadata().slice( 10, 12 ) )
-       };
-       assert.deepEqual( replace, expectedReplace, 'removing one element at 
offset 10' );
-
-       replace = doc.getMetadataReplace( 5, 2, [] );
-       expectedReplace = {
-               'remove': doc.getMetadata().slice( 5, 8 ),
-               'insert': ve.dm.MetaLinearData.static.merge( 
doc.getMetadata().slice( 5, 8 ) )
-       };
-       assert.deepEqual( replace, expectedReplace, 'removing two elements at 
offset 5' );
-
-       replace = doc.getMetadataReplace( 1, 8, [] );
-       expectedReplace = {
-               'remove': doc.getMetadata().slice( 1, 10 ),
-               'insert': ve.dm.MetaLinearData.static.merge( 
doc.getMetadata().slice( 1, 10 ) )
-       };
-       assert.deepEqual( replace, expectedReplace, 'blanking paragraph, 
removing 8 elements at offset 1' );
-} );
-
 QUnit.test( 'getNodeFromOffset', function ( assert ) {
        var i, j, node,
                doc = ve.dm.example.createExampleDocument(),
diff --git a/modules/ve/test/dm/ve.dm.Transaction.test.js 
b/modules/ve/test/dm/ve.dm.Transaction.test.js
index ac04de0..96223ed 100644
--- a/modules/ve/test/dm/ve.dm.Transaction.test.js
+++ b/modules/ve/test/dm/ve.dm.Transaction.test.js
@@ -577,8 +577,13 @@
                                                'type': 'replace',
                                                'remove': ['B', 'a'],
                                                'insert': [],
-                                               'removeMetadata': 
metaDoc.getMetadata().slice( 7, 10 ),
-                                               'insertMetadata': 
ve.dm.MetaLinearData.static.merge( metaDoc.getMetadata().slice( 7, 10 ) )
+                                               'removeMetadata': 
metaDoc.getMetadata().slice( 7, 9 ),
+                                               'insertMetadata': []
+                                       },
+                                       {
+                                               'type': 'replaceMetadata',
+                                               'remove': [],
+                                               'insert': 
ve.dm.MetaLinearData.static.merge( metaDoc.getMetadata().slice( 7, 9 ) )[0]
                                        },
                                        { 'type': 'retain', 'length': 4 }
                                ]
@@ -1139,22 +1144,34 @@
                                        { 'type': 'replace',
                                          'remove': [ { 'type': 'list' }, { 
'type': 'listItem', 'attributes': { 'styles': ['bullet'] } } ],
                                          'insert': [],
-                                         'insertMetadata': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(0, 3) ),
-                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(0, 3)
+                                         'insertMetadata': [],
+                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(0, 2)
+                                       },
+                                       { 'type': 'replaceMetadata',
+                                         'insert': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(0, 2) )[0],
+                                         'remove': []
                                        },
                                        { 'type': 'retain', 'length': 3 },
                                        { 'type': 'replace',
                                          'remove': [ { 'type': '/listItem' }, 
{ 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } } ],
                                          'insert': [],
-                                         'insertMetadata': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(5, 8) ),
-                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(5, 8)
+                                         'insertMetadata': [],
+                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(5, 7)
+                                       },
+                                       { 'type': 'replaceMetadata',
+                                         'insert': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(5, 7) )[0],
+                                         'remove': []
                                        },
                                        { 'type': 'retain', 'length': 3 },
                                        { 'type': 'replace',
                                          'remove': [ { 'type': '/listItem' }, 
{ 'type': '/list' } ],
                                          'insert': [],
-                                         'insertMetadata': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(10, 13) ),
-                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(10, 13)
+                                         'insertMetadata': [],
+                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(10, 12)
+                                       },
+                                       { 'type': 'replaceMetadata',
+                                         'insert': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(10, 12) )[0],
+                                         'remove': []
                                        }
                                ]
                        },

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I1d600405b855ca1cb569853bb885b0752df47173
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Cscott <canan...@wikimedia.org>
Gerrit-Reviewer: Catrope <roan.katt...@gmail.com>
Gerrit-Reviewer: Cscott <canan...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to