Skip to content

Commit

Permalink
fix: p in visual-mode should save last selection
Browse files Browse the repository at this point in the history
According to Vim's help (:help gv):

> After using "p" or "P" in Visual mode the text
> that was put will be selected.
  • Loading branch information
tyru committed May 1, 2018
1 parent 71f41af commit 3946c08
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
79 changes: 79 additions & 0 deletions test/mode/modeVisual.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(['<Esc>']);
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(['<Esc>']);
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(['<Esc>']);
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);
});
});
});
79 changes: 79 additions & 0 deletions test/mode/modeVisualLine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(['<Esc>']);
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(['<Esc>']);
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(['<Esc>']);
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);
});
});
});

0 comments on commit 3946c08

Please sign in to comment.