Fix EditSession.getScreenLength due to FoldLine refactoring

This commit is contained in:
Julian Viereck 2011-04-23 22:04:38 +02:00
commit b8437cde60
5 changed files with 360 additions and 169 deletions

View file

@ -149,7 +149,7 @@ exports.launch = function(env) {
// BEGING TESTING // BEGING TESTING
var Range = require("ace/range").Range; var Range = require("ace/range").Range;
docs.js.addFold(new Range(0, 13, 0, 18), "args..."); // docs.js.addFold(new Range(0, 13, 0, 18), "args...");
docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); docs.js.addFold(new Range(2, 20, 2, 25), "bar...");
docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); docs.js.addFold(new Range(1, 10, 2, 10), "foo...");
window.s = docs.js; window.s = docs.js;

View file

@ -463,7 +463,7 @@ var EditSession = function(text, mode) {
for ( var i = 0; i < lines.length; i++) { for ( var i = 0; i < lines.length; i++) {
var line = lines[i], var line = lines[i],
len = line.length, len = line.length,
screenLen = this.$getStringScreenWidth(line); screenLen = this.$getStringScreenWidth(line)[0];
longestLine = Math.max(longestLine, len); longestLine = Math.max(longestLine, len);
longestScreenLine = Math.max(longestScreenLine, screenLen); longestScreenLine = Math.max(longestScreenLine, screenLen);
} }
@ -1014,19 +1014,68 @@ var EditSession = function(text, mode) {
return arr; return arr;
} }
this.$getStringDisplayData = function(str, maxScreenColumn, column, offset) {
if (maxScreenColumn == null) {
maxScreenColumn = str.length * Math.max(this.getTabSize(), 2);
}
column = column || 0;
var arr = [],
tabSize,
c;
for (column; column < str.length; column++) {
c = str.charCodeAt(column);
// Tab
if (c == 9) {
tabSize = this.getScreenTabSize(arr.length + offset);
arr.push(TAB);
for (var n = 1; n < tabSize; n++) {
arr.push(TAB_SPACE);
}
}
// Space
else if(c == 32) {
arr.push(SPACE);
}
// full width characters
else if (isFullWidth(c)) {
arr.push(CHAR, CHAR_EXT);
} else {
arr.push(CHAR);
}
if ((arr.length + offset) > maxScreenColumn) {
column --;
break;
}
}
return {
tokens: arr,
column: column
};
}
/** /**
* Calculates the width of the a string on the screen while assuming that * Calculates the width of the a string on the screen while assuming that
* the string starts at the first column on the screen. * the string starts at the first column on the screen.
* *
* @param string str String to calculate the screen width of * @param string str String to calculate the screen width of
* @return int number of columns for str on screen. * @return array
* [0]: number of columns for str on screen.
* [1]: docColumn position that was read until (useful with screenColumn)
*/ */
this.$getStringScreenWidth = function(str) { this.$getStringScreenWidth = function(str, maxScreenColumn, screenColumn) {
var screenColumn = 0; if (maxScreenColumn == 0) {
var tabSize = this.getTabSize(); return [0, 0];
}
if (maxScreenColumn == null) {
maxScreenColumn = str.length * Math.max(this.getTabSize(), 2);
}
screenColumn = screenColumn || 0;
for (var i=0; i<str.length; i++) { var c;
var c = str.charCodeAt(i); for (column = 0; column < str.length; column++) {
c = str.charCodeAt(column);
// tab // tab
if (c == 9) { if (c == 9) {
screenColumn += this.getScreenTabSize(screenColumn); screenColumn += this.getScreenTabSize(screenColumn);
@ -1037,9 +1086,12 @@ var EditSession = function(text, mode) {
} else { } else {
screenColumn += 1; screenColumn += 1;
} }
if (screenColumn > maxScreenColumn) {
break
}
} }
return screenColumn; return [screenColumn, column];
} }
this.getRowLength = function(row) { this.getRowLength = function(row) {
@ -1056,7 +1108,7 @@ var EditSession = function(text, mode) {
this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { this.getScreenLastRowColumn = function(screenRow, returnDocPosition) {
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
return this.$getStringScreenWidth(this.getLine(screenRow)); return this.$getStringScreenWidth(this.getLine(screenRow))[0];
} }
var rowData = this.$screenToDocumentRow(screenRow); var rowData = this.$screenToDocumentRow(screenRow);
@ -1073,7 +1125,7 @@ var EditSession = function(text, mode) {
start = (this.$wrapData[docRow][row - 1] || 0); start = (this.$wrapData[docRow][row - 1] || 0);
} }
if (!returnDocPosition) { if (!returnDocPosition) {
return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end)); return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end))[0];
} else { } else {
return end; return end;
} }
@ -1134,14 +1186,6 @@ var EditSession = function(text, mode) {
return [docRow, row]; return [docRow, row];
}; };
this.screenToDocumentRow = function(screenRow) {
return this.$screenToDocumentRow(screenRow)[0];
};
this.screenToDocumentColumn = function(screenRow, screenColumn) {
return this.screenToDocumentPosition(screenRow, screenColumn).column;
};
/** /**
* Returns the width of a tab character at screenColumn. * Returns the width of a tab character at screenColumn.
*/ */
@ -1149,7 +1193,108 @@ var EditSession = function(text, mode) {
return this.$tabSize - screenColumn % this.$tabSize; return this.$tabSize - screenColumn % this.$tabSize;
}; };
this.screen2Doc = function(screenRow, screenColumn) {
var line,
docRow = 0,
docColumn = 0, column,
foldLine,
lastFoldLine,
foldLineRowLength;
var row = 0,
rowLength;
while (row <= screenRow) {
foldLine = this.getFoldLine(docRow, lastFoldLine);
if (foldLine) {
lastFoldLine = foldLine;
rowLength = foldLine.getRowLength();
} else {
rowLength = this.getRowLength(row);
}
if (row + rowLength - 1 >= screenRow) {
break;
} else {
docRow ++;
row += rowLength;
if (foldLine) {
docRow = foldLine.end.row + 1;
}
}
}
var splits = null;
if (foldLine) {
splits = foldLine.getSplitData();
docColumn = splits[screenRow - row] || 0;
walkScreenColumn = 0;
foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
var data, str;
if (placeholder) {
data = this.$getStringScreenWidth(placeholder, null, walkScreenColumn);
if (data[0] > screenColumn) {
return true; // Stop walk.
}
docColumn += data[1];
} else {
if (isNewRow) {
line = this.getLine(row);
}
str = line.substring(lastColumn, column);
data = this.$getStringScreenWidth(str, screenColumn, walkScreenColumn);
docColumn += data[1];
if (data[0] == screenColumn) {
return true; // Stop walk.
}
}
walkScreenColumn += data[0];
}.bind(this));
} else {
line = this.getLine(docRow);
splits = this.$wrapData[docRow];
if (this.$useWrapMode && splits) {
docColumn = splits[screenRow - row - 1] || 0;
line = line.substring(docColumn);
}
docColumn += this.$getStringScreenWidth(line, screenColumn)[1];
// Need to do some clamping action here.
if (splits) {
column = this.$wrapData[docRow][screenRow - row]
if (docColumn >= column) {
// We remove one character at the end such that the docColumn
// position returned is not associated to the next row on the
// screen.
docColumn = column - 1;
}
} else {
docColumn = Math.min(docColumn, line.length);
}
}
return [docRow, docColumn, screenRow, row]
}
this.screenToDocumentRow = function(screenRow, screenColumn) {
if (screenColumn == null) {
screenColumn = 0;
// throw "Need to pass in screenColumn";
}
return this.$screenToDocumentRow(screenRow)[0];
};
this.screenToDocumentColumn = function(screenRow, screenColumn) {
return this.screenToDocumentPosition(screenRow, screenColumn).column;
};
this.screenToDocumentPosition = function(row, column) { this.screenToDocumentPosition = function(row, column) {
var ret = this.screen2Doc(row, column);
return {
row: ret[0],
column: ret[1]
};
var line; var line;
var docRow; var docRow;
var docColumn; var docColumn;
@ -1179,38 +1324,7 @@ var EditSession = function(text, mode) {
var tabSize, var tabSize,
screenColumn = 0; screenColumn = 0;
for(var i = 0; i < line.length; i++) { docColumn += this.$getStringScreenWidth(line, column)[1];
var c = line.charCodeAt(i);
if (remaining > 0) {
docColumn += 1;
// tab
if (c == 9) {
tabSize = this.getScreenTabSize(screenColumn);
if (remaining >= tabSize) {
remaining -= tabSize;
screenColumn += tabSize;
} else {
remaining = 0;
docColumn -= 1;
}
}
// full width characters
else if (isFullWidth(c)) {
if (remaining >= 2) {
remaining -= 2;
} else {
remaining = 0;
docColumn -= 1;
}
} else {
screenColumn += 1;
remaining -= 1;
}
} else {
break;
}
}
// Clamp docColumn. // Clamp docColumn.
if (this.$useWrapMode) { if (this.$useWrapMode) {
@ -1231,55 +1345,47 @@ var EditSession = function(text, mode) {
}; };
}; };
this.documentToScreenColumn = function(row, docColumn) { // this.$buildFoldedTextLine = function(foldLine, endRow, endColumn) {
return this.documentToScreenPosition(row, docColumn).column; // var textLine = "";
}; //
// // Build a one line string for the current fold sequence that starts
this.documentToScreenRow = function(docRow, docColumn) { // // at foldStartRow.
return this.documentToScreenPosition(docRow, docColumn).row; // var lastEnd = 0,
} // line = this.getLine(foldLine.start.row),
// comp,
this.$buildFoldedTextLine = function(foldLine, endRow, endColumn) { // folds = foldLine.folds,
var textLine = ""; // fold;
//
// Build a one line string for the current fold sequence that starts // for (var i = 0; i < folds.length; i++) {
// at foldStartRow. // fold = folds[i];
var lastEnd = 0, //
line = this.getLine(foldLine.start.row), // comp = fold.compare(endRow, endColumn);
comp, // // This fold is after the endRow/Column.
folds = foldLine.folds, // if (comp == -1) {
fold; // textLine += line.substring(lastEnd, endColumn);
// return textLine;
for (var i = 0; i < folds.length; i++) { // }
fold = folds[i]; // // The endRow/Column is inside of the current fold.
// else if (comp == 0) {
comp = fold.compare(endRow, endColumn); // textLine += line.substring(lastEnd, fold.start.column);
// This fold is after the endRow/Column. // return textLine;
if (comp == -1) { // }
textLine += line.substring(lastEnd, endColumn); //
return textLine; // textLine += line.substring(lastEnd, fold.start.column);
} // textLine += fold.placeholder;
// The endRow/Column is inside of the current fold. //
else if (comp == 0) { // if (fold.sameLine) {
textLine += line.substring(lastEnd, fold.start.column); // lastEnd = fold.end.column;
return textLine; // } else {
} // row = fold.end.row;
//
textLine += line.substring(lastEnd, fold.start.column); // line = this.getLine(row);
textLine += fold.placeholder; // lastEnd = fold.end.column;
// }
if (fold.sameLine) { // }
lastEnd = fold.end.column; // textLine += line.substring(lastEnd, endColumn);
} else { // return textLine;
row = fold.end.row; // }
line = this.getLine(row);
lastEnd = fold.end.column;
}
}
textLine += line.substring(lastEnd, endColumn);
return textLine;
}
this.doc2Screen = function(docRow, docColumn) { this.doc2Screen = function(docRow, docColumn) {
var screenRow = 0, var screenRow = 0,
@ -1288,7 +1394,7 @@ var EditSession = function(text, mode) {
fold = null, fold = null,
folds, folds,
comp, comp,
foldLine = null; foldLine = null, lastFoldLine = null;
// Clamp the docRow position in case it's inside of a folded block. // Clamp the docRow position in case it's inside of a folded block.
foldLine = this.getFoldLine(docRow); foldLine = this.getFoldLine(docRow);
@ -1309,8 +1415,9 @@ var EditSession = function(text, mode) {
} }
for (var row = 0; row < docRow; row++) { for (var row = 0; row < docRow; row++) {
foldLine = this.getFoldLine(row); foldLine = this.getFoldLine(row, lastFoldLine);
if (foldLine) { if (foldLine) {
lastFoldLine = foldLine;
if (foldLine.end.row >= docRow) { if (foldLine.end.row >= docRow) {
break; break;
} }
@ -1323,17 +1430,24 @@ var EditSession = function(text, mode) {
// Calculate the text line that is displayed in docRow on the screen. // Calculate the text line that is displayed in docRow on the screen.
var textLine = ""; var textLine = "";
foldLine = this.getFoldLine(docRow, lastFoldLine);
foldLine = this.getFoldLine(docRow);
// Check if the final row we want to reach is inside of a fold. // Check if the final row we want to reach is inside of a fold.
if (!foldLine) { if (!foldLine) {
textLine = this.getLine(docRow).substring(0, docColumn); textLine = this.getLine(docRow).substring(0, docColumn);
foldStartRow = docRow; foldStartRow = docRow;
} else { } else {
textLine = this.$buildFoldedTextLine( // Build the textline using the FoldLine walker.
foldLine, var line;
docRow, foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
docColumn); if (placeholder) {
textLine += placeholder;
} else {
if (isNewRow) {
line = this.getLine(row);
}
textLine += line.substring(lastColumn, column);
}
}.bind(this), docRow, docColumn)
} }
// Clamp textLine if in wrapMode. // Clamp textLine if in wrapMode.
@ -1348,7 +1462,7 @@ var EditSession = function(text, mode) {
wrapData[screenRowOffset - 1] || 0, textLine.length); wrapData[screenRowOffset - 1] || 0, textLine.length);
} }
return [screenRow, this.$getStringScreenWidth(textLine), textLine]; return [screenRow, this.$getStringScreenWidth(textLine)[0], textLine];
} }
this.documentToScreenPosition = function(pos, column) { this.documentToScreenPosition = function(pos, column) {
@ -1369,6 +1483,14 @@ var EditSession = function(text, mode) {
}; };
}; };
this.documentToScreenColumn = function(row, docColumn) {
return this.documentToScreenPosition(row, docColumn).column;
};
this.documentToScreenRow = function(docRow, docColumn) {
return this.documentToScreenPosition(docRow, docColumn).row;
};
this.getScreenLength = function() { this.getScreenLength = function() {
var length = this.getLength(); var length = this.getLength();
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
@ -1382,10 +1504,8 @@ var EditSession = function(text, mode) {
var foldData = this.$foldData; var foldData = this.$foldData;
for (var i = 0; i < foldData.length; i++) { for (var i = 0; i < foldData.length; i++) {
var foldRow = foldData[i]; var foldLine = foldData[i];
if (foldRow.linkOut) { screenRows -= foldLine.end.row - foldLine.start.row;
screenRows -= foldRow.linkOut.end.row - foldRow.linkOut.start.row;
}
} }
return screenRows; return screenRows;
} }
@ -1494,7 +1614,48 @@ var EditSession = function(text, mode) {
this.getSplitData = function() { this.getSplitData = function() {
// TODO: Add support for wrapped lines here. // TODO: Add support for wrapped lines here.
return undefined; return [];
}
this.walk = function(callback, endRow, endColumn) {
var lastEnd = 0,
folds = this.folds,
fold,
comp, stop, isNewRow = true;
if (endRow == null) {
endRow = this.end.row;
endColumn = this.end.column;
}
for (var i = 0; i < folds.length; i++) {
fold = folds[i];
comp = fold.compare(endRow, endColumn);
// This fold is after the endRow/Column.
if (comp == -1) {
callback(null, endRow, endColumn, lastEnd, isNewRow);
return;
}
// The endRow/Column is inside of the current fold.
else if (comp == 0) {
callback(null, endRow, fold.start.column, lastEnd, isNewRow);
return;
}
stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow);
stop = stop || callback(fold.placeholder);
if (stop) {
return;
}
// Note the new lastEnd might not be on the same line. However,
// it's the callback's job to recognize this.
isNewRow = !fold.sameLine;
lastEnd = fold.end.column;
}
callback(null, endRow, endColumn, lastEnd, isNewRow);
} }
}).call(FoldLine.prototype); }).call(FoldLine.prototype);
@ -1503,7 +1664,7 @@ var EditSession = function(text, mode) {
var i = Math.max(foldData.indexOf(startFoldLine), 0); var i = Math.max(foldData.indexOf(startFoldLine), 0);
for (i; i < foldData.length; i++) { for (i; i < foldData.length; i++) {
var foldLine = foldData[i]; var foldLine = foldData[i];
if (foldLine.start.row >= docRow || foldLine.end.row >= docRow) { if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) {
return foldLine; return foldLine;
} else if (foldLine.end.row > docRow) { } else if (foldLine.end.row > docRow) {
return null; return null;

View file

@ -65,6 +65,66 @@ function createFoldTestSession() {
return session; return session;
} }
var foldTests = {
"fold-same-row": [
[0, 0, 0, 0],
[0, 13, 0, 13],
[0, 14, 0, 13],
[0, 17, 0, 13],
[0, 18, 0, 20],
[0, 19, 0, 20]
],
"fold-on-some-other-row": [
[1, 0, 1, 0],
[1, 10, 1, 10],
[1, 11, 1, 10],
[1, 99, 1, 10],
[2, 0, 1, 10],
[2, 9, 1, 10],
[2, 10, 1, 16],
[2, 11, 1, 17]
],
"fold-in-the-same-row+fold-other-row": [
[2, 19, 1, 25],
[2, 20, 1, 26],
[2, 21, 1, 26],
[2, 24, 1, 26],
[2, 25, 1, 32],
[2, 26, 1, 33],
[2, 99, 1, 40],
],
"fold-after": [
[3, 0, 2, 0]
]
};
function runFoldTests(unitFunc) {
var session = createFoldTestSession();
function assertScreen2Doc(screenRow, screenCol, docRow, docCol) {
assert.position(
session.screenToDocumentPosition(docRow, docCol),
screenRow, screenCol
);
}
var foldTests = this.foldTests,
tests, test, testArray;
for (tests in foldTests) {
testArray = foldTests[tests];
for (var i = 0; i < testArray.length; i++) {
test = testArray[i];
unitFunc(test[0], test[1], test[2], test[3]);
}
}
}
module.exports = { module.exports = {
"test: find matching opening bracket" : function() { "test: find matching opening bracket" : function() {
@ -372,44 +432,23 @@ module.exports = {
}, },
"test fold documentToScreen": function() { "test fold documentToScreen": function() {
var session = createFoldTestSession();
function assertDoc2Screen(docRow, docCol, screenRow, screenCol) { function assertDoc2Screen(docRow, docCol, screenRow, screenCol) {
assert.position( assert.position(
session.documentToScreenPosition(docRow, docCol), session.documentToScreenPosition(docRow, docCol),
screenRow, screenCol screenRow, screenCol
); );
} }
runFoldTests(assertDoc2Screen);
},
// One fold ending in the same row. "test fold screenToDocument": function() {
assertDoc2Screen(0, 0, 0, 0); function assertScreen2Doc(docRow, docCol, screenRow, screenCol) {
assertDoc2Screen(0, 13, 0, 13); assert.position(
assertDoc2Screen(0, 14, 0, 13); session.screenToDocumentPosition(screenRow, screenCol),
assertDoc2Screen(0, 17, 0, 13); docRow, docCol
assertDoc2Screen(0, 18, 0, 20); );
}
// Fold ending on some other row. runFoldTests(assertScreen2Doc);
assertDoc2Screen(1, 0, 1, 0);
assertDoc2Screen(1, 10, 1, 10);
assertDoc2Screen(1, 11, 1, 10);
assertDoc2Screen(1, 99, 1, 10);
assertDoc2Screen(2, 0, 1, 10);
assertDoc2Screen(2, 9, 1, 10);
assertDoc2Screen(2, 10, 1, 16);
assertDoc2Screen(2, 11, 1, 17);
// Fold in the same row with fold over more then one row in the same row.
assertDoc2Screen(2, 19, 1, 25);
assertDoc2Screen(2, 20, 1, 26);
assertDoc2Screen(2, 21, 1, 26);
assertDoc2Screen(2, 24, 1, 26);
assertDoc2Screen(2, 25, 1, 32);
assertDoc2Screen(2, 26, 1, 33);
assertDoc2Screen(2, 99, 1, 40);
// Test one position after the folds. Should be all like normal.
assertDoc2Screen(3, 0, 2, 0);
} }
}; };

View file

@ -415,9 +415,7 @@ var Text = function(parentEl) {
this.$renderFoldLine = function(stringBuilder, row, tokens) { this.$renderFoldLine = function(stringBuilder, row, tokens) {
var session = this.session, var session = this.session,
foldLine = session.getFoldLine(row), foldLine = session.getFoldLine(row),
folds = foldLine.folds; renderTokens = [];
var renderTokens = [];
function addTokens(tokens, from, to) { function addTokens(tokens, from, to) {
var idx = 0, col = 0; var idx = 0, col = 0;
@ -459,26 +457,19 @@ var Text = function(parentEl) {
} }
} }
var lastCol = 0, foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
fold = null; if (placeholder) {
renderTokens.push({
for (var i = 0; i < folds.length; i++) { type: "fold",
fold = folds[i]; value: placeholder
addTokens(tokens, lastCol, fold.start.column); });
renderTokens.push({ } else {
type: "fold", if (isNewRow) {
value: fold.placeholder tokens = this.tokenizer.getTokens(row, row)[0].tokens;
}); }
addTokens(tokens, lastColumn, column);
if (fold.start.row != fold.end.row) {
tokens = this.tokenizer.getTokens(
fold.end.row, fold.end.row)[0].tokens;
} }
}.bind(this), foldLine.end.row, this.session.getLine(foldLine.end.row).length);
lastCol = fold.end.column;
}
// Add the rest of the line
addTokens(tokens, lastCol, session.getLine(foldLine.end.row).length);
// TODO: Build a fake splits array! // TODO: Build a fake splits array!
var splits = foldLine.getSplitData(); var splits = foldLine.getSplitData();

View file

@ -477,7 +477,7 @@ var VirtualRenderer = function(container, theme) {
// Map lines on the screen to lines in the document. // Map lines on the screen to lines in the document.
var firstRowScreen, firstRowHeight; var firstRowScreen, firstRowHeight;
var lineHeight = { lineHeight: this.lineHeight }; var lineHeight = { lineHeight: this.lineHeight };
firstRow = session.screenToDocumentRow(firstRow); firstRow = session.screenToDocumentRow(firstRow, 0);
firstRowScreen = session.documentToScreenRow(firstRow); firstRowScreen = session.documentToScreenRow(firstRow);
firstRowHeight = session.getRowHeight(lineHeight, firstRow); firstRowHeight = session.getRowHeight(lineHeight, firstRow);