Merge pull request #771 from ajaxorg/screen2doc

performance improvements for large documents
This commit is contained in:
Fabian Jakobs 2012-05-24 12:00:54 -07:00
commit 31cfc0a921
4 changed files with 124 additions and 135 deletions

View file

@ -74,9 +74,10 @@ var EditSession = function(text, mode) {
this.$frontMarkers = {};
this.$backMarkers = {};
this.$markerId = 1;
this.$rowCache = [];
this.$resetRowCache(0);
this.$wrapData = [];
this.$foldData = [];
this.$rowLengthCache = [];
this.$undoSelect = true;
this.$foldData.toString = function() {
var str = "";
@ -86,7 +87,7 @@ var EditSession = function(text, mode) {
return str;
}
if (typeof text == "object") {
if (typeof text == "object" && text.getLine) {
this.setDocument(text);
} else {
this.setDocument(new Document(text));
@ -139,18 +140,37 @@ var EditSession = function(text, mode) {
*
*
**/
this.$resetRowCache = function(row) {
if (row == 0) {
this.$rowCache = [];
this.$resetRowCache = function(docRrow) {
if (!docRrow) {
this.$docRowCache = [];
this.$screenRowCache = [];
return;
}
var rowCache = this.$rowCache;
for (var i = 0; i < rowCache.length; i++) {
if (rowCache[i].docRow >= row) {
rowCache.splice(i, rowCache.length);
return;
}
var i = this.$getRowCacheIndex(this.$docRowCache, docRrow) + 1;
var l = this.$docRowCache.length;
this.$docRowCache.splice(i, l);
this.$screenRowCache.splice(i, l);
};
this.$getRowCacheIndex = function(cacheArray, val) {
var low = 0;
var hi = cacheArray.length - 1;
while (low <= hi) {
var mid = (low + hi) >> 1;
var c = cacheArray[mid];
if (val > c)
low = mid + 1;
else if (val < c)
hi = mid - 1;
else
return mid;
}
return low && low -1;
};
/**
@ -295,7 +315,6 @@ var EditSession = function(text, mode) {
**/
this.setUndoManager = function(undoManager) {
this.$undoManager = undoManager;
this.$resetRowCache(0);
this.$deltas = [];
this.$deltasDoc = [];
this.$deltasFold = [];
@ -406,6 +425,7 @@ var EditSession = function(text, mode) {
if (isNaN(tabSize) || this.$tabSize === tabSize) return;
this.$modified = true;
this.$rowLengthCache = [];
this.$tabSize = tabSize;
this._emit("changeTabSize");
};
@ -977,16 +997,6 @@ var EditSession = function(text, mode) {
return this.$scrollLeft;
};
/**
* EditSession.getWidth() -> Number
*
* Returns the width of the document.
**/
this.getWidth = function() {
this.$computeWidth();
return this.width;
};
/**
* EditSession.getScreenWidth() -> Number
*
@ -1001,38 +1011,33 @@ var EditSession = function(text, mode) {
if (this.$modified || force) {
this.$modified = false;
if (this.$useWrapMode)
return this.screenWidth = this.$wrapLimit;
var lines = this.doc.getAllLines();
var longestLine = 0;
var cache = this.$rowLengthCache;
var longestScreenLine = 0;
var foldIndex = 0;
var foldLine = this.$foldData[foldIndex];
var foldStart = foldLine ? foldLine.start.row : Infinity;
var len = lines.length;
for ( var i = 0; i < lines.length; i++) {
var foldLine = this.getFoldLine(i),
line, len;
line = lines[i];
if (foldLine) {
var end = foldLine.range.end;
line = this.getFoldDisplayLine(foldLine);
// Continue after the foldLine.end.row. All the lines in
// between are folded.
i = end.row;
for (var i = 0; i < len; i++) {
if (i > foldStart) {
i = foldLine.end.row + 1;
if (i >= len)
break
foldLine = this.$foldData[foldIndex++];
foldStart = foldLine ? foldLine.start.row : Infinity;
}
len = line.length;
longestLine = Math.max(longestLine, len);
if (!this.$useWrapMode) {
longestScreenLine = Math.max(
longestScreenLine,
this.$getStringScreenWidth(line)[0]
);
}
}
this.width = longestLine;
if (this.$useWrapMode) {
this.screenWidth = this.$wrapLimit;
} else {
this.screenWidth = longestScreenLine;
if (cache[i] == null)
cache[i] = this.$getStringScreenWidth(lines[i])[0];
if (cache[i] > longestScreenLine)
longestScreenLine = cache[i];
}
this.screenWidth = longestScreenLine;
}
};
@ -1615,7 +1620,7 @@ var EditSession = function(text, mode) {
if (len != 0) {
if (action.indexOf("remove") != -1) {
useWrapMode && this.$wrapData.splice(firstRow, len);
this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len);
var foldLines = this.$foldData;
removedFolds = this.getFoldsInRange(e.data.range);
@ -1649,6 +1654,10 @@ var EditSession = function(text, mode) {
args = [firstRow, 0];
for (var i = 0; i < len; i++) args.push([]);
this.$wrapData.splice.apply(this.$wrapData, args);
} else {
args = Array(len);
args.unshift(firstRow, 0);
this.$rowLengthCache.splice.apply(this.$rowLengthCache, args);
}
// If some new line is added inside of a foldLine, then split
@ -1702,15 +1711,24 @@ var EditSession = function(text, mode) {
console.error("doc.getLength() and $wrapData.length have to be the same!");
}
useWrapMode && this.$updateWrapData(firstRow, lastRow);
if (useWrapMode)
this.$updateWrapData(firstRow, lastRow);
else
this.$updateRowLengthCache(firstRow, lastRow);
return removedFolds;
};
this.$updateRowLengthCache = function(firstRow, lastRow, b) {
//console.log(firstRow, lastRow, b)
this.$rowLengthCache[firstRow] = null;
this.$rowLengthCache[lastRow] = null;
//console.log(this.$rowLengthCache)
};
/** internal, hide
* EditSession.$updateWrapData(firstRow, lastRow)
*
*
**/
this.$updateWrapData = function(firstRow, lastRow) {
var lines = this.doc.getAllLines();
@ -1944,13 +1962,10 @@ var EditSession = function(text, mode) {
*
**/
this.$getStringScreenWidth = function(str, maxScreenColumn, screenColumn) {
if (maxScreenColumn == 0) {
if (maxScreenColumn == 0)
return [0, 0];
}
if (maxScreenColumn == null) {
maxScreenColumn = screenColumn +
str.length * Math.max(this.getTabSize(), 2);
}
if (maxScreenColumn == null)
maxScreenColumn = Infinity;
screenColumn = screenColumn || 0;
var c, column;
@ -2083,12 +2098,8 @@ var EditSession = function(text, mode) {
*
**/
this.screenToDocumentPosition = function(screenRow, screenColumn) {
if (screenRow < 0) {
return {
row: 0,
column: 0
}
}
if (screenRow < 0)
return {row: 0, column: 0};
var line;
var docRow = 0;
@ -2097,17 +2108,17 @@ var EditSession = function(text, mode) {
var row = 0;
var rowLength = 0;
var rowCache = this.$rowCache;
for (var i = 0; i < rowCache.length; i++) {
if (rowCache[i].screenRow < screenRow) {
row = rowCache[i].screenRow;
docRow = rowCache[i].docRow;
}
else {
break;
}
var rowCache = this.$screenRowCache;
var i = this.$getRowCacheIndex(rowCache, screenRow);
var row1 = rowCache[i];
var docRow1 = this.$docRowCache[i];
if (0 < i && i < rowCache.length) {
var row = rowCache[i];
var docRow = this.$docRowCache[i];
var doCache = screenRow > row || (screenRow == row && i == rowCache.length - 1);
} else {
var doCache = true;
}
var doCache = !rowCache.length || i == rowCache.length;
var maxRow = this.getLength() - 1;
var foldLine = this.getNextFoldLine(docRow);
@ -2127,10 +2138,8 @@ var EditSession = function(text, mode) {
}
}
if (doCache) {
rowCache.push({
docRow: docRow,
screenRow: row
});
this.$docRowCache.push(docRow);
this.$screenRowCache.push(row);
}
}
@ -2163,18 +2172,13 @@ var EditSession = function(text, mode) {
// We remove one character at the end so that the docColumn
// position returned is not associated to the next row on the screen.
if (this.$useWrapMode && docColumn >= column) {
if (this.$useWrapMode && docColumn >= column)
docColumn = column - 1;
}
if (foldLine) {
if (foldLine)
return foldLine.idxToPosition(docColumn);
}
return {
row: docRow,
column: docColumn
}
return {row: docRow, column: docColumn};
};
/** related to: EditSession.screenToDocumentPosition
@ -2198,20 +2202,6 @@ var EditSession = function(text, mode) {
docRow = pos.row;
docColumn = pos.column;
var wrapData;
// Special case in wrapMode if the doc is at the end of the document.
if (this.$useWrapMode) {
wrapData = this.$wrapData;
if (docRow > wrapData.length - 1) {
return {
row: this.getScreenLength(),
column: wrapData.length == 0
? 0
: (wrapData[wrapData.length - 1].length - 1)
};
}
}
var screenRow = 0;
var foldStartRow = null;
var fold = null;
@ -2224,17 +2214,17 @@ var EditSession = function(text, mode) {
}
var rowEnd, row = 0;
var rowCache = this.$rowCache;
for (var i = 0; i < rowCache.length; i++) {
if (rowCache[i].docRow < docRow) {
screenRow = rowCache[i].screenRow;
row = rowCache[i].docRow;
} else {
break;
}
var rowCache = this.$docRowCache;
var i = this.$getRowCacheIndex(rowCache, docRow);
if (0 < i && i < rowCache.length) {
var row = rowCache[i];
var screenRow = this.$screenRowCache[i];
var doCache = docRow > row || (docRow == row && i == rowCache.length - 1);
} else {
var doCache = true;
}
var doCache = !rowCache.length || i == rowCache.length;
var foldLine = this.getNextFoldLine(row);
var foldStart = foldLine ?foldLine.start.row :Infinity;
@ -2255,10 +2245,8 @@ var EditSession = function(text, mode) {
row = rowEnd;
if (doCache) {
rowCache.push({
docRow: row,
screenRow: screenRow
});
this.$docRowCache.push(row);
this.$screenRowCache.push(screenRow);
}
}
@ -2274,7 +2262,7 @@ var EditSession = function(text, mode) {
}
// Clamp textLine if in wrapMode.
if (this.$useWrapMode) {
var wrapRow = wrapData[foldStartRow];
var wrapRow = this.$wrapData[foldStartRow];
var screenRowOffset = 0;
while (textLine.length >= wrapRow[screenRowOffset]) {
screenRow ++;

View file

@ -340,6 +340,8 @@ function Folding() {
if (this.$useWrapMode)
this.$updateWrapData(foldLine.start.row, foldLine.start.row);
else
this.$updateRowLengthCache(foldLine.start.row, foldLine.start.row);
// Notify that fold data has changed.
this.$modified = true;
@ -395,9 +397,10 @@ function Folding() {
newFoldLine.start.column = folds[0].start.column;
}
if (this.$useWrapMode) {
if (this.$useWrapMode)
this.$updateWrapData(startRow, endRow);
}
else
this.$updateRowLengthCache(startRow, endRow);
// Notify that fold data has changed.
this.$modified = true;

View file

@ -423,22 +423,18 @@ module.exports = {
"test get longest line" : function() {
var session = new EditSession(["12"]);
session.setTabSize(4);
assert.equal(session.getWidth(), 2);
assert.equal(session.getScreenWidth(), 2);
session.doc.insertNewLine(0);
session.doc.insertNewLine({row: 0, column: Infinity});
session.doc.insertLines(1, ["123"]);
assert.equal(session.getWidth(), 3);
assert.equal(session.getScreenWidth(), 3);
session.doc.insertNewLine(0);
session.doc.insertNewLine({row: 0, column: Infinity});
session.doc.insertLines(1, ["\t\t"]);
assert.equal(session.getWidth(), 3);
assert.equal(session.getScreenWidth(), 8);
session.setTabSize(2);
assert.equal(session.getWidth(), 3);
assert.equal(session.getScreenWidth(), 4);
},

View file

@ -50,34 +50,36 @@ var assert = require("./test/assertions");
module.exports = {
"test: screen2text the column should be rounded to the next character edge" : function() {
var el = document.createElement("div");
if (!el.getBoundingClientRect) {
console.log("Skipping test: This test only runs in the browser");
return;
}
el.style.left = "0px";
el.style.top = "0px";
el.style.width = "100px";
el.style.left = "20px";
el.style.top = "30px";
el.style.width = "300px";
el.style.height = "100px";
document.body.style.margin = "0px";
document.body.style.padding = "0px";
document.body.appendChild(el);
var renderer = new VirtualRenderer(el);
renderer.setPadding(0);
renderer.setSession(new EditSession("1234"));
var r = renderer.scroller.getBoundingClientRect();
function testPixelToText(x, y, row, column) {
assert.position(renderer.screenToTextCoordinates(x+r.left, y+r.top), row, column);
}
renderer.characterWidth = 10;
renderer.lineHeight = 15;
assert.position(renderer.screenToTextCoordinates(0, 0), 0, 0);
assert.position(renderer.screenToTextCoordinates(4, 0), 0, 0);
assert.position(renderer.screenToTextCoordinates(5, 0), 0, 1);
assert.position(renderer.screenToTextCoordinates(9, 0), 0, 1);
assert.position(renderer.screenToTextCoordinates(10, 0), 0, 1);
assert.position(renderer.screenToTextCoordinates(14, 0), 0, 1);
assert.position(renderer.screenToTextCoordinates(15, 0), 0, 2);
testPixelToText(4, 0, 0, 0);
testPixelToText(5, 0, 0, 1);
testPixelToText(9, 0, 0, 1);
testPixelToText(10, 0, 0, 1);
testPixelToText(14, 0, 0, 1);
testPixelToText(15, 0, 0, 2);
document.body.removeChild(el);
}