diff --git a/lib/motions/general-motions.coffee b/lib/motions/general-motions.coffee index 35fe4e5f..d5b2f9a7 100644 --- a/lib/motions/general-motions.coffee +++ b/lib/motions/general-motions.coffee @@ -110,15 +110,38 @@ class Motion class CurrentSelection extends Motion constructor: (@editor, @vimState) -> super(@editor, @vimState) - @selection = @editor.getSelectedBufferRanges() + @lastSelectionRange = @editor.getSelectedBufferRange() + @wasLinewise = @isLinewise() execute: (count=1) -> _.times(count, -> true) select: (count=1) -> - @editor.setSelectedBufferRanges(@selection) + # in visual mode, the current selections are already there + # if we're not in visual mode, we are repeating some operation and need to re-do the selections + unless @vimState.mode is 'visual' + if @wasLinewise + @selectLines() + else + @selectCharacters() + _.times(count, -> true) + selectLines: -> + lastSelectionExtent = @lastSelectionRange.getExtent() + for selection in @editor.getSelections() + cursor = selection.cursor.getBufferPosition() + selection.setBufferRange [[cursor.row, 0], [cursor.row + lastSelectionExtent.row, 0]] + return + + selectCharacters: -> + lastSelectionExtent = @lastSelectionRange.getExtent() + for selection in @editor.getSelections() + {start} = selection.getBufferRange() + newEnd = start.traverse(lastSelectionExtent) + selection.setBufferRange([start, newEnd]) + return + # Public: Generic class for motions that require extra input class MotionWithInput extends Motion constructor: (@editor, @vimState) -> diff --git a/spec/motions-spec.coffee b/spec/motions-spec.coffee index e7fe3377..eefd1846 100644 --- a/spec/motions-spec.coffee +++ b/spec/motions-spec.coffee @@ -1599,6 +1599,24 @@ describe "Motions", -> normalModeInputKeydown('b') expect(editor.getText()).toBe 'bcabcabcabc\n' + describe 'the v keybinding', -> + beforeEach -> + editor.setText("01\n002\n0003\n00004\n000005\n") + editor.setCursorScreenPosition([1, 1]) + + it "selects down a line", -> + keydown('v') + keydown('j') + keydown('j') + expect(editor.getSelectedText()).toBe "02\n0003\n00" + expect(editor.getSelectedBufferRange().isSingleLine()).toBeFalsy() + + it "selects right", -> + keydown('v') + keydown('l') + expect(editor.getSelectedText()).toBe "02" + expect(editor.getSelectedBufferRange().isSingleLine()).toBeTruthy() + describe 'the V keybinding', -> beforeEach -> editor.setText("01\n002\n0003\n00004\n000005\n") @@ -1606,9 +1624,11 @@ describe "Motions", -> it "selects down a line", -> keydown('V', shift: true) + expect(editor.getSelectedBufferRange().isSingleLine()).toBeFalsy() keydown('j') keydown('j') expect(editor.getSelectedText()).toBe "002\n0003\n00004\n" + expect(editor.getSelectedBufferRange().isSingleLine()).toBeFalsy() it "selects up a line", -> keydown('V', shift: true) diff --git a/spec/operators-spec.coffee b/spec/operators-spec.coffee index ac8b2569..9240b08a 100644 --- a/spec/operators-spec.coffee +++ b/spec/operators-spec.coffee @@ -731,6 +731,130 @@ describe "Operators", -> keydown('escape') expect(editor.getText()).toBe("12345\n\nABCDE") + describe "in visual mode", -> + beforeEach -> + editor.setText "123456789\nabcde\nfghijklmnopq\nuvwxyz" + editor.setCursorScreenPosition [1, 1] + + describe "with characterwise selection on a single line", -> + it "repeats with .", -> + keydown 'v' + keydown '2' + keydown 'l' + keydown 'c' + editor.insertText "ab" + keydown 'escape' + expect(editor.getText()).toBe "123456789\naabe\nfghijklmnopq\nuvwxyz" + + editor.setCursorScreenPosition [0, 1] + keydown '.' + expect(editor.getText()).toBe "1ab56789\naabe\nfghijklmnopq\nuvwxyz" + + it "repeats shortened with . near the end of the line", -> + editor.setCursorScreenPosition [0, 2] + keydown 'v' + keydown '4' + keydown 'l' + keydown 'c' + editor.insertText "ab" + keydown 'escape' + expect(editor.getText()).toBe "12ab89\nabcde\nfghijklmnopq\nuvwxyz" + + editor.setCursorScreenPosition [1, 3] + keydown '.' + expect(editor.getText()).toBe "12ab89\nabcab\nfghijklmnopq\nuvwxyz" + + it "repeats shortened with . near the end of the line regardless of whether motion wrapping is enabled", -> + atom.config.set('vim-mode.wrapLeftRightMotion', true) + editor.setCursorScreenPosition [0, 2] + keydown 'v' + keydown '4' + keydown 'l' + keydown 'c' + editor.insertText "ab" + keydown 'escape' + expect(editor.getText()).toBe "12ab89\nabcde\nfghijklmnopq\nuvwxyz" + + editor.setCursorScreenPosition [1, 3] + keydown '.' + # this differs from VIM, which would eat the \n before fghij... + expect(editor.getText()).toBe "12ab89\nabcab\nfghijklmnopq\nuvwxyz" + + describe "is repeatable with characterwise selection over multiple lines", -> + it "repeats with .", -> + keydown 'v' + keydown 'j' + keydown '3' + keydown 'l' + keydown 'c' + editor.insertText "x" + keydown 'escape' + expect(editor.getText()).toBe "123456789\naxklmnopq\nuvwxyz" + + editor.setCursorScreenPosition [0, 1] + keydown '.' + expect(editor.getText()).toBe "1xnopq\nuvwxyz" + + it "repeats shortened with . near the end of the line", -> + # this behaviour is unlike VIM, see #737 + keydown 'v' + keydown 'j' + keydown '6' + keydown 'l' + keydown 'c' + editor.insertText "x" + keydown 'escape' + expect(editor.getText()).toBe "123456789\naxnopq\nuvwxyz" + + editor.setCursorScreenPosition [0, 1] + keydown '.' + expect(editor.getText()).toBe "1x\nuvwxyz" + + describe "is repeatable with linewise selection", -> + describe "with one line selected", -> + it "repeats with .", -> + keydown 'V', shift: true + keydown 'c' + editor.insertText "x" + keydown 'escape' + expect(editor.getText()).toBe "123456789\nx\nfghijklmnopq\nuvwxyz" + + editor.setCursorScreenPosition [0, 7] + keydown '.' + expect(editor.getText()).toBe "x\nx\nfghijklmnopq\nuvwxyz" + + editor.setCursorScreenPosition [2, 0] + keydown '.' + expect(editor.getText()).toBe "x\nx\nx\nuvwxyz" + + describe "with multiple lines selected", -> + it "repeats with .", -> + keydown 'V', shift: true + keydown 'j' + keydown 'c' + editor.insertText "x" + keydown 'escape' + expect(editor.getText()).toBe "123456789\nx\nuvwxyz" + + editor.setCursorScreenPosition [0, 7] + keydown '.' + expect(editor.getText()).toBe "x\nuvwxyz" + + it "repeats shortened with . near the end of the file", -> + keydown 'V', shift: true + keydown 'j' + keydown 'c' + editor.insertText "x" + keydown 'escape' + expect(editor.getText()).toBe "123456789\nx\nuvwxyz" + + editor.setCursorScreenPosition [1, 7] + keydown '.' + expect(editor.getText()).toBe "123456789\nx\n" + + xdescribe "is repeatable with block selection", -> + # there is no block selection yet + describe "the C keybinding", -> beforeEach -> editor.getBuffer().setText("012\n") diff --git a/spec/vim-state-spec.coffee b/spec/vim-state-spec.coffee index f9120f27..c4b79c8d 100644 --- a/spec/vim-state-spec.coffee +++ b/spec/vim-state-spec.coffee @@ -86,13 +86,19 @@ describe "VimState", -> expect(editor.getCursors().length).toBe 1 describe "the v keybinding", -> - beforeEach -> keydown('v') + beforeEach -> + editor.setText("012345\nabcdef") + editor.setCursorScreenPosition([0, 0]) + keydown('v') it "puts the editor into visual characterwise mode", -> expect(editorElement.classList.contains('visual-mode')).toBe(true) expect(vimState.submode).toEqual 'characterwise' expect(editorElement.classList.contains('normal-mode')).toBe(false) + it "selects the current character", -> + expect(editor.getLastSelection().getText()).toEqual '0' + describe "the V keybinding", -> beforeEach -> editor.setText("012345\nabcdef")