aboutsummaryrefslogblamecommitdiffstats
path: root/trunk/infrastructure/ace/www/spanlist.js
blob: 756a411851cdc8d276e14d712b043d5b09e2e30b (plain) (tree)






















































































































































































































































































                                                                                     
/**
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


function newSpanList() {
  function encodeInt(num) {
    num = num & 0x1fffffff;
    // neither 16-bit char can be 0x0001 or mistakable for the other
    return String.fromCharCode(((num >> 15) & 0x3fff) | 0x4000) +
      String.fromCharCode((num & 0x7fff) | 0x8000);
  }
  function decodeInt(str) {
    return ((str.charCodeAt(0) & 0x3fff) << 15) | (str.charCodeAt(1) & 0x7fff);
  }
  function indexOfInt(str, n) {
    var result = str.indexOf(encodeInt(n));
    if (result < 0) return result;
    return result/2;
  }
  function intAt(str, i) {
    var idx = i*2;
    return decodeInt(str.substr(idx, 2));
  }
  function numInts(str) { return str.length/2; }
  function subrange(str, start, end) {
    return str.substring(start*2, end*2);
  }
  var nil = "\1\1";
  var repeatNilCache = [''];
  function repeatNil(times) {
    while (times >= repeatNilCache.length) {
      repeatNilCache.push(repeatNilCache[repeatNilCache.length-1] + nil);
    }
    return repeatNilCache[times];
  }
  function indexOfNonnil(str, start) {
    startIndex = (start || 0)*2;
    var nonnilChar = /[^\1]/g;
    nonnilChar.lastIndex = startIndex;
    var result = nonnilChar.exec(str);
    if (! result) {
      return str.length/2;
    }
    return (nonnilChar.lastIndex - 1)/2;
  }
  function intSplice(str, start, delCount, newStr) {
    return str.substring(0, start*2) + newStr + str.substring((start+delCount)*2);
  }

  function entryWidth(entry) {
    if ((typeof entry) == "number") return entry;
    return (entry && entry.width) || 1;
  }

  // "func" is a function over 0..(numItems-1) that is monotonically
  // "increasing" with index (false, then true).  Finds the boundary
  // between false and true, a number between 0 and numItems inclusive.
  function binarySearch(numItems, func) {
    if (numItems < 1) return 0;
    if (func(0)) return 0;
    if (! func(numItems-1)) return numItems;
    var low = 0; // func(low) is always false
    var high = numItems-1; // func(high) is always true
    while ((high - low) > 1) {
      var x = Math.floor((low+high)/2); // x != low, x != high
      if (func(x)) high = x;
      else low = x;
    }
    return high;
  }
  
  var NEXT_ID = 1;
  var entryList = ""; // e.g. 2, 4, 3, 6
  var charList = ""; // e.g. nil, nil, nil, 2, nil, 4, nil, nil, nil, nil, 3, 6
  var keyToId = {};
  var idToKey = {};
  var idToEntry = {};
  var idToNextId = {};
  var idToPrevId = {};
  var length = 0;
  var totalWidth = 0;

  function idAtIndex(i) { return intAt(entryList, i); }
  function indexOfId(id) { return indexOfInt(entryList, id); }
  function offsetOfId(id) {
    if (! id) return totalWidth;
    var entry = idToEntry[id];
    var wid = entryWidth(entry);
    var lastCharLoc = indexOfInt(charList, id);
    return lastCharLoc + 1 - wid;
  }
  function idAtOffset(n) {
    return intAt(charList, indexOfNonnil(charList, n));
  }
  
  var self = {
    length: function() { return length; },
    totalWidth: function() { return totalWidth; },
    next: function (entryOrKey) {
      if ((typeof entryOrKey) == "object") {
	var entry = entryOrKey;
	var id = idToNextId[keyToId[entry.key]];
	if (id) return idToEntry[id];
	return null;
      }
      else {
	var k = entryOrKey;
	var id = idToNextId[keyToId[k]];
	if (id) return idToKey[id];
	return null;
      }
    },
    prev: function (entryOrKey) {
      if ((typeof entryOrKey) == "object") {
	var entry = entryOrKey;
	var id = idToPrevId[keyToId[entry.key]];
	if (id) return idToEntry[id];
	return null;
      }
      else {
	var k = entryOrKey;
	var id = idToPrevId[keyToId[k]];
	if (id) return idToKey[id];
	return null;
      }
    },
    atKey: function (k) { return idToEntry[keyToId[k]]; },
    atIndex: function (i) { return idToEntry[idAtIndex(i)]; },
    keyAtIndex: function(i) { return idToKey[idAtIndex(i)]; },
    keyAtOffset: function(n) { return idToKey[idAtOffset(n)]; },
    containsKey: function (k) { return !! keyToId[k]; },
    indexOfKey: function (k) { return indexOfId(keyToId[k]); },
    indexOfEntry: function (entry) { return self.indexOfKey(entry.key); },
    setKeyWidth: function (k, width) {
      var id = keyToId[k];
      var charStart = offsetOfId(id);
      var oldWidth = entryWidth(idToEntry[id]);
      var toDelete = 0;
      var toInsert = 0;
      if (width < oldWidth) toDelete = oldWidth - width;
      else if (width > oldWidth) toInsert = width - oldWidth;
      charList = intSplice(charList, charStart, toDelete, repeatNil(toInsert));      
      totalWidth += (width - oldWidth);
    },
    setEntryWidth: function (entry, width) {
      return self.setKeyWidth(entry.key, width);
    },
    getEntryWidth: function (entry) {
      return entryWidth(entry);
    },
    getKeyWidth: function (k) {
      return entryWidth(idToEntry[keyToId[k]]);
    },
    offsetOfKey: function(k) { return offsetOfId(keyToId[k]); },
    offsetOfEntry: function(entry) { return self.offsetOfKey(entry.key); },
    offsetOfIndex: function (i) {
      if (i < 0) return 0;
      else if (i >= length) {
	return totalWidth;
      }
      else {
	return offsetOfId(idAtIndex(i));
      }
    },
    atOffset: function (n) {
      return idToEntry[idAtOffset(n)];
    },
    indexOfOffset: function (n) {
      if (n < 0) return 0;
      else if (n >= totalWidth) return length;
      return indexOfId(idAtOffset(n));
    },
    search: function(entryFunc) {
      return binarySearch(length, function (i) {
	return entryFunc(idToEntry[idAtIndex(i)]);
      });
    },
    push: function(entry, optKey) {
      self.splice(length, 0, [entry], (optKey && [optKey]));
    },
    // entries can be objects with a 'key' property, possibly a 'width' property,
    // and any other properties;  OR they can be just a width number, for an
    // ultra-light-weight representation.  In the latter case the key array is
    // used to get the keys.  Some functions become useless with this usage, i.e.
    // the ones that use an entry for identity.
    splice: function (start, deleteCount, newEntryArray, optKeyArray) {
      var charStart = self.offsetOfIndex(start);
      var charsToDelete = 0;
      var idBefore = ((start == 0) ? null : intAt(entryList, start-1));
      // idAfter is mutated into id of node following deleted nodes
      var idAfter = ((start == length) ? null : (idBefore ? idToNextId[idBefore] :
						 intAt(entryList, start)));
      if (deleteCount > 0) {
	var deleteId = idAfter;
	for(var i=0;i<deleteCount;i++) {
	  var nextId = idToNextId[deleteId];
	  var entry = idToEntry[deleteId];
	  var wid = entryWidth(entry);
	  delete keyToId[idToKey[deleteId]];
	  delete idToKey[deleteId];
	  delete idToEntry[deleteId];
	  delete idToNextId[deleteId];
	  delete idToPrevId[deleteId];
	  length--;
	  totalWidth -= wid;
	  charsToDelete += wid;
	  deleteId = nextId;
	}
	idAfter = (deleteId || null);
      }
      var newChars = [];
      var newIds = [];
      var prevId = idBefore;
      if (newEntryArray && newEntryArray.length > 0) {
	for(var i=0,n=newEntryArray.length; i<n; i++) {
	  var entry = newEntryArray[i];
	  var newId = (NEXT_ID++);
	  var encId = encodeInt(newId);
	  newIds.push(encId);
	  var wid = entryWidth(entry);
	  newChars.push(repeatNil(wid-1), encId);
	  var key = (optKeyArray ? optKeyArray[i] : entry.key);
	  keyToId[key] = newId;
	  idToKey[newId] = key;
	  idToEntry[newId] = entry;
	  if (prevId) {
	    idToNextId[prevId] = newId;
	    idToPrevId[newId] = prevId;
	  }
	  prevId = newId;
	  length++;
	  totalWidth += wid;
	}
	if (prevId && idAfter) {
	  idToNextId[prevId] = idAfter;
	  idToPrevId[idAfter] = prevId;
	}
      }
      else {
	if (idBefore && idAfter) {
	  idToNextId[idBefore] = idAfter;
	  idToPrevId[idAfter] = idBefore;
	}
	else if (idBefore) delete idToNextId[idBefore];
	else if (idAfter) delete idToPrevId[idAfter];
      }
      entryList = intSplice(entryList, start, deleteCount, newIds.join(''));
      charList = intSplice(charList, charStart, charsToDelete, newChars.join(''));
      
      if (length > 0) {
	// checkrep
	if (idToPrevId[idAtIndex(0)]) console.error("a");
	if (idToNextId[idAtIndex(length-1)]) console.error("b");
	for(var i=0;i<length-1;i++) {
	  if (idToNextId[idAtIndex(i)] != idAtIndex(i+1)) console.error("c"+i);
	}
	for(var i=1;i<length;i++) {
	  if (idToPrevId[idAtIndex(i)] != idAtIndex(i-1)) console.error("d"+i);
	}
      }
    }
  };

  return self;
}