Fix move to first/last character in line if in wrap mode. Fixes some other bugs on the way and add some simple unit tests.

This commit is contained in:
Julian Viereck 2011-01-11 15:47:55 +01:00
commit 28ea247f17
8 changed files with 171 additions and 51 deletions

View file

@ -91,6 +91,7 @@ exports.launch = function(env) {
var container = document.getElementById("editor"); var container = document.getElementById("editor");
env.editor = new Editor(new Renderer(container, theme)); env.editor = new Editor(new Renderer(container, theme));
window.$editor = env.editor;
function onDocChange() { function onDocChange() {
var doc = getDoc(); var doc = getDoc();
@ -124,7 +125,7 @@ exports.launch = function(env) {
docEl.onchange = onDocChange; docEl.onchange = onDocChange;
function getDoc() { function getDoc() {
return docs[docEl.value]; return docs.plain//docs[docEl.value];
} }
var modeEl = document.getElementById("mode"); var modeEl = document.getElementById("mode");

View file

@ -800,9 +800,12 @@ var Document = function(text, mode) {
this.$useWrapMode = false; this.$useWrapMode = false;
this.setUseWrapMode = function(useWrapMode) { this.setUseWrapMode = function(useWrapMode) {
this.$useWrapMode = useWrapMode; if (useWrapMode != this.$useWrapMode) {
this.$updateWrapData(0, this.lines.length - 1); this.$useWrapMode = useWrapMode;
this._dispatchEvent("changeWrapMode"); this.$updateWrapData(0, this.lines.length - 1);
this._dispatchEvent("changeWrapMode");
this.fireChangeEvent(0);
}
}; };
this.getUseWrapMode = function() { this.getUseWrapMode = function() {
@ -810,7 +813,10 @@ var Document = function(text, mode) {
}; };
this.setWrapLimit = function(wrapLimit) { this.setWrapLimit = function(wrapLimit) {
this.$wrapLimit = wrapLimit; if (wrapLimit != this.$wrapLimit) {
this.$wrapLimit = wrapLimit;
this.$updateWrapData(0, this.lines.length - 1);
}
}; };
this.getWrapLimit = function() { this.getWrapLimit = function() {
@ -1008,21 +1014,67 @@ var Document = function(text, mode) {
return rows * config.lineHeight; return rows * config.lineHeight;
}; };
this.getScreenRowLength = function(screenRow) {
/**
* Calculates the width of the a string on the screen while assuming that
* the string starts at the first column on the screen.
*
* @param string str String to calculate the screen width of
* @return int number of columns for str on screen.
*/
this.$getStringScreenWidth = function(str) {
var len = str.length;
str.replace("\t", function(m) {
len += tabSize-1;
return m;
});
return len;
}
this.getScreenLastRowColumn = function(screenRow, returnDocPosition) {
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
return this.getLine(screenRow).length; return this.$getStringScreenWidth(this.getLine(screenRow));
} }
var rowData = this.$screenToDocumentRow(screenRow); var rowData = this.$screenToDocumentRow(screenRow);
var docRow = rowData[0], var docRow = rowData[0],
row = rowData[1]; row = rowData[1];
var start, end;
if (this.$wrapData[docRow][row]) { if (this.$wrapData[docRow][row]) {
return this.$wrapData[docRow][row] - (this.$wrapData[docRow][row - 1] || 0); start = (this.$wrapData[docRow][row - 1] || 0);
end = this.$wrapData[docRow][row];
returnDocPosition && end--;
} else { } else {
return this.lines[docRow].length - end = this.lines[docRow].length;
(this.$wrapData[docRow][row - 1] || 0) ; start = (this.$wrapData[docRow][row - 1] || 0);
} }
if (!returnDocPosition) {
return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end));
} else {
return end;
}
};
this.getDocumentLastRowColumn = function(docRow, docColumn) {
if (!this.$useWrapMode) {
return this.getLine(docRow).length;
}
var screenRow = this.documentToScreenRow(docRow, docColumn);
return this.getScreenLastRowColumn(screenRow, true);
}
this.getScreenFirstRowColumn = function(screenRow) {
if (!this.$useWrapMode) {
return 0;
}
var rowData = this.$screenToDocumentRow(screenRow);
var docRow = rowData[0],
row = rowData[1];
return this.$wrapData[docRow][row - 1] || 0;
}; };
this.getRowSplitData = function(row) { this.getRowSplitData = function(row) {
@ -1033,6 +1085,12 @@ var Document = function(text, mode) {
} }
}; };
/**
*
* @returns array
* - array[0]: The documentRow aquivalent.
* - array[1]: The screenRowOffset to the first documentRow on the screen.
*/
this.$screenToDocumentRow = function(row) { this.$screenToDocumentRow = function(row) {
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
return [row, 0]; return [row, 0];
@ -1063,13 +1121,14 @@ var Document = function(text, mode) {
var remaining = column; var remaining = column;
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
docRow = row; docRow = row;
row = 0;
docColumn = 0; docColumn = 0;
line = this.getLine(docRow).split("\t"); line = this.getLine(docRow).split("\t");
} else { } else {
var wrapData = this.$wrapData, linesCount = this.lines.length; var wrapData = this.$wrapData, linesCount = this.lines.length;
var rowData = this.$screenToDocumentRow(row); var rowData = this.$screenToDocumentRow(row);
var row = rowData[1]; row = rowData[1];
docRow = rowData[0]; docRow = rowData[0];
if (docRow >= this.lines.length) { if (docRow >= this.lines.length) {
@ -1101,7 +1160,12 @@ var Document = function(text, mode) {
// Clamp docColumn. // Clamp docColumn.
if (docRow < linesCount && wrapData[docRow][row]) { if (docRow < linesCount && wrapData[docRow][row]) {
docColumn = Math.min(docColumn, wrapData[docRow][row]); if (docColumn >= wrapData[docRow][row]) {
// We remove one character at the end such that the docColumn
// position returned is not associated to the next row on the
// screen.
docColumn = wrapData[docRow][row] - 1;
}
} else if (this.lines[docRow]) { } else if (this.lines[docRow]) {
docColumn = Math.min(docColumn, this.lines[docRow].length); docColumn = Math.min(docColumn, this.lines[docRow].length);
} }
@ -1116,33 +1180,57 @@ var Document = function(text, mode) {
return this.documentToScreenPosition(row, docColumn).column; return this.documentToScreenPosition(row, docColumn).column;
}; };
this.documentToScreenRow = function(row) { /**
*
* @return array[2]
* - array[0]: The number of the row on the screen (aka screenRow)
* - array[1]: The number of rows from the first docRow on the screen
* (aka screenRowOffset);
*/
this.$documentToScreenRow = function(docRow, docColumn) {
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
return row; return [docRow, 0];
} }
var wrapData = this.$wrapData; var wrapData = this.$wrapData;
var screenRow = 0; var screenRow = 0;
// Handle special case where the row is outside of the range of lines. // Handle special case where the row is outside of the range of lines.
if (row > wrapData.length - 1) { if (docRow > wrapData.length - 1) {
for (row = 0; row < wrapData.length; row ++) { return [this.getScreenLength(),
screenRow += wrapData[row].length + 1; this.wrapData[this.wrapData.length - 1].length - 1];
}
return screenRow;
} }
for (var i = 0; i < row; i++) { for (var i = 0; i < docRow; i++) {
screenRow += wrapData[i].length + 1; screenRow += wrapData[i].length + 1;
} }
return screenRow; var screenRowOffset = 0;
while (docColumn >= wrapData[docRow][screenRowOffset]) {
screenRow ++;
screenRowOffset++;
}
return [screenRow, screenRowOffset];
} }
this.documentToScreenPosition = function(row, column) { this.documentToScreenRow = function(docRow, docColumn) {
return this.$documentToScreenRow(docRow, docColumn)[0];
}
this.documentToScreenPosition = function(pos, column) {
var str; var str;
var tabSize = this.getTabSize(); var tabSize = this.getTabSize();
// Normalize the passed in arguments.
var row;
if (column != null) {
row = pos;
} else {
row = pos.row;
column = pos.column;
}
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
str = this.getLine(row).substring(0, column); str = this.getLine(row).substring(0, column);
column += (str.split("\t").length - 1) * (tabSize - 1); column += (str.split("\t").length - 1) * (tabSize - 1);
@ -1151,26 +1239,24 @@ var Document = function(text, mode) {
column: column column: column
}; };
} }
var screenRow = this.documentToScreenRow(row);
if (row >= this.lines.length) { if (row >= this.lines.length) {
return { return {
row: screenRow, row: screenRow,
column: 0 column: 0
}; };
} }
var screenColumn = column;
var wrapRowData = this.$wrapData[row];
var split;
for (split = 0; wrapRowData && split < wrapRowData.length; split++) {
if (column > wrapRowData[split]) {
screenColumn = column - wrapRowData[split];
screenRow ++;
} else {
break;
}
}
str = this.getLine(row).substring(wrapRowData[split - 1] || 0, column); var split;
var wrapRowData = this.$wrapData[row];
var screenRow, screenRowOffset;
var screenColumn;
var rowData = this.$documentToScreenRow(row, column);
screenRow = rowData[0];
screenRowOffset = rowData[1];
screenColumn = column - (wrapRowData[screenRowOffset - 1] || 0);
str = this.getLine(row).substring(
wrapRowData[screenRowOffset - 1] || 0, column);
screenColumn += (str.split("\t").length - 1) * (tabSize - 1); screenColumn += (str.split("\t").length - 1) * (tabSize - 1);
return { return {

View file

@ -59,7 +59,7 @@ var Cursor = function(parentEl) {
this.setCursor = function(position, overwrite) { this.setCursor = function(position, overwrite) {
this.position = this.position =
this.doc.documentToScreenPosition(position.row, position.column); this.doc.documentToScreenPosition(position);
if (overwrite) { if (overwrite) {
dom.addCssClass(this.cursor, "ace_overwrite"); dom.addCssClass(this.cursor, "ace_overwrite");
} else { } else {

View file

@ -111,7 +111,7 @@ var Marker = function(parentEl) {
// selection start // selection start
var row = range.start.row; var row = range.start.row;
var lineRange = new Range(row, range.start.column, var lineRange = new Range(row, range.start.column,
row, this.doc.getScreenRowLength(row)); row, this.doc.getScreenLastRowColumn(row));
this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig);
// selection end // selection end
@ -122,7 +122,7 @@ var Marker = function(parentEl) {
for (var row = range.start.row + 1; row < range.end.row; row++) { for (var row = range.start.row + 1; row < range.end.row; row++) {
lineRange.start.row = row; lineRange.start.row = row;
lineRange.end.row = row; lineRange.end.row = row;
lineRange.end.column = this.doc.getScreenRowLength(row); lineRange.end.column = this.doc.getScreenLastRowColumn(row);
this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig);
} }
}; };

View file

@ -147,9 +147,9 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
this.toScreenRange = function(doc) { this.toScreenRange = function(doc) {
var screenPosStart = var screenPosStart =
doc.documentToScreenPosition(this.start.row, this.start.column); doc.documentToScreenPosition(this.start);
var screenPosEnd = var screenPosEnd =
doc.documentToScreenPosition(this.end.row, this.end.column); doc.documentToScreenPosition(this.end);
return new Range( return new Range(
screenPosStart.row, screenPosStart.column, screenPosStart.row, screenPosStart.column,
screenPosEnd.row, screenPosEnd.column screenPosEnd.row, screenPosEnd.column

View file

@ -170,7 +170,7 @@ var Selection = function(doc) {
this.$updateDesiredColumn = function() { this.$updateDesiredColumn = function() {
var cursor = this.getCursor(); var cursor = this.getCursor();
if (cursor) { if (cursor) {
this.$desiredColumn = this.doc.documentToScreenPosition(cursor.row, cursor.column).column; this.$desiredColumn = this.doc.documentToScreenColumn(cursor.row, cursor.column);
} }
}; };
@ -311,19 +311,27 @@ var Selection = function(doc) {
this.moveCursorLineStart = function() { this.moveCursorLineStart = function() {
var row = this.selectionLead.row; var row = this.selectionLead.row;
var column = this.selectionLead.column; var column = this.selectionLead.column;
var beforeCursor = this.doc.getLine(row).slice(0, column); var screenRow = this.doc.documentToScreenRow(row, column);
var firstRowColumn = this.doc.getScreenFirstRowColumn(screenRow);
var beforeCursor = this.doc.getLine(row).slice(firstRowColumn, column);
var leadingSpace = beforeCursor.match(/^\s*/); var leadingSpace = beforeCursor.match(/^\s*/);
if (leadingSpace[0].length == 0) if (leadingSpace[0].length == 0) {
this.moveCursorTo(row, this.doc.getLine(row).match(/^\s*/)[0].length); var lastRowColumn = this.doc.getDocumentLastRowColumn(row, column);
else if (leadingSpace[0].length >= column) leadingSpace = this.doc.getLine(row).
this.moveCursorTo(row, 0); substring(firstRowColumn, lastRowColumn).
else match(/^\s*/);
this.moveCursorTo(row, leadingSpace[0].length); this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length);
} else if (leadingSpace[0].length >= column) {
this.moveCursorTo(row, firstRowColumn);
} else {
this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length);
}
}; };
this.moveCursorLineEnd = function() { this.moveCursorLineEnd = function() {
this.moveCursorTo(this.selectionLead.row, var selLead = this.selectionLead;
this.doc.getLine(this.selectionLead.row).length); this.moveCursorTo(selLead.row,
this.doc.getDocumentLastRowColumn(selLead.row, selLead.column));
}; };
this.moveCursorFileEnd = function() { this.moveCursorFileEnd = function() {

View file

@ -321,6 +321,31 @@ var Test = {
// Ignore spaces/tabs at beginning of split. // Ignore spaces/tabs at beginning of split.
computeAndAssert("foo \t \t \t \t bar", [ 14]); computeAndAssert("foo \t \t \t \t bar", [ 14]);
},
"test: documentToScreen": function() {
var tabSize = 4;
var wrapLimit = 12;
var doc = new Document(["foo bar foo bar"]);
doc.setUseWrapMode(true);
doc.setWrapLimit(12);
assert.position(doc.documentToScreenPosition(0, 11), 0, 11);
assert.position(doc.documentToScreenPosition(0, 12), 1, 0);
},
"test: screenToDocument": function() {
var tabSize = 4;
var wrapLimit = 12;
var doc = new Document(["foo bar foo bar"]);
doc.setUseWrapMode(true);
doc.setWrapLimit(12);
assert.position(doc.screenToDocumentPosition(1, 0), 0, 12);
assert.position(doc.screenToDocumentPosition(0, 11), 0, 11);
// Check if the position is clamped the right way.
assert.position(doc.screenToDocumentPosition(0, 12), 0, 11);
assert.position(doc.screenToDocumentPosition(0, 20), 0, 11);
} }
}; };

View file

@ -585,7 +585,7 @@ var VirtualRenderer = function(container, theme) {
var row = Math.floor((pageY + this.scrollTop - canvasPos.top) var row = Math.floor((pageY + this.scrollTop - canvasPos.top)
/ this.lineHeight); / this.lineHeight);
return this.doc.screenToDocumentPosition(row, col); return this.doc.screenToDocumentPosition(row, Math.max(col, 0));
}; };
this.textToScreenCoordinates = function(row, column) { this.textToScreenCoordinates = function(row, column) {