Index: trunk/extensions/VisualEditor/tests/ve/ve.dm.DocumentSynchronizer.test.js |
— | — | @@ -119,7 +119,7 @@ |
120 | 120 | model.data[0].attributes = { 'level': 1 }; |
121 | 121 | model.data[4].type = '/heading'; |
122 | 122 | // Push rebuild action |
123 | | - sync.pushRebuild( node, 0 ); |
| 123 | + sync.pushRebuild( new ve.Range( 0, 5 ), new ve.Range( 0, 5 ) ); |
124 | 124 | // Sync |
125 | 125 | sync.synchronize(); |
126 | 126 | return model.getChildren()[0].getElementType(); |
— | — | @@ -135,7 +135,7 @@ |
136 | 136 | // Insert element after first paragraph |
137 | 137 | ve.insertIntoArray( model.data, 5, data ); |
138 | 138 | // Push rebuild action with a length adustment of 3 to account for the new element |
139 | | - sync.pushRebuild( node, 3 ); |
| 139 | + sync.pushRebuild( new ve.Range( 0, 5 ), new ve.Range( 0, 8 ) ); |
140 | 140 | // Sync |
141 | 141 | sync.synchronize(); |
142 | 142 | return model.getChildren()[1].getContentData(); |
Index: trunk/extensions/VisualEditor/modules/ve/dm/ve.dm.DocumentSynchronizer.js |
— | — | @@ -44,18 +44,20 @@ |
45 | 45 | }; |
46 | 46 | |
47 | 47 | /** |
48 | | - * Add a rebuild action to the queue. This rebuilds a node from data |
| 48 | + * Add a rebuild action to the queue. This rebuilds one or more nodes from data |
49 | 49 | * found in the linear model. |
50 | | - * @param {ve.dm.BranchNode} node Node to rebuild |
51 | | - * @param {Integer} adjustment Length adjustment to apply to the node |
52 | | - * @param {Integer} offset Offset of the node, if known |
| 50 | + * @param {ve.Range} oldRange Range that the old nodes used to span. This is |
| 51 | + * used to find the old nodes in the model tree. |
| 52 | + * @param {ve.Range} newRange Range that contains the new nodes. This is used |
| 53 | + * to get the new node data from the linear model. |
53 | 54 | */ |
54 | | -ve.dm.DocumentSynchronizer.prototype.pushRebuild = function( node, adjustment, offset ) { |
| 55 | +ve.dm.DocumentSynchronizer.prototype.pushRebuild = function( oldRange, newRange ) { |
| 56 | + oldRange.normalize(); |
| 57 | + newRange.normalize(); |
55 | 58 | this.actions.push( { |
56 | 59 | 'type': 'rebuild', |
57 | | - 'node': node, |
58 | | - 'adjustment': adjustment, |
59 | | - 'offset': offset || null |
| 60 | + 'oldRange': oldRange, |
| 61 | + 'newRange': newRange |
60 | 62 | } ); |
61 | 63 | }; |
62 | 64 | |
— | — | @@ -124,16 +126,44 @@ |
125 | 127 | parent.splice( parent.indexOf( action.node ), 1 ); |
126 | 128 | break; |
127 | 129 | case 'rebuild': |
128 | | - // Compute the offset if it wasn't provided |
129 | | - if ( offset === null ) { |
130 | | - offset = this.model.getOffsetFromNode( action.node ); |
| 130 | + // Generate the new nodes |
| 131 | + var newNodes = ve.dm.DocumentNode.createNodesFromData( |
| 132 | + this.model.getData( action.newRange ) |
| 133 | + ); |
| 134 | + |
| 135 | + // Find the node(s) contained by oldRange. This is done by repeatedly |
| 136 | + // invoking selectNodes() in shallow mode until we find the right node(s). |
| 137 | + // TODO this traversal could be made more efficient once we have an offset map |
| 138 | + // TODO I need to add this recursive shallow stuff to selectNodes() as a 'siblings' mode |
| 139 | + var selection, node = this.model, range = action.oldRange; |
| 140 | + while ( true ) { |
| 141 | + selection = node.selectNodes( range, true ); |
| 142 | + // We stop descending if: |
| 143 | + // * we got more than one node, OR |
| 144 | + // * we got a leaf node, OR |
| 145 | + // * we got no range, which means the entire node is covered, OR |
| 146 | + // * we got the same node back, which means we'd get in an infinite loop |
| 147 | + if ( selection.length != 1 || |
| 148 | + !selection[0].node.hasChildren() || |
| 149 | + !selection[0].range || |
| 150 | + selection[0].node == node |
| 151 | + ) { |
| 152 | + break; |
| 153 | + } |
| 154 | + // Descend into this node |
| 155 | + node = selection[0].node; |
| 156 | + range = selection[0].range; |
131 | 157 | } |
132 | | - // Replace original node with new node |
133 | | - var newNodes = ve.dm.DocumentNode.createNodesFromData( this.model.getData( |
134 | | - new ve.Range( offset, action.node.getElementLength() + action.adjustment ) |
135 | | - ) ); |
136 | | - parent = action.node.getParent(); |
137 | | - ve.batchedSplice( parent, parent.indexOf( action.node ), 1, newNodes ); |
| 158 | + if ( selection[0].node == this.model ) { |
| 159 | + // We got some sort of weird input, ignore it |
| 160 | + break; |
| 161 | + } |
| 162 | + |
| 163 | + // The first node we're removing is selection[0].node , and we're removing |
| 164 | + // selection.length adjacent nodes |
| 165 | + parent = selection[0].node.getParent(); |
| 166 | + // TODO selectNodes() output knows the index of selection[0].node in its parent, should expose it |
| 167 | + ve.batchedSplice( parent, parent.indexOf( selection[0].node ), selection.length, newNodes ); |
138 | 168 | break; |
139 | 169 | case 'resize': |
140 | 170 | // Adjust node length - causes update events to be emitted |