Merge branch 'dmrewrite' of ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor into dmrewrite

This commit is contained in:
Rob Moen 2012-05-11 10:31:12 -07:00
commit 469a55bde4
6 changed files with 130 additions and 14 deletions

View file

@ -115,6 +115,10 @@ ve.dm.DocumentFragment = function( data, parentDocument ) {
children = stack.pop();
currentStack = parentStack;
parentStack = stack[stack.length - 2];
if ( !parentStack ) {
// This can only happen if we got unbalanced data
throw 'Unbalanced input passed to DocumentFragment';
}
// Attach the children to the node
ve.batchSplice( currentNode, 0, 0, children );
}

View file

@ -89,12 +89,20 @@ ve.dm.DocumentSynchronizer.synchronizers = {
return;
}
// TODO index of firstNode in parent should be in the selectNodes result
var firstNode = selection[0].node,
var firstNode, parent, index, numNodes;
if ( 'indexInNode' in selection[0] ) {
// Insertion
parent = selection[0].node;
index = selection[0].indexInNode;
numNodes = 0;
} else {
// Rebuild
firstNode = selection[0].node,
parent = firstNode.getParent(),
index = parent.indexOf( firstNode );
this.document.rebuildNodes( parent, index, selection.length, action.oldRange.from,
index = selection[0].index;
numNodes = selection.length;
}
this.document.rebuildNodes( parent, index, numNodes, action.oldRange.from,
action.newRange.getLength()
);
}

View file

@ -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 ) );
}
};

View file

@ -34,7 +34,11 @@ ve.Document.prototype.getDocumentNode = function() {
* @returns {Array} List of objects describing nodes in the selection and the ranges therein
* 'node': Reference to a ve.dm.Node
* 'range': ve.Range, missing if the entire node is covered
* 'index': Index of the node in its parent
* 'index': Index of the node in its parent, missing if node has no parent
* 'indexInNode': If range is a zero-length range between two children of node,
* this is set to the index of the child following range (or to
* node.children.length+1 if range is between the last child and
* the end). Missing in all other cases
* 'nodeRange': Range covering the inside of the entire node
* @throws 'Invalid start offset' if range.start is out of range
* @throws 'Invalid end offset' if range.end is out of range
@ -51,7 +55,7 @@ ve.Document.prototype.selectNodes = function( range, mode ) {
// Index of the child in node we're visiting
'index': 0,
// First offset inside node
'startOffset': 1
'startOffset': 0
} ],
node,
prevNode,
@ -106,15 +110,18 @@ ve.Document.prototype.selectNodes = function( range, mode ) {
if ( start == end && ( startBetween || endBetween ) && node.isWrapped() ) {
// Empty range in the parent, outside of any child
parentFrame = stack[stack.length - 2];
return [ {
retval = [ {
'node': currentFrame.node,
'indexInNode': currentFrame.index + ( endBetween ? 1 : 0 ),
'range': new ve.Range( start, end ),
'index': parentFrame.index,
'nodeRange': new ve.Range( parentFrame.startOffset,
parentFrame.startOffset + currentFrame.node.getLength()
'nodeRange': new ve.Range( currentFrame.startOffset,
currentFrame.startOffset + currentFrame.node.getLength()
)
} ];
parentFrame = stack[stack.length - 2];
if ( parentFrame ) {
retval[0].index = parentFrame.index;
}
} else if ( startBetween ) {
// start is between the previous sibling and node
// so the selection covers all of node and possibly more

View file

@ -2,10 +2,21 @@ module( 've.dm.DocumentFragment' );
/* Tests */
test( 'constructor', 114, function() {
test( 'constructor', 115, function() {
var fragment = new ve.dm.DocumentFragment( ve.dm.example.data );
// Test count: ( ( 4 tests x 21 branch nodes ) + ( 3 tests x 10 leaf nodes ) ) = 114
ve.example.nodeTreeEqual( fragment.getDocumentNode(), ve.dm.example.tree );
raises(
function() {
fragment = new ve.dm.DocumentFragment( [
{ 'type': '/paragraph' },
{ 'type': 'paragraph' }
] );
},
/^Unbalanced input passed to DocumentFragment$/,
'unbalanced input causes exception'
);
} );
test( 'getData', 1, function() {

View file

@ -154,6 +154,20 @@ ve.example.getSelectNodesCases = function( doc ) {
'nodeRange': new ve.Range( 1, 4 )
}
]
},
// Zero-length range between two children of the document
{
'actual': doc.selectNodes( new ve.Range( 5, 5 ), 'leaves' ),
'expected': [
// document
{
'node': documentNode,
'range': new ve.Range( 5, 5 ),
// no 'index' because documentNode has no parent
'indexInNode': 1,
'nodeRange': new ve.Range( 0, 53 )
}
]
}
];
};
@ -196,6 +210,7 @@ ve.example.nodeSelectionEqual = function( a, b ) {
strictEqual( 'range' in a[i], 'range' in b[i], 'range existence match' );
}
deepEqual( a[i].index, b[i].index, 'index match' );
deepEqual( a[i].indexInNode, b[i].indexInNode, 'indexInNode match' );
deepEqual( a[i].nodeRange, b[i].nodeRange, 'nodeRange match' );
}
};