diff --git a/src/ace/Document.js b/src/ace/Document.js index 442ebd27..3facbc99 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -562,4 +562,51 @@ ace.Document = function(text, mode) { return Math.max(0, Math.min(row, this.lines.length-1)); }; + this.documentToScreenColumn = function(row, docColumn) { + var tabSize = this.getTabSize(); + + var screenColumn = 0; + var remaining = docColumn; + + var line = this.getLine(row).split("\t"); + for (var i=0; i len) { + remaining -= (len + 1); + screenColumn += len + tabSize; + } + else { + screenColumn += remaining; + break; + } + } + + return screenColumn; + }; + + this.screenToDocumentColumn = function(row, screenColumn) { + var tabSize = this.getTabSize(); + + var docColumn = 0; + var remaining = screenColumn; + + var line = this.getLine(row).split("\t"); + for (var i=0; i= len + tabSize) { + remaining -= (len + tabSize); + docColumn += (len + 1); + } + else if (remaining > len){ + docColumn += len; + break; + } + else { + docColumn += remaining; + break; + } + } + return docColumn; + }; + }).call(ace.Document.prototype); diff --git a/src/ace/Editor.js b/src/ace/Editor.js index b1e53be1..97a959db 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -93,6 +93,7 @@ ace.Editor = function(renderer, doc) { this.doc.addEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); this.selection = doc.getSelection(); + this.$desiredColumn = 0; this.$onCursorChange = ace.bind(this.onCursorChange, this); this.selection.addEventListener("changeCursor", this.$onCursorChange); @@ -279,10 +280,12 @@ ace.Editor = function(renderer, doc) { this.onMouseDoubleClick = function(e) { this.selection.selectWord(); + this.$updateDesiredColumn(); }; this.onMouseTripleClick = function(e) { this.selection.selectLine(); + this.$updateDesiredColumn(); }; this.onMouseWheel = function(e) { @@ -467,6 +470,7 @@ ace.Editor = function(renderer, doc) { var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); this.selection.shiftSelection(addedColumns); + this.$updateDesiredColumn(); }; this.blockOutdent = function(indentString) { @@ -477,6 +481,7 @@ ace.Editor = function(renderer, doc) { var addedColumns = this.doc.outdentRows(this.getSelectionRange(), indentString); this.selection.shiftSelection(addedColumns); + this.$updateDesiredColumn(); }; this.toggleCommentLines = function() { @@ -656,14 +661,17 @@ ace.Editor = function(renderer, doc) { this.clearSelection = function() { this.selection.clearSelection(); + this.$updateDesiredColumn(); }; this.moveCursorTo = function(row, column) { this.selection.moveCursorTo(row, column); + this.$updateDesiredColumn(); }; this.moveCursorToPosition = function(pos) { this.selection.moveCursorToPosition(pos); + this.$updateDesiredColumn(); }; @@ -680,16 +688,34 @@ ace.Editor = function(renderer, doc) { this.navigateTo = function(row, column) { this.clearSelection(); this.moveCursorTo(row, column); + this.$updateDesiredColumn(column); }; this.navigateUp = function() { - this.clearSelection(); - this.selection.moveCursorUp(); + this.selection.clearSelection(); + this.selection.moveCursorBy(-1, 0); + + if (this.$desiredColumn) { + var cursor = this.getCursorPosition(); + var column = this.doc.screenToDocumentColumn(cursor.row, this.$desiredColumn); + this.selection.moveCursorTo(cursor.row, column); + } }; this.navigateDown = function() { - this.clearSelection(); - this.selection.moveCursorDown(); + this.selection.clearSelection(); + this.selection.moveCursorBy(1, 0); + + if (this.$desiredColumn) { + var cursor = this.getCursorPosition(); + var column = this.doc.screenToDocumentColumn(cursor.row, this.$desiredColumn); + this.selection.moveCursorTo(cursor.row, column); + } + }; + + this.$updateDesiredColumn = function() { + var cursor = this.getCursorPosition(); + this.$desiredColumn = this.doc.documentToScreenColumn(cursor.row, cursor.column); }; this.navigateLeft = function() { @@ -715,39 +741,40 @@ ace.Editor = function(renderer, doc) { }; this.navigateLineStart = function() { - this.clearSelection(); this.selection.moveCursorLineStart(); + this.clearSelection(); }; this.navigateLineEnd = function() { - this.clearSelection(); this.selection.moveCursorLineEnd(); + this.clearSelection(); }; this.navigateFileEnd = function() { - this.clearSelection(); this.selection.moveCursorFileEnd(); + this.clearSelection(); }; this.navigateFileStart = function() { - this.clearSelection(); this.selection.moveCursorFileStart(); + this.clearSelection(); }; this.navigateWordRight = function() { - this.clearSelection(); this.selection.moveCursorWordRight(); + this.clearSelection(); }; this.navigateWordLeft = function() { - this.clearSelection(); this.selection.moveCursorWordLeft(); + this.clearSelection(); }; this.replace = function(replacement) { var range = this.$tryReplace(this.getSelectionRange(), replacement); if (range !== null) this.selection.setSelectionRange(range); + this.$updateDesiredColumn(); }, this.replaceAll = function(replacement) { @@ -763,6 +790,7 @@ ace.Editor = function(renderer, doc) { this.$tryReplace(range, replacement); } this.selection.setSelectionRange(range); + this.$updateDesiredColumn(); }, this.$tryReplace = function(range, replacement) { @@ -800,8 +828,10 @@ ace.Editor = function(renderer, doc) { }); var range = this.$search.find(this.doc); - if (range) + if (range) { this.selection.setSelectionRange(range); + this.$updateDesiredColumn(); + } }; this.undo = function() { diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index e7732de8..35f017c5 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -250,57 +250,10 @@ ace.VirtualRenderer = function(container) { this.$documentToScreenPosition = function(pos) { return { row: pos.row, - column: this.$documentToScreenColumn(pos.row, pos.column) + column: this.doc.documentToScreenColumn(pos.row, pos.column) }; }; - this.$documentToScreenColumn = function(row, docColumn) { - var tabSize = this.doc.getTabSize(); - - var screenColumn = 0; - var remaining = docColumn; - - var line = this.doc.getLine(row).split("\t"); - for (var i=0; i len) { - remaining -= (len + 1); - screenColumn += len + tabSize; - } - else { - screenColumn += remaining; - break; - } - } - - return screenColumn; - }; - - this.$screenToDocumentColumn = function(row, screenColumn) { - var tabSize = this.doc.getTabSize(); - - var docColumn = 0; - var remaining = screenColumn; - - var line = this.doc.getLine(row).split("\t"); - for (var i=0; i= len + tabSize) { - remaining -= (len + tabSize); - docColumn += (len + 1); - } - else if (remaining > len){ - docColumn += len; - break; - } - else { - docColumn += remaining; - break; - } - } - return docColumn; - }; - this.hideCursor = function() { this.$cursorLayer.hideCursor(); }; @@ -369,7 +322,7 @@ ace.VirtualRenderer = function(container) { return { row : row, - column : this.$screenToDocumentColumn(Math.max(0, Math.min(row, this.doc.getLength()-1)), col) + column : this.doc.screenToDocumentColumn(Math.max(0, Math.min(row, this.doc.getLength()-1)), col) }; }; diff --git a/src/test/ace/DocumentTest.js b/src/test/ace/DocumentTest.js index 67814be0..25817208 100644 --- a/src/test/ace/DocumentTest.js +++ b/src/test/ace/DocumentTest.js @@ -162,5 +162,51 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { undoManager.undo(); doc.$informUndoManager.call(); assertEquals(initialText, doc.toString()); + }, + + "test: convert document to screen coordinates" : function() { + var doc = new ace.Document("01234\t567890\t1234"); + doc.setTabSize(4); + + assertEquals(0, doc.documentToScreenColumn(0, 0)); + assertEquals(4, doc.documentToScreenColumn(0, 4)); + assertEquals(5, doc.documentToScreenColumn(0, 5)); + assertEquals(9, doc.documentToScreenColumn(0, 6)); + assertEquals(15, doc.documentToScreenColumn(0, 12)); + assertEquals(19, doc.documentToScreenColumn(0, 13)); + + doc.setTabSize(2); + + assertEquals(0, doc.documentToScreenColumn(0, 0)); + assertEquals(4, doc.documentToScreenColumn(0, 4)); + assertEquals(5, doc.documentToScreenColumn(0, 5)); + assertEquals(7, doc.documentToScreenColumn(0, 6)); + assertEquals(13, doc.documentToScreenColumn(0, 12)); + assertEquals(15, doc.documentToScreenColumn(0, 13)); + }, + + "test: convert document to scrren coordinates with leading tabs": function() { + var doc = new ace.Document("\t\t123"); + doc.setTabSize(4); + + assertEquals(0, doc.documentToScreenColumn(0, 0)); + assertEquals(4, doc.documentToScreenColumn(0, 1)); + assertEquals(8, doc.documentToScreenColumn(0, 2)); + assertEquals(9, doc.documentToScreenColumn(0, 3)); + }, + + "test: convert screen to document coordinates" : function() { + var doc = new ace.Document("01234\t567890\t1234"); + doc.setTabSize(4); + + assertEquals(0, doc.screenToDocumentColumn(0, 0)); + assertEquals(4, doc.screenToDocumentColumn(0, 4)); + assertEquals(5, doc.screenToDocumentColumn(0, 5)); + assertEquals(5, doc.screenToDocumentColumn(0, 6)); + assertEquals(5, doc.screenToDocumentColumn(0, 7)); + assertEquals(5, doc.screenToDocumentColumn(0, 8)); + assertEquals(6, doc.screenToDocumentColumn(0, 9)); + assertEquals(12, doc.screenToDocumentColumn(0, 15)); + assertEquals(13, doc.screenToDocumentColumn(0, 19)); } }); \ No newline at end of file diff --git a/src/test/ace/NavigationTest.js b/src/test/ace/NavigationTest.js index 37b84579..f7eee858 100644 --- a/src/test/ace/NavigationTest.js +++ b/src/test/ace/NavigationTest.js @@ -11,7 +11,7 @@ var NavigationTest = TestCase("NavigationTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.navigateFileEnd(); - var cursor = editor.getSelection().getCursor(); + var cursor = editor.getCursorPosition(); assertTrue(editor.getFirstVisibleRow() <= cursor.row); assertTrue(editor.getLastVisibleRow() >= cursor.row); @@ -32,32 +32,32 @@ var NavigationTest = TestCase("NavigationTest", editor.navigateTo(0, 0); editor.gotoLine(101); - assertPosition(100, 0, editor.getSelection().getCursor()); + assertPosition(100, 0, editor.getCursorPosition()); assertEquals(90, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); editor.gotoLine(11); - assertPosition(10, 0, editor.getSelection().getCursor()); + assertPosition(10, 0, editor.getCursorPosition()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); editor.gotoLine(6); - assertPosition(5, 0, editor.getSelection().getCursor()); + assertPosition(5, 0, editor.getCursorPosition()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); editor.gotoLine(1); - assertPosition(0, 0, editor.getSelection().getCursor()); + assertPosition(0, 0, editor.getCursorPosition()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(0, 0); editor.gotoLine(191); - assertPosition(190, 0, editor.getSelection().getCursor()); + assertPosition(190, 0, editor.getCursorPosition()); assertEquals(180, editor.getFirstVisibleRow()); editor.navigateTo(0, 0); editor.gotoLine(196); - assertPosition(195, 0, editor.getSelection().getCursor()); + assertPosition(195, 0, editor.getCursorPosition()); assertEquals(180, editor.getFirstVisibleRow()); }, @@ -66,12 +66,41 @@ var NavigationTest = TestCase("NavigationTest", editor.navigateTo(0, 0); editor.gotoLine(12); - assertPosition(11, 0, editor.getSelection().getCursor()); + assertPosition(11, 0, editor.getCursorPosition()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(30, 0); editor.gotoLine(33); - assertPosition(32, 0, editor.getSelection().getCursor()); + assertPosition(32, 0, editor.getCursorPosition()); assertEquals(30, editor.getFirstVisibleRow()); + }, + + "test: navigate from the end of a long line down to a short line and back should maintain the curser column": function() { + var editor = new ace.Editor(new MockRenderer(), new ace.Document(["123456", "1"])); + + editor.navigateTo(0, 6); + assertPosition(0, 6, editor.getCursorPosition()); + + editor.navigateDown(); + assertPosition(1, 1, editor.getCursorPosition()); + + editor.navigateUp(); + assertPosition(0, 6, editor.getCursorPosition()); + }, + + "test: reset desired column on navigate left or right": function() { + var editor = new ace.Editor(new MockRenderer(), new ace.Document(["123456", "12"])); + + editor.navigateTo(0, 6); + assertPosition(0, 6, editor.getCursorPosition()); + + editor.navigateDown(); + assertPosition(1, 2, editor.getCursorPosition()); + + editor.navigateLeft(); + assertPosition(1, 1, editor.getCursorPosition()); + + editor.navigateUp(); + assertPosition(0, 1, editor.getCursorPosition()); } }); \ No newline at end of file diff --git a/src/test/ace/VirtualRendererTest.js b/src/test/ace/VirtualRendererTest.js index 506fdabc..3a943485 100644 --- a/src/test/ace/VirtualRendererTest.js +++ b/src/test/ace/VirtualRendererTest.js @@ -1,62 +1,5 @@ var VirtualRendererTest = new TestCase("VirtualRendererTest", { - "test: convert document to screen coordinates" : function() { - var el = document.createElement("div"); - var renderer = new ace.VirtualRenderer(el); - - var doc = new ace.Document("01234\t567890\t1234"); - doc.setTabSize(4); - renderer.setDocument(doc); - - assertEquals(0, renderer.$documentToScreenColumn(0, 0)); - assertEquals(4, renderer.$documentToScreenColumn(0, 4)); - assertEquals(5, renderer.$documentToScreenColumn(0, 5)); - assertEquals(9, renderer.$documentToScreenColumn(0, 6)); - assertEquals(15, renderer.$documentToScreenColumn(0, 12)); - assertEquals(19, renderer.$documentToScreenColumn(0, 13)); - - doc.setTabSize(2); - - assertEquals(0, renderer.$documentToScreenColumn(0, 0)); - assertEquals(4, renderer.$documentToScreenColumn(0, 4)); - assertEquals(5, renderer.$documentToScreenColumn(0, 5)); - assertEquals(7, renderer.$documentToScreenColumn(0, 6)); - assertEquals(13, renderer.$documentToScreenColumn(0, 12)); - assertEquals(15, renderer.$documentToScreenColumn(0, 13)); - }, - - "test: convert document to scrren coordinates with leading tabs": function() { - var el = document.createElement("div"); - var renderer = new ace.VirtualRenderer(el); - - var doc = new ace.Document("\t\t123"); - doc.setTabSize(4); - renderer.setDocument(doc); - assertEquals(0, renderer.$documentToScreenColumn(0, 0)); - assertEquals(4, renderer.$documentToScreenColumn(0, 1)); - assertEquals(8, renderer.$documentToScreenColumn(0, 2)); - assertEquals(9, renderer.$documentToScreenColumn(0, 3)); - }, - - "test: convert screen to document coordinates" : function() { - var el = document.createElement("div"); - var renderer = new ace.VirtualRenderer(el); - - var doc = new ace.Document("01234\t567890\t1234"); - doc.setTabSize(4); - renderer.setDocument(doc); - - assertEquals(0, renderer.$screenToDocumentColumn(0, 0)); - assertEquals(4, renderer.$screenToDocumentColumn(0, 4)); - assertEquals(5, renderer.$screenToDocumentColumn(0, 5)); - assertEquals(5, renderer.$screenToDocumentColumn(0, 6)); - assertEquals(5, renderer.$screenToDocumentColumn(0, 7)); - assertEquals(5, renderer.$screenToDocumentColumn(0, 8)); - assertEquals(6, renderer.$screenToDocumentColumn(0, 9)); - assertEquals(12, renderer.$screenToDocumentColumn(0, 15)); - assertEquals(13, renderer.$screenToDocumentColumn(0, 19)); - }, - "test: screen2text the column should be rounded to the next character edge" : function() { var el = document.createElement("div"); el.style.left = "0px";