tpope / vim-repeat

repeat.vim: enable repeating supported plugin maps with "."
http://www.vim.org/scripts/script.php?script_id=2136
2.59k stars 81 forks source link

Can't repeat change made immediately after pressing . (repeat#run) #25

Open glts opened 10 years ago

glts commented 10 years ago

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!

gmnimrod commented 5 years ago

I think I have an explanation for what is happening.

This can happen for other plugins. The conditions are:

  1. A plugin calls repeat#set and doesn't move the cursor after it does.
  2. Without moving the cursor, some other operation that is repeatable moves the cursor. repeat#set must 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:

  1. User invokes the <Plug>TransposeCharacters.
  2. repeat#set sets up an autocmd to update g:repeat_tick on CursorMoved event (code). The example plugin doesn't move the cursor after repeat#set is called, so the autocommand is not executed yet.
  3. User invokes ofoo<Esc>.
  4. The cursor has moved, so g:repeat_tick is updated after ofoo<Esc>.
  5. The user invokes dot-repeat. In repeat#run vim-repeat sees that g: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?