diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 25dc557dc..06e0b95b5 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -2260,3 +2260,93 @@ describe "Editor", -> it "copies the absolute path to the editor's file to the pasteboard", -> editor.trigger 'editor:copy-path' expect(pasteboard.read()[0]).toBe editor.getPath() + + describe "when editor:move-line-up is triggered", -> + describe "when there is no selection", -> + it "moves the line where the cursor is up", -> + editor.setCursorBufferPosition([1,0]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {' + expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {' + + it "moves the cursor to the new row and the same column", -> + editor.setCursorBufferPosition([1,2]) + editor.trigger 'editor:move-line-up' + expect(editor.getCursorBufferPosition()).toEqual [0,2] + + describe "where there is a selection", -> + describe "when the selection falls inside the line", -> + it "maintains the selection", -> + editor.setSelectedBufferRange([[1, 2], [1, 5]]) + expect(editor.getSelectedText()).toBe 'var' + editor.trigger 'editor:move-line-up' + expect(editor.getSelectedBufferRange()).toEqual [[0, 2], [0, 5]] + expect(editor.getSelectedText()).toBe 'var' + + describe "where there are multiple lines selected", -> + it "moves the selected lines up", -> + editor.setSelectedBufferRange([[2, 0], [3, Infinity]]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {' + expect(buffer.lineForRow(1)).toBe ' if (items.length <= 1) return items;' + expect(buffer.lineForRow(2)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(buffer.lineForRow(3)).toBe ' var sort = function(items) {' + + it "maintains the selection", -> + editor.setSelectedBufferRange([[2, 0], [3, 62]]) + editor.trigger 'editor:move-line-up' + expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [2, 62]] + + describe "when the last line is selected", -> + it "moves the selected line up", -> + editor.setSelectedBufferRange([[12, 0], [12, Infinity]]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(11)).toBe '};' + expect(buffer.lineForRow(12)).toBe ' return sort(Array.apply(this, arguments));' + + describe "when the last two lines are selected", -> + it "moves the selected lines up", -> + editor.setSelectedBufferRange([[11, 0], [12, Infinity]]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(10)).toBe ' return sort(Array.apply(this, arguments));' + expect(buffer.lineForRow(11)).toBe '};' + expect(buffer.lineForRow(12)).toBe '' + + describe "when the cursor is on the first line", -> + it "does not move the line", -> + editor.setCursorBufferPosition([0,0]) + originalText = editor.getText() + editor.trigger 'editor:move-line-up' + expect(editor.getText()).toBe originalText + + describe "when the cursor is on the trailing newline", -> + it "does not move the line", -> + editor.moveCursorToBottom() + editor.insertNewline() + editor.moveCursorToBottom() + originalText = editor.getText() + editor.trigger 'editor:move-line-up' + expect(editor.getText()).toBe originalText + + describe "when the cursor is on a folded line", -> + it "moves all lines in the fold up and preserves the fold", -> + editor.setCursorBufferPosition([4, 0]) + editor.foldCurrentRow() + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {' + expect(buffer.lineForRow(7)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(editor.getSelectedBufferRange()).toEqual [[3, 0], [3, 0]] + expect(editor.isFoldedAtScreenRow(3)).toBeTruthy() + + describe "when a fold is selected", -> + it "moves the selected lines up and preserves the fold", -> + editor.setCursorBufferPosition([4, 0]) + editor.foldCurrentRow() + editor.setCursorBufferPosition([3, 4]) + editor.selectDown() + expect(editor.isFoldedAtScreenRow(4)).toBeTruthy() + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(2)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {' + expect(editor.getSelectedBufferRange()).toEqual [[2, 4], [3,0]] + expect(editor.isFoldedAtScreenRow(3)).toBeTruthy() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 74fe4a2a3..fcedbd28f 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -334,6 +334,39 @@ class EditSession toggleLineCommentsForBufferRows: (start, end) -> @languageMode.toggleLineCommentsForBufferRows(start, end) + moveLineUp: -> + selection = @getSelectedBufferRange() + return if selection.start.row is 0 + lastRow = @buffer.getLastRow() + return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is '' + + @transact => + for row in [selection.start.row..selection.end.row] + screenPosition = @screenPositionForBufferPosition([row, 0]) + isRowFolded = @isFoldedAtScreenRow(screenPosition.row) + if isRowFolded + bufferRange = @bufferRangeForScreenRange([[screenPosition.row, 0], [screenPosition.row + 1, 0]]) + startRow = bufferRange.start.row + endRow = bufferRange.end.row - 1 + else + startRow = row + endRow = row + + endPosition = Point.min([endRow + 1, 0], @buffer.getEofPosition()) + lines = @buffer.getTextInRange([[startRow, 0], endPosition]) + if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row) + lines = "#{lines}\n" + @buffer.deleteRows(startRow, endRow) + @buffer.insert([startRow - 1, 0], lines) + @foldBufferRow(startRow - 1) if isRowFolded + + newStartPosition = [selection.start.row - 1, selection.start.column] + if selection.isEmpty() + @setCursorBufferPosition(newStartPosition) + else + newEndPosition = [selection.end.row - 1, selection.end.column] + @setSelectedBufferRange([newStartPosition, newEndPosition], preserveFolds: true) + mutateSelectedText: (fn) -> @transact => fn(selection) for selection in @getSelections() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index d0041c1bd..2afa97e4d 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -183,6 +183,7 @@ class Editor extends View 'editor:close-all-edit-sessions': @destroyAllEditSessions 'editor:select-grammar': @selectGrammar 'editor:copy-path': @copyPathToPasteboard + 'editor:move-line-up': @moveLineUp documentation = {} for name, method of editorBindings @@ -204,6 +205,7 @@ class Editor extends View moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine() moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine() moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine() + moveLineUp: -> @activeEditSession.moveLineUp() setCursorScreenPosition: (position) -> @activeEditSession.setCursorScreenPosition(position) getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition() getCursorScreenRow: -> @activeEditSession.getCursorScreenRow() diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index 06f0dd571..4cb724be2 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -38,3 +38,4 @@ 'meta-P': 'editor:close-all-edit-sessions' 'meta-L': 'editor:select-grammar' 'ctrl-C': 'editor:copy-path' + 'ctrl-meta-up': 'editor:move-line-up' diff --git a/src/app/line-map.coffee b/src/app/line-map.coffee index eae630887..1a5a74147 100644 --- a/src/app/line-map.coffee +++ b/src/app/line-map.coffee @@ -133,6 +133,7 @@ class LineMap new Range(start, end) bufferRangeForScreenRange: (screenRange) -> + screenRange = Range.fromObject(screenRange) start = @bufferPositionForScreenPosition(screenRange.start) end = @bufferPositionForScreenPosition(screenRange.end) new Range(start, end) @@ -141,4 +142,3 @@ class LineMap for row in [start..end] line = @lineForScreenRow(row).text console.log row, line, line.length - diff --git a/src/app/point.coffee b/src/app/point.coffee index a216cad0b..0b07e3ed7 100644 --- a/src/app/point.coffee +++ b/src/app/point.coffee @@ -11,6 +11,12 @@ class Point new Point(row, column) + @min: (point1, point2) -> + if @fromObject(point1).isLessThanOrEqual(@fromObject(point2)) + point1 + else + point2 + constructor: (@row=0, @column=0) -> copy: ->