8

Is there any way to store the number of matches in a variable in a VIMScript function?

For instance I'm using:

%s/,//gn
7
  • You can't. Could you describe what you are trying to do instead of your failed attempts?
    – romainl
    Commented Mar 22, 2016 at 14:29
  • 4
    Why don't you use a simple substitution? Something like :s/,/,\r/g?
    – romainl
    Commented Mar 22, 2016 at 14:42
  • 1
    I ended up using %s/,\W*/,\r/g because of white space.
    – leeand00
    Commented Mar 22, 2016 at 14:49
  • 3
    The regex for white spaces is \s, \_s if you want to match newlines as well. Commented Mar 22, 2016 at 16:09
  • 1
    It has nothing to do with Windows or Linux. Vim always uses \r to represent EOL in the replacement (and \n in the search) regardless of what line ending is in effect for the buffer. The type of line ending is controlled by the 'fileformat' option.
    – jamessan
    Commented Mar 23, 2016 at 0:42

4 Answers 4

2

There is another way that doesn't move the cursor nor requires :exe, but uses other tricks. Here I count by using a function that modifies and returns something (if += was an expression like in C, it would have been simpler)

command! -nargs=1 -range Count call s:Count2(<f-args>, <line1>,<line2>)

" Not using the usual :function-range to avoid moving the cursor
function! s:Count2(param, firstl, lastl) abort
  let s:c = []
  call map(getline(a:firstl, a:lastl), { k,v -> substitute(v, 'line', '\=add(s:c, v)[-1]', 'g')})
  echo len(s:c)
endfunction
3
  • You might want let l:c, no reason to leak it to the script namespace.
    – D. Ben Knoble
    Commented Jul 18, 2019 at 15:48
  • @D.BenKnoble When I've tested the function, I've tried with local variables, but it failed -- with Vim 8.1-1655. I had to use a more global scope. It looks like a defect though because the old syntax works: call map(getline(a:firstl, a:lastl), "substitute(v:val, 'line', '\\=add(l:c, v:val)[-1]', 'g')"), and I don't even need to add this noisy l: prefix. Commented Jul 18, 2019 at 16:06
  • Thats bizarre...
    – D. Ben Knoble
    Commented Jul 18, 2019 at 16:07
9

I know that @romainl debugged the question as a XY problem in the comments but I guess it still could be useful to some people, so here is a solution. (It is deeply inspired from this answer)

You can use this function (to add to your .vimrc or to your script):

function! Count( word )
  redir => cnt
    silent exe '%s/' . a:word . '//gn'
  redir END

  let res = strpart(cnt, 0, stridx(cnt, " "))
  return res
endfunction

You can call the function like this: :call Count("pattern").

For example in this file:

a,bc
akj,dh
jlkdfa,
oiua ,lkj
oiua, lkj
oiua , lkj
i,

With :echo Count(",") you'll get 7.

2
  • Well in this context Romainl's suggestion wasn't awful because it was really the best solution for OP's context. It's still nice if the answer was helpful for you! :)
    – statox
    Commented Jun 21, 2018 at 8:37
  • This actually returns a ^@ in-front of the result, so can't really be used. e.g. echo Count("word") + 1 will always return 1. See my amended answer below. Commented Jul 18, 2019 at 12:06
2

Warning: The accepted answer does not return a number or number-like object. This does.

function! Count( word )
  redir => cnt
    silent exe '%s/' . a:word . '//n'
  redir END
  return matchstr( cnt, '\d\+' )
endfunction

With the same instructions as the accepted answer, except you can actually perform further calculations with the result, e.g.:

echo Count("word") + 2

or something superbly more complex:

function! MyMatch()
  let results = ingo#text#frompattern#Get(1, '$', 'j\w\{1,5\} \zs\(loc\|lab\)_\w\+\ze', '', '', '1')
  let counter=1
  for result in results
    if Count(result) != 2
      execute '%s/' . result . '/label' . counter . '/g'
      let counter+=1
    endif
  endfor
endfun
2
  • It's cool to see an answer improved 3 years after it was posted :) Well done!
    – statox
    Commented Jul 18, 2019 at 12:12
  • 1
    @statox my stackexchange golden rule: "If you can't copy the solution from someone else's hard work, then submit your own hard work where possible." a.k.a. "Nobody likes a leech." Commented Aug 16, 2019 at 16:14
1

If you have a new enough version of vim which suport :h execute() , you can get count like this:

function! GetCount(pattern)
  let l:cnt = 0
  silent exe '%s/' . a:pattern . '/\=execute(''let l:cnt += 1'')/gn'
  return l:cnt
endfunction

In fact you can get all the matches and put them in a list:

function! GetMatches(pattern)
  let l:matches = []
  silent exe '%s/' . a:pattern .
        \ '/\=execute(''let l:matches += [submatch(0)]'')/gn'
  return l:matches
endfunction

update

As Christian Brabandt pointed out, you can use :h add() , it's simpler :

function! GetMatches(pattern)
  let l:matches = []
  silent exe '%s/' . a:pattern . '/\=add(l:matches, submatch(0))/gn'
  return l:matches
endfunction
2
  • 1
    simpler: \=add(matches, submatch(0))/gn, don't need execute() for that. Commented Jul 18, 2019 at 14:43
  • @ChristianBrabandt Thanks, updated.
    – dedowsdi
    Commented Jul 19, 2019 at 0:06

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.