Last active
June 14, 2018 13:26
-
-
Save adscriven/64a6a8e807c42b4d916be7c8132b0676 to your computer and use it in GitHub Desktop.
Repeat macros with dot.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
" 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