Can't repeat change made immediately after pressing . (repeat#run)
I was trying the mapping from Vimcasts episode 61.
nnoremap <silent> <Plug>TransposeCharacters xp:call repeat#set("\<Plug>TransposeCharacters")<CR>
nmap cp <Plug>TransposeCharacters
After running the mapping cp, repeating it ., and making another change ofoo<Esc>, the . becomes ineffective. It doesn't repeat ofoo<Esc>.
If this is a fundamental limitation of repeat.vim (CursorMoved ...?) I believe it should be mentioned in the docs. Thanks!
I think I have an explanation for what is happening.
This can happen for other plugins. The conditions are:
- A plugin calls
repeat#setand doesn't move the cursor after it does. - Without moving the cursor, some other operation that is repeatable moves the cursor.
repeat#setmust not be called by this operation.
Result: The next time . is invoked, vim will repeat the plugin on stage (1), instead of the operation on stage (2).
What is happening?
@glts is right. It does have something to do with CursorMoved.
In short, g:repeat_tick is updated after (2) instead of in (1), because that is when CursorMoved event first occurs. This prevents vim-repeat in repeat#run from knowing (2) ever took place.
Detailed example:
- User invokes the
<Plug>TransposeCharacters. -
repeat#setsets up an autocmd to updateg:repeat_tickonCursorMovedevent (code). The example plugin doesn't move the cursor afterrepeat#setis called, so the autocommand is not executed yet. - User invokes
ofoo<Esc>. - The cursor has moved, so
g:repeat_tickis updated afterofoo<Esc>. - The user invokes dot-repeat. In
repeat#runvim-repeat sees thatg:repeat_tick == b:changedtick(code), concludes that the last operation was the plugin, instead of the last insert, and repeats it.
Another example where this would happen is with tpope/vim-unimpaired, but only if the cursor is first placed on the first non-blank character. Then: ]<Space>ofoo<Esc>. will add a blank line instead of a foo line. (It may seem like an artificial edge case, but that is what got me into this bug hunt.)
Possible Solution
The easiest solution that I can think of is to separate repeat#set into two different functions, one that sets up the autocommand and one that doesn't. The latter should be used in cases where the plugin does not pend on a motion to complete.
The drawbacks are that it complicates vim-repeat's API, and doesn't provide an automatic fix where vim-repeat is already used.
Maybe there is a solution that doesn't require a new function, but I don't see it right now.
Any thoughts?