Search code examples
searchvimmatchbufferlines

How to check if line has match when I search something and catch the linenumber?


I want to catch all linenumbers in the buffer with 1 or more matches.

Till now I used this code:

let lines = []

for line in range(startline, endline)
  let @e = ''
  :redir @e
  :silent exe line.'s/'.mysearch.'/&/gne'
  :redir END
  if matchstr(@e, 'match') != ''
    let nummer = matchstr(@e, '\d\+\ze')
    if nummer > 0
      call add(lines, ''.line.'')
    endif
  endif
endfor

But this is incredible slow.
Is there no way to do this faster?
(p.e. execute search silently: 1 for match and catch the linenumber and 0 for no match)


Solution

  • I would use filter() on getline(1,'$'). To obtain line numbers instead we could play with map() and the new (Vim 8) lambdas. Or may be simply filter (range (1, line ('$')), 'getline (v:val) =~ regex')

    You will hardly find anything faster with vim, especially manual loops.

    EDIT:

    For instance, searching "home" on a 7850 lines long vim log file where it occurs 6438 times:

    • echo lh#time#bench('filter', range (1, line ('$')), 'getline (v:val) =~ "home"') takes 0.033297s
    • :echo lh#time#bench('filter', range (1, line ('$')),{ idx, val -> getline (val) =~ 'home'})[1] takes 0.078506s -- a little bit slower, but the regex is simpler
    • let r=[] + echo = lh#time#bench('map', range(1, line('$')), 'substitute(getline(v:val), "home", "\\=add(g:r, v:val)", "")')[1] takes 8.841528s -- I guess this is the kind of result you have with a manual loop
    • :let start=reltime()|:let lines=filter(map(getline(1, '$'), {idx, val -> val =~ "home"?idx:0}), "v:val>0")|:echo reltimefloat(reltime(start, reltime())) takes: 0.075132s

    BTW, I've also measured on the previous snippets, on my vim 8.0-134, that match() is a little bit (not really perceptible) slower than plain old =~. Don't ask me why.

    IOW, cryptic regexes seems to be the fastest solution, and manual loops shall be avoided at all cost when performances matter.