Index: trunk/extensions/VisualEditor/tests/es/es.DocumentModel.test.js |
— | — | @@ -549,7 +549,7 @@ |
550 | 550 | ); |
551 | 551 | } ); |
552 | 552 | |
553 | | -test( 'es.DocumentModel.commit, es.DocumentModel.rollback', /*18*/ 15, function() { |
| 553 | +test( 'es.DocumentModel.commit, es.DocumentModel.rollback', 18, function() { |
554 | 554 | var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj ); |
555 | 555 | |
556 | 556 | // FIXME: These tests shouldn't use prepareFoo() because those functions |
— | — | @@ -759,7 +759,6 @@ |
760 | 760 | 'commit keeps model tree up to date with paragraph split (paragraph 2)' |
761 | 761 | ); |
762 | 762 | |
763 | | - /* FIXME broken |
764 | 763 | // Test 16 |
765 | 764 | documentModel.rollback( paragraphBreak ); |
766 | 765 | deepEqual( |
— | — | @@ -791,5 +790,4 @@ |
792 | 791 | 'table', |
793 | 792 | 'rollback keeps model tree up to date with paragraph split (table follows the paragraph)' |
794 | 793 | ); |
795 | | - */ |
796 | 794 | } ); |
Index: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js |
— | — | @@ -108,6 +108,7 @@ |
109 | 109 | ); |
110 | 110 | } else { |
111 | 111 | // Perform insert on linear data model |
| 112 | + // TODO this is duplicated from above |
112 | 113 | es.insertIntoArray( this.data, this.cursor, op.data ); |
113 | 114 | annotate.call( this, this.cursor + op.data.length ); |
114 | 115 | // Update model tree |
— | — | @@ -119,23 +120,55 @@ |
120 | 121 | } |
121 | 122 | |
122 | 123 | function remove( op ) { |
123 | | - var elementLeft = es.DocumentModel.isElementData( this.data, this.cursor ), |
124 | | - elementRight = es.DocumentModel.isElementData( this.cursor + op.data.length ); |
125 | | - if ( elementLeft && elementRight ) { |
126 | | - // TODO: Support tree updates when removing whole elements |
| 124 | + if ( es.DocumentModel.containsElementData( op.data ) ) { |
| 125 | + // Figure out which nodes are covered by the removal |
| 126 | + var ranges = this.tree.selectNodes( new es.Range( this.cursor, this.cursor + op.data.length ) ); |
| 127 | + var oldNodes = [], newData = [], firstKeptNode = true, lastElement; |
| 128 | + for ( var i = 0; i < ranges.length; i++ ) { |
| 129 | + oldNodes.push( ranges[i].node ); |
| 130 | + if ( ranges[i].globalRange !== undefined ) { |
| 131 | + // We have to keep part of this node |
| 132 | + if ( firstKeptNode ) { |
| 133 | + // This is the first node we're keeping |
| 134 | + // Keep its opening as well |
| 135 | + newData.push( ranges[i].node.getElement() ); |
| 136 | + firstKeptNode = false; |
| 137 | + } |
| 138 | + |
| 139 | + // Compute the start and end offset of this node |
| 140 | + // We could do that with getOffsetFromNode() but |
| 141 | + // we already have all the numbers we need so why would we |
| 142 | + var startOffset = ranges[i].globalRange.start - ranges[i].range.start, |
| 143 | + endOffset = startOffset + ranges[i].node.getContentLength(), |
| 144 | + // Get this node's data |
| 145 | + nodeData = this.data.slice( startOffset, endOffset ); |
| 146 | + // Remove data covered by the range from nodeData |
| 147 | + nodeData.splice( ranges[i].range.start, ranges[i].range.end - ranges[i].range.start ); |
| 148 | + // What remains in nodeData is the data we need to keep |
| 149 | + // Append it to newData |
| 150 | + newData = newData.concat( nodeData ); |
| 151 | + |
| 152 | + lastElement = ranges[i].node.getElementType(); |
| 153 | + } |
| 154 | + } |
| 155 | + if ( lastElement !== undefined ) { |
| 156 | + // Keep the closing of the last element that was partially kept |
| 157 | + newData.push( { 'type': '/' + lastElement } ); |
| 158 | + } |
| 159 | + |
| 160 | + // Perform the rebuild. This updates the model tree |
| 161 | + rebuild( newData, oldNodes ); |
127 | 162 | } else { |
128 | | - if ( es.DocumentModel.containsElementData( op.data ) ) { |
129 | | - // TODO: Support tree updates when removing partial elements |
130 | | - } else { |
131 | | - // Get the node we are removing content from |
132 | | - var node = this.tree.getNodeFromOffset( this.cursor ); |
133 | | - // Remove content from linear data model |
134 | | - this.data.splice( this.cursor, op.data.length ); |
135 | | - // Update model tree |
136 | | - node.adjustContentLength( -op.data.length, true ); |
137 | | - node.emit( 'update', this.cursor - this.tree.getOffsetFromNode( node ) ); |
138 | | - } |
| 163 | + // We're removing content only. Take a shortcut |
| 164 | + // Get the node we are removing content from |
| 165 | + var node = this.tree.getNodeFromOffset( this.cursor ); |
| 166 | + // Update model tree |
| 167 | + node.adjustContentLength( -op.data.length, true ); |
| 168 | + node.emit( 'update', this.cursor - this.tree.getOffsetFromNode( node ) ); |
139 | 169 | } |
| 170 | + |
| 171 | + // Update the linear model |
| 172 | + this.data.splice( this.cursor, op.data.length ); |
140 | 173 | } |
141 | 174 | |
142 | 175 | function attribute( op, invert ) { |