5

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.

Community
  • 1
  • 1
labyrinth
  • 13,397
  • 7
  • 35
  • 44

2 Answers2

7

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
Community
  • 1
  • 1
ib.
  • 27,830
  • 11
  • 80
  • 100
  • Here's what I tried for

    tags, but it didn't work: `bufdo %s/\c

    \ze//gen let n=[0]|bufdo %s//\=map(n,'v:val+1')[1:]/gen echo "number of

    tags: " n[0] "\n"`

    – labyrinth Nov 10 '11 at 05:43
  • @labyrinth: Do not use the `n` flag in this substitution command, since it orders Vim to ignore and not to evaluate the substitute part that does all the work! Just use `:let n=[0] | bufdo %s/\c

    \ze/\=map(n,'v:val+1')[1:]/ge`.

    – ib. Nov 10 '11 at 05:52
  • You can't use `:let`, but you can use `g:` dictionary: `bufdo %s/\c

    \zs/\=[extend(g:, {'numptags': get(g:, 'numptags', 0)+1})][1:]/ge`.

    – ZyX Nov 12 '11 at 11:44
  • @ZyX: That is exactly what I said: to change a variable in expression you have to use functions modifying a list or a dictionary (since variables store that objects as references). I have considered using the dictionary containing global variables, but that kind of a solution is neither simpler nor shorter. And it has a disadvantage of necessity of polluting the globals namespace. So, if one prefers to use a dictionary it is probably better to use a custom dictionary (similarly to the list example in the answer) instead of `g:`. Anyway, thanks for the addition! – ib. Nov 13 '11 at 01:48
  • @ib. You are already polluting global namespace with `n`. If it is to be used from function, you can always replace `g:` with `l:`. – ZyX Nov 13 '11 at 14:38
  • @ZyX: Please be careful in wording: Whether the variable is in global namespace or not, depends on the context that command is run. If it is inside a function, the global namespace is untouched. Of course, one can use the `l:` dictionary of local variables, but it does not give any significant advantages over using a separate dictionary or a list (regardless of whether they are local or global variables). Furthermore, in this case, dictionary solutions are a little more bloated. – ib. Nov 14 '11 at 02:07
0

I think that answer above is hard to understand and more pretty way to use external command grep like this:

:let found=0
:bufdo let found=found+(system('grep "<p>" '.expand('%:p') . '| wc -l'))
:echo found
Tahimoto
  • 36
  • 3