diff --git a/src/Editor.js b/src/Editor.js index 7a3436bb..55ca87de 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -290,17 +290,6 @@ ace.Editor.prototype.removeLeft = function() { this.clearSelection(); }; -ace.Editor.prototype.removeLine = function() { - this.selection.selectLine(); - this.moveCursorToPosition(this.doc.remove(this.getSelectionRange())); - this.clearSelection(); - - if (this.getCursorPosition().row == this.doc.getLength() - 1) { - this.removeLeft(); - this.selection.moveCursorLineStart(); - } -}; - ace.Editor.prototype.blockIndent = function(indentString) { var indentString = indentString || this.doc.getTabString(); var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); @@ -324,6 +313,15 @@ ace.Editor.prototype.toggleCommentLines = function() { this.selection.shiftSelection(addedColumns); }; +ace.Editor.prototype.removeLines = function() { + var rows = this._getSelectedRows(); + this.selection.setSelectionAnchor(rows.last+1, 0); + this.selection.selectTo(rows.first, 0); + + this.doc.remove(this.getSelectionRange()); + this.clearSelection(); +}; + ace.Editor.prototype.moveLinesDown = function() { this._moveLines(function(firstRow, lastRow) { return this.doc.moveLinesDown(firstRow, lastRow); @@ -336,7 +334,33 @@ ace.Editor.prototype.moveLinesUp = function() { }); }; +ace.Editor.prototype.copyLinesUp = function() { + this._moveLines(function(firstRow, lastRow) { + this.doc.duplicateLines(firstRow, lastRow); + return 0; + }); +}; + +ace.Editor.prototype.copyLinesDown = function() { + this._moveLines(function(firstRow, lastRow) { + return this.doc.duplicateLines(firstRow, lastRow); + }); +}; + + ace.Editor.prototype._moveLines = function(mover) { + var rows = this._getSelectedRows(); + + var linesMoved = mover.call(this, rows.first, rows.last); + + var selection = this.selection; + selection.setSelectionAnchor(rows.last+linesMoved+1, 0); + selection._moveSelection(function() { + selection.moveCursorTo(rows.first+linesMoved, 0); + }); +}; + +ace.Editor.prototype._getSelectedRows = function() { var range = this.getSelectionRange(); var firstRow = range.start.row; var lastRow = range.end.row; @@ -344,16 +368,12 @@ ace.Editor.prototype._moveLines = function(mover) { lastRow -= 1; } - var linesMoved = mover.call(this, firstRow, lastRow); - - var selection = this.selection; - selection.setSelectionAnchor(lastRow+linesMoved+1, 0); - selection._moveSelection(function() { - selection.moveCursorTo(firstRow+linesMoved, 0); - }); + return { + first: firstRow, + last: lastRow + }; }; - ace.Editor.prototype.onCompositionStart = function() { this.renderer.showComposition(this.getCursorPosition()); this.onTextInput(" "); diff --git a/src/KeyBinding.js b/src/KeyBinding.js index 48417b97..e2ef51fd 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -35,7 +35,7 @@ ace.KeyBinding = function(element, editor) { case keys.D: if (e.metaKey) { - editor.removeLine(); + editor.removeLines(); return ace.stopEvent(e); } break; @@ -58,7 +58,10 @@ ace.KeyBinding = function(element, editor) { break; case keys.UP: - if (e.altKey) { + if (e.altKey && e.metaKey ) { + editor.copyLinesUp(); + } + else if (e.altKey) { editor.moveLinesUp(); } else if (e.metaKey && e.shiftKey) { @@ -76,7 +79,10 @@ ace.KeyBinding = function(element, editor) { return ace.stopEvent(e); case keys.DOWN: - if (e.altKey) { + if (e.altKey && e.metaKey ) { + editor.copyLinesDown(); + } + else if (e.altKey) { editor.moveLinesDown(); } else if (e.metaKey && e.shiftKey) { diff --git a/src/Selection.js b/src/Selection.js index 20f0f84d..af2078f2 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -114,6 +114,12 @@ ace.Selection.prototype._moveSelection = function(mover) { this.updateSelection(); }; +ace.Selection.prototype.selectTo = function(row, column) { + this._moveSelection(function() { + this.moveCursorTo(row, column); + }); +}; + ace.Selection.prototype.selectToPosition = function(pos) { this._moveSelection(function() { this.moveCursorToPosition(pos); diff --git a/src/TextDocument.js b/src/TextDocument.js index 5a5e408b..17c6400c 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -92,10 +92,8 @@ ace.TextDocument.prototype.getTextRange = function(range) { else { var lines = []; lines.push(this.lines[range.start.row].substring(range.start.column)); - lines.push.apply(lines, this.lines.slice(range.start.row + 1, - range.end.row)); + lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); lines.push(this.lines[range.end.row].substring(0, range.end.column)); - return lines.join("\n"); } }; @@ -203,6 +201,12 @@ ace.TextDocument.prototype.insert = function(position, text) { return end; }; +ace.TextDocument.prototype._insertLines = function(row, lines) { + var args = [row, 0]; + args.push.apply(args, lines); + this.lines.splice.apply(this.lines, args); +}, + ace.TextDocument.prototype._insert = function(position, text) { this.modified = true; @@ -237,9 +241,7 @@ ace.TextDocument.prototype._insert = function(position, text) { + line.substring(position.column); if (newLines.length > 2) { - var args = [ position.row + 1, 0 ]; - args.push.apply(args, newLines.slice(1, -1)); - this.lines.splice.apply(this.lines, args); + this._insertLines(position.row + 1, newLines.slice(1, -1)); } return { @@ -318,10 +320,7 @@ 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._insertLines(firstRow-1, removed); this.fireChangeEvent(firstRow-1, lastRow); return -1; @@ -331,11 +330,26 @@ 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._insertLines(firstRow+1, removed); this.fireChangeEvent(firstRow, lastRow+1); return 1; -}; \ No newline at end of file +}; + +ace.TextDocument.prototype.duplicateLines = function(firstRow, lastRow) { + var firstRow = this._clipRowToDocument(firstRow); + var lastRow = this._clipRowToDocument(lastRow); + + var lines = this.getLines(firstRow, lastRow); + this._insertLines(firstRow, lines); + + var addedRows = lastRow - firstRow + 1; + this.fireChangeEvent(firstRow, lastRow+addedRows); + + return addedRows; +}; + +ace.TextDocument.prototype._clipRowToDocument = function(row) { + return Math.max(0, Math.min(row, this.lines.length-1)); +}; + diff --git a/test/TextDocumentTest.js b/test/TextDocumentTest.js index 6a169841..58539654 100644 --- a/test/TextDocumentTest.js +++ b/test/TextDocumentTest.js @@ -63,5 +63,26 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { doc.moveLinesUp(2, 2); assertEquals(["3", "1", "4", "2"].join("\n"), doc.toString()); + }, + + "test: duplicate lines" : function() { + var doc = new ace.TextDocument(["1", "2", "3", "4"].join("\n")); + + doc.duplicateLines(1, 2); + assertEquals(["1", "2", "3", "2", "3", "4"].join("\n"), doc.toString()); + }, + + "test: duplicate last line" : function() { + var doc = new ace.TextDocument(["1", "2", "3"].join("\n")); + + doc.duplicateLines(2, 2); + assertEquals(["1", "2", "3", "3"].join("\n"), doc.toString()); + }, + + "test: duplicate first line" : function() { + var doc = new ace.TextDocument(["1", "2", "3"].join("\n")); + + doc.duplicateLines(0, 0); + assertEquals(["1", "1", "2", "3"].join("\n"), doc.toString()); } }); \ No newline at end of file diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 77f78751..cb635f35 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -5,17 +5,44 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(1, 1); - editor.removeLine(); + editor.removeLines(); assertEquals("a\nc\nd", doc.toString()); assertPosition(1, 0, editor.getCursorPosition()); + + editor.removeLines(); + + assertEquals("a\nd", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + + editor.removeLines(); + + assertEquals("a\n", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + + editor.removeLines(); + + assertEquals("a\n", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + }, + + "test: delete multiple selected lines" : function() { + var doc = new ace.TextDocument(["a", "b", "c", "d"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(1, 1); + editor.getSelection().selectDown(); + + editor.removeLines(); + assertEquals("a\nd", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); }, "test: delete first line" : function() { var doc = new ace.TextDocument(["a", "b", "c"].join("\n")); var editor = new ace.Editor(new MockRenderer(), doc); - editor.removeLine(); + editor.removeLines(); assertEquals("b\nc", doc.toString()); assertPosition(0, 0, editor.getCursorPosition()); @@ -26,10 +53,10 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(2, 1); - editor.removeLine(); + editor.removeLines(); - assertEquals("a\nb", doc.toString()); - assertPosition(1, 0, editor.getCursorPosition()); + assertEquals("a\nb\n", doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); }, "test: indent block" : function() { @@ -190,6 +217,36 @@ var TextEditTest = TestCase("TextEditTest", assertPosition(1, 0, editor.getCursorPosition()); }, + "test: copy lines down should select lines and place cursor at the selection start" : 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.getSelection().selectDown(); + + editor.copyLinesDown(); + assertEquals(["11", "22", "33", "22", "33", "44"].join("\n"), doc.toString()); + + assertPosition(3, 0, editor.getCursorPosition()); + assertPosition(5, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(3, 0, editor.getSelection().getSelectionLead()); + }, + + "test: copy lines up should select lines and place cursor at the selection start" : 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.getSelection().selectDown(); + + editor.copyLinesUp(); + assertEquals(["11", "22", "33", "22", "33", "44"].join("\n"), doc.toString()); + + assertPosition(1, 0, editor.getCursorPosition()); + assertPosition(3, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(1, 0, editor.getSelection().getSelectionLead()); + }, + "test: input a tab with soft tab should convert it to spaces" : function() { var doc = new ace.TextDocument(""); var editor = new ace.Editor(new MockRenderer(), doc);