The blog of Karol Moroz
HomeBlog

203. Project-wide Search and Replace in Vim


Taipei, 2017-01-10 10:22:53 +0800 I wrote this post as a tutorial for my colleague who also uses Vim as his primary text editor.

To learn how to perform search and replace in Vim, we first need a code sample. If you don’t have a project to practise on, you can start by cloning into this COBOL Hello World repository from Github:

bc. git clone https://github.com/now-examples/cobol-hello-world/

A common way to perform project-wide search and replace in Vim is using quickfix list. To use this functionality in classic Vim, you need to make sure your copy of Vim is compiled with the @quickfix@ option. You can check it by running:

bc. vim --version | grep quickfix

If your copy of Vim supports quickfix, the output should include @+quickfix@ (a minus means that the option was not enabled in compilation):

bc. +cursorbind +listcmds +quickfix +wildmenu

If your Vim binary has no quickfix support, try installing a different package or compiling it from source. You can also consider switching to Neovim, an improved, modernised fork of Vim, which supports quickfix by default.

The first step to perform project-wide search and replace in Vim is to search for a pattern. To this end, you can use Vim’s built-in command @:vimgrep@. If you have @ag@ (a.k.a. the Silver Searcher) installed on your system, you can configure Vim to use it instead of @grep@. Add these lines to your @~/.vimrc@ (or @~/.config/nvim/init.vim@ on Neovim)[1]:

bc. if executable(‘ag’) " Note we extract the column as well as the file and line number set grepprg=ag\ --nogroup\ --nocolor\ --column set grepformat=%f:%l:%c%m endif

Save the file with @:w@ and source it with @:source %@.

Suppose we want to replace all occurences of @identification division@ in the COBOL Hello World Project with “chunky bacon”. First, let’s perform a project-wide search:

bc. :vimg ‘identification division’ **

The command @vimgrep@ takes two parameters, a search pattern (if it contains spaces or special characters, you have to enclose it in quote marks,) and the search path. If you use @ag@, you don’t have to specify the search path (ag will search recursively through all files in current directory, ignoring files specified in the project’s @.gitignore@ or @.agignore@ files):

bc. :grep ‘identification division’

Both @vimgrep@ and @ag@ populate the quickfix list with search results. You can then see the list using @:copen@:

[[Image:203_quickfix_list.png]]

The general syntax for performing a file-wide search and replace in Vim is as follows:

bc. :%s/procedure division/spam and eggs/g

The part of the command before @s@ denotes a range in the file, @%@ meaning all lines in the current file[2]. Then come the search and replace patterns delimited by arbitrary separators, usually slashes or colons. You can use regular expressions in the search pattern. Keep in mind, however, that Vim’s regex syntax differs significantly from the most widespread Perl-like syntax. The @g@ flag at the end tells Vim to substitute all occurrences of the search pattern in each line, rather than only the first occurence in the line. Another flags include @c@, which will ask you to confirm each substitution, and @i@, which makes the search pattern case-insensitive.

We can invoke the substitution command for several files using @:argdo@, first, however, we need to populate the @args@ list with all files in the quickfix list. To do this, we can use the @Qargs@ command[3]. Add these lines to your @.vimrc@ or @init.vim@:

bc. 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 bufnr = quickfix_item[‘bufnr’] " Lines without files will appear as bufnr=0 if bufnr > 0 let buffer_numbers[bufnr] = bufname(bufnr) endif endfor return join(map(values(buffer_numbers), ‘fnameescape(v:val)’)) endfunction

Remember to save the file with @:w@ and source it with @:source %@. If you use @:Qargs@ now, your argument list should be populated with vimgrep’s or ag’s search results. You can verify it using the command @:args@. Now let’s get the actual job done:

bc. :argdo %s/identification division/chunky bacon/g

All occurrences of @identification division@ in the project have been substituted with @chunky bacon@! To save the results, use:

:argdo w
or
:argdo update

fn1. Winterbottom D., Using the silver searcher with Vim, codeinthehole.com. Retrieved 10th Dec., 2017.

fn2. Read more on ranges on “Vim Tips Wiki – Ranges”:http://vim.wikia.com/wiki/Ranges. Retrieved 10th Dec., 2017.

fn3. See nelstrom’s answer to “this question”:http://stackoverflow.com/questions/5686206/search-replace-using-quickfix-list-in-vim/5686810#5686810 on Stack Overflow. Retrieved 10th Dec., 2017.