Search code examples
regexrustvimgrepripgrep

How to use alternation operator in Vim + RipGrep?


I have the following in my .vimrc which (I believe) makes :grep within Vim use rg:

if executable('rg')
  set grepprg=rg\ --no-heading\ --vimgrep\ --hidden\ --case-sensitive\ --ignore-vcs\ --glob\ '!.git'\ --glob\ '!node_modules'
endif

I want to search for all definitions of functions named render.... If I do

rg -e \(const\|let\)\ render .

on the command line, I get what I'm looking for.

But

:grep -e \(const\|let\)\ render

in vim results in

zsh:1: command not found: let) render
regex parse error:
    (const
    ^
error: unclosed group

I've tried some other combos of \, putting the whole query in /.../, can't quite get it working.

How do I use the alternation operator in ripgrep in vim?


Solution

  • There are three pieces of machinery involved, here, each with its own idiosyncrasies: Vim, your shell, and RipGrep.

    Ideally, this is how your pattern should look with RipGrep's syntax:

    (let|const) render
    

    If you try it as-is:

    :grep (let|const) render
    

    you should get a cascade of messages (irrelevant lines removed):

    :!rg (let 2>&1| tee /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/26                                
    /opt/local/bin/bash: -c: line 1: syntax error near unexpected token `let'
    /opt/local/bin/bash: -c: line 1: `rg (let 2>&1| tee /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/26'
    
    shell returned 2
    E40: Can't open errorfile 
    /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/26
    

    Vim

    The first line:

    :!rg (let 2>&1| tee /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/26
      ^^^^^^^
    

    tells you that the command executed under the hood is:

    rg (let
    

    which is obviously incomplete. That is because Vim thinks that the | is a command separator (:help :bar) so it tries to execute the broken :grep (let. If you want your | to pass through, you must escape it:

    :grep (let\|const) render
    

    OK, all the arguments are now passed to rg:

    :!rg (let|const) render 2>&1| tee /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/27
      ^^^^^^^^^^^^^^^^^^^^^
    

    Your shell

    You are not done yet, though:

    /opt/local/bin/bash: -c: line 1: syntax error near unexpected token `let'
    /opt/local/bin/bash: -c: line 1: `rg (let|const) render 2>&1| tee /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/27'
    
    shell returned 2
    E40: Can't open errorfile /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/27
    

    Your pattern includes a capture group delimited with parentheses, which confuses the hell out of your shell because it looks like an attempt to execute the command let|const in a subshell, which is bound to fail anyway, but in a context where it can't be done.

    You can try to solve those problems by escaping things with backslashes but you are entering an escaping arms race between the shell and Vim. That is the kind of race where there is no winner.

    A better approach is to wrap your whole pattern with single quotes:

    :grep '(let\|const) render'
    

    which tells your shell to treat what is between the quotes literally, without trying to be smart.

    You can check what arguments are passed to rg by forcing an error:

    :grep '(let\|const) render' foobar
    

    which should show you this:

    :!rg '(let|const) render' foo 2>&1| tee /var/folders/q4/8ckdmdb136z10l1nh7ss_hsw0000gn/T/vphU3gH/29            
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    Well done!

    RipGrep

    Without the single quotes, RipGrep wouldn't know that render is part of the pattern so it treats it as a filename and you get errors because that filename doesn't exist.

    Wrapping the pattern in single quotes killed two birds with one stone: your shell expansion issue is solved and RipGrep knows where your pattern ends.


    NOTE: While it is inconsequential, here, the -e flag is not necessary because your pattern doesn't start with a -.