How can I create a recursive macro so that it only runs until the end of the line?
Or how to run a recursive macro until the end of the line only?
There is probably a simpler method but maybe you could try the following.
Let's say you will use register q
to record your recursive macro.
At the very beginning of the recording, type:
:let a = line('.')
Then, at the very end of the recording, instead of hitting @q
to make the macro recursive, type the following command:
:if line('.') == a | exe 'norm @q' | endif
Finally end the recording of the macro with q
.
The last command you typed will replay the macro q
(exe 'norm @q'
) but only if the current line number (line('.')
) is the same as the one initially stored in variable a
.
The :normal
command allows you to type normal commands (like @q
) from Ex mode.
And the reason why the command is wrapped into a string and executed by the command :execute
is to prevent :normal
from consuming (typing) the rest of the command (|endif
).
Usage example.
Let's say you have the following buffer:
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
And you want to increment all the numbers from an arbitrary line with a recursive macro.
You could type 0
to move your cursor to the beginning of a line then start the recording of the macro :
qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
qqq
clears the contents of register q
so that when you initially call it during the definition of the macro, it will not interfereqq
starts the recording:let a=line('.')
stores current line number inside variable a
w
moves the cursor to the next number:if line('.')==a|exe 'norm @q'|endif
recalls the macro but only if
the line number didn't changeq
stops the recordingOnce you have defined your macro, if you position your cursor on the third line, hit 0
to move it to the beginning of the line, then hit @q
to replay the macro q
, it should only affect the current line and not the others:
1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4
Make a macro recursive after the recording
If you want, you can make your macro recursive after its recording using the fact that it's stored in a string inside a register and that you can concatenate two strings with the dot .
operator.
This would give you several benefits:
@q
will be added in the macro after it has been defined, and after you have overwritten whatever old contents was thereIf you record your macro as usual (non-recursively), you can make it recursive afterwards with the following command:
let @q = @q . "@q"
Or even shorter: let @q .= "@q"
.=
is an operator which allows to append a string to another one.
This should add the 2 characters @q
at the very end of the sequence of keystrokes stored inside register q
.
You could also define a custom command:
command! -register RecursiveMacro let @<reg> .= "@<reg>"
It defines the command :RecursiveMacro
which waits for the name of a register as an argument (because of the -register
attribute passed to :command
).
It's the same command as before, the only difference is you replace every occurrence of q
with <reg>
. When the command will be executed, Vim will automatically expand every occurrence of <reg>
with the register name you provided.
Now, all you have to do is record your macro as usual (non-recursively), then type :RecursiveMacro q
to make the macro stored inside register q
recursive.
You could do the same thing to make a macro recursive on the condition it stays on the current line:
let @q = ":let a=line('.')\r" . @q . ":if line('.')==a|exe 'norm @q'|endif\r"
It's the exact same thing described at the beginning of the post, except this time you do it after the recording. You just concatenate two strings, one before and one after whatever keystrokes the q
register currently contains:
let @q =
redefines the contents of register q
":let a=line('.')\r"
stores the current line number inside the variable a
before the macro does its work\r
is necessary to tell Vim to press Enter and execute the command, see :help expr-quote
for a list of similar special characters, . @q .
concatenates the current contents of the q
register with the previous string and the next one,":if line('.')==a|exe 'norm @q'|endif\r"
recalls the macro q
on the condition that the line didn't changeAgain, to save some keystrokes, you can automate the process by defining the following custom command:
command! -register RecursiveMacroOnLine let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r"
And again, all you have to do is record your macro as usual (non-recursively), then type :RecursiveMacroOnLine q
to make the macro stored inside register q
recursive on the condition it stays on the current line.
Merge the 2 commands
You could also tweak :RecursiveMacro
so that it covers the 2 cases:
To do this, you could pass a second argument to :RecursiveMacro
. The latter would simply test its value and, depending on the value, would execute one of the 2 previous commands. It would give something like this:
command! -register -nargs=1 RecursiveMacro if <args> | let @<reg> .= "@<reg>" | else | let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r" | endif
Or (using line continuations/backslashes to make it a little more readable):
command! -register -nargs=1 RecursiveMacro
\ if <args> |
\ let @<reg> .= "@<reg>" |
\ else |
\ let @<reg> = ":let a = line('.')\r" .
\ @<reg> .
\ ":if line('.')==a | exe 'norm @<reg>' | endif\r" |
\ endif
It's the same as before, except this time you have to provide a 2nd argument to :RecursiveMacro
(because of the -nargs=1
attribute).
When this new command will be executed, Vim will automatically expand <args>
with the value you provided.
If this 2nd argument is non-zero/true (if <args>
) the first version of the command will be executed (the one which makes a macro recursive unconditionally), otherwise if it's zero/false then the second version will be executed (the one which makes a macro recursive on the condition it stays on the current line).
So going back to the previous example, it would give the following thing:
qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
qq
begins the recording of a macro inside register q
<C-a>
increments the number under the cursorw
moves the cursor to the next numberq
ends the recording:RecursiveMacro q 0
makes the macro stored inside register q
recursive but only until the end of the line (because of the second argument 0
)3G
moves your cursor to an arbitrary line (3 for example)0@q
replays the recursive macro from the beginning of the lineIt should give the same result as before:
1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4
But this time you didn't have to type the distracting commands during the recording of your macro, you could simply focus on making a working one.
And during step 5, if you had passed a non-zero argument to the command, that is if you had typed :RecursiveMacro q 1
instead of :RecursiveMacro q 0
, the macro q
would have become recursive unconditionally, which would have given the following buffer:
1 2 3 4
1 2 3 4
2 3 4 5
2 3 4 5
This time the macro wouldn't have stopped at the end of the 3rd line but at the end of the buffer.
For more information, see:
:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register
:lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@q
will increment all numbers on line 3. Maybe there's a way to make this solution less fragile?
1 2 3 4 5 6 7 8 9 10
, I get 2 3 4 5 6 7 8 9 10 12
instead of 2 3 4 5 6 7 8 9 10 11
. I don't know why, maybe I mistyped something. Anyway it seems more sophisticated than my simple approach, and it involves regexes to describe where the macro should move the cursor, as well as a location list which I have never seen used in this way. I like it a lot!
\d\+
to describe multiple digits numbers.
:lv ...
command, the :lla
command can be used to jump to the last match and the :lp
command can be used to advance over the matches in reverse order.
l
A recursive macro will stop as soon as it encounters a command that fails. Therefore, to stop at the end of a line, you need a command that will fail at the end of the line.
By default*, the l
command is such a command, so you can use it to stop a recursive macro. If the cursor is not at the end of the line, then you just need to move it back afterwards with the command h
.
So, using the same example macro as saginaw:
qqqqq<c-a>lhw@qq
Broken down:
qqq
: Clear out the q register,qq
: Start recording a macro in the q
register,<c-a>
: Increment the number under the cursor,lh
: If we are at the end of the line, abort the macro. Otherwise, do nothing.w
: Advance to the next word on the line.@q
: Recurseq
: Stop recording.You can then run the macro with the same 0@q
command as described by saginaw.
* The 'whichwrap'
option allows you to define which movement keys will wrap around to the next line when you are at the beginning or end of a line (See :help 'whichwrap'
). If you have l
set in this option, then it will break the solution described above.
However, it is likely that you only use one of the three default normal-mode commands for advancing a single character (<Space>
, l
, and <Right>
), so if you have l
included in your 'whichwrap'
setting, you can remove one that you don't use from the 'whichwrap'
option, e.g. for <Space>
:
:set whichwrap-=s
Then you can replace the l
command in step 4 of the macro with a <Space>
command.
virtualedit=onemore
will interfere with using l
to detect end-of-line, albeit not as severely as whichwrap=l
.