Search & replace using quickfix list in Vim - search

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?

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

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.)

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!

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

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

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

Related

How to add user command to vi/vim to have multiple conditions to search the content?

Our servers only have vi/vim to check the log files and I feel searching in vi is painful and limited.
I wonder if it's possible to provide an executable file in the path and then use the vi user command to call it and the user command is able to accept multiple conditions like A=value1 && B=value2, A=value1 || B=value2, etc and then it searches the content in the open vi editor and places the cursor in matching text.
If it's possible please describe a bit the steps.
If what you have on your machines is actually Vim, you should be able to leverage the "quickfix" feature. In a nutshell, you can:
feed a list of locations to Vim,
optionally tell Vim how to parse that list,
go through that list with commands like :cnext or :lprevious,
display that list in a special window,
batch operate on every line in the list.
In the simplest scenario, your hypothetical external program would output a list formatted in a way Vim already understand, like:
filename.txt:3067:12:some text
filename.txt:4321:7:some text
which could be fed "directly" to Vim:
$ vim -q <(yourcommand)
# also open the quickfix window
$ vim -q <(yourcommand) +cwindow
or via some file:
$ vim -q yourfile
It can all be done from within Vim by telling it to use your program instead of grep for the :grep command:
set grepprg=yourcommand
and doing:
:grep <your arguments>
References:
:help quickfix
:help :cnext
:help :lprevious
:help -q
:help -+c
:help :cwindow
:help :grep
:help 'grepprg'
:help 'grepformat'

Find a file (via recursive directory search) in Vim

Is there any way to search a directory recursively for a file (using wildcards when needed) in Vim? If not natively, is there a plugin that can handle this?
You can use wildcards with the :edit command. So,
:e **/test/Suite.java
will open test/Suite.java no matter where it is in the current directory hierarchy. This works with tab-completion so you can use [tab] to expand the wildcards before opening the file. See also the wildmode option for a way to browse through all possible extensions instead.
Another trick is to use
:r! find . -type f
to load a list of all files in the current directory into a buffer. Then you can use all the usual vim text manipulation tools to navigate/sort/trim the list, and CTRL+W gf to open the file under the cursor in a new pane.
There is a find command. If you add ** (see :help starstar) to your 'path' then you can search recursively:
:set path
will show you your current path, add ** by doing something like
:set path+=**
then you can just type
:find myfile.txt
and it opens magically!
If you add the set command to your .vimrc it'll make sure you can do recursive search in future. It doesn't seem to search dot directories (.ssh for example)
I'd recommend ctrlp.vim. It's a very good plugin, ideal to work inside large projects. It has search by file name or full path, regexp search, automatic detection of the project root (the one with the .git|hg|svn|bzr|_darcs folder), personalized file name exclusions, and many more.
Just press <c-p> and it will open a very intuitive pane where you can search what you want:
It's possible to select and open several files at once. It also accepts additional arbitrary commands, like jump to a certain line, string occurrence or any other Vim command.
Repo: https://github.com/kien/ctrlp.vim
vim as a builtin find command (:help find) but only open the first found file. However you can use this amazing plugin : FuzzyFinder which does everything you want and even more
You can browse the file system with :ex ., but I do not know how to search recursively (I am a Vim novice — I have been using it only ten years).
There are a few popular file browsers plug-ins:
NERD tree
Lusty explorer
vtreexplorer
See also this thread on SuperUser.
Command-T lets you find a file very fast just by typing some letters. You can also open the file in a new tab, but it need vim compiled with ruby support.
You can use ! to run shell commands :
:! find . -name *.xml
vim has bild in commands named grep, lgrep, vimgrep or lvimgrep that can do this
here is a tutorial on how to use them
http://vim.wikia.com/wiki/Find_in_files_within_Vim#Recursive_Search
you can also use an external command like find or grep from vim by executing it like this
:!find ...
Quickfix-like result browsing
Usage:
Find my.regex
Outcome:
a new tab opens
each line contains a relative path that matches a grep -E regex
hit:
<enter> or <C-w>gf to open the file on the current line in a new tab
gf to open the file on the current tab and lose the file list
Find all files instead:
Find
Alternative methods:
Gfind my.regex: only search for Git tracked files (git ls-files). Fugitive request: https://github.com/tpope/vim-fugitive/issues/132#issuecomment-200749743
Gtfind my.regex: like Gfind, but search from the git Top level instead of current directory
Locate somefile: locate version
Code:
function! Find(cmd)
let l:files = system(a:cmd)
if (l:files =~ '^\s*$')
echomsg 'No matching files.'
return
endif
tabedit
set filetype=filelist
set buftype=nofile
" TODO cannot open two such file lists with this. How to get a nice tab label then?
" http://superuser.com/questions/715928/vim-change-label-for-specific-tab
"file [filelist]
put =l:files
normal ggdd
nnoremap <buffer> <Enter> <C-W>gf
execute 'autocmd BufEnter <buffer> lcd ' . getcwd()
endfunction
command! -nargs=1 Find call Find("find . -iname '*'" . shellescape('<args>') . "'*'")
command! -nargs=1 Gfind call Find('git ls-files | grep -E ' . shellescape('<args>'))
command! -nargs=1 Gtfind call Find('git rev-parse --show-toplevel && git ls-files | grep -E ' . shellescape('<args>'))
command! -nargs=1 Locate call Find('locate ' . shellescape('<args>'))
Depending on your situation (that is, assuming the following command will find just a single file), perhaps use a command like:
:e `locate SomeUniqueFileName.java`
This will cause Vim to open, in the current tab (the e command) a file that is the result of running (in this example),
locate SomeUniqueFileName.java
Note that the magic here is the backticks around the command, which will convert the output from the shell command into text usable in the Vim command.
You don't need a plugin only for this function, below code snippet is enough.
function! FindFiles()
call inputsave()
let l:dir = input("Find file in: ", expand("%:p:h"), "dir")
call inputrestore()
if l:dir != ""
call inputsave()
let l:file = input("File name: ")
call inputrestore()
let l:nf = 'find '.l:dir.' -type f -iname '.l:file.' -exec grep -nH -m 1 ".*" {} \;'
lexpr system(l:nf)
endif
endfunction
nnoremap <silent> <leader>fo :call FindFiles()<CR>
Run:
:args `find . -name '*xml'`
Vim will run the shell command in backticks, put the list of files to arglist and open the first file.
Then you can use :args to view the arglist (i.e. list the files found) and :n and :N to navigate forward and bacwards through the files in arglist.
See https://vimhelp.org/editing.txt.html#%7Barglist%7D and https://vimhelp.org/editing.txt.html#backtick-expansion
You can find files recursively in your "path" with this plugin. It supports tab completion for the filename as well.
I am surprised no one mentioned Unite.vim yet.
Finding files (fuzzily or otherwise) is just the very tip of the iceberg of what it can do for a developer. It has built in support for ag, git, and a myriad of other programs/utilities/vim plugins. The learning curve can be a bit steep, but i cannot imagine my life without it. User base is big, and bugs are fixed immediately.
ag tool and corresponding Ag vim plugin solves this problem perfectly:
To find a file using some pattern use:
AgFile! pattern
It will open quickfix window with results where you can choose.
You can add vim keybinding to call this command using selected word as a pattern.
nnoremap <silent> <C-h> :AgFile! '<C-R><C-W>'<CR>
vnoremap <silent> <C-h> y :AgFile! '<C-R>"'<CR>

Open all files that match a certain pattern in Vim

I’m in ~/src. I can do git grep _pattern_ and get a list of all *.cpp or *.hpp files that match this pattern.
Now I would like to go through all the files that match the pattern and make edits on them. How do I do this in Vim? (Basically, I want Vim to go through my directory like git grep does, and jump me to the right files.)
You can use the single inverted commas (also a unix shell feature), something like:
vim `git grep --name-only <your expression>`
In bash, you could do
vim $(grep -l _pattern_ *.cpp *.hpp)
but that's a bash feature, not a vim feature.
you can use the args ex command:
:args *.cpp *.hpp
This will open all cpp and hpp files in the current directory.
You can use any file path expansions available to :grep as well.
You could possibly set the grepprg and grepformat options to run git grep... and interpret the result. This would then let you run the command :grep and read the results into the quickfix buffer - see :h quickfix for more information. You can then step through them with :cnext and :cprev, or :copen to open a separate window with the list of files - putting the cursor on a filename and pressing return will open that file for editing.
The advantage of this over Zoran's and ammoQ's suggestions is that it will not read the files into memory until you want to edit them. Their suggestion will load possibly hundreds of files into memory at once, and can be a nightmare to manage. It is also cross platform so should work on Windows without having to use a third-party shell such as cygwin bash.
By properly using the quickfix list, you can even go immediately to the the right line (using the :help quickfix commands, eg. :cn or :cw). So, if you are using bash or zsh:
vim -q &lt(git grep foo)

How to exclude file patterns in vimgrep?

In vim, I do search with vimgrep frequently. I have mapping like below:
map <leader>s :execute "noautocmd vimgrep /\\<" . expand("<cword>") . "\\>/gj **/*.*" <Bar>
cw<CR> 5
The problem is that there are some temporary subfolders (like obj, objd) that I don't want to search for. How can I exclude subfolders matching given patterns. For example, subfolders with prefix "objd" should not be included in searching.
As of Vim 7.3.570, you can use wildignore to exclude patterns with vimgrep.
For example, to ignore the objd subfolder:
:set wildignore+=objd/**
Additional exclusions can be added by separating patterns with a comma:
:set wildignore+=objd/**,obj/**,*.tmp,test.c
See Vim's help documentation for a few more details.
:help wildignore
As showed in http://vimcasts.org/blog/2013/03/combining-vimgrep-with-git-ls-files/ you could instead of exclude files, include the files you want to search. So you can search in the files tracked by Git with
:noautocmd vimgrep /{pattern}/gj `git ls-files`
In this way you are not searching the files stated in the .gitignore.
I use it so much I created a command for that, so I just need to
:Sch {pattern}
and I did it by adding the following line to my .vimrc
command -nargs=1 Sch noautocmd vimgrep /<args>/gj `git ls-files` | cw
You could try ack instead. It integrates nicely with vim and has lots of options for doing the sort of thing you want to do.
There are several ack-vim integrations on GitHub. For example: here.
For example in Ubuntu just
sudo apt-get install ack-grep
sudo ln -s /usr/bin/ack-grep /usr/bin/ack
then install http://www.vim.org/scripts/script.php?script_id=2572
and now add next line to your .vimrc
noremap <C-f> :copen<CR>:Ack --ignore-dir #first_ignore_dir# --ignore-dir #second_ignore_dir# -ai
its open search frame by Ctr+F, have fun

redirection to a file of a search in Vim

My problem is simple. I search a specific pattern in a file (let's say label in a Tex file)
:g/label/#
but there are lots of occurrences. So I'd like to redirect this output to another file to be able to work easily with it.
Do you have a trick or a command that I don't know?
it's not clear from the original post what you mean by "work easily with it" but it's often useful to see and quickly jump between all of the matches in a buffer without "extracting" the matches to a separate buffer.
vim has an internal grep built in. your example would be something like this (in vim, % denotes the current file)
:vimgrep /label/ %
This will take you to the first occurrence and report how many matches there were. What's cool is that you can look at all of the matches listed by opening up the quickfix error list using
:cope
Now you can just scroll around and press enter on a line to jump to the exact position of the match.
The quickfix error list is exactly the same buffer you use if you run make from inside vim and your compiler throws errors: it gives you a list of what and where the errors are.
After you've jumped to one location pointed by quickfix, you can go to forwards and backwards in the list via :cn and :cp. :ccl closes the error list.
You can also expand your "error" list via :vimgrepa /newpattern/ % or :vimgrepadd
The (documented) caveat is that vim's internal grep is slower than most native grep implementations (but you do get it "for free" in windows, for example). If you do have a grep installed, you can use :grep instead of :vimgrep for similar results.
quoting :help grep
Vim has two ways to find matches for a
pattern: Internal and external. The
advantage of the internal grep is that
it works on all systems and uses the
powerful Vim search patterns. An
external grep program can be used when
the Vim grep does not do what you
want.
The internal method will be slower,
because files are read into memory.
The advantages are:
- Line separators and encoding are automatically recognized, as if a file
is being edited.
- Uses Vim search patterns. Multi-line patterns can be used.
- When plugins are enabled: compressed and remote files can be searched.
You can also use the location list if you're already using the error list for dealing with compilation errors. just add l (for location) to the beginning of the grep command (:lvimgrep,:lvimgrepa :lgrep, :lgrepa) and use :lopen :ln :lp :lcl instead of the :c* ones.
For more commands consult
:help grep
:help quickfix-window
:help quickfix
:help quickfix-error-lists
:redir > matches.txt|execute 'g/foo/#'|redir END
See :h :redir, you can also redirect to registers, variables, the clipboard etc.
What you're doing is essentially 'grep -n label file' from command line. So you can run that command and > it into a file easily enough.
The derivation of 'grep' is even from basically the same source.
I've gotten this of the net at some point:
function GoToLine(mainbuffer)
let linenumber = expand("<cword>")
silent bd!
silent execute "buffer" a:mainbuffer
silent execute ":"linenumber
silent nunmap <Enter>
endfunction
command -nargs=1 GoToLine :call GoToLine(<f-args>)
function GrepToBuffer(pattern)
let mainbuffer = bufnr("%")
silent %yank g
enew
silent put! g
execute "%!egrep -n" a:pattern "| cut -b1-80 | sed 's/:/ /'"
silent 1s/^/\="# Press Enter on a line to view it\n"/
silent :2
silent execute "nmap <Enter> 0:silent GoToLine" mainbuffer "<Enter>"
" silent nmap <C-G> <C-O>:bd!<Enter>
endfunction
command -nargs=+ Grep :call GrepToBuffer(<q-args>)
Put it in your .vimrc, then :Grep Foo
Requires external grep program to work properly.
(Just an idea -- untested.)
You can delete all the lines with your pattern in it, write to another file, and undo the delete.
:g/label/d
:w matches
u

Resources