6

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

For instance I'm using:

%s/,//gn
  • You can't. Could you describe what you are trying to do instead of your failed attempts? – romainl Mar 22 '16 at 14:29
  • 4
    Why don't you use a simple substitution? Something like :s/,/,\r/g? – romainl Mar 22 '16 at 14:42
  • 1
    I ended up using %s/,\W*/,\r/g because of white space. – leeand00 Mar 22 '16 at 14:49
  • 3
    The regex for white spaces is \s, \_s if you want to match newlines as well. – Luc Hermitte Mar 22 '16 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 Mar 23 '16 at 0:42
1

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
  • You might want let l:c, no reason to leak it to the script namespace. – D. Ben Knoble 23 hours ago
  • @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. – Luc Hermitte 23 hours ago
  • Thats bizarre... – D. Ben Knoble 22 hours ago
7

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.

  • You were right that someone else would want this answer. I am trying to debug a problem where some array is accidentally allocated twice, and have to search through about a hundred statements -- or just 'count' the occurrences of each variable with a macro, and find the odd man out. "Do Y instead of X" is really an awful answer. – Luke Davis Jun 20 '18 at 19:36
  • 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 Jun 21 '18 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. – Orwellophile yesterday
1

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
New contributor
Orwellophile is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
  • It's cool to see an answer improved 3 years after it was posted :) Well done! – statox yesterday
0

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
  • 1
    simpler: \=add(matches, submatch(0))/gn, don't need execute() for that. – Christian Brabandt yesterday
  • @ChristianBrabandt Thanks, updated. – dedowsdi 15 hours ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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