0

The context

Given a {pattern}, I can list the ocurrences of {pattern} in all files from a {directory}, in the quickfix window with the following command

:chdir {directory}
:vimgrep /{pattern}/ **/*
:cwindow

The question

Given a set of patterns, I want to list the ocurrences of these patterns in those files that contain all the patterns of the set of patterns in the quickfix window.

I really need to list the ocurrences in the quickfix window because thus I can jump exactly to position of the ocurrences.

In other words, it would be like executing vimgrep for each pattern but instead of searching through all files just limit the search to the files present in the quickfix window. Recall that the fact that they were in the quickfix window shows that they contained the previous pattern which accomplishes the same.

Additional context

The current workaround

I will explain how I would do this by using grep and vim as a pager. The problem with this approach is

  • it doesn't fill the quickfix window which implies that I have to repeteadly press n and N instead of having the ocurrences listed in an order manner in the quickfix window.
  • the output only shows the name of the files that contain the set of patterns but doesn't show in which line the pattern occurs.

Consider the set of patterns \<main\> and \<c and the following directoryj

$ mkdir cpp && echo main.cpp > cpp/main.cpp
$ mkdir c && echo main.c > c/main.c
$ mkdir py && echo main.py > py/main.py

Executing the following command will display the name of the files that contain all the patterns in a buffer which implies that I can use <c-w>f to jump to those files.

$ find -type f | xargs grep -l '\<main\>' | xargs grep -l '\<c' | vim -

This would be the result in the vim buffer. Note that main.py file is not present because it didn't contain the \<c pattern. The set of patterns in this scenario were \<main\> and \<c.

1| ./cpp/main.cpp
2| ./c/main.c
2
  • 1
    Is your question « given a quickfix list, search every file in it with a different pattern to fill a quickfix list »?
    – D. Ben Knoble
    May 27 '20 at 12:29
  • No, given a set of patterns $s = {p_1, p_2, \ldots, p_n}$. List ocurrences of $p_1, p_2, \ldots, p_n$ in those files whose content contains $p_1$ and $p_2$ and $\ldots$ and $p_n$. That is, if file contains $p_1$ but not $p_2$, then ocurrences of $p_1$ in that specific file must not be shown in the quickfix list.
    – gfe
    May 27 '20 at 15:07
1

Here's a potential solution. I'll use 4 test files. 3 contain the lines

foo
bar
baz

while the 4th contains any proper subset. The patterns of interest are the same 3 lines.

  1. Run :vimgrep with all your patterns as concats. This gives the files that contain all the matches. For example,
:vimgrep /\v(\_.*foo\_.*)&(\_.*bar\_.*)&(\_.*baz\_.*)/ ##

Replace ## with your directory, if you like (it stands for the current argument list, so I ran vim * and then this command from the directory where my sample files are located).

  1. This pattern only matched once (for me) for each file1. So, for each file in the list, you can repeat the following:

    1. Run :vimgrep on just that file with all the patterns. For example, :vimgrep /\vfoo|bar|baz/ %.
    2. When you're satisfied with that particular file, run :colder to go back to the list of files.
    3. Rinse and repeat.

You could theoretically also write a function to do the first bit, use getqflist() to process the list of files, and build up a new quickfix list.


  1. Adding another line with foo to one file caused multiple matches to appear. The pattern is a bit of and oddball, so I find it better to not rely on how it will appear and instead grab the list of unique files for further processing (e.g., :echo getqflist()->map({_, v -> bufname(v.bufnr)})->sort()->uniq()).
2
  • That's a gnarly pattern, but yeah using & is a great idea to match only files with all of them. You can probably drop the final \_.* since you only need to match until you find the word and no further... You could use a \%^ at the start (you might need an extra set of parens) to match at the first line only (effectively matching once per file.) Your last command has an extra PP there, not sure what's that about... If you use getqflist() and map() etc. you might as well build a :vimgrep at the end to find the individual matches in the file list, no?
    – filbranden
    Jun 22 at 15:02
  • 1
    @filbranden dropping the end is probably a good idea. Forcing a single match per file definitely fits better with the idea; I'll have to investigate. (The PP is from scriptease; I was supposed to change it to echo but obviously only did half the job, lol.) Yeah, I suppose I could turn that into a vimgrep... todo :)
    – D. Ben Knoble
    Jun 22 at 15:40
0

NOTE I just realized that this still won't accomplish what the OP is after. My function would give entries from files containing any of the multiple patterns, while OP wants entries from files containing all of the patterns. Concats (\&) might help, but I'm not sure.


If you don't mind leaving the original quickfix list prepended to the new one, you want to be able to do

cfdo vimgrepadd /pattern/ %

But because of the command line processing % too early, it won't actually search each file (and I haven't yet found a way around this).

However, you can use the following function (which is similar to an unrolled cfdo):

function! AddQuickfix(pattern) abort
  const files = getqflist()->map({ _, item -> bufname(item.bufnr) })
  " clear quickfix
  cexpr []
  for file in files
    execute 'vimgrepadd' a:pattern file
  endfor
endfunction

Then OP can do

  1. :vimgrep /pattern/ **/*
  2. :call AddQuickfix('pattern')

Notes

  1. If you want to keep the original quickfix at the top of the new ones, remove the :cexpr [] line.
  2. You can improve the ergonomics with :command -nargs=1 QfAdd call AddQuickix(<q-args>); in both versions, any form of pattern that :vimgrep accepts is allowed, but the function argument must be quoted.
  3. If you want to add matches for multiple patterns, you can use branches \| or concats \&.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.