The Problem#
You’re working on a project with hundreds of files. You need to find where a function is called, where an error message is defined, or every file that references a configuration value. Opening files one by one and searching with / isn’t practical.
Vim has built-in project-wide search that populates the quickfix list — letting you jump between results across files instantly.
Method 1: :grep (External grep)#
:grep runs an external search tool and loads results into the quickfix list.
Basic usage#
" Search for a string in all files recursively
:grep "processOrder" **/*
" Search in specific file types
:grep "TODO" **/*.py
:grep "import" **/*.js
" Case-insensitive
:grep -i "error" **/*.logAfter running :grep, use :copen to see all matches and :cnext/:cprev to jump between them.
Make it fast with ripgrep#
The default grep is slow on large projects. Configure Vim to use ripgrep — it’s dramatically faster and respects .gitignore automatically:
" In your .vimrc
set grepprg=rg\ --vimgrep\ --smart-case
set grepformat=%f:%l:%c:%mNow :grep uses ripgrep behind the scenes:
" Same command, 10-100x faster
:grep "processOrder"
" No need for **/* — ripgrep searches recursively by default
" No need for -i — smart-case handles it (case-insensitive unless you use uppercase)Why ripgrep?#
| Feature | grep | ripgrep |
|---|---|---|
| Speed | Slow on large trees | Extremely fast |
| .gitignore | Doesn’t respect it | Skips ignored files automatically |
| Recursive | Needs -r flag | Recursive by default |
| Binary files | Searches them | Skips them |
Install it:
# Debian/Ubuntu
sudo apt install ripgrep
# Fedora/RHEL
sudo dnf install ripgrepMethod 2: :vimgrep (Built-in)#
:vimgrep uses Vim’s own regex engine. Slower than external grep, but available everywhere and uses familiar Vim regex syntax.
" Search for a pattern
:vimgrep /pattern/ **/*.js
" Vim regex features work
:vimgrep /function\s\+\w\+/ **/*.js
:vimgrep /TODO\|FIXME\|HACK/ **/*
" Case-insensitive with \c
:vimgrep /\cerror/ **/*.pyWhen to use vimgrep over grep#
- When you need Vim’s regex syntax (lookaheads,
\vvery magic, etc.) - When you don’t have ripgrep or ag installed
- When working on a remote system with limited tools
Navigating Results#
Both :grep and :vimgrep populate the quickfix list. Navigate with:
| Command | Action |
|---|---|
:copen | Open the quickfix window |
:cclose | Close it |
:cnext / :cn | Jump to next match |
:cprev / :cp | Jump to previous match |
:cfirst | Jump to first match |
:clast | Jump to last match |
:cc 5 | Jump to match #5 |
Recommended mappings#
" In .vimrc
nnoremap ]q :cnext<CR>
nnoremap [q :cprev<CR>
nnoremap <leader>co :copen<CR>
nnoremap <leader>cc :cclose<CR>Filtering Results#
Narrow down after searching#
" Keep only results matching a second pattern
:cfilter /controller/
" Remove results matching a pattern
:cfilter! /test/
:cfilter! /node_modules/Previous search results#
Vim keeps a stack of quickfix lists:
:colder " Go back to previous search results
:cnewer " Go forward
:chistory " Show all stored listsSearching for the Word Under the Cursor#
Quick project-wide search#
" Search for the exact word under cursor across the project
:grep <cword>Map it for one-keystroke project search:
" In .vimrc — press <leader>g to grep for word under cursor
nnoremap <leader>g :grep <cword><CR>:copen<CR>Now: cursor on processOrder, press <leader>g, and every occurrence across the project appears in the quickfix list.
Search and Replace Across a Project#
Using grep + cfdo#
" 1. Find all occurrences
:grep "oldFunction"
" 2. Replace in all files that matched
:cfdo %s/oldFunction/newFunction/gc | updatecfdo runs a command on every file in the quickfix list. The | update saves each file.
Using arglist#
" 1. Load files into arglist
:args **/*.js
" 2. Replace across all of them
:argdo %s/oldFunction/newFunction/gc | updateWith confirmation at each match#
The c flag in both approaches asks you at each occurrence — safe for renaming where context matters.
Searching for Specific Patterns#
Find function definitions#
" JavaScript/TypeScript
:grep "function processOrder"
:grep "const processOrder ="
" Python
:grep "def process_order"
" C
:grep "int process_order("Find TODO/FIXME comments#
:grep "TODO\|FIXME\|HACK\|XXX"
:copenFind imports/requires#
" JavaScript
:grep "require.*database"
:grep "import.*from.*utils"
" Python
:grep "from.*import\|import.*"Find configuration values#
:grep "DATABASE_URL\|DB_HOST\|DB_PORT"Live Search with :grep Workflow#
A fast iterative workflow:
" 1. Search broadly
:grep "order"
" 2. Too many results — narrow down
:cfilter /process/
" 3. Still too many — remove test files
:cfilter! /test/
" 4. Now browse the focused results
:cnext
:cnextSearching Within Specific Directories#
" Only in src/
:grep "pattern" src/
" Only in tests
:grep "pattern" tests/
" Exclude a directory (ripgrep)
:grep "pattern" --glob "!node_modules"
" Only specific file types (ripgrep)
:grep "pattern" -t py
:grep "pattern" -t jsUsing the Location List for Parallel Searches#
The quickfix list is global — one list at a time. The location list is per-window, so you can have multiple searches open:
" Search in one split
:lgrep "functionA" **/*.js
:lopen
" Switch to another split, different search
:lgrep "functionB" **/*.js
:lopenNavigate with :lnext/:lprev instead of :cnext/:cprev.
Complete .vimrc Setup for Project Search#
" Use ripgrep for :grep
set grepprg=rg\ --vimgrep\ --smart-case
set grepformat=%f:%l:%c:%m
" Quick search for word under cursor
nnoremap <leader>g :grep <cword><CR>:copen<CR>
" Prompt for a search term
nnoremap <leader>/ :grep ""<Left>
" Quickfix navigation
nnoremap ]q :cnext<CR>
nnoremap [q :cprev<CR>
nnoremap <leader>co :copen<CR>
nnoremap <leader>cc :cclose<CR>With this setup:
<leader>g— instantly search for the word under your cursor<leader>/— type a search term and hit Enter]q/[q— step through results<leader>co/<leader>cc— open/close the results window
Best Practices#
- Install ripgrep and set it as
grepprg— the speed difference on real projects is enormous - Use
:grepover:vimgrepfor project searches — external tools are always faster - Map
<leader>gto grep the word under cursor — it becomes your “find usages” shortcut - Use
:cfilterto narrow results instead of re-running the search - Use
:colder/:cnewerto keep previous search results accessible - Combine with
:cfdofor project-wide find-and-replace - Use the location list (
:lgrep) when you need multiple searches open in different splits - Add
--smart-caseto ripgrep so searches are case-insensitive unless you use uppercase


