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

View file

@ -800,9 +800,12 @@ var Document = function(text, mode) {
this.$useWrapMode = false;
this.setUseWrapMode = function(useWrapMode) {
this.$useWrapMode = useWrapMode;
this.$updateWrapData(0, this.lines.length - 1);
this._dispatchEvent("changeWrapMode");
if (useWrapMode != this.$useWrapMode) {
this.$useWrapMode = useWrapMode;
this.$updateWrapData(0, this.lines.length - 1);
this._dispatchEvent("changeWrapMode");
this.fireChangeEvent(0);
}
};
this.getUseWrapMode = function() {
@ -810,7 +813,10 @@ var Document = function(text, mode) {
};
this.setWrapLimit = function(wrapLimit) {
this.$wrapLimit = wrapLimit;
if (wrapLimit != this.$wrapLimit) {
this.$wrapLimit = wrapLimit;
this.$updateWrapData(0, this.lines.length - 1);
}
};
this.getWrapLimit = function() {
@ -1008,21 +1014,67 @@ var Document = function(text, mode) {
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) {
return this.getLine(screenRow).length;
return this.$getStringScreenWidth(this.getLine(screenRow));
}
var rowData = this.$screenToDocumentRow(screenRow);
var docRow = rowData[0],
row = rowData[1];
var start, end;
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 {
return this.lines[docRow].length -
(this.$wrapData[docRow][row - 1] || 0) ;
end = this.lines[docRow].length;
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) {
@ -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) {
if (!this.$useWrapMode) {
return [row, 0];
@ -1063,13 +1121,14 @@ var Document = function(text, mode) {
var remaining = column;
if (!this.$useWrapMode) {
docRow = row;
row = 0;
docColumn = 0;
line = this.getLine(docRow).split("\t");
} else {
var wrapData = this.$wrapData, linesCount = this.lines.length;
var rowData = this.$screenToDocumentRow(row);
var row = rowData[1];
row = rowData[1];
docRow = rowData[0];
if (docRow >= this.lines.length) {
@ -1101,7 +1160,12 @@ var Document = function(text, mode) {
// Clamp docColumn.
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]) {
docColumn = Math.min(docColumn, this.lines[docRow].length);
}
@ -1116,33 +1180,57 @@ var Document = function(text, mode) {
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) {
return row;
return [docRow, 0];
}
var wrapData = this.$wrapData;
var screenRow = 0;
// Handle special case where the row is outside of the range of lines.
if (row > wrapData.length - 1) {
for (row = 0; row < wrapData.length; row ++) {
screenRow += wrapData[row].length + 1;
}
return screenRow;
if (docRow > wrapData.length - 1) {
return [this.getScreenLength(),
this.wrapData[this.wrapData.length - 1].length - 1];
}
for (var i = 0; i < row; i++) {
for (var i = 0; i < docRow; i++) {
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 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) {
str = this.getLine(row).substring(0, column);
column += (str.split("\t").length - 1) * (tabSize - 1);
@ -1151,26 +1239,24 @@ var Document = function(text, mode) {
column: column
};
}
var screenRow = this.documentToScreenRow(row);
if (row >= this.lines.length) {
return {
row: screenRow,
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);
return {

View file

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

View file

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

View file

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

View file

@ -170,7 +170,7 @@ var Selection = function(doc) {
this.$updateDesiredColumn = function() {
var cursor = this.getCursor();
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() {
var row = this.selectionLead.row;
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*/);
if (leadingSpace[0].length == 0)
this.moveCursorTo(row, this.doc.getLine(row).match(/^\s*/)[0].length);
else if (leadingSpace[0].length >= column)
this.moveCursorTo(row, 0);
else
this.moveCursorTo(row, leadingSpace[0].length);
if (leadingSpace[0].length == 0) {
var lastRowColumn = this.doc.getDocumentLastRowColumn(row, column);
leadingSpace = this.doc.getLine(row).
substring(firstRowColumn, lastRowColumn).
match(/^\s*/);
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.moveCursorTo(this.selectionLead.row,
this.doc.getLine(this.selectionLead.row).length);
var selLead = this.selectionLead;
this.moveCursorTo(selLead.row,
this.doc.getDocumentLastRowColumn(selLead.row, selLead.column));
};
this.moveCursorFileEnd = function() {

View file

@ -321,6 +321,31 @@ var Test = {
// Ignore spaces/tabs at beginning of split.
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)
/ this.lineHeight);
return this.doc.screenToDocumentPosition(row, col);
return this.doc.screenToDocumentPosition(row, Math.max(col, 0));
};
this.textToScreenCoordinates = function(row, column) {