Is there any way to store the number of matches in a variable in a VIMScript function?
For instance I'm using:
%s/,//gn
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
let l:c
, no reason to leak it to the script namespace.
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
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
.
^@
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
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
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
\=add(matches, submatch(0))/gn
, don't need execute()
for that.
Commented
Jul 18, 2019 at 14:43
:s/,/,\r/g
?%s/,\W*/,\r/g
because of white space.\s
,\_s
if you want to match newlines as well.\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.