Search code examples
gitvimneovimvimgrepvim-quickfix

Trying to use quickfix list to navigate a git show in Neovim — :vimgrep /^@@ ~/temp.git.log — "E682 invalid search pattern or separator"


I have the file tmp.git.log in my $HOME:

commit 07f5bbb6a3a112f7b601d6cd1f72ec3f20112920
Author: me
Date:   2024 

    some

    comit

    messages



diff --git path/file.end
index e969a7691b..0bf58bcb94 100644
--- removed
+++ added
@@ -1,7 +1,7 @@
  
    
      
-      removed
+      added
       :key="index"
       label
       :color="color"
@@ -10,11 +10,13 @@
       :class="fontWeight"

etc.

I wanted to fill my quickfix list with the beginnings and endings of the changes, marked here by @@ at the line start. I tried to execute

:vimgrep /^@@ ~/tmp.git.log

And Nvim answered with

E682 invalid search pattern or separator

What is wrong with the above vimgrep command?

Also: Can I :vimgrep in buffers which aren't written, e.g. non-files?

I want to :read !git show <git-hash> into nvim to see changes done in my repo by certain commits, I don't need the files persisted. I know, there are plugins, but I want to understand what vanilla nvim can do here.

I want to read the result of git show <git-gash> within Neovim, and populate the quickfix list with vimgrep.

Vimgrep fails to accept my search

:vimgrep /^@@ ~/tmp.git.log

with E682.

How do I accomplish to fill my quickfix list with the added/removed file chunk positions for faster navigation?

Ideally without saving the git show result to a temporary file first.


Solution

  • :vimgrep /^@@ ~/tmp.git.log
    

    What is wrong with the above vimgrep command?

    You must enclose your pattern with / and / (or a pair of another character), as per :help :vimgrep:

    :vimgrep /^@@/ ~/tmp.git.log
                 ^ you forgot this slash
    

    Also: Can I :vimgrep in buffers which aren't written, e.g. non-files?

    No. Both :grep and :vimgrep can only be used to search through files.

    The mechanisms for searching in a file and searching in a buffer can't really be identical because the two tasks sit at different abstraction levels. You have very old and simple commands like :vimgrep for the former but, sadly, only low-level stuff for the latter.

    I came up with this monstrous one-liner years ago because I wanted to use the quickfix for buffer-level search but it only works for named buffers. I guess one could use the same basic logic (filter the lines from current buffer) but use :help setqflist()/:help setloclist() instead.

    --- EDIT ---

    FWIW, I just wrote this more versatile version of the linked snippet that makes use of the location list:

    function! Global(pat, bang) range
        " reverse operation if we have a bang
        let operator = a:bang == '!' ? '!~' : '=~'
    
        " padding is needed if range starts after line 1
        let padding_top = a:firstline > 1 ? a:firstline - 1 : 0
    
        " one call is better than many
        let bufnr = bufnr()
    
        " build the range for a clean quickfix title
        let qf_title_range = a:firstline == 1 && a:lastline == line('$') ? '%' : a:firstline .. ',' .. a:lastline
        
        " 1. get all lines in range into list
        " 2. transform each item into syntactically valid qf item
        " 3. keep items that match/don't match given pattern
        let qf_list = getline(a:firstline, a:lastline)
                    \ ->map({ idx, val -> { 'bufnr': bufnr, 'lnum': idx + 1 + padding_top, 'text': val, 'valid': 1 } })
                    \ ->filter({ idx, val -> eval("val.text " .. operator .. "'.*' . a:pat . '.*'") })
    
        " if possible…
        " 1. set location list with list of items and proper title
        " 2. open location list
        " 3. jump to first location
        if qf_list->empty() == 0
            call setloclist(win_getid(), [], ' ', { 'title': ':' .. qf_title_range .. 'Global ' .. a:pat, 'items': qf_list })
            lwindow
            lfirst
        endif
    endfunction
    
    " custom Ex command :Global calls custom function Global()
    command! -bang -nargs=1 -range=% Global <line1>,<line2>call Global(<q-args>, expand('<bang>'))
    

    which seems to work fine with everything I threw at it, named buffer or not.

    Usage, populate location list with matching lines in given range:

    :Global @@
    :%Global foo
    :3,21Global bar
    

    Usage, populate location list with non-matching lines in given range:

    :Global! @@
    :%Global! foo
    :3,21Global! bar