53

I'd like to optimize my "find and replace" workflow in Vim. It's something I do often, as I'm sure most of you do too. Usually something along the lines of -- copy a block and change the name of a variable in a few places. I know, I know, that probably triggers your "why are you copying and pasting code" reflex, but let's not go down that road... There are plenty of valid use cases :)

I'm well aware of the search and replace commands: :s or :%s but I don't like them. It forces me to type out both the full variable name I'm searching for and what I'm changing it to. Maybe there is a better way fix the the amount of typing with :%s? I often use long descriptive variable names, so that is really a deal breaker for me. I also don't like how typing out a variable name from scratch is typo prone and can consume time and brainpower hunting down typos. I much prefer typing it once, and then copying and pasting to just avoid this entirely if possible.

My current workflow uses some combination of movement/yank/select/search/put to move around the file and replace one by one. It is not great but has the benefit of avoiding typing out full variable names. I might just need to type the first few letters with / or use another movement command (e.g. fx) depending on what's around and then hit ve to select the whole word. I also don't mind that I have to repeat for every instance. I never do a full find replace without confirming each change. But it would be much preferable if I could repeat the replacement action with a single keystroke (which I can't do with this method). each replacement is usually something like n then ve then p (or even worse "0p)

Is there a faster way?

  • 1
    . repeats the last "command block" (not sure of the exact term. for example: move + action + text. for exemple: cwtoto<Esc> (change from cursor to end of the word with "toto"), c/foo<enter>bar<Esc> (change from cursor to just before "foo", and replace with "bar"). you can then move (with cursor, hjkl, or a number+hjkl (does it n times), G (go to end of the file), /something (go just before "something"), etc) and then press . to redo the same "command block" – Olivier Dulac Sep 26 '17 at 15:19
  • I do use this quite a bit! However, it doesn't work if you want to yank a word and then replace another word with it in multiple places. That's the main use case I have a problem with, and goes something like n ve "0p. Which unfortunately can't be replicated with just a . – Joel Sep 27 '17 at 15:51
  • 5
    n ce ctrl-r 0 <esc> n.n.n.n.n.n. – Rich Sep 27 '17 at 16:31
  • @Joel you can also record a macro (qa then everything you want, then q : creates macro a), and then use : @a to replay it (and, as usual, 134@a to do it 134 times). the macro can ve as elaborate as needed (exemple: find something, move to the proper place, then change word into that, then move to proper place to be able to get called again in a better position) – Olivier Dulac Sep 28 '17 at 6:30
  • 1
    I've learned more useful VIM tricks from this question than from any other question on the site. Thank you! – dotancohen Oct 4 '17 at 10:40

11 Answers 11

81

I actually have a pretty similar workflow to yours, (copying and pasting blocks that are similar, then using :s to change variable names) especially when I'm writing lots of lines that are similar except for which variable they use. But there are a couple things I do that you might find useful.

General vim things

  1. The first thing that will help you is realizing that :s//foo/g will fill in whatever search term you used last and replace that with foo. That alone can save a lot of time, but when you combine it with the asterisk command (search for the word under the current cursor) it saves tons of time. For example, to change all foo to spam, instead of doing

    :%s/foo/spam/g
    

    Which requires you to type out both foo and spam (which may be long variables), you can simply navigate to foo, and do:

    *:%s//spam/g
    

    This way, you can quickly get the name of the variable you're changing by searching for it, then you won't need to type it out. This will also work nicely with your current workflow of using n a lot. It's also helpful to have hlsearch on, because then you can visually tell where the variables you're replacing are.

    (side note: The asterisk command will add word boundaries, that is \< and \> around the word under the cursor. If you don't want this, use g* instead.)

  2. it would be much preferable if I could repeat the replacement action with a single keystroke

    You can use @: to redo the last substitute command. That is nice if you want to do an :s command on lots of lines, but not all of them so :%s will not work. Another option is the confirmation flag, i.e. :%s///gc. This will allow you to type y or n on each occurrence to decide if you want to replace it or not.

    As Desty pointed out, you can also do & to re-run the last substitute command, but note that this will not keep your flags (such as global or confirm). Whether that is good or bad depends on your personal opinion. If you would like to use this but have it keep your flags also, you could do

    nnoremap & :&&<cr>
    
  3. If you want to change every occurrence in a block, but not every match in the file, you could always use a visual selection of the block, which will fill in

    :'<,'>
    

    to your substitute, which restricts it to the lines you have selected. When combined with the asterisk approach, I find this extremely useful. Additionally, if you are making multiple substitute commands on the same block of lines, you can use gv to reselect the same lines to do another command. (whether it's a :substitute or a normal command)

    If you do another substitute command, you do not need to reselect the same lines, because :'<,'> will still operate on the same lines. However, it is more convenient to type

    gv:s
    

    rather than

    :'<,'>s
    

Plugins/Mappings I like

  1. I have the following mapping in my .vimrc which extends the asterisk approach so that I only have to type the new desired name:

    "(R)eplace all
    nnoremap <leader>r yiw:%s/\<<C-r>"\>//g<left><left>
    

    To use this, just put your cursor on the word you want to change, and type <leader>rNewVarName<cr> and you're done. Note that this will replace all matches without giving you an option to confirm them, so you might not like it as much. Of course, you could just change it to

    nnoremap <leader>r yiw:%s/\<<C-r>"\>//gc<left><left><left>
    

    To allow for the confirm option.

    edit: Following both Mass's answer and rcorre's comment, you could also write this as

    nnoremap <leader>r :%s/\<<C-r><C-w>\>//g<left><left>
    

    Which has the advantage of keeping your unnamed register the same.

  2. If you're renaming groups of similar variables at the same time, for example, changing

    fooSuffix
    PrefixFoo
    foo1
    SHOUTINGFOO
    

    to

    barSuffix
    PrefixBar
    bar1
    SHOUTINGBAR
    

    it can be a pain to substitute all of them individually. This is where tpope/vim-abolish comes in handy. You can replace all of these in one command with:

    :%S/foo/bar/g
    

    and it takes care of the capitalization for you.

These should help you a lot, but let me know if there's anything about these approaches you don't like or feel is lacking. I'm happy to talk about more approaches. :)


Recommended Reading:

  • Oh I just learned several things here. Nice! An alternate approach you could take is <leader>rc (or cr) to add that /c at the end. Also, I have set gdefault set, because I almost never ever want to replace one single result, so it would just be yiw:%s/\<<C-r>"\>//<left><left> – Wayne Werner Sep 26 '17 at 12:11
  • 2
    What a fantastic answer! – Scott Lundberg Sep 26 '17 at 15:16
  • 2
    If you use ctrl+r-ctrl+w in the mapping (as suggested by @Mass) it should work the same but won't mess with your yank register (which may or may not be desirable) – rcorre Sep 26 '17 at 17:23
  • 1
    One thing to remember when working with blocks of text which you highlight (before cutting/yanking etc) is that you can enter gv in normal mode to re-highlight the last highlighted text. – user859 Sep 27 '17 at 12:13
  • 1
    I have written github.com/hauleth/sad.vim plugin that works similar to method 2. hovewer allows you to use . to perform further substitutions. – Hauleth Sep 29 '17 at 21:37
20

The c_CTRL-R family of maps can improve your workflow quite a bit:

  1. You need not type variable names when using :s/, just use CTRL-R CTRL-W to insert the word under the cursor into the command line (caveat: this does not escape special characters, like * does).

  2. After making a small change using ce, you can search for the old word using / CTRL-R -. Then use . to repeat the change.

  3. If you need to make changes to the search pattern CTRL-R / will place the existing search into the command line.

  4. Finally, you can put the contents of any register using CTRL-R {register}

  • Good point! I always forget about Ctrl R. I'll see if I can work that in too. – Joel Sep 25 '17 at 23:54
  • 2
    Worth specifically mentioning Ctrl-R /? I use this quite frequently in :s commands if I want to use a search term that is almost but not quite exactly the same as the previous one. – Rich Sep 27 '17 at 15:32
12

There are a couple more methods that (to my surprise!) haven't been mentioned yet.

Using the gn Command

gn works like the n command, except that in addition to jumping to the match, it enters visual mode, with the entire match selected.

So to change a word (or anything you can match with a regular expression!) first search for it1, and then press cgn followed by the text you want to replace the match with and Esc to return to normal mode.

(You mention that sometimes the replacement text you want to use is stored in the "0 yank register — note that you can quickly enter this without having to retype it by pressing Ctrl+R0 in insert mode.)

You can then jump to the next match and repeat the change with a single . keystroke!

If there are any places where you don't want to make the replacement, simply press un to undo the change and jump to the next match.

1: Speedily, by using the other suggestions on this page such as *, Ctrl+RCtrl+W, etc. See also Peter Rincker's comment for more suggestions about how to initially populate your search.

Using Select Mode

(I learned this from romainl's post on the vim subreddit. I don't know whether he invented it himself or was just passing it on.)

Select mode is a bit like visual mode, except if you start typing the selected text is immediately replaced with what you type (like how selection works in most other editors).

You can thus use this trio of mappings to do a very quick find/replace, which you can change mid file, if necessary:

nnoremap § *``gn<C-g>
inoremap § <C-o>gn<C-g>
snoremap <expr> . @.

Normal mode mapping: Enters select mode with the word nearest the cursor selected.

You can then type your replacement for the selected word and (without pressing Esc) use the:

Insert mode mapping: Jumps to the next match and enters selection mode.

You can then either type a new replacement or press . to use the:

Select mode mapping: Re-enters the last-inserted text — essentially, repeats the last insertion.

  • I did mention the gn method at the end of my answer vi.stackexchange.com/a/13696/488 yesterday for what its worth! Your answer has much nicer formatting though. – TankorSmash Sep 27 '17 at 16:56
  • 1
    @TankorSmash So you did! Note sure how I missed that. Note that the actual method I'm using is different from yours, though (I only invoke gn once), and it's the specific method, (rather than just the gn command) that I was surprised hadn't been mentioned yet. – Rich Sep 27 '17 at 18:04
  • I would recommend having some kind of "visual star" plugin to use with gn. Gives you more searching options. Poor man's visual star mapping: xnoremap * :<c-u>let @/=@"<cr>gvy:let [@/,@"]=[@",@/]<cr>/\V<c-r>=substitute(escape(@/,'/\'),'\n','\\n','g')<cr><cr>. Related Vimcasts episode: Operating on search matches using gn – Peter Rincker Sep 27 '17 at 19:48
  • 1
    I have created plugin sad.vim that help with such flow. – Hauleth Sep 28 '17 at 9:24
7

The plugin https://github.com/terryma/vim-multiple-cursors takes care of this problem for the most part.

My workflow is usually as follows:

  1. Move the cursor to the word to replace.
  2. Hold ctrl-n until everything is selected.
  3. Press c and start typing the replacement.

The convenient thing here is that you can also do more complicated operations than just search-replace, although I find that if you take that too far, the plugin starts showing some glitches.

4

Another way to do things that hasn't been mentioned yet: You can use the command-line window (opened with q: in normal mode — see :help q: for more information), in which you have full access to Vim's autocompletion features (i_CTRL-N/i_CTRL-P and the various i_CTRL-X sequences being among them).

In the command-line window you can also use normal-mode commands to edit the command line, which, for example, means you can yank and paste parts of previous commands in your command-line history.

Note that your cursor will be moved to the command-line window when using it, meaning that CTRL-R CTRL-W and similar methods that involve inserting the text under the cursor won't work there, unlike in the regular command line.

  • 1
    You can also open the command-line window when you're already halfway through entering your :substitute command by pressing <ctrl-f> – Rich Sep 27 '17 at 18:17
2

The solution to this is somewhat in between the other answers for me:

Say you've got 6 identical lines, and you want to replace 'apple' in the middle two:

I have apples for days
I have apples for days
I have apples for days
I have apples for days
I have apples for days
I have apples for days

What I do is:

  1. I get to the start of the text I want to replace, 'apple'
  2. Hit v to get into visual mode
  3. Go down to the last word I want to replace, in this case a few lines so 2j
  4. :s/<C-R><C-W>/ which uses the visual selection as the range for :s, and then inserts the word under the cursor
  5. I hit <C-F> which puts you into a mode where you can edit your command as if it was a buffer. So you can use whatever completion or plugins you want to choose the replacement word, and you're done.

All in one line, the flow for this would be /apples<CR>2nv/apples<CR>nn:s/<C-R><C-W/oranges<CR> but that looks worse than it is.

In your case, you can append the /c to the :s command to skip the ones you don't want, if your range contains a few you want to skip: :s/<C-R><C-W/orange/c.

Alternatively (and perhaps more simply), you could search for the entire word you want to replace, change it, then gn and . to repeat the search and apply the same change again. It would look like /\<apple\><CR>viwcorange<ESC>gn.gn.gn..ngn. to repeat it on three, then skip an instance, then repeat it one last time.

  • The method you describe at the end doesn't work for me (in Vim 7.4, invoked with vim -Nu NONE). It just beeps when I press the first .. Any idea why that is? In any case, it would be simpler to use ciw instead of viw and n instead of gn to make changes in this way. – Rich Sep 27 '17 at 18:02
2

This is not entirely related to the question, but still should help to select the word to replace.

Incremental search (set incsearch) allows you to type the prefix of the word you need to find, and vim will automatically jump to the first occurrence of the prefix as you type more and more. The search will be confirmed if you press Enter and discarded on Esc (in the latter case the cursor will jump back to where it was before the search started).

However, incremental search provides one more useful feature. When you've already typed the prefix of a word and the cursor is at the correct position, press <C-L> to add characters from the word under the cursor to the search string. This allows you to enter the replaced string with almost no keystrokes.

So, if I want to replace someOddVariableName with evenOdderVariableName, I

  1. type /someOddV and arrive at the occurrence of someOddVariableName;
  2. press and hold <C-L> until someOddVariableName appears in the search line completely;
  3. press Enter to confirm search;
  4. :%s//evenOdderVariableName/g or whichever replace command you need; you may wish to follow other answers here.
2

You can put the variable name in buffer first (yw to copy a word), then copy-paste it to :%s by hitting <C-r>".

2

If you want to replace a variable within the new (pasted) block, I have two suggestions:

Automated replace

  • Go to the first line of the new block, hit ma. This is marking a location and naming it a.

  • Go to the last line of the new block, hit mb. Now you have two named locations.

  • Now replace between the two lines, like so: :'a,'bs/oldVarName/newVarName/g.

  • That is, the scope of the replace is between your two marks.

The manual way

  • Go to the first line of the new block.

  • Use /oldVarName to find the first instance of oldVarName.

  • Change it like so: cwnewVarName<Esc>, which will change word to newVarName and hitting escape.

  • Use n to go to next instance of oldVarName.

  • Use . to repeat the last change (i.e. repeats that cw).

2

Many of the other answers here already cover the built in ways to improve the find-and-replace workflow but I wanted to mention abolish.vim, a plugin by Tim Pope. It makes replacements where you want to cover a wider range of cases, normally caused by variations in case or because you need to deal with both singular and plural forms of a word.

:S/child{,ren}/adult{,s}/g correctly converts child to adult, Children to Adults and CHILD to ADULT.

If you're dealing with camel cased words, you need to include the capitals where you need them in the original and replacement words.

%:S/wordWrap/textBreak/g correctly handles both wordWrap and wordwrap.

0

As other have already pointed, if you have your cursor positioned on top of your word you can use Ctrl+r family of commands, being Ctrl+r Ctrl+w probably the most suitable to use.

That method, however, only works for inserting one word (the one just below the cursor), but what if the there are several several portions of text in the buffer that we want to use? As you cant move your cursor while in the command line. The best way to do this is to copy portions of text you will need into different registers, which you can do by prefixing "{reg_letter} (quote and the letter from the register) to the copy operator. This way it will be possible to insert various words in the command line later with Ctrl+r{reg_letter}. This is a little awkward but works.

But returning to the previous idea, it would be great if the we would move the cursor position over the buffer while writing a pattern. That way we could "pick" text from different positions with Ctrl+r Ctrl+w, so I created a plugin that does just that: EXtend.vim The plugin also have much other features to make substitution operations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.