From ca17ea09433823947add19c7ef773783cfb0a036 Mon Sep 17 00:00:00 2001 From: Catrope Date: Thu, 10 May 2012 20:12:07 -0700 Subject: [PATCH] Port structural replace code from the old VE code This makes TransactionProcessor work for regular replacements, as well as insertions and deletions of self-contained pieces of data. This does NOT yet work for inserting and deleting unbalanced data (splitting/merging nodes). I've tested this from the console for insertions and deletions and simple replacements, but I haven't tested wrappings. We should write a bunch of unit tests for this some time :) Change-Id: Ic2fd75d1cf2e127bc9ae58debce67576be2c912f --- modules/ve2/dm/ve.dm.TransactionProcessor.js | 73 +++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/modules/ve2/dm/ve.dm.TransactionProcessor.js b/modules/ve2/dm/ve.dm.TransactionProcessor.js index ebec6821e2..78d38ac794 100644 --- a/modules/ve2/dm/ve.dm.TransactionProcessor.js +++ b/modules/ve2/dm/ve.dm.TransactionProcessor.js @@ -232,6 +232,77 @@ ve.dm.TransactionProcessor.prototype.replace = function( op ) { this.cursor += replacement.length; } else { // Structural replacement - // TODO implement + // TODO generalize for insert/remove + + // It's possible that multiple replace operations are needed before the + // model is back in a consistent state. This loop applies the current + // replace operation to the linear model, then keeps applying subsequent + // operations until the model is consistent. We keep track of the changes + // and queue a single rebuild after the loop finishes. + var operation = op, + removeLevel = 0, + replaceLevel = 0, + startOffset = this.cursor, + adjustment = 0, + i, + type; + + while ( true ) { + if ( operation.type == 'replace' ) { + var opRemove = this.reversed ? operation.replacement : operation.remove, + opReplacement = this.reversed ? operation.remove : operation.replacement; + // Update the linear model for this replacement + ve.batchSplice( this.document.data, this.cursor, opRemove.length, opReplacement ); + this.cursor += opReplacement.length; + adjustment += opReplacement.length - opRemove.length; + + // Walk through the remove and replacement data + // and keep track of the element depth change (level) + // for each of these two separately. The model is + // only consistent if both levels are zero. + for ( i = 0; i < opRemove.length; i++ ) { + type = opRemove[i].type; + if ( type === undefined ) { + // This is content, ignore + } else if ( type.charAt( 0 ) === '/' ) { + // Closing element + removeLevel--; + } else { + // Opening element + removeLevel++; + } + } + for ( i = 0; i < opReplacement.length; i++ ) { + type = opReplacement[i].type; + if ( type === undefined ) { + // This is content, ignore + } else if ( type.charAt( 0 ) === '/' ) { + // Closing element + replaceLevel--; + } else { + // Opening element + replaceLevel++; + } + } + } else { + // We know that other operations won't cause adjustments, so we + // don't have to update adjustment + this.executeOperation( operation ); + } + + if ( removeLevel === 0 && replaceLevel === 0 ) { + // The model is back in a consistent state, so we're done + break; + } + + // Get the next operation + operation = this.nextOperation(); + if ( !operation ) { + throw 'Unbalanced set of replace operations found'; + } + } + // Queue a rebuild for the replaced node + this.synchronizer.pushRebuild( new ve.Range( startOffset, this.cursor - adjustment ), + new ve.Range( startOffset, this.cursor ) ); } };