diff --git a/README.md b/README.md index 5c30ca0330e..8e5dbcb06bc 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,15 @@ These settings are specific to VSCodeVim. #### `"vim.useSolidBlockCursor"` We have removed this option, due to it making VSCodeVim's performance suffer immensely. +#### `"vim.substituteGlobalFlag"` +* Similar to Vim's `gdefault` setting. +* `/g` flag in a substitute command replaces all occurrences in the line. + Without this argument, replacement occurs only for the first occurrence in each line. +* When `"vim.substituteGlobalFlag"` is `true`, the 'g' is default on. + This means that all matches in a line are substituted instead of one. + When a 'g' flag is given to a ":substitute" command, this will toggle the substitution + of all or one match. + #### `"vim.useCtrlKeys"` * Enable Vim ctrl keys overriding common VS Code operations (eg. copy, paste, find, etc). Enabling this setting will: * `ctrl+c`, `ctrl+[` => `` diff --git a/package.json b/package.json index 13298af5247..ef0be1872be 100644 --- a/package.json +++ b/package.json @@ -530,6 +530,11 @@ "type": "boolean", "description": "Get rid of that annoying message that shows up everytime you make a new file", "default": false + }, + "vim.substituteGlobalFlag": { + "type": "boolean", + "description": "Automatically apply the global flag, /g, to substitute commands. When set to true, use /g to mean only first match should be replaced.", + "default": "false" } } } @@ -566,4 +571,4 @@ "typescript": "^2.3.2", "vscode": "^1.0.5" } -} \ No newline at end of file +} diff --git a/src/cmd_line/commands/substitute.ts b/src/cmd_line/commands/substitute.ts index e62250eb85e..acd27a23ae4 100644 --- a/src/cmd_line/commands/substitute.ts +++ b/src/cmd_line/commands/substitute.ts @@ -5,6 +5,7 @@ import * as node from '../node'; import * as token from '../token'; import { ModeHandler } from '../../mode/modeHandler'; import { TextEditor } from '../../textEditor'; +import { Configuration } from '../../configuration/configuration'; export interface ISubstituteCommandArguments extends node.ICommandArgs { pattern: string; @@ -62,8 +63,16 @@ export class SubstituteCommand extends node.CommandBase { getRegex(args: ISubstituteCommandArguments, modeHandler: ModeHandler) { let jsRegexFlags = ''; - if (args.flags & SubstituteFlags.ReplaceAll) { - jsRegexFlags += 'g'; + if (Configuration.substituteGlobalFlag === true) { + // the gdefault flag is on, then /g if on by default and /g negates that + if (!(args.flags & SubstituteFlags.ReplaceAll)) { + jsRegexFlags += 'g'; + } + } else { + // the gdefault flag is off, then /g means replace all + if (args.flags & SubstituteFlags.ReplaceAll) { + jsRegexFlags += 'g'; + } } if (args.flags & SubstituteFlags.IgnoreCase) { diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index 3b42664156b..b83d5b3848a 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -303,6 +303,11 @@ class ConfigurationClass { neovimPath = 'nvim'; disableAnnoyingNeovimMessage = false; + + /** + * Automatically apply the /g flag to substitute commands. + */ + substituteGlobalFlag = false; } function overlapSetting(args: { diff --git a/src/neovim/nvimUtil.ts b/src/neovim/nvimUtil.ts index 5fc8a5bd1c3..dd9fb705565 100644 --- a/src/neovim/nvimUtil.ts +++ b/src/neovim/nvimUtil.ts @@ -28,6 +28,8 @@ export class Neovim { if (Configuration.expandtab) { await vscode.commands.executeCommand('editor.action.indentationToTabs'); } + + await nvim.setOption('gdefault', Configuration.substituteGlobalFlag === true); await buf.setLines(0, -1, true, TextEditor.getText().split('\n')); const [rangeStart, rangeEnd] = [ Position.EarlierOf(vimState.cursorPosition, vimState.cursorStartPosition), diff --git a/test/cmd_line/substitute.test.ts b/test/cmd_line/substitute.test.ts index ecf7b98a4e0..9153996ed7c 100644 --- a/test/cmd_line/substitute.test.ts +++ b/test/cmd_line/substitute.test.ts @@ -2,6 +2,7 @@ import { ModeHandler } from '../../src/mode/modeHandler'; import { setupWorkspace, cleanUpWorkspace, assertEqualLines } from './../testUtils'; import { runCmdLine } from '../../src/cmd_line/main'; import { getAndUpdateModeHandler } from '../../extension'; +import { Configuration } from '../../src/configuration/configuration'; suite('Basic substitute', () => { let modeHandler: ModeHandler; @@ -102,4 +103,107 @@ suite('Basic substitute', () => { assertEqualLines(['dbc', 'dbc', 'abc']); }); + + suite('Effects of substituteGlobalFlag=true', () => { + let originalGlobalFlag = false; + + setup(async () => { + originalGlobalFlag = Configuration.substituteGlobalFlag; + Configuration.substituteGlobalFlag = true; + }); + + teardown(async () => { + Configuration.substituteGlobalFlag = originalGlobalFlag; + }); + + test('Replace all matches in the line', async () => { + await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '']); + await runCmdLine('%s/a/d', modeHandler); + + assertEqualLines(['dbd']); + }); + + test('Replace with `g` flag inverts global flag', async () => { + await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '']); + await runCmdLine('%s/a/d/g', modeHandler); + + assertEqualLines(['dba']); + }); + + test('Replace multiple lines', async () => { + await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '', 'o', 'a', 'b']); + await runCmdLine('%s/a/d/', modeHandler); + + assertEqualLines(['dbd', 'db']); + }); + + test('Replace across specific lines', async () => { + await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '', 'o', 'a', 'b']); + await runCmdLine('1,1s/a/d/', modeHandler); + + assertEqualLines(['dbd', 'ab']); + }); + + test('Replace current line with no active selection', async () => { + await modeHandler.handleMultipleKeyEvents([ + 'i', + 'a', + 'b', + 'a', + '', + 'o', + 'a', + 'b', + '', + ]); + await runCmdLine('s/a/d/', modeHandler); + + assertEqualLines(['aba', 'db']); + }); + + test('Replace text in selection', async () => { + await modeHandler.handleMultipleKeyEvents([ + 'i', + 'a', + 'b', + 'a', + '', + 'o', + 'a', + 'b', + '', + '$', + 'v', + 'k', + '0', + ]); + await runCmdLine("'<,'>s/a/d/", modeHandler); + + assertEqualLines(['dbd', 'db']); + }); + + test('Substitute support marks', async () => { + await modeHandler.handleMultipleKeyEvents([ + 'i', + 'a', + 'b', + 'c', + '', + 'y', + 'y', + '2', + 'p', + 'g', + 'g', + 'm', + 'a', + 'j', + 'm', + 'b', + ]); + await runCmdLine("'a,'bs/a/d/", modeHandler); + + assertEqualLines(['dbc', 'dbc', 'abc']); + }); + }); });