Skip to content

Instantly share code, notes, and snippets.

@adscriven
Last active June 14, 2018 13:26
Show Gist options
  • Save adscriven/64a6a8e807c42b4d916be7c8132b0676 to your computer and use it in GitHub Desktop.
Save adscriven/64a6a8e807c42b4d916be7c8132b0676 to your computer and use it in GitHub Desktop.
Repeat macros with dot.
" File: macrorepeat.vim -- Vim global plugin
" Description: Repeat macro recordings and ex commands with dot.
" Last change: 2017-06-16 Fri
" Maintainer: Antony Scriven <[email protected]>
" Licence: This file is placed in the public domain.
"
" Installation: copy to $HOME/.vim/plugin (Unix)
" $HOME/vimfiles/plugin (Windows)
"
" Features:
" * Allows repeating a macro immediately after recording using ".".
" * Allows repeating any register after invocation with "@" using ".".
" * Counts to "@" and "." are retained for subsequent presses of ".".
"
" Experimental:
" * Allows repeating an ex command with ".", e.g. ":d<cr>3.".
" This is only for Command-line mode, not the command-line window.
" Caveats:
" * Commands that scroll the window aren't made repeatable because
" this skips the "hit enter" prompt. The last screen also has to
" be redisplayed with "g<"; so you can see it, but you can't
" scroll back.
" * Error messages can cause a hit-enter prompt.
" * CTRL-R isn't handled.
"
" Configuration:
" Enable repeating an ex command with
" let g:macrorepeat_repeatexcmd = 1
" in your vimrc. The variable just needs to exist.
"
" This plugin works by mapping "@" so that "g@l" ("l" is a dummy motion)
" is used afterwards, thus "g@" becomes the last operator and is subject
" to repeating with ".". "q" is similarly mapped. <CR> and <ESC> are
" mapped to handle execution of the command line. Since "." is not
" mapped, other repeats work natively.
"
" There are ways to interfere with this scheme (but, hopefully, nothing
" overly concerning):
" :norm!q...
" :norm!@...
" :set opfunc=...
if exists("g:loaded_macrorepeat")
finish
endif
let g:loaded_macrorepeat = 1
fun! s:getSNR()
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_')
endfun
let s:SNR = s:getSNR()
" Allow using <SID> where you normally can't.
fun! s:sid(s)
return "\<SNR>" . s:SNR . "_" . a:s
endfun
" @ {{{1
" When "." repeats "g@", invoke "@@".
fun! s: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
" Called when "g@" is invoked, setting 'opfunc' ready for repeats with ".".
fun! s:atSetRepeat(_)
let &opfunc = s:sid('atRepeat')
endfun
" Called after "@{reg}", invoking "g@" directly for the first time.
fun! s:atEnd()
" Make sure setting 'opfunc' happens here, after initial playback
" of the macro recording, in case 'opfunc' is set there.
let &opfunc = s:sid('atSetRepeat')
return 'g@l'
endfun
" Enable calling a function within the mapping for @
nno <expr> <sid>[atEnd] <sid>atEnd()
" A macro could, albeit unusually, end in Insert mode.
ino <expr> <sid>[atEnd] '<c-o>' . <sid>atEnd()
" Called when "@" is pressed, obtaining a register from the user and
" invoking "@{reg}" proper.
fun! s:atBegin()
let s:atCount = v:count1
let c = nr2char(getchar())
return '@' . c . s:sid("[atEnd]")
endfun
nno <expr> <script> @ <sid>atBegin()
" q {{{1
fun! s:qRepeat(_)
call feedkeys((v:count ? v:count : '') . '@' . s:qRegister)
endfun
fun! s:qSetRepeat(_)
let &opfunc = s:sid('qRepeat')
endfun
fun! s:qEnd()
let &opfunc = s:sid('qSetRepeat')
return 'g@l'
endfun
nno <expr> <sid>[qEnd] <sid>qEnd()
ino <expr> <sid>[qEnd] '<c-o>' . <sid>qEnd()
let s:isRecording = 0
fun! s:qBegin()
if s:isRecording
let s:isRecording = 0
return "q" . s:sid('[qEnd]')
else
let s:qRegister = nr2char(getchar())
if s:qRegister =~# '[0-9a-zA-Z"]'
let s:isRecording = 1
endif
return 'q' . s:qRegister
endif
endfun
nno <expr> <script> q <sid>qBegin()
" : {{{1
if !exists('g:macrorepeat_repeatexcmd')
finish
endif
fun! s:exRepeat(_)
call feedkeys((v:count ? v:count : '') . '@:')
endfun
fun! s:exSetRepeat(_)
let &opfunc = s:sid('exRepeat')
endfun
fun! s:exEnd()
if v:scrollstart != ''
return 'g<'
endif
let &opfunc = s:sid('exSetRepeat')
return 'g@l'
endfun
nno <expr> <sid>[exEnd] <sid>exEnd()
ino <expr> <sid>[exEnd] '<c-o>' . <sid>exEnd()
fun! s:exBegin(didEsc)
if a:didEsc && &cpo !~# 'x'
" <esc> in mappings executes a command-line.
return "\<c-c>"
endif
let v:scrollstart = ''
redrawstatus
return "\<cr>" . s:sid("[exEnd]")
endfun
cno <expr> <script> <cr> <sid>exBegin(0)
cno <expr> <script> <esc> <sid>exBegin(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment