From 3946c082157fb1c7587ddc55643b6b1de8471db0 Mon Sep 17 00:00:00 2001 From: tyru Date: Tue, 1 May 2018 18:51:06 +0900 Subject: [PATCH] fix: p in visual-mode should save last selection According to Vim's help (:help gv): > After using "p" or "P" in Visual mode the text > that was put will be selected. --- src/actions/commands/actions.ts | 20 ++++++++ test/mode/modeVisual.test.ts | 79 ++++++++++++++++++++++++++++++++ test/mode/modeVisualLine.test.ts | 79 ++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) diff --git a/src/actions/commands/actions.ts b/src/actions/commands/actions.ts index 62a5353efb4..29f886798ac 100644 --- a/src/actions/commands/actions.ts +++ b/src/actions/commands/actions.ts @@ -1325,6 +1325,26 @@ export class PutCommand extends BaseCommand { } } + // After using "p" or "P" in Visual mode the text that was put will be + // selected (from Vim's ":help gv"). + if ( + vimState.currentMode === ModeName.Visual || + vimState.currentMode === ModeName.VisualLine || + vimState.currentMode === ModeName.VisualBlock + ) { + vimState.lastVisualMode = vimState.currentMode; + vimState.lastVisualSelectionStart = whereToAddText; + let textToEnd = textToAdd; + if ( + vimState.currentMode === ModeName.VisualLine && + textToAdd[textToAdd.length - 1] === '\n' + ) { + // don't go next line + textToEnd = textToAdd.substring(0, textToAdd.length - 1); + } + vimState.lastVisualSelectionEnd = whereToAddText.advancePositionByText(textToEnd); + } + // More vim weirdness: If the thing you're pasting has a newline, the cursor // stays in the same place. Otherwise, it moves to the end of what you pasted. diff --git a/test/mode/modeVisual.test.ts b/test/mode/modeVisual.test.ts index c6187e01260..3de987a6b72 100644 --- a/test/mode/modeVisual.test.ts +++ b/test/mode/modeVisual.test.ts @@ -939,4 +939,83 @@ suite('Mode Visual', () => { assertEqual(modeHandler.currentMode.name, ModeName.Normal); }); }); + + suite('replace text in characterwise visual-mode with characterwise register content', () => { + test('gv selects the last pasted text (which is shorter than original)', async () => { + await modeHandler.handleMultipleKeyEvents( + 'ireplace this\nwith me\nor with me longer than the target'.split('') + ); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents( + '2ggv$hy'.split('') // yank the second line + ); + await modeHandler.handleMultipleKeyEvents( + 'ggv$hp'.split('') // replace the first line + ); + await modeHandler.handleMultipleKeyEvents(['g', 'v']); + + assertEqual(modeHandler.currentMode.name, ModeName.Visual); + assertEqualLines(['with me', 'with me', 'or with me longer than the target']); + + const selection = TextEditor.getSelection(); + + // ensuring selecting 'with me' at the first line + assertEqual(selection.start.character, 0); + assertEqual(selection.start.line, 0); + assertEqual(selection.end.character, 'with me'.length); + assertEqual(selection.end.line, 0); + }); + + test('gv selects the last pasted text (which is longer than original)', async () => { + await modeHandler.handleMultipleKeyEvents( + 'ireplace this\nwith me\nor with me longer than the target'.split('') + ); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents( + 'v0y'.split('') // yank the last line + ); + await modeHandler.handleMultipleKeyEvents( + 'ggv$hp'.split('') // replace the first line + ); + await modeHandler.handleMultipleKeyEvents(['g', 'v']); + + assertEqual(modeHandler.currentMode.name, ModeName.Visual); + assertEqualLines([ + 'or with me longer than the target', + 'with me', + 'or with me longer than the target', + ]); + + const selection = TextEditor.getSelection(); + + // ensuring selecting 'or with me longer than the target' at the first line + assertEqual(selection.start.character, 0); + assertEqual(selection.start.line, 0); + assertEqual(selection.end.character, 'or with me longer than the target'.length); + assertEqual(selection.end.line, 0); + }); + + test('gv selects the last pasted text (multiline)', async () => { + await modeHandler.handleMultipleKeyEvents('ireplace this\nfoo\nbar'.split('')); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents( + '2ggvjey'.split('') // yank 'foo\nbar' + ); + await modeHandler.handleMultipleKeyEvents( + 'ggvep'.split('') // replace 'replace' + ); + await modeHandler.handleMultipleKeyEvents(['g', 'v']); + + assertEqual(modeHandler.currentMode.name, ModeName.Visual); + assertEqualLines(['foo', 'bar this', 'foo', 'bar']); + + const selection = TextEditor.getSelection(); + + // ensuring selecting 'foo\nbar' + assertEqual(selection.start.character, 0); + assertEqual(selection.start.line, 0); + assertEqual(selection.end.character, 3); + assertEqual(selection.end.line, 1); + }); + }); }); diff --git a/test/mode/modeVisualLine.test.ts b/test/mode/modeVisualLine.test.ts index 93aa8311e8c..662e3cf7f68 100644 --- a/test/mode/modeVisualLine.test.ts +++ b/test/mode/modeVisualLine.test.ts @@ -324,4 +324,83 @@ suite('Mode Visual Line', () => { end: ['|foo', 'bar', 'fun', 'baz'], }); }); + + suite('replace text in linewise visual-mode with linewise register content', () => { + test('gv selects the last pasted text (which is shorter than original)', async () => { + await modeHandler.handleMultipleKeyEvents( + 'ireplace this\nwith me\nor with me longer than the target'.split('') + ); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents( + '2ggyy'.split('') // yank the second line + ); + await modeHandler.handleMultipleKeyEvents( + 'ggVp'.split('') // replace the first line + ); + await modeHandler.handleMultipleKeyEvents(['g', 'v']); + + assertEqual(modeHandler.currentMode.name, ModeName.VisualLine); + assertEqualLines(['with me', 'with me', 'or with me longer than the target']); + + const selection = TextEditor.getSelection(); + + // ensuring selecting 'with me' at the first line + assertEqual(selection.start.character, 0); + assertEqual(selection.start.line, 0); + assertEqual(selection.end.character, 'with me'.length); + assertEqual(selection.end.line, 0); + }); + + test('gv selects the last pasted text (which is longer than original)', async () => { + await modeHandler.handleMultipleKeyEvents( + 'ireplace this\nwith me\nor with me longer than the target'.split('') + ); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents( + 'yy'.split('') // yank the last line + ); + await modeHandler.handleMultipleKeyEvents( + 'ggVp'.split('') // replace the first line + ); + await modeHandler.handleMultipleKeyEvents(['g', 'v']); + + assertEqual(modeHandler.currentMode.name, ModeName.VisualLine); + assertEqualLines([ + 'or with me longer than the target', + 'with me', + 'or with me longer than the target', + ]); + + const selection = TextEditor.getSelection(); + + // ensuring selecting 'or with me longer than the target' at the first line + assertEqual(selection.start.character, 0); + assertEqual(selection.start.line, 0); + assertEqual(selection.end.character, 'or with me longer than the target'.length); + assertEqual(selection.end.line, 0); + }); + + test('gv selects the last pasted text (multiline)', async () => { + await modeHandler.handleMultipleKeyEvents('ireplace this\nfoo\nbar'.split('')); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents( + 'Vky'.split('') // yank 'foo\nbar\n' + ); + await modeHandler.handleMultipleKeyEvents( + 'ggVp'.split('') // replace the first line + ); + await modeHandler.handleMultipleKeyEvents(['g', 'v']); + + assertEqual(modeHandler.currentMode.name, ModeName.VisualLine); + assertEqualLines(['foo', 'bar', 'foo', 'bar']); + + const selection = TextEditor.getSelection(); + + // ensuring selecting 'foo\nbar\n' + assertEqual(selection.start.character, 0); + assertEqual(selection.start.line, 0); + assertEqual(selection.end.character, 3); + assertEqual(selection.end.line, 1); + }); + }); });