25

Can I repeat a macro with the . command? For example, I'd like to be able to type:

@q...

and have it effectively call @q 4 times. With the default behavior, this works only if 'q' holds a single command, for example dw, x, or iHello<esc>. (This makes sense though, since '.' repeats the last change, not the last keystrokes/command)

I'd also love to have this work with counts. For example, typing:

3@q.

would be effectively the same as running @q 6 times. How can I configure this behavior?

2
  • 1
    Not exactly what you're asking for, but maybe it will be helpful github.com/wincent/replay . This records lastly recorded macro and replays it on pressing Enter
    – grodzik
    Commented Jan 31, 2017 at 19:35
  • FYI, @@ repeats the last macro, so if you are asking this for the sake of mappings and such, it should work for you.
    – Shahbaz
    Commented Jun 11, 2017 at 23:46

5 Answers 5

12

Give this a try. It remaps @ so that g@ (plus a dummy motion l) is used afterwards, thus becoming the last operator and subject to repeating with ..

" When . repeats g@, repeat the last macro.
fun! AtRepeat(_)
    " If no count is supplied use the one saved in s:atcount.
    " Otherwise save the new count in s:atcount, so it will be
    " applied to repeats.
    let s:atcount = v:count ? v:count : s:atcount
    " feedkeys() rather than :normal allows finishing in Insert
    " mode, should the macro do that. @@ is remapped, so 'opfunc'
    " will be correct, even if the macro changes it.
    call feedkeys(s:atcount.'@@')
endfun

fun! AtSetRepeat(_)
    set opfunc=AtRepeat
endfun

" Called by g@ being invoked directly for the first time. Sets
" 'opfunc' ready for repeats with . by calling AtSetRepeat().
fun! AtInit()
    " Make sure setting 'opfunc' happens here, after initial playback
    " of the macro recording, in case 'opfunc' is set there.
    set opfunc=AtSetRepeat
    return 'g@l'
endfun

" Enable calling a function within the mapping for @
nno <expr> <plug>@init AtInit()
" A macro could, albeit unusually, end in Insert mode.
ino <expr> <plug>@init "\<c-o>".AtInit()

fun! AtReg()
    let s:atcount = v:count1
    let c = nr2char(getchar())
    return '@'.c."\<plug>@init"
endfun

nmap <expr> @ AtReg()

I've tried to handle as many corner cases as I can think of. You can repeat @: with .. Counts to @ or . are retained for subsequent presses of ..

This is tricky, and I'm not convinced that something won't break somewhere along the way. So no guarantees, warranties, or promises with this one.

Personally, I'm okay having a difference between the fine-grained repeats of . for the last change, and the macro repeats of @@.

EDIT

I figured, having gone this far, that I may as well add some additional code that will allow pressing . immediately after recording a macro to play it back.

fun! QRepeat(_)
    call feedkeys('@'.s:qreg)
endfun

fun! QSetRepeat(_)
    set opfunc=QRepeat
endfun

fun! QStop()
    set opfunc=QSetRepeat
    return 'g@l'
endfun

nno <expr> <plug>qstop QStop()
ino <expr> <plug>qstop "\<c-o>".QStop()

let s:qrec = 0
fun! QStart()
    if s:qrec == 1
        let s:qrec = 0
        return "q\<plug>qstop"
    endif
    let s:qreg = nr2char(getchar())
    if s:qreg =~# '[0-9a-zA-Z"]'
        let s:qrec = 1
    endif
    return 'q'.s:qreg
endfun

nmap <expr> q QStart()
5
  • This works fantastic! Since posting this question, my workflow has changed, so I don't really want to be able to repeat macros with dot anymore. However, this works really well, so I'll accept it anyway (though I probably won't add it to my .vimrc)
    – DJMcMayhem
    Commented Jun 9, 2017 at 16:45
  • No worries, interesting problem regardless!
    – Antony
    Commented Jun 9, 2017 at 18:23
  • 2
    Edit: allow playing back a macro with . immediately after recording.
    – Antony
    Commented Jun 11, 2017 at 22:24
  • I think \<plug>qstop should be before q in the QStart function Commented Dec 13, 2019 at 8:31
  • @Antony The second snippet of code doesn't work anymore (the original code still does). Any chance you know what the fix is?
    – Vaderico
    Commented Nov 18, 2022 at 3:13
10

To repeat your last macro you can use @@ so 3@@ would essentially run @q 3 times. However the @ keystrokes can be unwieldy, so in my .vimrc I have the lines:

  "- Lazy macro repeat
  nmap <leader>m @@

This allows me to use my leader key (,) plus m to run the last macro. Then you can precede this with a number to repeat the macro.

Now 3,m is equivalent of 3@@. Same total keys and no need to hold shift.

EDIT: Upon rethinking this I came up with a new macro. nmap <leader>. @@ This also works when preceded by a number so 3,. is now 3@@ Would love to see this work so that I could pass in a macro letter and have that macro repeat instead of the last macro.

2
  • The OP wants 3@q. to run @q 6 times. This doesn't do that.
    – user41805
    Commented Feb 1, 2017 at 17:20
  • Rereading your question I'm not sure this answers it. Can you give an example of when you would use 3@q.? It would seem you might as well just prepend it with a count. I tend to set a search before beginning a macro then use n in the macro to jump to the start of where it can be replayed.
    – Shadoath
    Commented Feb 1, 2017 at 17:20
6

You can record the use of a macro in another macro, and repeat that.

qqdwq
qr2@qq

Now 3@r is like running @q six times.

2

By default space in vim is "move right 1 character" which is already covered by l, so I've remapped space to re-run a macro from the q buffer:

noremap <Space> @q

To use, just record a macro with qq first, stop recording with q and then hit space to replay.

2
  • FYI, for the reason you state (it's not a required key) a lot of people use space as their leader key so there might not be as many people with it free as you think. :)
    – B Layer
    Commented May 11, 2019 at 6:05
  • If you have dvorak remapped then it is noremap <Space> @' Commented May 16, 2020 at 23:07
1

As I don't use , and like a simple easy to type solution, this works for me:

:map , @@
1
  • You Sir like to live dangerously. Commented Jul 28, 2022 at 9:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.