diff options
author | Jonathan Slenders <jonathan@slenders.be> | 2018-06-05 21:07:09 +0200 |
---|---|---|
committer | Jonathan Slenders <jonathan@slenders.be> | 2018-06-05 22:07:12 +0200 |
commit | a54fc0bd7579b11e290ccd4d70037d68c92f7be3 (patch) | |
tree | e2a16f3f307f5100ae3133aaa92012b92b5a6612 | |
parent | c0a44da1898876340b5de262cbc4f78498d072cc (diff) |
Fixed off by one error in Vi line selection.
-rw-r--r-- | prompt_toolkit/key_binding/bindings/vi.py | 11 | ||||
-rw-r--r-- | tests/test_cli.py | 34 |
2 files changed, 44 insertions, 1 deletions
diff --git a/prompt_toolkit/key_binding/bindings/vi.py b/prompt_toolkit/key_binding/bindings/vi.py index 68084e24..22d0f018 100644 --- a/prompt_toolkit/key_binding/bindings/vi.py +++ b/prompt_toolkit/key_binding/bindings/vi.py @@ -84,6 +84,9 @@ class TextObject(object): Return a (start, end) tuple with start <= end that indicates the range operators should operate on. `buffer` is used to get start and end of line positions. + + This should return something that can be used in a slice, so the `end` + position is *not* included. """ start, end = self.sorted() doc = document @@ -126,7 +129,13 @@ class TextObject(object): from_ += buffer.cursor_position to += buffer.cursor_position - to -= 1 # SelectionState does not include the end position, `operator_range` does. + + # For Vi mode, the SelectionState does include the upper position, + # while `self.operator_range` does not. So, go one to the left, unless + # we're in the line mode, then we don't want to risk going to the + # previous line, and missing one line in the selection. + if self.type != TextObjectType.LINEWISE: + to -= 1 document = Document(buffer.text, to, SelectionState( original_cursor_position=from_, type=self.selection_type)) diff --git a/tests/test_cli.py b/tests/test_cli.py index c5320b4a..8befba95 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -773,6 +773,40 @@ def test_vi_visual_line_copy(): assert (result.text == '-line1\n-line2\n-line3\n-line4\n-line2\n-line3\n-line2\n-line3\n-line5\n-line6') + +def test_vi_visual_empty_line(): + """ + Test edge case with an empty line in Visual-line mode. + """ + feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, + multiline=True) + + # 1. Delete first two lines. + operations = ( + # Three lines of text. The middle one is empty. + 'hello\r\rworld' + # Go to the start. + '\x1bgg' + # Visual line and move down. + 'Vj' + # Delete. + 'd\r') + result, cli = feed(operations) + assert result.text == 'world' + + # 1. Delete middle line. + operations = ( + # Three lines of text. The middle one is empty. + 'hello\r\rworld' + # Go to middle line. + '\x1bggj' + # Delete line + 'Vd\r') + + result, cli = feed(operations) + assert result.text == 'hello\nworld' + + def test_vi_character_delete_after_cursor(): " Test 'x' keypress. " feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, |