Last active
January 19, 2026 15:43
-
-
Save Konfekt/d6bf2941abd810768992368b4f069fb3 to your computer and use it in GitHub Desktop.
Set &grepprg / &findfunc to git-grep / git-ls-files inside repo and fall back rg, ugrep or grep / fd, ... outside of it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| " Set &grepprg to git-grep inside repo, fall back rg, ugrep or grep otherwise. | |
| " Assumes a global ignore file `$XDG_CONFIG_HOME/grep/ignore`; create it by, | |
| " say `touch ~/.config/grep/ignore` to ensure its existence. | |
| augroup vimrcFindGrep | |
| autocmd! | |
| augroup END | |
| if has('unix') && executable('chrt') && executable('ionice') | |
| let s:scheduler = 'chrt --idle 0 ionice -c2 -n7 ' | |
| " " hard getting escaping right | |
| " elseif has('win32') | |
| " let s:scheduler = (&shell =~? '\v(^|\\)cmd\.exe$' ? '' : 'cmd.exe ') .. 'start /B /LOW ' | |
| else | |
| let s:scheduler = '' | |
| endif | |
| let s:nul = ' 2> ' .. (has('win32') ? 'nul' : '/dev/null') | |
| let s:smartcase = &ignorecase && &smartcase ? '--smart-case' : (&ignorecase ? '--ignore-case' : '--case-sensitive') | |
| if !filereadable(expand('$XDG_CONFIG_HOME/grep/ignore')) | |
| call writefile(['.git', 'tags', '!tags/'], expand('$XDG_CONFIG_HOME/grep/ignore')) | |
| endif | |
| set grepformat=%f:%l:%c:%m,%f:%l:%m | |
| if executable('rg') | |
| let &g:grepprg = s:scheduler .. 'rg --multiline --follow --no-heading --ignore-file=' .. expand('$XDG_CONFIG_HOME/grep/ignore') .. ' --with-filename --line-number --column ' .. s:smartcase .. ' --color never $*' .. s:nul | |
| elseif executable('ugrep') | |
| " From https://github.com/Genivia/ugrep#using-ugrep-within-vim | |
| let &g:grepprg = s:scheduler .. 'ugrep -R -n --ignore-binary --ungroup --tabs=1 --ignore-files=' .. expand('$XDG_CONFIG_HOME/grep/ignore') .. ' --column-number ' .. s:smartcase .. ' --color=never --no-messages -- $*' .. s:nul | |
| setglobal grepformat=%f:%l:%c:%m,%f+%l+%c+%m,%-G%f\\\|%l\\\|%c\\\|%m | |
| elseif executable('grep') | |
| let &g:grepprg = s:scheduler .. 'grep -rnHIsE $* --exclude=tags --exclude-dir=.git' .. s:nul | |
| endif | |
| if has('patch-9.1.0810') " = if exists('&findfunc') | |
| let s:filescache = [] | |
| let s:fd = executable('fd') ? 'fd' : (executable('fdfind') ? 'fdfind' : '') | |
| if !empty(s:fd) | |
| let g:findcmd = s:fd .. ' --type=file --full-path --path-separator=/ --hidden --follow --exclude=.git --exclude=tags --ignore-file='..expand('$XDG_CONFIG_HOME/grep/ignore')..' --strip-cwd-prefix --color=never '..(has('win32') ? '--fixed-strings ' : '')..' ""' | |
| elseif executable('rg') | |
| let g:findcmd = 'rg --files --path-separator=/ --hidden --follow --no-messages --ignore-file='..expand('$XDG_CONFIG_HOME/grep/ignore')..' --no-ignore --color never --glob=!.git --glob=!tags --glob=""' | |
| elseif executable('ugrep') | |
| let g:findcmd = 'ugrep -Rl -I --ignore-files='..expand('$XDG_CONFIG_HOME/grep/ignore')..' --exclude-dir=.git --exclude=tag --color=never ""' | |
| else | |
| if has('win32') | |
| let s:pwsh = executable('pwsh') ? 'pwsh' : (executable('powershell') ? 'powershell' : '') | |
| if !empty(s:pwsh) | |
| let g:findcmd = s:pwsh .. ' -NoProfile -Command "Get-ChildItem . -Name -File -Recurse -Force | Where-Object { $_ -NotLike ''*\.git\*'' -and $_ -NotLike ''.git\*'' -and $_ -NotLike ''tags'' }"' | |
| " " Vim's built-in globbing is quite efficient | |
| " else | |
| " let g:findcmd = 'dir . /s/b/a:-d-h' | |
| endif | |
| elseif has('unix') | |
| let g:findcmd = 'find -L . -type f -not -path "*/.git/*" -not -name tags -print' | |
| endif | |
| let g:findcmd = '' | |
| endif | |
| " See :help live-grep | |
| func Find(arg, _) | |
| try | |
| if empty(s:filescache) | |
| if empty(g:findcmd) | |
| let s:filescache = globpath('.', '**', 1, 1) | |
| call filter(s:filescache, '!isdirectory(v:val)') | |
| call map(s:filescache, "fnamemodify(v:val, ':.')") | |
| else | |
| let s:filescache = systemlist(s:scheduler .. g:findcmd .. s:nul) | |
| if has('win32') && stridx(g:findcmd, s:pwsh) == 0 | |
| " strip trailing CR characters from powershell output | |
| call map(s:filescache, 'trim(v:val)') | |
| endif | |
| endif | |
| " Clean up filescache, may contain tens of thousands of paths | |
| " Note: cannot use CmdlineLeave as it applies before leaving the | |
| " command line and before &findfunc is used to find the file to edit | |
| autocmd BufEnter * ++once let s:filescache = [] | |
| endif | |
| " limit matches to avoid wildmenu list hiding cmdline | |
| let max = (&lines - &cmdheight - 1) | |
| return empty(a:arg) ? s:filescache : slice(matchfuzzy(s:filescache, a:arg), 0, max) | |
| catch /^Vim:Interrupt/ | |
| " avoid E1514: 'findfunc' did not return a List type | |
| return [] | |
| endtry | |
| endfunc | |
| autocmd vimrcFindGrep CmdlineEnter : let s:filescache = [] | |
| set findfunc=Find | |
| endif | |
| if executable('git') | |
| function! s:outside_repo() | |
| if exists('*FugitiveGitDir') | |
| return empty(FugitiveGitDir()) | |
| else | |
| " let outside_repo = empty(finddir('.git', getcwd().';')) && empty(findfile('.git', getcwd().';')) | |
| silent let repo = system('git rev-parse --is-inside-work-tree' .. s:nul .. ' 1>&2') | |
| return v:shell_error != 0 | |
| endif | |
| endfunction | |
| let s:grepprg = &g:grepprg | |
| let s:grepformat = &g:grepformat | |
| let s:git_grep = &ignorecase && &smartcase && executable('git-grep') ? | |
| \ 'git-grep' : (&ignorecase ? 'git grep --ignore-case' : 'git grep') | |
| let s:git_grep = s:scheduler .. s:git_grep .. ' --exclude-standard --column --line-number -I --untracked --extended-regexp --no-color $*' .. s:nul | |
| function! s:SetGrepPrg() | |
| if s:outside_repo() | |
| let &g:grepformat= s:grepformat | |
| let &g:grepprg = s:grepprg | |
| else | |
| setglobal grepformat=%f:%l:%c:%m | |
| let &g:grepprg = s:git_grep | |
| endif | |
| endfunction | |
| autocmd vimrcFindGrep VimEnter,DirChanged * call <SID>SetGrepPrg() | |
| if has('patch-9.1.0810') " = if exists('&findfunc') | |
| let s:findcmd = g:findcmd | |
| let s:git_ls = s:scheduler .. 'git ls-files . --exclude-from=' .. expand('$XDG_CONFIG_HOME/grep/ignore') .. ' --exclude-standard --cached --others' .. s:nul | |
| function! s:SetFindCmd() | |
| let g:findcmd = s:outside_repo() ? s:findcmd : s:git_ls | |
| endfunction | |
| autocmd vimrcFindGrep VimEnter,DirChanged * call <SID>SetFindCmd() | |
| endif | |
| endif | |
| if has('patch-9.1.1329') " = if exists('##CmdlineLeavePre') | |
| " See :help live-grep | |
| " Consider an Alias such as :Alias l LiveGrep | |
| command! -nargs=+ -complete=customlist,<SID>Grep LiveGrep call <SID>VisitFile() | |
| func s:Grep(arglead, cmdline, cursorpos) | |
| if match(&grepprg, '\$\*') == -1 | let &grepprg .= ' $*' | endif | |
| let cmd = substitute(&grepprg, '\$\*', shellescape(escape(a:arglead, '\')), '') | |
| return len(a:arglead) > 1 ? systemlist(cmd) : [] | |
| endfunc | |
| func s:VisitFile() | |
| let item = getqflist(#{lines: [s:selected]}).items[0] | |
| let pos = '[0,\ item.lnum,\ item.col,\ 0]' | |
| exe $':b +call\ setpos(".",\ {pos}) {item.bufnr}' | |
| call setbufvar(item.bufnr, '&buflisted', 1) | |
| endfunc | |
| " Automatically select the first item in the completion list when leaving the | |
| " command-line, and for `:Grep`, add the typed pattern to the command-line history: | |
| autocmd vimrcFindGrep CmdlineLeavePre : | |
| \ if get(cmdcomplete_info(), 'matches', []) != [] | | |
| \ let s:info = cmdcomplete_info() | | |
| \ if getcmdline() =~ '^\s*fin\%[d]\s' && s:info.selected == -1 | | |
| \ call setcmdline($'find {s:info.matches[0]}') | | |
| \ endif | | |
| \ if getcmdline() =~# '^\s*LiveGrep\s' | | |
| \ let s:selected = s:info.selected != -1 | |
| \ ? s:info.matches[s:info.selected] : s:info.matches[0] | | |
| \ call setcmdline(s:info.cmdline_orig) | | |
| \ endif | | |
| \ endif | |
| " autocomplete matches for :find and :LiveGrep | |
| if has('patch-9.0.1576') " = if exists('*wildtrigger') | |
| augroup vimrcWildTrigger | |
| autocmd! | |
| augroup END | |
| autocmd vimrcFindGrep CmdlineChanged : | |
| \ if getcmdline() =~# '\v^\s*%(fin%[d]|LiveGrep)\s\S' && !exists('s:wildtrigger') | call s:wildtrigger() | endif | |
| function! s:wildtrigger() | |
| let s:wildmode = &wildmode | |
| set wildmode=noselect,full | |
| autocmd vimrcWildTrigger CmdlineChanged : call wildtrigger() | |
| let s:wildtrigger = 1 | |
| autocmd vimrcWildTrigger CmdlineLeavePre : ++once | |
| \ unlet! s:wildtrigger | let &wildmode = s:wildmode | | |
| \ autocmd! vimrcWildTrigger CmdlineChanged : | |
| endfunction | |
| endif | |
| endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See also https://gist.github.com/andlrc/c8e1a3b9c1ec5c761111ea0e49bda6c4