diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index 36eb57bee..5b0af428c 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -321,6 +321,21 @@ describe "Editor", -> editor.moveLeft() expect(editor.getCursorScreenPosition()).toEqual [1, 7] + it "moves the cursor by n columns to the left", -> + editor.setCursorScreenPosition([1, 8]) + editor.moveLeft(4) + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + + it "moves the cursor by two rows up when the columnCount is longer than an entire line", -> + editor.setCursorScreenPosition([2, 2]) + editor.moveLeft(34) + expect(editor.getCursorScreenPosition()).toEqual [0, 29] + + it "moves the cursor to the beginning columnCount is longer than the position in the buffer", -> + editor.setCursorScreenPosition([1, 0]) + editor.moveLeft(100) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + describe "when the cursor is in the first column", -> describe "when there is a previous line", -> it "wraps to the end of the previous line", -> @@ -328,12 +343,28 @@ describe "Editor", -> editor.moveLeft() expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: buffer.lineForRow(0).length) + it "moves the cursor by one row up and n columns to the left", -> + editor.setCursorScreenPosition([1, 0]) + editor.moveLeft(4) + expect(editor.getCursorScreenPosition()).toEqual [0, 26] + + describe "when the next line is empty", -> + it "wraps to the beginning of the previous line", -> + editor.setCursorScreenPosition([11, 0]) + editor.moveLeft() + expect(editor.getCursorScreenPosition()).toEqual [10, 0] + describe "when the cursor is on the first line", -> it "remains in the same position (0,0)", -> editor.setCursorScreenPosition(row: 0, column: 0) editor.moveLeft() expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0) + it "remains in the same position (0,0) when columnCount is specified", -> + editor.setCursorScreenPosition([0, 0]) + editor.moveLeft(4) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + describe "when softTabs is enabled and the cursor is preceded by leading whitespace", -> it "skips tabLength worth of whitespace at a time", -> editor.setCursorBufferPosition([5, 6]) @@ -368,6 +399,21 @@ describe "Editor", -> editor.moveRight() expect(editor.getCursorScreenPosition()).toEqual [3, 4] + it "moves the cursor by n columns to the right", -> + editor.setCursorScreenPosition([3, 7]) + editor.moveRight(4) + expect(editor.getCursorScreenPosition()).toEqual [3, 11] + + it "moves the cursor by two rows down when the columnCount is longer than an entire line", -> + editor.setCursorScreenPosition([0, 29]) + editor.moveRight(34) + expect(editor.getCursorScreenPosition()).toEqual [2, 2] + + it "moves the cursor to the end of the buffer when columnCount is longer than the number of characters following the cursor position", -> + editor.setCursorScreenPosition([11, 5]) + editor.moveRight(100) + expect(editor.getCursorScreenPosition()).toEqual [12, 2] + describe "when the cursor is on the last column of a line", -> describe "when there is a subsequent line", -> it "wraps to the beginning of the next line", -> @@ -375,6 +421,17 @@ describe "Editor", -> editor.moveRight() expect(editor.getCursorScreenPosition()).toEqual [1, 0] + it "moves the cursor by one row down and n columns to the right", -> + editor.setCursorScreenPosition([0, buffer.lineForRow(0).length]) + editor.moveRight(4) + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + describe "when the next line is empty", -> + it "wraps to the beginning of the next line", -> + editor.setCursorScreenPosition([9, 4]) + editor.moveRight() + expect(editor.getCursorScreenPosition()).toEqual [10, 0] + describe "when the cursor is on the last line", -> it "remains in the same position", -> lastLineIndex = buffer.getLines().length - 1 @@ -924,6 +981,27 @@ describe "Editor", -> expect(selection1.getScreenRange()).toEqual([[0, 9], [1, 21]]) expect(selection1.isReversed()).toBeFalsy() + describe "when counts are passed into the selection functions", -> + it "expands each selection to its cursor's new location", -> + editor.setSelectedBufferRanges([[[0,9], [0,13]], [[3,16], [3,21]]]) + [selection1, selection2] = editor.getSelections() + + editor.selectRight(2) + expect(selection1.getBufferRange()).toEqual [[0,9], [0,15]] + expect(selection2.getBufferRange()).toEqual [[3,16], [3,23]] + + editor.selectLeft(3) + expect(selection1.getBufferRange()).toEqual [[0,9], [0,12]] + expect(selection2.getBufferRange()).toEqual [[3,16], [3,20]] + + editor.selectDown(3) + expect(selection1.getBufferRange()).toEqual [[0,9], [3,12]] + expect(selection2.getBufferRange()).toEqual [[3,16], [6,20]] + + editor.selectUp(2) + expect(selection1.getBufferRange()).toEqual [[0,9], [1,12]] + expect(selection2.getBufferRange()).toEqual [[3,16], [4,20]] + describe ".selectToBufferPosition(bufferPosition)", -> it "expands the last selection to the given position", -> editor.setSelectedBufferRange([[3, 0], [4, 5]]) diff --git a/src/cursor.coffee b/src/cursor.coffee index f86721d6d..68018a72c 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -225,6 +225,11 @@ class Cursor extends Model @editor.lineTextForBufferRow(@getBufferRow()) # Public: Moves the cursor up one screen row. + # + # * `rowCount` (optional) {Number} number of rows to move (default: 1) + # * `options` (optional) {Object} with the following keys: + # * `moveToEndOfSelection` if true, move to the left of the selection if a + # selection exists. moveUp: (rowCount=1, {moveToEndOfSelection}={}) -> range = @marker.getScreenRange() if moveToEndOfSelection and not range.isEmpty() @@ -237,7 +242,12 @@ class Cursor extends Model @goalColumn = column # Public: Moves the cursor down one screen row. - moveDown: (rowCount = 1, {moveToEndOfSelection}={}) -> + # + # * `rowCount` (optional) {Number} number of rows to move (default: 1) + # * `options` (optional) {Object} with the following keys: + # * `moveToEndOfSelection` if true, move to the left of the selection if a + # selection exists. + moveDown: (rowCount=1, {moveToEndOfSelection}={}) -> range = @marker.getScreenRange() if moveToEndOfSelection and not range.isEmpty() { row, column } = range.end @@ -250,30 +260,51 @@ class Cursor extends Model # Public: Moves the cursor left one screen column. # + # * `columnCount` (optional) {Number} number of columns to move (default: 1) # * `options` (optional) {Object} with the following keys: # * `moveToEndOfSelection` if true, move to the left of the selection if a # selection exists. - moveLeft: ({moveToEndOfSelection}={}) -> + moveLeft: (columnCount=1, {moveToEndOfSelection}={}) -> range = @marker.getScreenRange() if moveToEndOfSelection and not range.isEmpty() @setScreenPosition(range.start) else {row, column} = @getScreenPosition() - [row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity] + + while columnCount > column and row > 0 + columnCount -= column + column = @editor.lineTextForScreenRow(--row).length + columnCount-- # subtract 1 for the row move + + column = column - columnCount @setScreenPosition({row, column}) # Public: Moves the cursor right one screen column. # + # * `columnCount` (optional) {Number} number of columns to move (default: 1) # * `options` (optional) {Object} with the following keys: # * `moveToEndOfSelection` if true, move to the right of the selection if a # selection exists. - moveRight: ({moveToEndOfSelection}={}) -> + moveRight: (columnCount=1, {moveToEndOfSelection}={}) -> range = @marker.getScreenRange() if moveToEndOfSelection and not range.isEmpty() @setScreenPosition(range.end) else { row, column } = @getScreenPosition() - @setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true) + maxLines = @editor.getScreenLineCount() + rowLength = @editor.lineTextForScreenRow(row).length + columnsRemainingInLine = rowLength - column + + while columnCount > columnsRemainingInLine and row < maxLines - 1 + columnCount -= columnsRemainingInLine + columnCount-- # subtract 1 for the row move + + column = 0 + rowLength = @editor.lineTextForScreenRow(++row).length + columnsRemainingInLine = rowLength + + column = column + columnCount + @setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true) # Public: Moves the cursor to the top of the buffer. moveToTop: -> diff --git a/src/editor.coffee b/src/editor.coffee index 337b5da9d..3ade2ecb9 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1619,7 +1619,7 @@ class Editor extends Model # Essential: Move every cursor up one row in screen coordinates. # - # * `lineCount` {Number} number of lines to move + # * `lineCount` (optional) {Number} number of lines to move moveUp: (lineCount) -> @moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true) moveCursorUp: (lineCount) -> @@ -1628,7 +1628,7 @@ class Editor extends Model # Essential: Move every cursor down one row in screen coordinates. # - # * `lineCount` {Number} number of lines to move + # * `lineCount` (optional) {Number} number of lines to move moveDown: (lineCount) -> @moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true) moveCursorDown: (lineCount) -> @@ -1636,15 +1636,19 @@ class Editor extends Model @moveDown(lineCount) # Essential: Move every cursor left one column. - moveLeft: -> - @moveCursors (cursor) -> cursor.moveLeft(moveToEndOfSelection: true) + # + # * `columnCount` (optional) {Number} number of columns to move (default: 1) + moveLeft: (columnCount) -> + @moveCursors (cursor) -> cursor.moveLeft(columnCount, moveToEndOfSelection: true) moveCursorLeft: -> deprecate("Use Editor::moveLeft() instead") @moveLeft() # Essential: Move every cursor right one column. - moveRight: -> - @moveCursors (cursor) -> cursor.moveRight(moveToEndOfSelection: true) + # + # * `columnCount` (optional) {Number} number of columns to move (default: 1) + moveRight: (columnCount) -> + @moveCursors (cursor) -> cursor.moveRight(columnCount, moveToEndOfSelection: true) moveCursorRight: -> deprecate("Use Editor::moveRight() instead") @moveRight() @@ -1978,7 +1982,7 @@ class Editor extends Model # Essential: Move the cursor of each selection one character upward while # preserving the selection's tail position. # - # * `rowCount` {Number} of rows to select up + # * `rowCount` (optional) {Number} number of rows to select (default: 1) # # This method may merge selections that end up intesecting. selectUp: (rowCount) -> @@ -1987,7 +1991,7 @@ class Editor extends Model # Essential: Move the cursor of each selection one character downward while # preserving the selection's tail position. # - # * `rowCount` {Number} of rows to select down + # * `rowCount` (optional) {Number} number of rows to select (default: 1) # # This method may merge selections that end up intesecting. selectDown: (rowCount) -> @@ -1996,16 +2000,20 @@ class Editor extends Model # Essential: Move the cursor of each selection one character leftward while # preserving the selection's tail position. # + # * `columnCount` (optional) {Number} number of columns to select (default: 1) + # # This method may merge selections that end up intesecting. - selectLeft: -> - @expandSelectionsBackward (selection) -> selection.selectLeft() + selectLeft: (columnCount) -> + @expandSelectionsBackward (selection) -> selection.selectLeft(columnCount) # Essential: Move the cursor of each selection one character rightward while # preserving the selection's tail position. # + # * `columnCount` (optional) {Number} number of columns to select (default: 1) + # # This method may merge selections that end up intesecting. - selectRight: -> - @expandSelectionsForward (selection) -> selection.selectRight() + selectRight: (columnCount) -> + @expandSelectionsForward (selection) -> selection.selectRight(columnCount) # Essential: Select from the top of the buffer to the end of the last selection # in the buffer. diff --git a/src/selection.coffee b/src/selection.coffee index a0db277c1..38198fef4 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -198,18 +198,26 @@ class Selection extends Model @modifySelection => @cursor.setBufferPosition(position) # Public: Selects the text one position right of the cursor. - selectRight: -> - @modifySelection => @cursor.moveRight() + # + # * `columnCount` (optional) {Number} number of columns to select (default: 1) + selectRight: (columnCount) -> + @modifySelection => @cursor.moveRight(columnCount) # Public: Selects the text one position left of the cursor. - selectLeft: -> - @modifySelection => @cursor.moveLeft() + # + # * `columnCount` (optional) {Number} number of columns to select (default: 1) + selectLeft: (columnCount) -> + @modifySelection => @cursor.moveLeft(columnCount) # Public: Selects all the text one position above the cursor. + # + # * `rowCount` (optional) {Number} number of rows to select (default: 1) selectUp: (rowCount) -> @modifySelection => @cursor.moveUp(rowCount) # Public: Selects all the text one position below the cursor. + # + # * `rowCount` (optional) {Number} number of rows to select (default: 1) selectDown: (rowCount) -> @modifySelection => @cursor.moveDown(rowCount)