nnoremap <silent> @R :set operatorfunc=MacroRepeat<CR>g@
xnoremap <silent> @ :<C-u>call <SID>MacroRepeat()<CR>
function! s:MacroRepeat(...)
execute (a:0 ? "'[,']" : "'<,'>") . 'normal @' . nr2char(getchar())
endfunction
When you hit @R
in normal mode, Vim will set the value of the operatorfunc
option to MacroRepeat
and then hit the operator g@
. g@
is an operator which waits for a motion. Once you provide one, Vim should automatically put the marks '[
and ']
around the text covered by the motion (it always does so for the last text object you used) and call the function whose name is stored inside operatorfunc
, in this case MacroRepeat
, passing as an argument the type of motion ('char'
for characterwise, 'line'
for linewise, and 'block'
for blockwise).
The function does the following thing:
it tests whether it received any extra arguments (a:0 ?
), if there are it means the function was called from normal mode, so it uses the marks '[
and ']
. Otherwise it means the function was called from visual mode (you can see in the visual mapping that no argument is passed to MacroRepeat()
), and in this case it uses the visual marks '<
and '>
which Vim also sets automatically for you whenever you visually select some text.
So (a:0 ? "'[,']" : "'<,'>")
is equal to '[,']
if the function was called from normal mode, or '<,'>
if it was called from visual mode. It's a range you can use before the :normal
command to tell it to replay a sequence of keystrokes on all the lines inside the range.
getchar()
is a function which asks for a character and returns its ASCII value, and nr2char(getchar())
converts back this number into the original character.
So execute (a:0 ? "'[,']" : "'<,'>").'normal @'.nr2char(getchar())
will evaluate to execute '[,']normal @{the character you type}
in normal mode and to execute '<,'>normal @{the character you type}
in visual mode.
The result is your macro should be replayed by the :normal
command on all the lines covered by {motion}
in normal mode, and on all the selected lines in visual mode.
To replay the macro q
on all the lines inside the current paragraph from normal mode, you would hit @Ripq
(because ip
is a text object which covers the whole paragraph).
To replay the macro q
on all the lines inside the current block of code (inside curly braces) from normal mode, you would hit @RiBq
(because iB
is a text object which covers a block of code).
To replay the macro q
on all the selected lines from visual mode, you would hit @q
.
If you find the code a little too confusing, then maybe you would prefer this simple mapping:
xnoremap <silent> <expr> @ ':normal @' . nr2char(getchar()) . "\r"
With this last mapping, you would replay your macro only from normal mode.
To replay the macro q
on all the lines inside the current paragraph, you would hit vip@q
(because vip
will select the current paragraph).
To replay the macro q
on all the lines inside the current block of code, you would hit viB@q
(because viB
will select the current block of code).