Rip out the offset map

The offset map was broken from the start because it wasn't updated when
adjusting the length of a text node, and if we fix that bug it's
doubtful whether the costs of updating the offset map outweigh the
benefits, especially considering that adjusting the length of a text
node is something we do for almost every keypress. If it turns out
having an offset map does make sense, we can always reintroduce it
later.

Change-Id: I59e8bc154f7d07aa1bab2f473c13ff466d0e463f
This commit is contained in:
Catrope 2012-06-11 18:05:59 -07:00
parent 77e923e323
commit ab0c37def9
4 changed files with 10 additions and 198 deletions

View file

@ -14,7 +14,6 @@ ve.dm.Document = function( data, parentDocument ) {
// Properties
this.parentDocument = parentDocument;
this.data = data || [];
this.offsetMap = new Array( this.data.length );
// Initialization
var doc = parentDocument || this;
@ -22,18 +21,10 @@ ve.dm.Document = function( data, parentDocument ) {
var root = doc.getDocumentNode();
this.documentNode.setRoot( root );
/*
* The offsetMap is always one element longer than data because it includes a reference to the
* root node at the offset just past the end. To make population work correctly, we have to
* start out with that one extra reference.
*/
this.offsetMap.push( this.documentNode );
/*
* Build a tree of nodes and nodes that will be added to them after a full scan is complete,
* then from the bottom up add nodes to their potential parents. This avoids massive length
* updates being broadcast upstream constantly while building is underway. Also populate the
* offset map as we go.
* updates being broadcast upstream constantly while building is underway.
*/
var node,
textLength = 0,
@ -46,25 +37,6 @@ ve.dm.Document = function( data, parentDocument ) {
parentStack = stack[0],
currentNode = this.documentNode;
for ( var i = 0, length = this.data.length; i < length; i++ ) {
/*
* Set the node reference for this offset in the offset cache.
*
* This looks simple, but there are three cases that result in the same thing:
*
* 1. data[i] is an opening, so offset i is before the opening, so we need to point to the
* parent of the opened element. currentNode will be set to the opened element later,
* but right now its still set to the parent of the opened element.
* 2. data[i] is a closing, so offset i is before the closing, so we need to point to the
* closed element. currentNode will be set to the parent of the closed element later,
* but right now it's still set to the closed element.
* 3. data[i] is content, so offset i is in the middle of an element, so obviously we need
* currentNode, which won't be changed by this iteration.
*
* We want to populate the offsetMap with branches only, but we've just written the actual
* node that lives at this offset. So if it's a leaf node, change it to its parent.
*/
this.offsetMap[i] = ve.dm.nodeFactory.canNodeHaveChildren( currentNode.getType() ) ?
currentNode : parentStack[parentStack.length - 1];
// Infer that if an item in the linear model has a type attribute than it must be an element
if ( this.data[i].type === undefined ) {
// Text node opening
@ -460,12 +432,13 @@ ve.dm.Document.prototype.getData = function( range, deep ) {
return deep ? ve.copyArray( data ) : data;
};
ve.dm.Document.prototype.getOffsetMap = function() {
return this.offsetMap;
};
ve.dm.Document.prototype.getNodeFromOffset = function( offset ) {
return this.offsetMap[offset];
// FIXME duplicated from ve.ce.Document
var node = this.documentNode.getNodeFromOffset( offset );
if ( !node.canHaveChildren() ) {
node = node.getParent();
}
return node;
};
/**
@ -741,14 +714,13 @@ ve.dm.Document.prototype.getAnnotationsFromRange = function( range ) {
*
* The data provided to this method may contain either one node or multiple sibling nodes, but it
* must be balanced and valid. Data provided to this method also may not contain any content at the
* top level. The tree and offset map are updated during this operation.
* top level. The tree is updated during this operation.
*
* Process:
* 1. Nodes between {index} and {index} + {numNodes} in {parent} will be removed
* 2. Data will be retrieved from this.data using {offset} and {newLength}
* 3. A document fragment will be generated from the retrieved data
* 4. The document fragment's offset map will be inserted into this document at {offset}
* 5. The document fragment's nodes will be inserted into {parent} at {index}
* 4. The document fragment's nodes will be inserted into {parent} at {index}
*
* Use cases:
* 1. Rebuild old nodes and offset data after a change to the linear model.
@ -763,18 +735,11 @@ ve.dm.Document.prototype.getAnnotationsFromRange = function( range ) {
* inserted before {index}. To insert nodes at the end, use number of children in {parent}
* - If {numNodes} == 1: Only the node at {index} will be rebuilt
* - If {numNodes} > 1: The node at {index} and the next {numNodes-1} nodes will be rebuilt
* @param {Integer} offset Linear model offset to rebuild or insert offset map data
* - If {numNodes} == 0: Offset to insert offset map data at
* - If {numNodes} >= 1: Offset to remove old and insert new offset map data at
* @param {Integer} offset Linear model offset to rebuild from
* @param {Integer} newLength Length of data in linear model to rebuild or insert nodes for
* @returns {ve.dm.Node[]} Array containing the rebuilt/inserted nodes
*/
ve.dm.Document.prototype.rebuildNodes = function( parent, index, numNodes, offset, newLength ) {
// Compute the length of the old nodes (so we can splice their offsets out of the offset map)
var oldLength = 0;
for ( var i = index; i < index + numNodes; i++ ) {
oldLength += parent.children[i].getOuterLength();
}
// Get a slice of the document where it's been changed
var data = this.data.slice( offset, offset + newLength );
// Build document fragment from data
@ -783,8 +748,6 @@ ve.dm.Document.prototype.rebuildNodes = function( parent, index, numNodes, offse
var nodes = fragment.getDocumentNode().getChildren();
// Replace nodes in the model tree
ve.batchSplice( parent, index, numNodes, nodes );
// Update offset map
ve.batchSplice( this.offsetMap, offset, oldLength, fragment.getOffsetMap() );
// Return inserted nodes
return nodes;
};

View file

@ -99,7 +99,6 @@ ve.Document.prototype.selectNodes = function( range, mode ) {
'nodeOuterRange': nodeRange
} ];
}
// TODO maybe we could find the start more efficiently using the offset map
left = doc.children[0].isWrapped() ? 1 : 0;
do {

View file

@ -41,17 +41,6 @@ test( 'getData', 1, function() {
deepEqual( doc.getData(), ve.dm.example.data );
} );
test( 'getOffsetMap', function() {
var doc = new ve.dm.Document( ve.dm.example.data ),
actual = doc.getOffsetMap(),
expected = ve.dm.example.getOffsetMap( doc.getDocumentNode() );
expect( actual.length + 1 );
ok( actual.length === expected.length, 'offset map lengths match' );
for ( var i = 0; i < actual.length; i++ ) {
ok( actual[i] === expected[i], 'reference at offset ' + i );
}
} );
test( 'getNodeFromOffset', function() {
var doc = new ve.dm.Document( ve.dm.example.data ),
root = doc.getDocumentNode().getRoot(),

View file

@ -286,145 +286,6 @@ ve.dm.example.tree = new ve.dm.DocumentNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )] )
] );
/* Methods */
/**
* Creates an offset map that references a node tree.
*
* This is part of what a ve.dm.DocumentFragment generates when given linear data.
*
* @method
* @param {ve.dm.DocumentNode} root Document node to reference
*/
ve.dm.example.getOffsetMap = function( root ) {
var lookup = ve.example.lookupNode;
return [
lookup( root ), // 0 - document
// <h1>
lookup( root, 0 ), // 1 - heading
// a
lookup( root, 0 ), // 2 - heading
// b (bold)
lookup( root, 0 ), // 3 - heading
// c (italic)
lookup( root, 0 ), // 4 - heading
// </h1>
lookup( root ), // 5 - document
// <table>
lookup( root, 1 ), // 6 - table
// <tbody>
lookup( root, 1, 0 ), // 7 - tableSection
// <tr>
lookup( root, 1, 0, 0 ), // 7 - tableRow
// <td>
lookup( root, 1, 0, 0, 0 ), // 8 - tableCell
// <p>
lookup( root, 1, 0, 0, 0, 0 ), // 9 - paragraph
// d
lookup( root, 1, 0, 0, 0, 0 ), // 10 - paragraph
// </p>
lookup( root, 1, 0, 0, 0 ), // 11 - tableCell
// <ul>
lookup( root, 1, 0, 0, 0, 1 ), // 12 - list
// <li>
lookup( root, 1, 0, 0, 0, 1, 0 ), // 13 - listItem
// <p>
lookup( root, 1, 0, 0, 0, 1, 0, 0 ), // 14 - paragraph
// e
lookup( root, 1, 0, 0, 0, 1, 0, 0 ), // 15 - paragraph
// </p>
lookup( root, 1, 0, 0, 0, 1, 0 ), // 16 - listItem
// <ul>
lookup( root, 1, 0, 0, 0, 1, 0, 1 ), // 17 - list
// <li>
lookup( root, 1, 0, 0, 0, 1, 0, 1, 0 ), // 18 - listItem
// <p>
lookup( root, 1, 0, 0, 0, 1, 0, 1, 0, 0 ), // 19 - paragraph
// f
lookup( root, 1, 0, 0, 0, 1, 0, 1, 0, 0 ), // 20 - paragraph
// </p>
lookup( root, 1, 0, 0, 0, 1, 0, 1, 0 ), // 21 - listItem
// </li>
lookup( root, 1, 0, 0, 0, 1, 0, 1 ), // 22 - list
// </ul>
lookup( root, 1, 0, 0, 0, 1, 0 ), // 23 - listItem
// </li>
lookup( root, 1, 0, 0, 0, 1 ), // 24 - list
// </ul>
lookup( root, 1, 0, 0, 0 ), // 25 - tableCell
// <ul>
lookup( root, 1, 0, 0, 0, 2 ), // 26 - list
// <li>
lookup( root, 1, 0, 0, 0, 2, 0 ), // 27 - listItem
// <p>
lookup( root, 1, 0, 0, 0, 2, 0, 0 ), // 28 - paragraph
// g
lookup( root, 1, 0, 0, 0, 2, 0, 0 ), // 29 - paragraph
// </p>
lookup( root, 1, 0, 0, 0, 2, 0 ), // 30 - listItem
// </li>
lookup( root, 1, 0, 0, 0, 2 ), // 31 - list
// </ul>
lookup( root, 1, 0, 0, 0 ), // 32 - tableCell
// </td>
lookup( root, 1, 0, 0 ), // 33 - tableRow
// </tr>
lookup( root, 1, 0 ), // 33 - tableSection
// </tbody>
lookup( root, 1 ), // 34 - table
// </table>
lookup( root ), // 35- document
// <pre>
lookup( root, 2 ), // 36 - preformatted
// h
lookup( root, 2 ), // 37 - preformatted
// <img>
lookup( root, 2 ), // 38 - preformatted
// </img>
lookup( root, 2 ), // 39 - preformatted
// i
lookup( root, 2 ), // 40 - preformatted
// </pre>
lookup( root ), // 41 - document
// <dl>
lookup( root, 3 ), // 42 - definitionList
// <dt>
lookup( root, 3, 0 ), // 43 - definitionListItem
// <p>
lookup( root, 3, 0, 0 ), // 44 - paragraph
// j
lookup( root, 3, 0, 0 ), // 45 - paragraph
// </p>
lookup( root, 3, 0 ), // 46 - definitionListItem
// </dt>
lookup( root, 3 ), // 47 - definitionList
// <dd>
lookup( root, 3, 1 ), // 48 - definitionListItem
// <p>
lookup( root, 3, 1, 0 ), // 49 - paragraph
// k
lookup( root, 3, 1, 0 ), // 50 - paragraph
// </p>
lookup( root, 3, 1 ), // 51 - definitionListItem
// </dd>
lookup( root, 3 ), // 52 - definitionList
// </dl>
lookup( root ), // 53 - document
// <p>
lookup( root, 4 ), // 54 - paragraph
// f
lookup( root, 4 ), // 55 - paragraph
// </p>
lookup( root ), // 56 - document
// <p>
lookup( root, 5 ), // 57 - paragraph
// f
lookup( root, 5 ), // 58 - paragraph
// </p>
lookup( root ) // 59 - document
];
};
ve.dm.example.conversions = {
'definitionListItem term': {
'domElement': ve.example.createDomElement( 'dt' ),