Using Vim, how do you use a variable to store the number of patterns found?


This question was helpful for getting a count of a certain pattern in Vim, but it would be useful to me to store the count and sum the results so I can echo a concise summary.

I'm teaching a class on basic HTML to some high schoolers, and I'm using this script to be quickly check numbers of required elements throughout all their pages without leaving Vim. It works fine, but when students have more than 10 .html files it gets cumbersome to add up the various sections by hand.

Something like:

img_sum = :bufdo %s/<img>//gen

would be nice. I think I'll write a ruby script to check the pages more thoroughly and check for structure, but for now I'm curious about how to do this in Vim.

The problem can be solved by a counter separate from the one built-in into the :substitute command: Use Vim-script variable to hold the number of pattern matches. A convenient way to register every match and modify a particular variable accordingly, is to take advantage of the substitute with an expression feature of the :substitute command (see :help sub-replace-\=). The idea is to use a substitution that evaluates an expression increasing a counter on every occurrence, and does not change the text it is operating on.

The first part of the technique cannot be implemented straightforwardly because it is forbidden to use Ex commands in expressions (including \= substitute expressions), and therefore it is not possible to use the :let command to modify a variable. Answering the question "gVim find/replace with counter", I have proposed a simple trick to overcome that limitation, which is based on using a single-item list (or dictionary containing a single key-value pair). Since the map() function transforms a list or a dictionary in place, that only item could be changed in a constrained expression context. To do that, one should call the map() function passing an expression evaluating to the new value along with the list containing the current value.

The second half of the technique is how to avoid changing text when using a substitution command. In order to achieve that, one can make the pattern have zero-width by prepending \ze or by appending \zs atoms to it (see :help /\zs, :help /\ze). In such a way, the modified pattern captures a string of zero width just before or after the occurrence of the initial pattern. So, if the replacement text is also empty, substitution does not cause any change in the contents of a buffer. To make the substitute expression evaluate to an empty string, one can just extract an empty substring or sublist from the resulting value of that expression.

The two ideas are put into action in the following command.

:let n=[0] | bufdo %s/pattern\zs/\=map(n,'v:val+1')[1:]/ge