49

So far I always used EasyGrep for replacing text in multiple files. Unfortunately it is quite slow when a project gets bigger. One thing that seems to be amazingly fast is Ggrep of fugitive.vim that only search my version controlled files. All results are also stored in the quickfix list.

How can I use the results of Ggrep for doing a simple replace over all those found files? Is it somehow possible to use %s/foo/bar/cg on all files in the quickfix list or are there any better ways?

medihack
  • 16,045
  • 21
  • 90
  • 134

6 Answers6

75

Update: Vim now has cdo, see Sid's answer.

Original Answer:

Vim has bufdo, windo, tabdo and argdo, which let you perform the same command in all open buffers, windows or files in the argument list. What we really need is something like quickfixdo, which would invoke a command on every file in the quickfix list. Sadly, that functionality is lacking from Vim, but here's a solution by Al that provides a home-rolled solution. Using this, it would be possible to run:

:QFDo %s/foo/bar/gc

And that would run the foo/bar substitution on all files in the quickfix list.

The bufdo, windo, tabdo and argdo commands have some common behaviour. For example, if the current file can't be abandoned, then all of these commands will fail. I'm not sure if the QFDo command referenced above follows the same conventions.

I've adapted Al's solution to create a command called Qargs. Running this command populates the argument list with all of the files listed in the quickfix list:

command! -nargs=0 -bar Qargs execute 'args ' . QuickfixFilenames()
function! QuickfixFilenames()
  " Building a hash ensures we get each buffer only once
  let buffer_numbers = {}
  for quickfix_item in getqflist()
    let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
  endfor
  return join(values(buffer_numbers))
endfunction

Using this, you could follow these steps to do a project-wide search and replace:

:Ggrep findme
:Qargs
:argdo %s/findme/replacement/gc
:argdo update

Edit: (with a hat tip to Peter Rincker)

Or you could join the last 3 commands together in a single line:

:Ggrep findme
:Qargs | argdo %s/findme/replacement/gc | update
Robert Claypool
  • 4,262
  • 9
  • 50
  • 61
nelstrom
  • 18,802
  • 13
  • 54
  • 70
  • I believe you should be using `-nargs=0` and `-bar` in your `Qargs` command. – Peter Rincker Apr 16 '11 at 15:41
  • 1
    It's an elegant idea to use `args` and `argdo`! I think it could be even better (and more convenient) to join `Qargs` and `argdo` commands in one: `command! -nargs=1 -complete=command -bang Qargdo exe 'args '.QuickfixFilenames() | argdo ` – ib. Aug 05 '11 at 03:31
  • 3
    I've created a simple plugin to wrap the `:Qargs` command: https://github.com/nelstrom/vim-qargs – nelstrom Jan 10 '12 at 18:59
  • 9
    @ib. Thanks for that (and to @nelstrom, of course). Forked @nelstrom's plugin to add your `Qargdo`, but as `Qdo` for less command completion conflicts: https://github.com/henrik/vim-qargs – Henrik N Jun 15 '12 at 22:37
  • 4
    A patch [was submitted](https://groups.google.com/forum/#!msg/vim_dev/dfyt-G6SMec/_6h8pDUpeZMJ) to add the :cdo and :ldo commands to Vim. – Fernando Correia Jul 03 '14 at 21:38
  • Should note in the answer that :`hidden` needs to be on (perhaps as a note to beginners, at the end). – AlexMA Mar 10 '16 at 16:55
62

cdo command has now been added! After you grep, you can use cdo to execute the given command to each term in your quickfix list:

cdo %s/<search term>/<replace term>/cg

(Take a look at this git commit and this vim developers google group discussion for more information on cdo and the motivations behind adding it.)

Sid
  • 2,683
  • 18
  • 22
  • 3
    I think replace command should not include `%` to prevent replacement in lines outside of selected list. Also it may cause unexpected result if replace isn't idempotent. – magras Apr 06 '20 at 14:59
  • Using `cdo` also opens up the amazing slew of options available to more flexible operations. Eg `cdo norm capMy new paragraph` This example runs `normal` at every occurrence of the search term, changing the entire paragraph around the search term to read "My new paragraph". Amazingly powerful. – Noel Evans Sep 28 '20 at 12:24
  • 2
    I also needed to pipe the above to "update" command, otherwise vim didn't want to move to next file because the buffer was edited but not saved. Command `:cdo %s///gc | update` – antisa Nov 29 '21 at 15:35
16

nelstrom's answer is quite comprehensive and reflects his brilliant contributions to vimdom. It also goes a bit beyond what is strictly needed here; the quickfix step can be omitted in favor of populating args with the result of a shell command:

:args `git grep -l findme`
:argdo %s/findme/replacement/gc
:argdo update

should be all you need.

Edit: as Domon notes, :set hidden must be done first if it's not already set!

rhowardiv
  • 171
  • 1
  • 5
  • 1
    +1 for pragmatic solution. Based on this you could also populate the arglist when you start vim: `vim \`git grep -l findme\`` – Stephen Emslie Dec 11 '13 at 14:30
  • 2
    This works for me. However, I have to `:set hidden` before `:argo ...` or Vim gives errors because of unsaved changes. – Domon Mar 04 '16 at 10:05
5

Using quickfix-reflector.vim, you can edit your search results in the quickfix window. The write command will then save the changes to your files.

:copen
:%s/foo/bar/cg
:write
stefandtw
  • 468
  • 6
  • 5
4

External grep

(uses grepprg, grepformat like in makeprg/errorformat; if grepprg=='internal' this is identical to internal grep)

:grep fopen *.c
:copen
:cnext

Internal grep

:vimgrep /\<myVimregexp\>/ **/*.c
:copen
:cnext

etc.

Location list internal grep

:lvimgrep /\<myVimregexp\>/ **/*.c
:lopen
:lnext

etc.

Bonus: doing external grep for the loaded buffers:

:silent bufdo grepadd fstream %
:copen
:cnext

etc.

External for all arguments:

:silent argdo grepadd fstream %
:copen
:cnext
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Not sure what you are trying to point out with those examples. I know how to use grep and vimgrep. I don't see how this answer fits the question. – medihack Apr 16 '11 at 22:31
  • 1
    (a) Showing you a few ways to reduce the scope of grep in vim (b) showing you how to do such things for external grep. I'm sorry if it is a bit boring, but it works for me :) – sehe Apr 16 '11 at 22:33
1

There's a patch to add the cdo (Quickfix do) command to vim, but it has not been pulled yet (as of 2015-03-25):

https://groups.google.com/forum/#!topic/vim_dev/dfyt-G6SMec

You may want to patch vim yourself to get this patch:

brew install hg # install mercurial, e.g. with homebrew
hg clone https://vim.googlecode.com/hg/ vim
cd vim
# copy/download patch to . folder
patch -b -p1 < cdo.diff
./configure
make && make install
Ralph von der Heyden
  • 1,715
  • 1
  • 15
  • 15