Merge current ajaxorg:master with jviereck:wrapMode

This commit is contained in:
Julian Viereck 2011-01-26 23:51:04 +01:00
commit 725820e05d
15 changed files with 937 additions and 358 deletions

View file

@ -61,6 +61,16 @@ exports.launch = function(env) {
var docs = {}; var docs = {};
// Make the lorem ipsum text a little bit longer.
var loreIpsum = document.getElementById("plaintext").innerHTML;
for (var i = 0; i < 5; i++) {
loreIpsum += loreIpsum;
}
docs.plain = new EditSession(loreIpsum);
docs.plain.setUseWrapMode(true);
docs.plain.setMode(new TextMode());
docs.plain.setUndoManager(new UndoManager());
docs.js = new EditSession(document.getElementById("jstext").innerHTML); docs.js = new EditSession(document.getElementById("jstext").innerHTML);
docs.js.setMode(new JavaScriptMode()); docs.js.setMode(new JavaScriptMode());
docs.js.setUndoManager(new UndoManager()); docs.js.setUndoManager(new UndoManager());
@ -129,7 +139,6 @@ exports.launch = function(env) {
return modes[modeEl.value]; return modes[modeEl.value];
} }
var modeEl = document.getElementById("mode"); var modeEl = document.getElementById("mode");
function setMode() { function setMode() {
env.editor.getSession().setMode(modes[modeEl.value] || modes.text); env.editor.getSession().setMode(modes[modeEl.value] || modes.text);

View file

@ -21,6 +21,7 @@
<option value="css">CSS Document</option> <option value="css">CSS Document</option>
<option value="python">Python Document</option> <option value="python">Python Document</option>
<option value="php">PHP Document</option> <option value="php">PHP Document</option>
<option value="plain">Text Document</option>
</select> </select>
</td> </td>
<td> <td>
@ -141,5 +142,17 @@ echo $output;
<script src="demo/require.js" type="text/javascript" charset="utf-8"></script> <script src="demo/require.js" type="text/javascript" charset="utf-8"></script>
<script src="demo/boot.js" type="text/javascript"></script> <script src="demo/boot.js" type="text/javascript"></script>
<script type="text/editor" id="plaintext">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur
</script>
</body> </body>
</html> </html>

View file

@ -51,6 +51,7 @@ var EditSession = function(text, mode) {
this.$modified = true; this.$modified = true;
this.selection = new Selection(this); this.selection = new Selection(this);
this.$breakpoints = []; this.$breakpoints = [];
this.$wrapData = [];
if (text instanceof Document) { if (text instanceof Document) {
this.setDocument(text) this.setDocument(text)
@ -86,6 +87,8 @@ var EditSession = function(text, mode) {
this.$deltas.push(delta); this.$deltas.push(delta);
this.$informUndoManager.schedule(); this.$informUndoManager.schedule();
} }
this.$updateWrapDataOnChange(e);
this._dispatchEvent("change", e); this._dispatchEvent("change", e);
}; };
@ -315,18 +318,18 @@ var EditSession = function(text, mode) {
}; };
this.getWidth = function() { this.getWidth = function() {
this.$computeWidth(); this.$computeWidth(!this.width);
return this.width; return this.width;
}; };
this.getScreenWidth = function() { this.getScreenWidth = function() {
this.$computeWidth(); this.$computeWidth(!this.screenWidth);
return this.screenWidth; return this.screenWidth;
}; };
this.$computeWidth = function() { this.$computeWidth = function(force) {
if (this.$modified) { if (this.modified || force) {
this.$modified = false; this.modified = false;
var lines = this.doc.getAllLines(); var lines = this.doc.getAllLines();
var longestLine = 0; var longestLine = 0;
@ -337,14 +340,19 @@ var EditSession = function(text, mode) {
var len = lines[i].length; var len = lines[i].length;
longestLine = Math.max(longestLine, len); longestLine = Math.max(longestLine, len);
lines[i].replace(/\t/g, function(m) { lines[i].replace("\t", function(m) {
len += tabSize-1; len += tabSize-1;
return m; return m;
}); });
longestScreenLine = Math.max(longestScreenLine, len); longestScreenLine = Math.max(longestScreenLine, len);
} }
this.width = longestLine; this.width = longestLine;
this.screenWidth = longestScreenLine;
if (this.$useWrapMode) {
this.screenWidth = this.$wrapLimit;
} else {
this.screenWidth = longestScreenLine;
}
} }
}; };
@ -627,15 +635,228 @@ var EditSession = function(text, mode) {
return Math.max(0, Math.min(row, this.doc.getLength()-1)); return Math.max(0, Math.min(row, this.doc.getLength()-1));
}; };
this.documentToScreenColumn = function(row, docColumn) { // WRAPMODE
this.$wrapLimit = 80;
this.$useWrapMode = false;
this.setUseWrapMode = function(useWrapMode) {
if (useWrapMode != this.$useWrapMode) {
this.$useWrapMode = useWrapMode;
this.$updateWrapData(0, this.getLength() - 1);
this._dispatchEvent("changeWrapMode");
// TODO: Add this one back.
// this.fireChangeEvent(0);
}
};
this.getUseWrapMode = function() {
return this.$useWrapMode;
};
this.setWrapLimit = function(wrapLimit) {
if (wrapLimit != this.$wrapLimit) {
this.$wrapLimit = wrapLimit;
this.$updateWrapData(0, this.getLength() - 1);
}
};
this.getWrapLimit = function() {
return this.$wrapLimit;
};
this.$updateWrapDataOnChange = function(e) {
if (!this.$useWrapMode) {
return;
}
var action = e.data.action;
var firstRow = e.data.range.start.row,
lastRow = e.data.range.end.row;
if (action.indexOf("Lines") != -1) {
if (action == "insertLines") {
lastRow = firstRow + e.data.lines.length;
} else {
firstRow = lastRow - e.data.lines.length;
}
}
if (firstRow != lastRow) {
var len = lastRow - firstRow;
if (action.indexOf("remove") != -1) {
this.$wrapData.splice(firstRow, len);
lastRow = firstRow;
} else {
var args = [firstRow, 0];
for (var i = 0; i < len; i++) args.push([]);
this.$wrapData.splice.apply(this.$wrapData, args);
}
}
this.$updateWrapData(firstRow, lastRow);
};
this.$updateWrapData = function(firstRow, lastRow) {
var lines = this.doc.getAllLines();
var tabSize = this.getTabSize(); var tabSize = this.getTabSize();
var wrapData = this.$wrapData;
var wrapLimit = this.$wrapLimit;
// Remove lines that are no longer there.
wrapData.splice(lines.length, wrapData.length - lines.length);
for (var row = firstRow; row <= lastRow; row++) {
wrapData[row] =
this.$computeWrapSplits(lines[row], wrapLimit, tabSize);
}
};
this.$computeWrapSplits = function(textLine, wrapLimit, tabSize) {
textLine = textLine.trimRight();
if (textLine.length == 0) {
return [];
}
var reSpaces = /^\s*$/;
var tabLine = textLine.split("\t");
var wrapSplits = [];
var screenColumn = 0; var screenColumn = 0;
var remaining = docColumn; var documentColumn = 0;
var line = this.getLine(row); var lastSplitColumn = 0;
for (var i=0; i<line.length; i++) { var value;
var c = line.charCodeAt(i); var addedNewSplit;
function addSplit(incrDocumentColumn) {
var valueLenLeadingWhitespaces;
value = value.substring(incrDocumentColumn);
valueLenLeadingWhitespaces = value.length;
value = value.trimLeft();
incrDocumentColumn += valueLenLeadingWhitespaces - value.length;
// If there was a split saved before, then add it to the resulting
// array. It's not possible to store the split column here directly,
// as there migth be tabs/spaces following the current column that
// will cause the split position to get incremented.
if (lastSplitColumn) {
wrapSplits.push(lastSplitColumn);
}
lastSplitColumn = documentColumn += incrDocumentColumn;
screenColumn = 0;
addedNewSplit = true;
}
for (var i = 0; i < tabLine.length; i++) {
value = tabLine[i];
while (screenColumn + value.length >= wrapLimit) {
// Find the last space that we use to split the line.
var lastSpaceIdx =
value.substring(0, wrapLimit - screenColumn + 1).
lastIndexOf(" ");
// There is no space to break the line.
if (lastSpaceIdx == -1) {
// True if this is the first tabLine we face
// during this split. This means, there is no
// space or tab to make the split -> force split.
if (screenColumn == 0) {
addSplit(wrapLimit);
}
// True if we've added some content in this split line
// already. Add a split after this already existing content
// in the split.
else {
addSplit(0);
}
}
// There is at least one space to wrap the line at.
else {
addSplit(lastSpaceIdx);
}
}
// Add new content to the current split.
if (!addedNewSplit) {
screenColumn += value.length + tabSize;
documentColumn += value.length + 1;
}
// True if at least one new split was added during the while loop.
else {
var lenBefore = value.length;
value = value.trimLeft();
// True if value has some content. This content goes directly
// after the last split.
// If there is no content but we are in the last tabLine, we
// already know that there is no following tabLine and we can
// shortcut by using this if instead of the following else.
if (value.length != 0 || i == tabLine.length - 1) {
documentColumn = lastSplitColumn += lenBefore - value.length;
screenColumn += value.length;
documentColumn += 1; // The tab after this for-loop.
}
// There is no value left after the split. This means, following
// spaces and tabs have to be ignored.
else {
// Tab for this for-loop.
documentColumn = lastSplitColumn += 1;
// While the following tabLines are empty/only spaces, set
// the latest split position behind them.
while ((i + 1) < tabLine.length - 1
&& reSpaces.test(tabLine[i + 1]))
{
i++;
documentColumn = lastSplitColumn += tabLine[i].length + 1;
}
// The tabLine[i + 1] is not empty. However, the trailing
// spaces have still to be removed.
value = tabLine[i + 1];
lenBefore = value.length;
value = value.trimLeft();
documentColumn = lastSplitColumn += lenBefore - value.length;
tabLine[i + 1] = value; // Store the trimed value.
}
addedNewSplit = false;
}
}
if (lastSplitColumn) {
wrapSplits.push(lastSplitColumn);
}
return wrapSplits;
};
this.getRowHeight = function(config, row) {
var rows;
if (!this.$useWrapMode) {
rows = 1;
} else {
rows = this.$wrapData[row].length + 1;
}
return rows * config.lineHeight;
};
/**
* 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 screenColumn = 0;
var remaining = docColumn;
for (var i=0; i<str.length; i++) {
var c = str.charCodeAt(i);
if (remaining > 0) { if (remaining > 0) {
remaining -= 1; remaining -= 1;
// tab // tab
@ -659,55 +880,257 @@ var EditSession = function(text, mode) {
} }
} }
return screenColumn; return screenColumn;
}
this.getScreenLastRowColumn = function(screenRow, returnDocPosition) {
if (!this.$useWrapMode) {
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]) {
start = (this.$wrapData[docRow][row - 1] || 0);
end = this.$wrapData[docRow][row];
returnDocPosition && end--;
} else {
end = this.getLine(docRow).length;
start = (this.$wrapData[docRow][row - 1] || 0);
}
if (!returnDocPosition) {
return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end));
} else {
return end;
}
}; };
this.screenToDocumentColumn = function(row, screenColumn) { 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) {
if (!this.$useWrapMode) {
return undefined;
} else {
return this.$wrapData[row];
}
};
/**
*
* @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];
}
var wrapData = this.$wrapData, linesCount = this.getLength();
var docRow = 0;
while (docRow < linesCount && row >= wrapData[docRow].length + 1) {
row -= wrapData[docRow].length + 1;
docRow ++;
}
return [docRow, row];
};
this.screenToDocumentRow = function(screenRow) {
return this.$screenToDocumentRow(screenRow)[0];
};
this.screenToDocumentColumn = function(screenRow, screenColumn) {
return this.screenToDocumentPosition(screenRow, screenColumn).column;
};
this.screenToDocumentPosition = function(row, column) {
var line;
var docRow;
var docColumn;
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.getLength();
var rowData = this.$screenToDocumentRow(row);
row = rowData[1];
docRow = rowData[0];
if (docRow >= linesCount) {
return {
row: docRow,
column: 0
};
}
docColumn = wrapData[docRow][row - 1] || 0;
line = this.getLine(docRow).substring(docColumn).split("\t");
}
var tabSize = this.getTabSize();
for (var i=0; i<line.length; i++) {
var len = line[i].length;
if (remaining >= len + tabSize) {
remaining -= (len + tabSize);
docColumn += (len + 1);
}
else if (remaining > len){
docColumn += len;
break;
}
else {
docColumn += remaining;
break;
}
}
// Clamp docColumn.
if (docRow < linesCount && 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.getLine(docRow)) {
docColumn = Math.min(docColumn, this.getLine(docRow).length);
}
return {
row: docRow,
column: docColumn
};
};
this.documentToScreenColumn = function(row, docColumn) {
return this.documentToScreenPosition(row, docColumn).column;
};
/**
*
* @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 [docRow, 0];
}
var wrapData = this.$wrapData;
var screenRow = 0;
// Handle special case where the row is outside of the range of lines.
if (docRow > wrapData.length - 1) {
return [
this.getScreenLength(),
wrapData[wrapData.length - 1].length - 1
];
}
for (var i = 0; i < docRow; i++) {
screenRow += wrapData[i].length + 1;
}
var screenRowOffset = 0;
while (docColumn >= wrapData[docRow][screenRowOffset]) {
screenRow ++;
screenRowOffset++;
}
return [screenRow, screenRowOffset];
}
this.documentToScreenRow = function(docRow, docColumn) {
return this.$documentToScreenRow(docRow, docColumn)[0];
}
this.documentToScreenPosition = function(pos, column) {
var str;
var tabSize = this.getTabSize(); var tabSize = this.getTabSize();
var docColumn = 0; // Normalize the passed in arguments.
var remaining = screenColumn; var row;
var line = this.getLine(row); if (column != null) {
row = pos;
} else {
row = pos.row;
column = pos.column;
}
for(var i=0; i<line.length; i++) { if (!this.$useWrapMode) {
var c = line.charCodeAt(i); str = this.getLine(row).substring(0, column);
column += (str.split("\t").length - 1) * (tabSize - 1);
return {
row: row,
column: column
};
}
if (row >= this.getLength()) {
return {
row: screenRow,
column: 0
};
}
if (remaining > 0) { var split;
docColumn += 1; var wrapRowData = this.$wrapData[row];
// tab var screenRow, screenRowOffset;
if (c == 9) { var screenColumn;
if (remaining >= tabSize) { var rowData = this.$documentToScreenRow(row, column);
remaining -= tabSize; screenRow = rowData[0];
} else { screenRowOffset = rowData[1];
remaining = 0; screenColumn = column - (wrapRowData[screenRowOffset - 1] || 0);
docColumn -= 1;
}
}
// CJK characters
else if (
c >= 0x3040 && c <= 0x309F || // Hiragana
c >= 0x30A0 && c <= 0x30FF || // Katakana
c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs
c >= 0xF900 && c <= 0xFAFF ||
c >= 0x3400 && c <= 0x4DBF
) {
if (remaining >= 2) {
remaining -= 2;
} else {
remaining = 0;
docColumn -= 1;
}
} else {
remaining -= 1;
}
} else {
break;
}
}
return docColumn; str = this.getLine(row).substring(
wrapRowData[screenRowOffset - 1] || 0, column);
screenColumn += (str.split("\t").length - 1) * (tabSize - 1);
return {
row: screenRow,
column: screenColumn
};
}; };
this.getScreenLength = function() {
if (!this.$useWrapMode) {
return this.getLength();
}
var screenRows = 0;
for (var row = 0; row < this.$wrapData.length; row++) {
screenRows += this.$wrapData[row].length + 1;
}
return screenRows;
}
}).call(EditSession.prototype); }).call(EditSession.prototype);
exports.EditSession = EditSession; exports.EditSession = EditSession;

View file

@ -22,6 +22,7 @@
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com) * Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -128,6 +129,7 @@ var Editor =function(renderer, session) {
this.session.removeEventListener("change", this.$onDocumentChange); this.session.removeEventListener("change", this.$onDocumentChange);
this.session.removeEventListener("changeMode", this.$onDocumentModeChange); this.session.removeEventListener("changeMode", this.$onDocumentModeChange);
this.session.removeEventListener("changeTabSize", this.$onDocumentChangeTabSize); this.session.removeEventListener("changeTabSize", this.$onDocumentChangeTabSize);
this.session.removeEventListener("changeWrapMode", this.$onDocumentChangeWrapMode);
this.session.removeEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); this.session.removeEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint);
this.session.removeEventListener("changeAnnotation", this.$onDocumentChangeAnnotation); this.session.removeEventListener("changeAnnotation", this.$onDocumentChangeAnnotation);
@ -150,6 +152,9 @@ var Editor =function(renderer, session) {
this.$onDocumentChangeTabSize = this.renderer.updateText.bind(this.renderer); this.$onDocumentChangeTabSize = this.renderer.updateText.bind(this.renderer);
session.addEventListener("changeTabSize", this.$onDocumentChangeTabSize); session.addEventListener("changeTabSize", this.$onDocumentChangeTabSize);
this.$onDocumentChangeWrapMode = this.renderer.updateFull.bind(this.renderer);
session.addEventListener("changeWrapMode", this.$onDocumentChangeWrapMode);
this.$onDocumentChangeBreakpoint = this.onDocumentChangeBreakpoint.bind(this); this.$onDocumentChangeBreakpoint = this.onDocumentChangeBreakpoint.bind(this);
this.session.addEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); this.session.addEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint);
@ -157,7 +162,6 @@ var Editor =function(renderer, session) {
this.session.addEventListener("changeAnnotation", this.$onDocumentChangeAnnotation); this.session.addEventListener("changeAnnotation", this.$onDocumentChangeAnnotation);
this.selection = session.getSelection(); this.selection = session.getSelection();
this.$desiredColumn = 0;
this.$onCursorChange = this.onCursorChange.bind(this); this.$onCursorChange = this.onCursorChange.bind(this);
this.selection.addEventListener("changeCursor", this.$onCursorChange); this.selection.addEventListener("changeCursor", this.$onCursorChange);
@ -414,13 +418,11 @@ var Editor =function(renderer, session) {
this.onMouseDoubleClick = function(e) { this.onMouseDoubleClick = function(e) {
this.selection.selectWord(); this.selection.selectWord();
this.$clickSelection = this.getSelectionRange(); this.$clickSelection = this.getSelectionRange();
this.$updateDesiredColumn();
}; };
this.onMouseTripleClick = function(e) { this.onMouseTripleClick = function(e) {
this.selection.selectLine(); this.selection.selectLine();
this.$clickSelection = this.getSelectionRange(); this.$clickSelection = this.getSelectionRange();
this.$updateDesiredColumn();
}; };
this.onMouseWheel = function(e) { this.onMouseWheel = function(e) {
@ -684,7 +686,6 @@ var Editor =function(renderer, session) {
var range = this.session.outdentRows(selection.getRange()); var range = this.session.outdentRows(selection.getRange());
selection.setSelectionRange(range, selection.isBackwards()); selection.setSelectionRange(range, selection.isBackwards());
this.$updateDesiredColumn();
}; };
this.toggleCommentLines = function() { this.toggleCommentLines = function() {
@ -874,17 +875,14 @@ var Editor =function(renderer, session) {
this.clearSelection = function() { this.clearSelection = function() {
this.selection.clearSelection(); this.selection.clearSelection();
this.$updateDesiredColumn();
}; };
this.moveCursorTo = function(row, column) { this.moveCursorTo = function(row, column) {
this.selection.moveCursorTo(row, column); this.selection.moveCursorTo(row, column);
this.$updateDesiredColumn();
}; };
this.moveCursorToPosition = function(pos) { this.moveCursorToPosition = function(pos) {
this.selection.moveCursorToPosition(pos); this.selection.moveCursorToPosition(pos);
this.$updateDesiredColumn();
}; };
@ -903,34 +901,16 @@ var Editor =function(renderer, session) {
this.navigateTo = function(row, column) { this.navigateTo = function(row, column) {
this.clearSelection(); this.clearSelection();
this.moveCursorTo(row, column); this.moveCursorTo(row, column);
this.$updateDesiredColumn(column);
}; };
this.navigateUp = function(times) { this.navigateUp = function(times) {
this.selection.clearSelection(); this.selection.clearSelection();
this.selection.moveCursorBy(-(times || 1), 0); this.selection.moveCursorBy(-1, 0);
if (this.$desiredColumn) {
var cursor = this.getCursorPosition();
var column = this.session.screenToDocumentColumn(cursor.row, this.$desiredColumn);
this.selection.moveCursorTo(cursor.row, column);
}
}; };
this.navigateDown = function(times) { this.navigateDown = function(times) {
this.selection.clearSelection(); this.selection.clearSelection();
this.selection.moveCursorBy(times || 1, 0); this.selection.moveCursorBy(1, 0);
if (this.$desiredColumn) {
var cursor = this.getCursorPosition();
var column = this.session.screenToDocumentColumn(cursor.row, this.$desiredColumn);
this.selection.moveCursorTo(cursor.row, column);
}
};
this.$updateDesiredColumn = function() {
var cursor = this.getCursorPosition();
this.$desiredColumn = this.session.documentToScreenColumn(cursor.row, cursor.column);
}; };
this.navigateLeft = function(times) { this.navigateLeft = function(times) {
@ -999,7 +979,6 @@ var Editor =function(renderer, session) {
this.$tryReplace(range, replacement); this.$tryReplace(range, replacement);
if (range !== null) if (range !== null)
this.selection.setSelectionRange(range); this.selection.setSelectionRange(range);
this.$updateDesiredColumn();
}, },
this.replaceAll = function(replacement, options) { this.replaceAll = function(replacement, options) {
@ -1018,7 +997,6 @@ var Editor =function(renderer, session) {
this.$tryReplace(ranges[i], replacement); this.$tryReplace(ranges[i], replacement);
if (ranges[0] !== null) if (ranges[0] !== null)
this.selection.setSelectionRange(ranges[0]); this.selection.setSelectionRange(ranges[0]);
this.$updateDesiredColumn();
}, },
this.$tryReplace = function(range, replacement) { this.$tryReplace = function(range, replacement) {
@ -1071,7 +1049,6 @@ var Editor =function(renderer, session) {
var range = this.$search.find(this.session); var range = this.$search.find(this.session);
if (range) { if (range) {
this.gotoLine(range.end.row+1, range.end.column); this.gotoLine(range.end.row+1, range.end.column);
this.$updateDesiredColumn();
this.selection.setSelectionRange(range); this.selection.setSelectionRange(range);
} }
}; };

View file

@ -20,6 +20,7 @@
* *
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -57,10 +58,9 @@ var Cursor = function(parentEl) {
}; };
this.setCursor = function(position, overwrite) { this.setCursor = function(position, overwrite) {
this.position = { this.position =
row : position.row, this.session.documentToScreenPosition(position);
column : this.session.documentToScreenColumn(position.row, position.column)
};
if (overwrite) { if (overwrite) {
dom.addCssClass(this.cursor, "ace_overwrite"); dom.addCssClass(this.cursor, "ace_overwrite");
} else { } else {
@ -100,7 +100,7 @@ var Cursor = function(parentEl) {
}, 1000); }, 1000);
}; };
this.getPixelPosition = function() { this.getPixelPosition = function(onScreen) {
if (!this.config || !this.position) { if (!this.config || !this.position) {
return { return {
left : 0, left : 0,
@ -108,10 +108,12 @@ var Cursor = function(parentEl) {
}; };
} }
var cursorLeft = Math.round(this.position.column * this.config.characterWidth); var pos = this.position;
var cursorTop = this.position.row * this.config.lineHeight; var cursorLeft = Math.round(pos.column * this.config.characterWidth);
var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) *
this.config.lineHeight;
return { return {
left : cursorLeft, left : cursorLeft,
top : cursorTop top : cursorTop
}; };
@ -123,17 +125,10 @@ var Cursor = function(parentEl) {
this.config = config; this.config = config;
var cursorLeft = Math.round(this.position.column * config.characterWidth); this.pixelPos = this.getPixelPosition(true);
var cursorTop = this.position.row * config.lineHeight;
this.pixelPos = { this.cursor.style.left = this.pixelPos.left + "px";
left : cursorLeft, this.cursor.style.top = this.pixelPos.top + "px";
top : cursorTop
};
this.cursor.style.left = cursorLeft + "px";
this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight))
+ "px";
this.cursor.style.width = config.characterWidth + "px"; this.cursor.style.width = config.characterWidth + "px";
this.cursor.style.height = config.lineHeight + "px"; this.cursor.style.height = config.lineHeight + "px";

View file

@ -20,6 +20,7 @@
* *
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -51,6 +52,10 @@ var Gutter = function(parentEl) {
(function() { (function() {
this.setSession = function(session) {
this.session = session;
};
this.addGutterDecoration = function(row, className){ this.addGutterDecoration = function(row, className){
if (!this.$decorations[row]) if (!this.$decorations[row])
this.$decorations[row] = ""; this.$decorations[row] = "";
@ -101,7 +106,7 @@ var Gutter = function(parentEl) {
this.$breakpoints[i] ? " ace_breakpoint " : " ", this.$breakpoints[i] ? " ace_breakpoint " : " ",
annotation.className, annotation.className,
"' title='", annotation.text.join("\n"), "' title='", annotation.text.join("\n"),
"' style='height:", config.lineHeight, "px;'>", (i+1), "</div>"); "' style='height:", this.session.getRowHeight(config, i), "px;'>", (i+1), "</div>");
html.push("</div>"); html.push("</div>");
} }
this.element = dom.setInnerHtml(this.element, html.join("")); this.element = dom.setInnerHtml(this.element, html.join(""));

View file

@ -20,6 +20,7 @@
* *
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -87,6 +88,8 @@ var Marker = function(parentEl) {
var range = marker.range.clipRows(config.firstRow, config.lastRow); var range = marker.range.clipRows(config.firstRow, config.lastRow);
if (range.isEmpty()) continue; if (range.isEmpty()) continue;
range = range.toScreenRange(this.session);
if (range.isMultiLine()) { if (range.isMultiLine()) {
if (marker.type == "text") { if (marker.type == "text") {
this.drawTextMarker(html, range, marker.clazz, config); this.drawTextMarker(html, range, marker.clazz, config);
@ -101,11 +104,16 @@ var Marker = function(parentEl) {
this.element = dom.setInnerHtml(this.element, html.join("")); this.element = dom.setInnerHtml(this.element, html.join(""));
}; };
this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) { this.$getTop = function(row, layerConfig) {
return (row - layerConfig.firstRowScreen) * layerConfig.lineHeight;
};
this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) {
// selection start // selection start
var row = range.start.row; var row = range.start.row;
var lineRange = new Range(row, range.start.column, row, this.session.getLine(row).length);
var lineRange = new Range(row, range.start.column,
row, this.session.getScreenLastRowColumn(row));
this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1);
// selection end // selection end
@ -116,18 +124,16 @@ 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.session.getLine(row).length; // account for endofline characters lineRange.end.column = this.session.getScreenLastRowColumn(row);
this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1);
} }
}; };
this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) {
var range = range.toScreenRange(this.session);
// from selection start to the end of the line // from selection start to the end of the line
var height = layerConfig.lineHeight; var height = layerConfig.lineHeight;
var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth)); var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth));
var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; var top = this.$getTop(range.start.row, layerConfig);
var left = Math.round(range.start.column * layerConfig.characterWidth); var left = Math.round(range.start.column * layerConfig.characterWidth);
stringBuilder.push( stringBuilder.push(
@ -139,7 +145,7 @@ var Marker = function(parentEl) {
); );
// from start of the last line to the selection end // from start of the last line to the selection end
var top = (range.end.row - layerConfig.firstRow) * layerConfig.lineHeight; var top = this.$getTop(range.end.row, layerConfig);
var width = Math.round(range.end.column * layerConfig.characterWidth); var width = Math.round(range.end.column * layerConfig.characterWidth);
stringBuilder.push( stringBuilder.push(
@ -153,7 +159,7 @@ var Marker = function(parentEl) {
var height = (range.end.row - range.start.row - 1) * layerConfig.lineHeight; var height = (range.end.row - range.start.row - 1) * layerConfig.lineHeight;
if (height < 0) if (height < 0)
return; return;
var top = (range.start.row + 1 - layerConfig.firstRow) * layerConfig.lineHeight; var top = this.$getTop(range.start.row + 1, layerConfig);
stringBuilder.push( stringBuilder.push(
"<div class='", clazz, "' style='", "<div class='", clazz, "' style='",
@ -164,11 +170,9 @@ var Marker = function(parentEl) {
}; };
this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig, extraLength) { this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig, extraLength) {
var range = range.toScreenRange(this.session);
var height = layerConfig.lineHeight; var height = layerConfig.lineHeight;
var width = Math.round((range.end.column + (extraLength || 0) - range.start.column) * layerConfig.characterWidth); var width = Math.round((range.end.column + (extraLength || 0) - range.start.column) * layerConfig.characterWidth);
var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; var top = this.$getTop(range.start.row, layerConfig);
var left = Math.round(range.start.column * layerConfig.characterWidth); var left = Math.round(range.start.column * layerConfig.characterWidth);
stringBuilder.push( stringBuilder.push(

View file

@ -20,6 +20,7 @@
* *
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -152,23 +153,31 @@ var Text = function(parentEl) {
} }
}; };
this.updateLines = function(layerConfig, firstRow, lastRow) { this.updateLines = function(config, firstRow, lastRow) {
this.$computeTabString(); this.$computeTabString();
this.config = layerConfig; // Due to wrap line changes there can be new lines if e.g.
// the line to updated wrapped in the meantime.
if (this.config.lastRow != config.lastRow ||
this.config.firstRow != config.firstRow) {
this.scrollLines(config);
}
this.config = config;
var first = Math.max(firstRow, layerConfig.firstRow); var first = Math.max(firstRow, config.firstRow);
var last = Math.min(lastRow, layerConfig.lastRow); var last = Math.min(lastRow, config.lastRow);
var lineElements = this.element.childNodes; var lineElements = this.element.childNodes;
var tokens = this.tokenizer.getTokens(first, last); var tokens = this.tokenizer.getTokens(first, last);
for (var i=first; i<=last; i++) { for (var i=first; i<=last; i++) {
var lineElement = lineElements[i - layerConfig.firstRow]; var lineElement = lineElements[i - config.firstRow];
if (!lineElement) if (!lineElement)
continue; continue;
var html = []; var html = [];
this.$renderLine(html, i, tokens[i-first].tokens); this.$renderLine(html, i, tokens[i-first].tokens);
dom.setInnerHtml(lineElement, html.join("")); lineElement = dom.setInnerHtml(lineElement, html.join(""));
lineElement.style.height =
this.session.getRowHeight(config, i) + "px";
} }
}; };
@ -214,7 +223,7 @@ var Text = function(parentEl) {
var lineEl = document.createElement("div"); var lineEl = document.createElement("div");
lineEl.className = "ace_line"; lineEl.className = "ace_line";
var style = lineEl.style; var style = lineEl.style;
style.height = this.$characterSize.height + "px"; style.height = this.session.getRowHeight(config, row) + "px";
style.width = config.width + "px"; style.width = config.width + "px";
var html = []; var html = [];
@ -231,14 +240,12 @@ var Text = function(parentEl) {
this.config = config; this.config = config;
var html = []; var html = [];
var tokens = this.tokenizer.getTokens(config.firstRow, config.lastRow); var tokens = this.tokenizer.getTokens(config.firstRow, config.lastRow)
for (var i=config.firstRow; i<=config.lastRow; i++) { var fragment = this.$renderLinesFragment(config, config.firstRow, config.lastRow);
html.push("<div class='ace_line' style='height:" + this.$characterSize.height + "px;", "width:",
config.width, "px'>");
this.$renderLine(html, i, tokens[i-config.firstRow].tokens), html.push("</div>");
}
this.element = dom.setInnerHtml(this.element, html.join("")); // Clear the current content of the element and add the rendered fragment.
this.element.innerHTML = "";
this.element.appendChild(fragment);
}; };
this.$textToken = { this.$textToken = {
@ -266,12 +273,9 @@ var Text = function(parentEl) {
var spaceReplace = "&nbsp;"; var spaceReplace = "&nbsp;";
} }
var characterWidth = this.config.characterWidth; var _self = this;
function addToken(token, value) {
for ( var i = 0; i < tokens.length; i++) { var output = value
var token = tokens[i];
var output = token.value
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
.replace(spaceRe, spaceReplace) .replace(spaceRe, spaceReplace)
@ -280,13 +284,51 @@ var Text = function(parentEl) {
return "<span class='ace_cjk' style='width:" + (characterWidth * 2) + "px'>" + c + "</span>" return "<span class='ace_cjk' style='width:" + (characterWidth * 2) + "px'>" + c + "</span>"
}); });
if (!this.$textToken[token.type]) { if (!_self.$textToken[token.type]) {
var classes = "ace_" + token.type.replace(/\./g, " ace_"); var classes = "ace_" + token.type.replace(/\./g, " ace_");
stringBuilder.push("<span class='", classes, "'>", output, "</span>"); stringBuilder.push("<span class='", classes, "'>", output, "</span>");
} }
else { else {
stringBuilder.push(output); stringBuilder.push(output);
} }
}
var splits = this.session.getRowSplitData(row);
var chars = 0, split = 0, splitChars;
if (!splits || splits.length == 0) {
splitChars = Number.MAX_VALUE;
} else {
splitChars = splits[0];
}
stringBuilder.push("<div style='height:",
this.config.lineHeight, "px",
"'>");
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
var value = token.value;
if (chars + value.length < splitChars) {
addToken(token, value);
chars += value.length;
} else {
while (chars + value.length >= splitChars) {
addToken(token, value.substring(0, splitChars - chars));
value = value.substring(splitChars - chars);
chars = splitChars;
stringBuilder.push("</div>",
"<div style='height:",
this.config.lineHeight, "px",
"'>");
split ++;
splitChars = splits[split] || Number.MAX_VALUE;
}
if (value.length != 0) {
chars += value.length;
addToken(token, value);
}
}
}; };
if (this.showInvisibles) { if (this.showInvisibles) {
@ -296,6 +338,7 @@ var Text = function(parentEl) {
stringBuilder.push("<span class='ace_invisible'>" + this.EOF_CHAR + "</span>"); stringBuilder.push("<span class='ace_invisible'>" + this.EOF_CHAR + "</span>");
} }
} }
stringBuilder.push("</div>");
}; };
}).call(Text.prototype); }).call(Text.prototype);

View file

@ -145,10 +145,14 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
return new Range(this.start.row, 0, this.end.row, 0) return new Range(this.start.row, 0, this.end.row, 0)
}; };
this.toScreenRange = function(doc) { this.toScreenRange = function(session) {
var screenPosStart =
session.documentToScreenPosition(this.start);
var screenPosEnd =
session.documentToScreenPosition(this.end);
return new Range( return new Range(
this.start.row, doc.documentToScreenColumn(this.start.row, this.start.column), screenPosStart.row, screenPosStart.column,
this.end.row, doc.documentToScreenColumn(this.end.row, this.end.column) screenPosEnd.row, screenPosEnd.column
); );
}; };

View file

@ -20,6 +20,7 @@
* *
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -170,6 +171,14 @@ var Selection = function(doc) {
this.setSelectionAnchor(range.start.row, range.start.column); this.setSelectionAnchor(range.start.row, range.start.column);
this.selectTo(range.end.row, range.end.column); this.selectTo(range.end.row, range.end.column);
} }
this.$updateDesiredColumn();
};
this.$updateDesiredColumn = function() {
var cursor = this.getCursor();
if (cursor) {
this.$desiredColumn = this.doc.documentToScreenColumn(cursor.row, cursor.column);
}
}; };
this.$moveSelection = function(mover) { this.$moveSelection = function(mover) {
@ -272,7 +281,7 @@ var Selection = function(doc) {
this.moveCursorLeft = function() { this.moveCursorLeft = function() {
if (this.selectionLead.column == 0) { if (this.selectionLead.column == 0) {
// cursor is a line start // cursor is a line (start
if (this.selectionLead.row > 0) { if (this.selectionLead.row > 0) {
this.moveCursorTo(this.selectionLead.row - 1, this.doc this.moveCursorTo(this.selectionLead.row - 1, this.doc
.getLine(this.selectionLead.row - 1).length); .getLine(this.selectionLead.row - 1).length);
@ -309,19 +318,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() {
@ -387,7 +404,21 @@ var Selection = function(doc) {
}; };
this.moveCursorBy = function(rows, chars) { this.moveCursorBy = function(rows, chars) {
this.moveCursorTo(this.selectionLead.row + rows, this.selectionLead.column + chars); if (this.doc.getUseWrapMode()) {
var screenPos = this.doc.documentToScreenPosition(
this.selectionLead.row, this.selectionLead.column);
var screenCol =
(chars == 0 && this.$desiredColumn) || screenPos.column;
var docPos = this.doc.screenToDocumentPosition(
screenPos.row + rows, screenCol);
this.moveCursorTo(docPos.row, docPos.column + chars, chars == 0);
} else {
var docColumn =
(chars == 0 && this.$desiredColumn) || this.selectionLead.column;
this.moveCursorTo(
this.selectionLead.row + rows, docColumn + chars, chars == 0);
}
}; };
@ -395,20 +426,17 @@ var Selection = function(doc) {
this.moveCursorTo(position.row, position.column); this.moveCursorTo(position.row, position.column);
}; };
this.moveCursorTo = function(row, column) { this.moveCursorTo = function(row, column, preventUpdateDesiredColumn) {
var cursor = this.$clipPositionToDocument(row, column); var cursor = this.$clipPositionToDocument(row, column);
// only dispatch change if the cursor actually changed // only dispatch change if the cursor actually changed
if (cursor.row !== this.selectionLead.row || cursor.column !== this.selectionLead.column) { if (cursor.row !== this.selectionLead.row || cursor.column !== this.selectionLead.column) {
this.selectionLead = cursor; this.selectionLead = cursor;
!preventUpdateDesiredColumn && this.$updateDesiredColumn(column);
this._dispatchEvent("changeCursor", { data: this.getCursor() }); this._dispatchEvent("changeCursor", { data: this.getCursor() });
} }
}; };
this.moveCursorUp = function() {
this.moveCursorBy(-1, 0);
};
this.$clipPositionToDocument = function(row, column) { this.$clipPositionToDocument = function(row, column) {
var pos = {}; var pos = {};

View file

@ -20,6 +20,7 @@
* *
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -277,6 +278,68 @@ var Test = {
doc.replace(new Range(0, 0, 2, 1), ["4", "5", "6"].join("\n")); doc.replace(new Range(0, 0, 2, 1), ["4", "5", "6"].join("\n"));
assert.equal(["4", "5", "6"].join("\n"), doc.getValue()); assert.equal(["4", "5", "6"].join("\n"), doc.getValue());
},
"test: wrapLine split function" : function() {
var splits;
var tabSize = 4;
var wrapLimit = 12;
var computeWrapSplits = Document.prototype.$computeWrapSplits;
var c = 0;
function computeAndAssert(line, assertEqual) {
splits = computeWrapSplits(line, wrapLimit, tabSize);
assert.ok(splits.length == assertEqual.length);
for (var i = 0; i < splits.length; i++) {
assert.ok(splits[i] == assertEqual[i]);
}
}
// Basic splitting.
computeAndAssert("foo bar foo bar", [ 12 ]);
computeAndAssert("foo bar f bar", [ 12 ]);
computeAndAssert("foo bar f r", [ 14 ]);
computeAndAssert("foo bar foo bar foo bara foo", [12, 25]);
// Don't split if there is only whitespaces/tabs at the end of the line.
computeAndAssert("foo foo foo \t \t", [ ]);
// If there is no space to split, force split.
computeAndAssert("foooooooooooooo", [ 12 ]);
computeAndAssert("fooooooooooooooooooooooooooo", [12, 24]);
computeAndAssert("foo bar fooooooooooobooooooo", [8, 20]);
// Basic splitting + tabs.
computeAndAssert("foo \t\tbar", [ 6 ]);
computeAndAssert("foo \t \tbar", [ 7 ]);
// 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

@ -22,6 +22,7 @@
* Contributor(s): * Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org> * Fabian Jakobs <fabian AT ajax DOT org>
* Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com) * Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)
* Julian Viereck <julian.viereck@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -120,7 +121,6 @@ var VirtualRenderer = function(container, theme) {
}; };
(function() { (function() {
this.showGutter = true; this.showGutter = true;
this.CHANGE_CURSOR = 1; this.CHANGE_CURSOR = 1;
@ -138,8 +138,8 @@ var VirtualRenderer = function(container, theme) {
this.session = session; this.session = session;
this.$cursorLayer.setSession(session); this.$cursorLayer.setSession(session);
this.$markerLayer.setSession(session); this.$markerLayer.setSession(session);
this.$gutterLayer.setSession(session);
this.$textLayer.setSession(session); this.$textLayer.setSession(session);
this.$loop.schedule(this.CHANGE_FULL); this.$loop.schedule(this.CHANGE_FULL);
}; };
@ -327,7 +327,7 @@ var VirtualRenderer = function(container, theme) {
}; };
this.$updateScrollBar = function() { this.$updateScrollBar = function() {
this.scrollBar.setInnerHeight(this.session.getLength() * this.lineHeight); this.scrollBar.setInnerHeight(this.session.getScreenLength() * this.lineHeight);
this.scrollBar.setScrollTop(this.scrollTop); this.scrollBar.setScrollTop(this.scrollTop);
}; };
@ -352,6 +352,7 @@ var VirtualRenderer = function(container, theme) {
this.$markerLayer.update(this.layerConfig); this.$markerLayer.update(this.layerConfig);
this.$cursorLayer.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig);
this.$updateScrollBar(); this.$updateScrollBar();
this.scrollCursorIntoView();
return; return;
} }
@ -392,20 +393,36 @@ var VirtualRenderer = function(container, theme) {
}; };
this.$computeLayerConfig = function() { this.$computeLayerConfig = function() {
var session = this.session;
var offset = this.scrollTop % this.lineHeight; var offset = this.scrollTop % this.lineHeight;
var minHeight = this.$size.scrollerHeight + this.lineHeight; var minHeight = this.$size.scrollerHeight + this.lineHeight;
var longestLine = this.$getLongestLine(); var longestLine = this.$getLongestLine();
var widthChanged = !this.layerConfig ? true : (this.layerConfig.width != longestLine); var widthChanged = !this.layerConfig ? true : (this.layerConfig.width != longestLine);
var lineCount = Math.ceil(minHeight / this.lineHeight); var lineCount = Math.ceil(minHeight / this.lineHeight) - 1;
var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight)); var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight));
var lastRow = Math.max(0, Math.min(this.session.getLength(), firstRow + lineCount) - 1); var lastRow = firstRow + lineCount;
// Map lines on the screen to lines in the document.
var firstRowScreen, firstRowHeight;
var lineHeight = { lineHeight: this.lineHeight };
firstRow = session.screenToDocumentRow(firstRow);
firstRowScreen = session.documentToScreenRow(firstRow);
firstRowHeight = session.getRowHeight(lineHeight, firstRow);
lastRow = Math.min(session.screenToDocumentRow(lastRow), session.getLength() - 1);
minHeight = this.$size.scrollerHeight + session.getRowHeight(lineHeight, lastRow)+
firstRowHeight;
offset = this.scrollTop - firstRowScreen * this.lineHeight;
var layerConfig = this.layerConfig = { var layerConfig = this.layerConfig = {
width : longestLine, width : longestLine,
padding : this.$padding, padding : this.$padding,
firstRow : firstRow, firstRow : firstRow,
firstRowScreen: firstRowScreen,
lastRow : lastRow, lastRow : lastRow,
lineHeight : this.lineHeight, lineHeight : this.lineHeight,
characterWidth : this.characterWidth, characterWidth : this.characterWidth,
@ -540,7 +557,7 @@ var VirtualRenderer = function(container, theme) {
}; };
this.scrollToY = function(scrollTop) { this.scrollToY = function(scrollTop) {
var maxHeight = this.session.getLength() * this.lineHeight - this.$size.scrollerHeight; var maxHeight = this.session.getScreenLength() * this.lineHeight - this.$size.scrollerHeight;
var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop));
if (this.scrollTop !== scrollTop) { if (this.scrollTop !== scrollTop) {
@ -569,17 +586,15 @@ 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 { return this.session.screenToDocumentPosition(row, Math.max(col, 0));
row : row,
column : this.session.screenToDocumentColumn(Math.max(0, Math.min(row, this.session.getLength()-1)), col)
};
}; };
this.textToScreenCoordinates = function(row, column) { this.textToScreenCoordinates = function(row, column) {
var canvasPos = this.scroller.getBoundingClientRect(); var canvasPos = this.scroller.getBoundingClientRect();
var pos = this.session.documentToScreenPosition(row, column);
var x = this.$padding + Math.round(this.session.documentToScreenColumn(row, column) * this.characterWidth); var x = this.$padding + Math.round(pos.column * this.characterWidth);
var y = row * this.lineHeight; var y = pos.row * this.lineHeight;
return { return {
pageX: canvasPos.left + x - this.getScrollLeft(), pageX: canvasPos.left + x - this.getScrollLeft(),