19

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.

5
  • Related: stackoverflow.com/q/6952636/2072269 (no answer given that can be used after you have already done a resize).
    – muru
    Commented Jul 18, 2015 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
    Commented Jul 18, 2015 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
    Commented Jul 18, 2015 at 3:25
  • oh! okay, then we're on the same page @muru :)
    – wchargin
    Commented Jul 18, 2015 at 3:36
  • Full example here mapping <C-W>++++ to <C-W>+<C-W>+<C-W>+<C-W>+
    – Tinmarino
    Commented Aug 29, 2020 at 2:05

6 Answers 6

13

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.

9

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>
2
  • 2
    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
    Commented Jul 20, 2015 at 14:52
  • Thanks! Also, I see what you mean with how the count works. I may change it so it works that way. Commented Jul 20, 2015 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.

3

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; ; ; ;

0
1

I have created a small vim-remotions plugin that allows to repeat the last motion using the ; and , keys (like for the f and t motions).

The following settings makes that the Ctrl < and Ctrl - together with their counterpart are repeated (including the count).

  let g:remotions_direction = 1
  let g:remotions_repeat_count = 1

  let g:remotions_motions = {
        \ 'vsplit' : { 'backward' : '<C-w><', 'forward' : '<C-w>>', 'direction' : 1 },
        \ 'hsplit' : { 'backward' : '<C-w>-', 'forward' : '<C-w>+', 'direction' : 1 },
        \ }

But the configuration can be extended to repeat motions and other actions.

0

When I was faced with this problem, I fell in love with the idea of having my own Vim mode for dealing with windows.

I quickly came across the vim-submode plugin, as already mentioned by tommcdo. Unfortunately, you would have to build such a window mode on top of this plugin yourself. This nice blog post shows how this could look like.

But finally, I found the plugin tinykeymap, which already comes with a window mode that works out of the box. So if you want to try it out, that's the way to go ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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