Skip to content

Instantly share code, notes, and snippets.

@romainl romainl/grep.md
Last active Mar 25, 2020

Embed
What would you like to do?
Instant grep + quickfix
  1. Tell Vim what external command to use for grepping

    set grepprg=ag\ --vimgrep
    

    If you prefer RipGrep to The Silver Searcher, or you are from the future and just thinking about those antiquated pieces of shit makes you laugh, feel free to use whatever you want.

    This is not strictly necessary for what we are trying to do but it's a cheap and non-negligible upgrade to the built-in :grep and :lgrep so, there.

  2. Perform the search in a sub-shell

    function! Grep(...)
        return system(join(extend([&grepprg], a:000), ' '))
    endfunction
    

    We could simply use :grep, with the appropriate grepprg, but the present solution has a huge advantage: the external command is not executed in the current shell so we don't have to put up with annoying screen flashes and <CR>s. The implementation of :getexpr is also simpler than :grep's so it is considerably faster.

  3. Create a command to populate the quickfix list (and another one for the location list) with the output of the function above

    command! -nargs=+ -complete=file_in_path -bar Grep  cgetexpr Grep(<q-args>)
    command! -nargs=+ -complete=file_in_path -bar LGrep lgetexpr Grep(<q-args>)
    
  4. Open the location/quickfix window automatically if there are valid entries in the list

    We define a self-clearing group so that our autocommands don't needlessly pile up if we re-:source our vimrc:

    augroup quickfix
        autocmd!
        " our autocommands here
    augroup END
    

    We add two autocommands:

    • one to execute :cwindow whenever :cgetexpr is executed,
    • another one to execute :lwindow whenever :lgetexpr is executed.

    :cwindow opens the quickfix window if there are valid entries in the quickfix list and :lwindow does the same for the location wlist and the location window.

    We need both if we don't want to lose the flexibility of the original commands we are trying to replace.

    augroup quickfix
        autocmd!
        autocmd QuickFixCmdPost cgetexpr cwindow
        autocmd QuickFixCmdPost lgetexpr lwindow
    augroup END
    

    Note that this snippet can be easily generalized to cover every quickfix command.

set grepprg=ag\ --vimgrep
function! Grep(...)
return system(join(extend([&grepprg], a:000), ' '))
endfunction
command! -nargs=+ -complete=file_in_path -bar Grep cgetexpr Grep(<q-args>)
command! -nargs=+ -complete=file_in_path -bar LGrep lgetexpr Grep(<q-args>)
augroup quickfix
autocmd!
autocmd QuickFixCmdPost cgetexpr cwindow
autocmd QuickFixCmdPost lgetexpr lwindow
augroup END
@george-b

This comment has been minimized.

Copy link

george-b commented May 29, 2019

return system(join(['ag --vimgrep', shellescape(args[0]), get(args, 1, '')], ' '))

It feels like calling ag is too explicit here, is there a reason for not referencing it as &grepprg?

@romainl

This comment has been minimized.

Copy link
Owner Author

romainl commented May 30, 2019

No reason whatsoever. You caught me between two edits.

@alfunx

This comment has been minimized.

Copy link

alfunx commented Nov 15, 2019

This is amazing, thank you! The split(a:args, ' ') might cause problems if the pattern contains spaces. I'm sticking to an earlier version, where you had &grepprg . ' ' . a:args, which makes this behave like :grep. To avoid escaping, one could always go for quotes (e.g. :Grep 'some regex.*' *.c).

@trobjo

This comment has been minimized.

Copy link

trobjo commented Dec 13, 2019

Very nice version of the quickfix list. I use a much simpler version, as I often grep for multiple words. The code looks like this:

set grepprg=rg\ --vimgrep\ --glob\ '!*{.git,node_modules,build,bin,obj,README.md,tags}'

function! Grep(args)
    return system(join([&grepprg, shellescape(a:args)], ' '))
endfunction

command! -nargs=+ -complete=file_in_path -bar Gr cgetexpr Grep(<q-args>)

augroup quickfix
    autocmd!
    autocmd QuickFixCmdPost cgetexpr cwindow
augroup END

My understanding of vimscript is not that great, so what is it you gain by having a more convoluted command and not being able to grep for more than one word. What am I missing?

@romainl

This comment has been minimized.

Copy link
Owner Author

romainl commented Dec 13, 2019

Very nice version of the quickfix list. I use a much simpler version, as I often grep for multiple words. The code looks like this:

My understanding of vimscript is not that great, so what is it you gain by having a more convoluted command and not being able to grep for more than one word. What am I missing?

I rarely grep for multiple words but I often grep in specific files. The command as it is satisfies that specific need but I should look for a more generic solution.

@george-b

This comment has been minimized.

Copy link

george-b commented Dec 17, 2019

FWIW I use the following which makes multiple words and files (including %) pretty transparent.

function! Grep(args)
  let args = split(a:args)
  if len(args) > 1 && !empty(glob(args[-1]))
    let cmdline = shellescape(join(args[0:-2])) . ' ' . expand(args[-1])
  else
    let cmdline = shellescape(a:args)
  endif
  return system(&grepprg . ' ' . cmdline)
endfunction
@romainl

This comment has been minimized.

Copy link
Owner Author

romainl commented Dec 17, 2019

@trobjo, @george-b, I just spent half an hour trying every "reasonable" combination of arguments I could think of with the snippet below:

function! Grep(...)
    return system(join(extend([&grepprg], a:000), ' '))
endfunction

and it worked for everything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.