Is there a way to insert the value from some sort of counter variable in Vim :substitute
command?
For instance, to convert this document:
<SomeElement Id="F" ... />
<SomeElement Id="F" ... />
<SomeElement Id="F" ... />
to this resulting document:
<SomeElement Id="1" ... />
<SomeElement Id="2" ... />
<SomeElement Id="3" ... />
I imagine, the command would look like so:
:%s/^\(\s*<SomeElement Id="\)F\(".*\)$/\1<insert-counter-here>\2/g
I am using a very recent Windows build, from their provided installer. I strongly prefer not to install any additional tools. Ideally, I'd like to also avoid having to install scripts to support this, but I'm willing to, if it is the only way to do it.
Basic Find and Replace In Vim, you can find and replace text using the :substitute ( :s ) command. To run commands in Vim, you must be in normal mode, the default mode when starting the editor. To go back to normal mode from any other mode, just press the 'Esc' key.
Change and repeat Search for text using / or for a word using * . In normal mode, type cgn (change the next search hit) then immediately type the replacement. Press Esc to finish. From normal mode, search for the next occurrence that you want to replace ( n ) and press . to repeat the last change.
Vim has several regex modes, one of which is very magic that's very similar to traditional regex. Just put \v in the front and you won't have to escape as much.
It is possible to have a counter using the substitute-with-an-expression
feature (see :help sub-replace-\=
). Unfortunately, since the
\=
construct allows only expressions, the :let
command cannot
be used, and therefore, a variable cannot not be set the usual way.
However, there is a simple trick to change the value of a variable in
expression if that variable is a list or a dictionary. In that case,
its contents could be modified by the map()
function.
In such a manner, substitution for the case described in the question would look as follows:
:let n=[0] | %s/Id="F"/\='Id="'.map(n,'v:val+1')[0].'"'/g
The tricky part here is in the substitute part of the replacement.
Since it starts with \=
, the rest of it is interpreted
as an expression by Vim. Thus, 'Id="'.map(n, 'v:val+1').'"'
is an ordinary expression. Here a string literal 'Id="'
is concatenated (using the .
operator) with return value
of the function call map(n, 'v:val+1')
, and with another
string, '"'
. The map
function expects two arguments:
a list (as in this case) or a dictionary, and a string containing
expression that should be evaluated for each of the items in the given
list or dictionary. Special variable v:val
denotes an individual
list item. So the 'v:val+1'
string will be evaluated to a list item
incremented by one.
In this case, we can even simplify the command further:
:let n=[0] | %s/Id="\zsF\ze"/\=map(n,'v:val+1')[0]/g
The \zs
and \ze
pattern atoms are used to set the start and
the end of the pattern to replace, respectively (see :help /\zs
and :help /\ze
). That way the whole search part of the substitute
command is matched, but only the part between \zs
and \ze
is
replaced. This avoids clumsy concatenations in the substitute
expression.
Either of these two short one-liners completely solves the issue.
For frequent replacements, one can even define an auxiliary function
function! Inc(x)
let a:x[0] += 1
return a:x[0]
endfunction
and make substitution commands even shorter:
:let n=[0] | %s/Id="\zsF\ze"/\=Inc(n)/g
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With