From a36577aef5e6a0b8cd0175fe041f9f6bc2094904 Mon Sep 17 00:00:00 2001 From: Jacek Kopecky Date: Mon, 6 Jul 2015 02:16:38 +0100 Subject: [PATCH 1/3] fix #737: redoing visual mode operations with `.` --- lib/motions/general-motions.coffee | 40 +++++++++- spec/motions-spec.coffee | 20 +++++ spec/operators-spec.coffee | 123 +++++++++++++++++++++++++++++ spec/vim-state-spec.coffee | 8 +- 4 files changed, 188 insertions(+), 3 deletions(-) diff --git a/lib/motions/general-motions.coffee b/lib/motions/general-motions.coffee index 35fe4e5f..f80d451e 100644 --- a/lib/motions/general-motions.coffee +++ b/lib/motions/general-motions.coffee @@ -110,15 +110,51 @@ class Motion class CurrentSelection extends Motion constructor: (@editor, @vimState) -> super(@editor, @vimState) - @selection = @editor.getSelectedBufferRanges() + @lastSelection = @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: -> + {start, end} = @lastSelection + lineCount = end.row - start.row + for selection in @editor.getSelections() + cursor = selection.cursor.getBufferPosition() + selection.setBufferRange + start: + row: cursor.row + column: 0 + end: + row: cursor.row + end.row - start.row + column: 0 + + selectCharacters: -> + {start, end} = @lastSelection + if @lastSelection.isSingleLine() + count = end.column - start.column + wrap = settings.wrapLeftRightMotion() + for selection in @editor.getSelections() + _.times count, -> + selection.selectRight() if wrap or not selection.cursor.isAtEndOfLine() + else + for selection in @editor.getSelections() + cursor = selection.cursor.getBufferPosition() + selection.selectToBufferPosition + row: cursor.row + end.row - start.row + column: end.column + # 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..043bdcfd 100644 --- a/spec/operators-spec.coffee +++ b/spec/operators-spec.coffee @@ -731,6 +731,129 @@ 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 "can affect newlines when repeated with . near the end of the line with motion wrapping 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 '.' + expect(editor.getText()).toBe "12ab89\nabcabhijklmnopq\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") From 321556157f64a41eb32ea97810d62edd629ea58a Mon Sep 17 00:00:00 2001 From: Jacek Kopecky Date: Sat, 1 Aug 2015 22:10:01 +0100 Subject: [PATCH 2/3] use more Range/Point methods suggested by @maxbrunsfeld , thanks --- lib/motions/general-motions.coffee | 40 +++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/lib/motions/general-motions.coffee b/lib/motions/general-motions.coffee index f80d451e..a358c46d 100644 --- a/lib/motions/general-motions.coffee +++ b/lib/motions/general-motions.coffee @@ -110,7 +110,7 @@ class Motion class CurrentSelection extends Motion constructor: (@editor, @vimState) -> super(@editor, @vimState) - @lastSelection = @editor.getSelectedBufferRange() + @lastSelectionRange = @editor.getSelectedBufferRange() @wasLinewise = @isLinewise() execute: (count=1) -> @@ -128,32 +128,26 @@ class CurrentSelection extends Motion _.times(count, -> true) selectLines: -> - {start, end} = @lastSelection - lineCount = end.row - start.row + lastSelectionExtent = @lastSelectionRange.getExtent() for selection in @editor.getSelections() cursor = selection.cursor.getBufferPosition() - selection.setBufferRange - start: - row: cursor.row - column: 0 - end: - row: cursor.row + end.row - start.row - column: 0 + selection.setBufferRange [[cursor.row, 0], [cursor.row + lastSelectionExtent.row, 0]] + return selectCharacters: -> - {start, end} = @lastSelection - if @lastSelection.isSingleLine() - count = end.column - start.column - wrap = settings.wrapLeftRightMotion() - for selection in @editor.getSelections() - _.times count, -> - selection.selectRight() if wrap or not selection.cursor.isAtEndOfLine() - else - for selection in @editor.getSelections() - cursor = selection.cursor.getBufferPosition() - selection.selectToBufferPosition - row: cursor.row + end.row - start.row - column: end.column + lastSelectionExtent = @lastSelectionRange.getExtent() + wrap = settings.wrapLeftRightMotion() + for selection in @editor.getSelections() + {start} = selection.getBufferRange() + newEnd = start.traverse(lastSelectionExtent) + selection.setBufferRange([start, newEnd]) + + if wrap + columnDifference = newEnd.column - selection.getBufferRange().end.column + if columnDifference > 0 + _.times columnDifference, -> selection.selectRight() + + return # Public: Generic class for motions that require extra input class MotionWithInput extends Motion From 886444f30708a03c315df3e9ecc5fbcef7b0cea9 Mon Sep 17 00:00:00 2001 From: Jacek Kopecky Date: Wed, 12 Aug 2015 22:21:12 +0100 Subject: [PATCH 3/3] visual repeat doesn't wrap --- lib/motions/general-motions.coffee | 7 ------- spec/operators-spec.coffee | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/motions/general-motions.coffee b/lib/motions/general-motions.coffee index a358c46d..d5b2f9a7 100644 --- a/lib/motions/general-motions.coffee +++ b/lib/motions/general-motions.coffee @@ -136,17 +136,10 @@ class CurrentSelection extends Motion selectCharacters: -> lastSelectionExtent = @lastSelectionRange.getExtent() - wrap = settings.wrapLeftRightMotion() for selection in @editor.getSelections() {start} = selection.getBufferRange() newEnd = start.traverse(lastSelectionExtent) selection.setBufferRange([start, newEnd]) - - if wrap - columnDifference = newEnd.column - selection.getBufferRange().end.column - if columnDifference > 0 - _.times columnDifference, -> selection.selectRight() - return # Public: Generic class for motions that require extra input diff --git a/spec/operators-spec.coffee b/spec/operators-spec.coffee index 043bdcfd..9240b08a 100644 --- a/spec/operators-spec.coffee +++ b/spec/operators-spec.coffee @@ -764,7 +764,7 @@ describe "Operators", -> keydown '.' expect(editor.getText()).toBe "12ab89\nabcab\nfghijklmnopq\nuvwxyz" - it "can affect newlines when repeated with . near the end of the line with motion wrapping enabled", -> + 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' @@ -777,7 +777,8 @@ describe "Operators", -> editor.setCursorScreenPosition [1, 3] keydown '.' - expect(editor.getText()).toBe "12ab89\nabcabhijklmnopq\nuvwxyz" + # 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 .", ->