From 410badcbf47816f87a3fb85b06219cc8189b2bae Mon Sep 17 00:00:00 2001 From: Jason Fields Date: Thu, 25 Jul 2019 10:21:12 -0400 Subject: [PATCH] Implement ampersand (&) action This action repeats the last substitution, equivalent to `:s`. Fixes #3808 --- src/actions/commands/actions.ts | 14 ++++++++++++++ test/cmd_line/substitute.test.ts | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/actions/commands/actions.ts b/src/actions/commands/actions.ts index 95b5a7f59f1..0601bb5aee4 100644 --- a/src/actions/commands/actions.ts +++ b/src/actions/commands/actions.ts @@ -2153,6 +2153,20 @@ class CommandDot extends BaseCommand { } } +@RegisterAction +class CommandRepeatSubstitution extends BaseCommand { + modes = [ModeName.Normal]; + keys = ['&']; + + public async exec(position: Position, vimState: VimState): Promise { + // Parsing the command from a string, while not ideal, is currently + // necessary to make this work with and without neovim integration + await commandLine.Run('s', vimState); + + return vimState; + } +} + abstract class CommandFold extends BaseCommand { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; commandName: string; diff --git a/test/cmd_line/substitute.test.ts b/test/cmd_line/substitute.test.ts index 97b520410d8..1f394859d58 100644 --- a/test/cmd_line/substitute.test.ts +++ b/test/cmd_line/substitute.test.ts @@ -333,6 +333,7 @@ suite('Basic substitute', () => { assertEqualLines(['bz']); }); }); + suite('Substitute should use various previous search/substitute states', () => { test('Substitute with previous search using *', async () => { await modeHandler.handleMultipleKeyEvents([ @@ -364,6 +365,7 @@ suite('Basic substitute', () => { assertEqualLines(['fighters', 'bar', 'fighters', 'bar']); }); + test('Substitute with previous search using #', async () => { await modeHandler.handleMultipleKeyEvents([ 'i', @@ -392,6 +394,7 @@ suite('Basic substitute', () => { assertEqualLines(['foo', 'fighters', 'foo', 'fighters']); }); + test('Substitute with previous search using /', async () => { await modeHandler.handleMultipleKeyEvents([ 'i', @@ -424,6 +427,7 @@ suite('Basic substitute', () => { assertEqualLines(['fighters', 'bar', 'fighters', 'bar']); }); + newTest({ title: 'Substitute with parameters should update search state', start: ['foo', 'bar', 'foo', 'bar|'], @@ -434,6 +438,7 @@ suite('Basic substitute', () => { 'rr', // and replace a with r end: ['foo', 'bite', 'foo', 'b|rr'], }); + newTest({ title: 'Substitute with empty replacement should delete previous substitution (all variants) and accepts flags', @@ -474,6 +479,7 @@ suite('Basic substitute', () => { '| is here', ], }); + newTest({ title: 'Substitute with no pattern should repeat previous substitution and not alter search state', @@ -486,12 +492,14 @@ suite('Basic substitute', () => { 'rp', // and replace l with p (confirming search state was unaltered) end: ['legend', 'zelda', 'legend', 'zelda', '|pink'], }); + newTest({ title: 'Substitute repeat previous should accept flags', start: ['|fooo'], keysPressed: ':s/o/un\n:s g\n', // repeated replacement accepts g flag, replacing all other occurrences end: ['|fununun'], }); + test('Substitute with empty search string should use last searched pattern', async () => { await modeHandler.handleMultipleKeyEvents([ 'i', @@ -528,5 +536,12 @@ suite('Basic substitute', () => { assertEqualLines(['foo', 'fighters', 'foo', 'fighters']); }); + + newTest({ + title: 'Ampersand (&) should repeat the last substitution', + start: ['|foo bar baz'], + keysPressed: ':s/ba/t\n' + '&', + end: ['|foo tr tz'], + }); }); });