Vim :vimgrep and External grepprg: Bounding Matches Before Quickfix Population
0 reputation · 02 Jun 2026, 19:30 UTC
0 reputation · 02 Jun 2026, 19:30 UTC
Goal: Reduce the size of the quickfix list generated by :vimgrep when searching large repositories so that pagination with :cnext/:cprev remains responsive.
Constraints: :vimgrep lacks a built‑in limit; users must either post‑filter with :cfilter or rely on an external grep program via 'grepprg'. Using an external grep’s -m/--max‑count flag can bound matches, but the resulting output must still conform to the current 'grepformat' for Vim to create correct quickfix entries, and the interaction with binary files or non‑standard encodings is not well documented.
Questions: Can GNU grep's -m option be used safely with :vimgrep while preserving accurate quickfix parsing? What 'grepformat' pattern ensures each limited match maps to exactly one quickfix entry? Does applying -m introduce any side effects such as truncated lines or missed matches in binary or UTF‑8 files?
35275 reputation · 02 Jun 2026, 20:46 UTC
Yes, GNU grep's -m flag bounds matches safely and produces output that parses cleanly into the quickfix list — but only if you stop using :vimgrep. :vimgrep is Vim's internal search engine. It never invokes 'grepprg', so any flags you configure there are silently ignored. The external-program path is :grep (or :lgrep for the location list). This is the single most common point of confusion, and it explains most reports of "my max-count setting does nothing."
Confirmed behavior: :grep runs the shell command in 'grepprg', captures its stdout, and parses each line against 'grepformat'. GNU grep's -m N stops scanning a file after N matching lines and emits those lines in the normal file:line:text format when combined with -n. Because the matched lines themselves are printed verbatim, each one maps to exactly one quickfix entry with a correct file and line number.
The likely explanation for your responsiveness problem is that a huge quickfix list makes :cnext/:cprev and :copen sluggish, and capping per-file matches shrinks the list enough to fix it. That is plausible and consistent with how Vim handles large lists, but whether -m alone is sufficient depends on your repository shape — see the caveat below.
set grepprg=grep\ -n\ -m\ 20\ $*
set grepformat=%f:%l:%mTwo details matter here:
-n is mandatory. Without line numbers, %l has nothing to bind to and parsing fails or produces bogus jumps.%f:%l:%m, not the longer %f:%l:%c:%m. GNU grep does not print a column number by default, so a format expecting %c will misparse. If you genuinely need columns, that requires a different tool (e.g., ripgrep with --column) and a matching format string.Then run it as:
:grep 'pattern' **/*.c
:copenVerify with :copen that no file contributes more than 20 entries, then :cnext a few times and confirm the cursor lands on the actual matching line.
-m limits matches per file. Across 5,000 files you can still collect 100,000 entries. If you need a global cap, pipe through head in the grepprg instead — but be aware that truncating with head can cut a line mid-way only if the final line is incomplete, which is rare with grep output; still, per-file -m is the cleaner tool.
Matches after the Nth one in a file are never emitted. The quickfix list is not "the first 20 of everything"; it is "the first 20 per file, full stop." If your workflow assumes completeness, post-filtering with :cfilter on a full result set is the safer option.
GNU grep detects binary files and prints "Binary file X matches" instead of match lines. That line does not conform to %f:%l:%m, so it either becomes an unparseable entry or an error entry in the quickfix list. It does not corrupt other entries. To avoid the noise, add -I (ignore binary files) to the grepprg.
UTF-8 text is handled fine as long as your locale is UTF-8; grep matches bytes, and Vim displays the captured line text. Truncated lines are not a concern: -m limits match count, never line length. Very long lines are passed through whole.
set grepprg=grep\ -n\ -I\ -m\ 20\ $*
set grepformat=%f:%l:%mIf you still see parsing errors after this, the one diagnostic detail worth checking is the exact raw output: run :!grep -n -I -m 20 'pattern' somefile and compare it against your 'grepformat' — any wrapper script or aliased grep that alters output (color codes from --color=always are a classic culprit) will break parsing.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.