13

When I search for something /search-term and it shows up more than 100 times in my file, vim shows me that I'm on match x/>99 instead of x/121 etc.

For smaller numbers it shows me the exact number of total matches: 10/23, but with more than 99 total matches it doesn't show the exact number of matches anymore.

How do I make it show me the total exact number of matches anyway?

1
  • 1
    not possible, this limit is hard coded for performance reasons. Commented Jul 1, 2020 at 9:25

7 Answers 7

9

If your Vim binary includes the patch 8.2.0877, you can get the search statistics via the searchcount() function. And the latter is not limited to 99 matches.

You can invoke it right after a / search with a CmdlineLeave autocmd, and right after a n motion by installing a wrapper mapping around the latter.

Example:

const s:MAXCOUNT = 1000
const s:TIMEOUT = 500

augroup index_after_slash | au!
    au CmdlineLeave /,\? call s:index_after_slash()
augroup END

fu s:index_after_slash() abort
    if getcmdline() is# '' || state() =~# 'm'
        return
    endif
    call timer_start(0, {-> mode() =~# '[nv]' ? s:search_index() : 0})
endfu

fu s:search_index() abort
    try
        let result = searchcount(#{maxcount: s:MAXCOUNT, timeout: s:TIMEOUT})
        let [current, total, incomplete] = [result.current, result.total, result.incomplete]
    catch
        echohl ErrorMsg | echom v:exception | echohl NONE
        return ''
    endtry
    let msg = ''
    let pat = substitute(@/, '\%x00', '^@', 'g')
    if incomplete == 0
        let msg = printf('[%*d/%d] %s', len(total), current, total, pat)
    elseif incomplete == 1 " recomputing took too much time
        let msg = printf('[?/??] '..%s', pat)
    elseif incomplete == 2 " too many matches
        if result.total == (result.maxcount+1) && result.current <= result.maxcount
            let msg = printf('[%*d/>%d] %s', len(total-1), current, total-1, pat)
        else
            let msg = printf('[>%*d/>%d] %s', len(total-1), current-1, total-1, pat)
        endif
    endif
    if strchars(msg, 1) > (v:echospace + (&cmdheight-1)*&columns)
        let n = v:echospace - 3
        let [n1, n2] = n%2 ? [n/2, n/2] : [n/2-1, n/2]
        let msg = matchlist(msg, '\(.\{' .. n1 .. '}\).*\(.\{' .. n2 .. '}\)')[1:2]->join('...')
    endif
    echo msg
    return ''
endfu

nmap n <plug>(n)<plug>(search_index)
nmap N <plug>(N)<plug>(search_index)
nno <plug>(n) n
nno <plug>(N) N
nno <expr> <plug>(search_index) <sid>search_index()

searchcount() won't show a total amount of matches greater than 1000. If that's not enough, increase s:MAXCOUNT. And it will stop trying to compute the number of matches after half-a-second. If that's too long, decrease s:TIMEOUT.

Note that increasing s:MAXCOUNT and s:TIMEOUT may have a negative impact on Vim's performance. The values used in the previous snippet work for me; they may or may not work for you. I guess it depends on the machine you're using and/or on the patterns you're usually looking for. Take that into consideration before setting these parameters.

You can improve the performance by rewriting the code in Vim9 script:

vim9script

const MAXCOUNT: number = 1'000
const TIMEOUT: number = 500

augroup index_after_slash | au!
    au CmdlineLeave /,\? IndexAfterSlash()
augroup END

def IndexAfterSlash()
    if getcmdline() == '' || state() =~ 'm'
        return
    endif
    timer_start(0, () => mode() =~ '[nv]' ? SearchIndex() : 0)
enddef

def SearchIndex(): string
    var incomplete: number
    var total: number
    var current: number
    var result: dict<any>
    try
        result = searchcount({maxcount: MAXCOUNT, timeout: TIMEOUT})
        current = result.current
        total = result.total
        incomplete = result.incomplete
    catch
        echohl ErrorMsg | echom v:exception | echohl NONE
        return ''
    endtry
    var msg: string = ''
    var pat: string = getreg('/')->substitute('\%x00', '^@', 'g')
    if incomplete == 0
        msg = printf('[%*d/%d] %s', len(total), current, total, pat)
    elseif incomplete == 1 # recomputing took too much time
        msg = printf('[?/??] %s', pat)
    elseif incomplete == 2 # too many matches
        if result.total == (result.maxcount + 1) && result.current <= result.maxcount
            msg = printf('[%*d/>%d] %s', len(total - 1), current, total - 1, pat)
        else
            msg = printf('[>%*d/>%d] %s', len(total - 1), current - 1, total - 1, pat)
        endif
    endif
    if strchars(msg, 1) > (v:echospace + (&cmdheight - 1) * &columns)
        var n: number = v:echospace - 3
        var n1: number = n % 2 ? n / 2 : n / 2 - 1
        var n2: number = n / 2
        var matchlist: list<string> = matchlist(msg, '\(.\{' .. n1 .. '}\).*\(.\{' .. n2 .. '}\)')
        msg = matchlist[1] .. '...' .. matchlist[2]
    endif
    echo msg
    return ''
enddef

nmap n <plug>(n)<plug>(search_index)
nmap N <plug>(N)<plug>(search_index)
nno <plug>(n) n
nno <plug>(N) N
nno <expr> <plug>(search_index) <sid>SearchIndex()

This requires a recent Vim version. It works on 8.2.2332.


For more info, see:

1
  • 2
    Really cool answer! Maybe it would be worth adding a warning about the potential performances issues when you increase s:MAXCOUNT and s:TIMEOUT because originally that's why vim has the limits I mention in my answer. But anyway I didn't know this function so that a very interesting answer.
    – statox
    Commented Jul 2, 2020 at 7:09
8

I assume you are talking of the built-in search counter here enter image description here

First for users who don't know, this search counter is controlled by the S flag in shortmess :h 'shortmess'

  S     do not show search count message when searching, e.g.
        "[1/5]"

but the doc doesn't mention how to change the limit, so let's have a look at vim source code:

here we have the define #define SEARCH_STAT_DEF_MAX_COUNT 99.

This value is passed to cmdline_search_stat here.

And here in cmdline_search_stat you can see how this value is used. When there is more than 99 matches the source code will use this limit:

if (stat.incomplete == 1)
    vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
else if (stat.cnt > maxcount && stat.cur > maxcount)
    vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", maxcount, maxcount);
else if (stat.cnt > maxcount)
    vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", stat.cur, maxcount);
else
    vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", stat.cur, stat.cnt);

So you can't change that without changing Vim's sources.

And for neovim the answer is the same because the code is pretty similar it just doesn't use a constant to hold the 99 limit.

4
  • Is it possible in neovim?
    – user9024
    Commented Jul 1, 2020 at 9:47
  • Probably not but I'll let you check the docs and the code by yourself :)
    – statox
    Commented Jul 1, 2020 at 9:53
  • I don't think so, since this feature comes from a vim patch and was later merged into neovim. Commented Jul 1, 2020 at 10:21
  • @ChristianBrabandt Indeed the code is pretty similar
    – statox
    Commented Jul 1, 2020 at 10:52
2

There are several plugins that support showing the number of matchings, for example, vim-indexed-search. In the alternatives section, you can also find several similar plugins.

1

I use Anzu which counts up to 1000 results by default.

You can change this limit:

let g:anzu_search_limit = 1000
0
1

For a quick check, try:

:lua print(vim.inspect(vim.fn.searchcount { maxcount = 0 }))

Example output:

{
  current = 314,
  exact_match = 1,
  incomplete = 0,
  maxcount = 0,
  total = 21782
}

Replacing the statusline

/u/Adk9p provides the following lua-based method:

You can't change the max value that is shown in the msgbuf without recompiling. The 99 value comes from a macro in the code (the macro, where it's used).

What you can do instead is hide it with set shortmess +=S (:h shortmess) and then create your own display in your statusline/winbar using :h searchcount()

if vim.v.hlsearch == 1 then
  local sinfo = vim.fn.searchcount { maxcount = 0 }
  local search_stat = sinfo.incomplete > 0 and '[?/?]'
    or sinfo.total > 0 and ('[%s/%s]'):format(sinfo.current, sinfo.total)
    or nil

  if search_stat ~= nil then
    -- add search_stat to statusline/winbar
  end
end

...Now, just return search_stat and call the function from your statusline appropriately. (Or simpler and less likely to hurt performance: just map it to a keybinding.)

A few useful performance tweaks for this would be to cache calculations until a new search is performed or the cursor moves to the next position. I'm not sure if neovim already does some of this internally. Also, limit the file size (e.g. 10MB), and limit maxcount = 100000.

1
  • @D.BenKnoble To help heat up a cold room, of course. :) Commented Oct 25, 2023 at 2:41
0

As others have pointed out both vim/neovim have this limit hardcoded, presumably for performance reasons. It seems very conservative in these days.

For a non-built in solution i find FZF's :BLines the quickest and best solution.

0

There is very neat plugin https://github.com/google/vim-searchindex, which does exactly that!

1
  • According to its readme it's been in Vim and neovim a long time already: "Note: This behavior is now supported natively in Vim 8.1.1270 and Neovim 0.4.0"
    – user9024
    Commented Aug 6 at 8:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.