How g makes loop in Vim ex command script
Asked Answered
R

2

6

Consider the following Vim ex command,

:let i=1 | '<,'>g/^/ s/^\ *-/\=i/ | let i+=1

It replaces the heading dash with ordered number in selected lines.

I don't understand why this command works as a loop from the first line to the last line of the selected lines. That is, how g can repeat let i+=1 over and over again.

Riendeau answered 9/2, 2015 at 5:56 Comment(0)
V
1

This is the general pattern of a :global command:

:g/foo/command

Because everything after the second separator is considered as one command, the counter is incremented each time the command is executed: one time for each matching line.

Venditti answered 9/2, 2015 at 6:13 Comment(1)
"Everything after the second separator is considered as one command." That's interesting! Thanks for answering @Venditti :DRiendeau
A
10

The pattern of a global command is:

:range g[lobal][!]/pattern/cmd

The global commands work by first scanning through the [range] of of the lines and marking each line where a match occurs. In a second scan the [cmd] is executed for each marked line with its line number prepended. If a line is changed or deleted its mark disappears. The default for the [range] is the whole file. (see http://vimregex.com/#global for more details)

Now let's analyse

:let i=1 | '<,'>g/^/ s/^\ *-/\=i/ | let i+=1

step by step.

  1. let i=1 is a single command executed setting the basic number for the loop. We can just execute it alone at the very beginning. Then '<,'>g/^/ s/^\ *-/\=i/ | let i+=1 looks a little more like a global command.
  2. '<,'>g defines the range. '< represents the first line and '> represents the last line of the selected area. (:help '< for more details)
  3. ^ of course matches every line in range.
  4. s/^\ *-/\=i/ | let i+=1 is the [cmd], the number of times it will be executed equals to the number of lines in the selected area, and this is the most important reason why the loop took place.
  5. The part before | is a typical substitute command :range s[ubstitute]/pattern/string/ (see http://vimregex.com/#substitute for more details)
  6. ^\ *- matches 0 or more whitespace followed by a dash at the beginning of a line. We substitute \=i for this pattern. (:help :s\= for more details)
  7. After s/^\ *-/\=i/, let i+=1 is executed. Then the next line, ... , till the last line of selected area.
  8. For better understanding that s/^\ *-/\=i/ | let i+=1 is a [cmd] as a whole, we can change the order of the two [sub-cmd], obtaining let i+=1 | s/^\ *-/\=i/. But for the same effect, let i=0 at the very beginning is essential.
Aiden answered 10/2, 2015 at 9:1 Comment(4)
Is there a way to put some other characters with \=i? It seems it doesn't work for me. For example, :let i=1 | '<,'>g/^/ s/^\ *-/[\=i]/ | let i+=1Lewin
I recommend :help sub-replace-expression for info on \=i, as :help :s\= currently points you there.Vinyl
@Lewin use :let i=1 | '<,'>g/^/ s/^\ *-/\="[" . i . "]"/ | let i+=1 to make it workAnselma
Thank you so much! Your detailed explanation helped me concoct a substitution of incrementing numbers that didn't require a visual '<,'> selection. (which was :let i=10800 | 1,$g/$/ s/$/\=i/ | let i+=10) — the only thing I couldn't get was appending a comma before the number ... s/$/,\=i/ didn't work ... so I just did it in two steps.Clarendon
V
1

This is the general pattern of a :global command:

:g/foo/command

Because everything after the second separator is considered as one command, the counter is incremented each time the command is executed: one time for each matching line.

Venditti answered 9/2, 2015 at 6:13 Comment(1)
"Everything after the second separator is considered as one command." That's interesting! Thanks for answering @Venditti :DRiendeau

© 2022 - 2024 — McMap. All rights reserved.