17

I know that I can use . to repeat the last editing command.

Is there a way to repeat the last UI manipulation command? For example, I can write 10<C-W>- to shrink a window by ten rows. It'd be nice to be able to press ⟨some key⟩ to easily repeat this command if I want to shrink it more.

  • Related: stackoverflow.com/q/6952636/2072269 (no answer given that can be used after you have already done a resize). – muru Jul 18 '15 at 3:20
  • @muru: nice, but that's for this specific case. What if I've done something like fz and then 10;? What about :tabm +1? Are these all going to have to be special-cased? – wchargin Jul 18 '15 at 3:23
  • I think you misunderstood me. I'm saying the linked post has useless answers (before somebody else comes and suggests it). – muru Jul 18 '15 at 3:25
  • oh! okay, then we're on the same page @muru :) – wchargin Jul 18 '15 at 3:36
12

The dot command . works because Vim "keeps track" of commands that change the contents of buffers. If you run :echo b:changedtick, you'll see it incrementing with each change to the current buffer.

But Vim doesn't "keep track" of non-editing commands. Thus, no, what you're asking for can't be done.

8

There is no way of doing this by default in vim because vim does not keep track of the previously executed wincmd. However, it is possible to do this through some clever mappings:

function! s:Wincmd(count, key)
    " If count is not zero, use the original count.  If otherwise, don't
    " include a count.
    let if_count = a:count ? a:count : ""
    " This builds a wincmd from the given key, and saves it so
    " it can be repeated.
    let g:last_wincmd = "wincmd " . nr2char(a:key)
    " Execute the built wincmd
    execute if_count . g:last_wincmd
endfunction

function! s:WincmdRepeat(count)
    " If no wincmd has been executed yet, don't do anything
    if !exists('g:last_wincmd') | return | endif
    " If a count is given, repeat the last wincmd that amount of times.
    " If otherwise, just repeat once.
    let if_count = a:count ? a:count : ""
    execute if_count . g:last_wincmd
endfunction

" Overwrite the default <C-w> mapping so that the last wincmd can be kept
" track of.  The getchar function is what captures the key pressed
" directly afterwards.  The <C-u> is to remove any cmdline range that vim
" automatically inserted.
nnoremap <silent> <C-w> :<C-u>call <SID>Wincmd(v:count, getchar())<CR>

" This just calls the function which repeats the previous wincmd.  It
" does accept a count, which is the number of times it should repeat the
" previous wincmd.  You can also replace Q with whatever key you want. 
nnoremap <silent> Q :<C-u> call <SID>WincmdRepeat(v:count)<CR>

Note that if you have any mappings that use <C-w> they can only be repeated if they are not of the nore variety. Any wincmds issued using :wincmd will not be repeated. Also, any wincmds that contain more than one character cannot be performed (such as <C-w>gf).

Relevant Help Topics

  • :help v:count
  • :help getchar()
  • :help nr2char()
  • :help expr1
  • :help :wincmd
  • :help :execute
  • :help :for
  • :help :map-<silent>
  • :help c_CTRL-U
  • :help <SID>
  • 1
    This is great, and an excellent example of well-written VimScript! Some minor (perhaps picky) feedback: This repeat command would behave different from the way the built-in . behaves with a count. When a count is supplied to ., the previous count is ignored. So 2dd followed by 3. would delete 2 lines and then 3 lines; in contrast, with your mappings, 2<C-w>- followed by 3Q would shrink the window by 2 lines and then by 6 (= 2x3) lines. That behaviour is fine, but it's nice to draw from analogous built-in Vim commands when choosing how a custom command should behave. – tommcdo Jul 20 '15 at 14:52
  • Thanks! Also, I see what you mean with how the count works. I may change it so it works that way. – EvergreenTree Jul 20 '15 at 15:17
4

The submode plugin can help with this. You could define a "submode" that you enter by typing <C-W>-, wherein you've defined - (and perhaps +) to continue resizing the window.

2

There is another plugin called repmo.vim ("repeat motions") which can do what you want.

But you will need to specify which motions (or actions in general) you want to repeat. Mine is currently configured like this:

let g:repmo_mapmotions = "j|k h|l zh|zl g;|g, <C-w>w|<C-w>W"
let g:repmo_mapmotions .= " <C-w>+|<C-w>- <C-w>>|<C-w><"
let g:repmo_key = ";" 
let g:repmo_revkey = "," 

So after doing 5 CTRL-W + I can hit ; to repeat it as many times as a like.

The plugin works by creating mappings for each of the keys specified.

When f or t are used, the ; and , mappings are cleared back to their default behaviour.

I find the mapping for g; especially useful, to get back to an earlier edit point. g; ; ; ;

  • @albfan I'm not sure I understand your question, but for me pressing , acts immediately, and 5, also works as expected. Is it possible you have set your mapleader to ,? – joeytwiddle Mar 10 '18 at 10:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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