diff --git a/src/Editor.js b/src/Editor.js index 6efd21db..c43e326c 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -288,6 +288,35 @@ ace.Editor.prototype.toggleCommentLines = function() { this.shiftSelection(addedColumns); }; +ace.Editor.prototype.moveLinesDown = function() { + this._moveLines(function(firstRow, lastRow) { + return this.doc.moveLinesDown(firstRow, lastRow); + }); +}; + +ace.Editor.prototype.moveLinesUp = function() { + this._moveLines(function(firstRow, lastRow) { + return this.doc.moveLinesUp(firstRow, lastRow); + }); +}; + +ace.Editor.prototype._moveLines = function(mover) { + var range = this.getSelectionRange(); + var firstRow = range.start.row; + var lastRow = range.end.row; + if (range.end.column == 0 && (range.start.row !== range.end.row)) { + lastRow -= 1; + } + + var linesMoved = mover.call(this, firstRow, lastRow); + + this.setSelectionAnchor(lastRow+linesMoved+1, 0); + this._moveSelection(function() { + this.moveCursorTo(firstRow+linesMoved, 0); + }); +}; + + ace.Editor.prototype.onCompositionStart = function() { this.renderer.showComposition(this.cursor); this.onTextInput(" "); @@ -532,21 +561,27 @@ ace.Editor.prototype.moveCursorToPosition = function(position) { this.moveCursorTo(position.row, position.column); }; -ace.Editor.prototype.moveCursorTo = function(row, column) { +ace.Editor.prototype._clipPositionToDocument = function(row, column) { + var pos = {}; + if (row >= this.doc.getLength()) { - this.cursor.row = this.doc.getLength() - 1; - this.cursor.column = this.doc.getLine(this.cursor.row).length; + pos.row = this.doc.getLength() - 1; + pos.column = this.doc.getLine(pos.row).length; } else if (row < 0) { - this.cursor.row = 0; - this.cursor.column = 0; + pos.row = 0; + pos.column = 0; } else { - this.cursor.row = row; - this.cursor.column = Math - .min(this.doc.getLine(this.cursor.row).length, Math - .max(0, column)); + pos.row = row; + pos.column = Math.min(this.doc.getLine(pos.row).length, + Math.max(0, column)); } + return pos; +}; + +ace.Editor.prototype.moveCursorTo = function(row, column) { + this.cursor = this._clipPositionToDocument(row, column); this.updateCursor(); }; @@ -580,12 +615,7 @@ ace.Editor.prototype.hasMultiLineSelection = function() { ace.Editor.prototype.setSelectionAnchor = function(row, column) { this.clearSelection(); - this.selectionAnchor = { - row : Math.min(this.doc.getLength() - 1, Math.max(0, row)), - column : Math.min(this.doc.getLine(this.cursor.row).length, Math - .max(0, column)) - }; - + this.selectionAnchor = this._clipPositionToDocument(row, column); this.selectionLead = null; }; @@ -624,26 +654,21 @@ ace.Editor.prototype.shiftSelection = function(columns) { }; ace.Editor.prototype.getSelectionRange = function() { - var anchor = this.selectionAnchor; - var lead = this.selectionLead; + var anchor = this.selectionAnchor || this.cursor; + var lead = this.selectionLead || this.cursor; - if (!anchor) { - return null; + if (anchor.row > lead.row + || (anchor.row == lead.row && anchor.column > lead.column)) { + return { + start : lead, + end : anchor + }; } else { - if (anchor.row > lead.row - || (anchor.row == lead.row && anchor.column > lead.column)) { - return { - start : lead, - end : anchor - }; - } - else { - return { - start : anchor, - end : lead - }; - } + return { + start : anchor, + end : lead + }; } }; diff --git a/src/KeyBinding.js b/src/KeyBinding.js index f53dfddd..7d1a5262 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -57,13 +57,16 @@ ace.KeyBinding = function(element, host) { break; case keys.UP: - if (e.metaKey && e.shiftKey) { + if (e.altKey) { + host.moveLinesUp(); + } + else if (e.metaKey && e.shiftKey) { host.selectFileStart(); } else if (e.metaKey) { host.navigateFileStart(); } - if (e.shiftKey) { + else if (e.shiftKey) { host.selectUp(); } else { @@ -72,13 +75,16 @@ ace.KeyBinding = function(element, host) { return ace.stopEvent(e); case keys.DOWN: - if (e.metaKey && e.shiftKey) { + if (e.altKey) { + host.moveLinesDown(); + } + else if (e.metaKey && e.shiftKey) { host.selectFileEnd(); } else if (e.metaKey) { host.navigateFileEnd(); } - if (e.shiftKey) { + else if (e.shiftKey) { host.selectDown(); } else { diff --git a/src/TextDocument.js b/src/TextDocument.js index 5934b835..20349760 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -301,4 +301,30 @@ ace.TextDocument.prototype.outdentRows = function(range, indentString) { this.fireChangeEvent(range.start.row, range.end.row); return -outdentLength; +}; + +ace.TextDocument.prototype.moveLinesUp = function(firstRow, lastRow) { + if (firstRow <= 0) return 0; + + var removed = this.lines.splice(firstRow, lastRow-firstRow+1); + + var args = [firstRow - 1, 0]; + args.push.apply(args, removed); + this.lines.splice.apply(this.lines, args); + + this.fireChangeEvent(firstRow-1, lastRow); + return -1; +}; + +ace.TextDocument.prototype.moveLinesDown = function(firstRow, lastRow) { + if (lastRow >= this.lines.length-1) return 0; + + var removed = this.lines.splice(firstRow, lastRow-firstRow+1); + + var args = [firstRow + 1, 0]; + args.push.apply(args, removed); + this.lines.splice.apply(this.lines, args); + + this.fireChangeEvent(firstRow, lastRow+1); + return 1; }; \ No newline at end of file diff --git a/test/TextDocumentTest.js b/test/TextDocumentTest.js index 39f6cbaa..6a169841 100644 --- a/test/TextDocumentTest.js +++ b/test/TextDocumentTest.js @@ -31,5 +31,37 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { assertPosition(0, 0, doc.findMatchingBracket({row: 1, column: 1})); assertPosition(0, 2, doc.findMatchingBracket({row: 1, column: 2})); assertPosition(0, 1, doc.findMatchingBracket({row: 1, column: 3})); + }, + + "test: move lines down" : function() { + var doc = new ace.TextDocument(["1", "2", "3", "4"].join("\n")); + + doc.moveLinesDown(0, 1); + assertEquals(["3", "1", "2", "4"].join("\n"), doc.toString()); + + doc.moveLinesDown(1, 2); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesDown(2, 3); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesDown(2, 2); + assertEquals(["3", "4", "2", "1"].join("\n"), doc.toString()); + }, + + "test: move lines up" : function() { + var doc = new ace.TextDocument(["1", "2", "3", "4"].join("\n")); + + doc.moveLinesUp(2, 3); + assertEquals(["1", "3", "4", "2"].join("\n"), doc.toString()); + + doc.moveLinesUp(1, 2); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesUp(0, 1); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesUp(2, 2); + assertEquals(["3", "1", "4", "2"].join("\n"), doc.toString()); } }); \ No newline at end of file diff --git a/test/TextEditTest.js b/test/TextEditTest.js index d2ab4796..50fbb046 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -107,5 +107,71 @@ var TextEditTest = TestCase("TextEditTest", var selection = editor.getSelectionRange(); assertPosition(0, 0, selection.start); assertPosition(1, 1, selection.end); + }, + + "test: move lines down should select moved lines" : function() { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(0, 1); + editor.selectDown(); + + editor.moveLinesDown(); + assertEquals(["33", "11", "22", "44"].join("\n"), doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + assertPosition(3, 0, editor.getSelectionAnchor()); + assertPosition(1, 0, editor.getSelectionLead()); + + editor.moveLinesDown(); + assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); + assertPosition(3, 2, editor.getSelectionAnchor()); + assertPosition(2, 0, editor.getSelectionLead()); + + // moving again should have no effect + editor.moveLinesDown(); + assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); + assertPosition(3, 2, editor.getSelectionAnchor()); + assertPosition(2, 0, editor.getSelectionLead()); + }, + + "test: move lines up should select moved lines" : function() { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(2, 1); + editor.selectDown(); + + editor.moveLinesUp(); + assertEquals(["11", "33", "44", "22"].join("\n"), doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + assertPosition(3, 0, editor.getSelectionAnchor()); + assertPosition(1, 0, editor.getSelectionLead()); + + editor.moveLinesUp(); + assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); + assertPosition(0, 0, editor.getCursorPosition()); + assertPosition(2, 0, editor.getSelectionAnchor()); + assertPosition(0, 0, editor.getSelectionLead()); + }, + + "test: move line without active selection should move cursor to start of the moved line" : function() + { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(1, 1); + editor.clearSelection(); + + editor.moveLinesDown(); + assertEquals(["11", "33", "22", "44"].join("\n"), doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); + + editor.clearSelection(); + + editor.moveLinesUp(); + assertEquals(["11", "22", "33", "44"].join("\n"), doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); } }); \ No newline at end of file