Karl Yngve Lervåg
Karl Yngve Lervåg

Reputation: 1715

Automatically resize vim gui according to number of vertical splits

I am writing a small plugin for gvim that automatically increases or decreases the width of the gui according to the number of vertical splits. The plugin works something like this

if has("gui_running")
  augroup resize
    autocmd WinEnter * call <SID>ResizeSplits()
    autocmd WinLeave * call <SID>ResizeSplits()
    autocmd BufLeave * call <SID>ResizeSplits()
  augroup END
endif

Here ResizeSplits() is the function that resizes the gui window:

function! s:ResizeSplits()
  let l:count = 0
  windo   if winwidth(winnr()) < &columns | 
        \   let l:count += 1              |
        \ endif
  if l:count > 0
    let l:totwidth = l:count - 1 + l:count*80
  else
    let l:totwidth = 80
  endif
  if &columns != l:totwidth
    execute 'set co=' . l:totwidth
  endif
endfunction

The plugin almost works as I want it to, but not quite. It seems like the BufLeave event (and similar ones) sometimes executes before windows are closed. This is a problem for instance when I do <c-w>o or :only. The problem is that the ResizeSplits function does not work, since it still counts the old number of windows.

Is there another autocommand that can be used to detect when the number of windows has been changed, or a BufLeave-like event that is guaranteed to be executed after windows are destroyed/removed?

It is trivial to get my plugin to work with mappings, but I am not able to get it to work reliably with ex commands like :only and :close.

Upvotes: 2

Views: 334

Answers (2)

Karl Yngve Lerv&#229;g
Karl Yngve Lerv&#229;g

Reputation: 1715

I found a solution that seems to work pretty well. First, I rewrote the ResizeSplits function:

function! s:ResizeSplits()
  let l:curwin = winnr()
  let l:colwidth = 80 + &foldcolumn
  if &number
    let l:colwidth += &numberwidth
  endif

  let l:count = 0
  windo   if winwidth(winnr()) < &columns |
        \   let l:count += getwinvar(winnr(), 'count') |
        \ endif
  if l:count > 0
    let l:totwidth = l:count - 1 + l:count*l:colwidth
  else
    let l:totwidth = l:colwidth
  endif

  if &columns != l:totwidth
    silent! execute 'set co=' . l:totwidth
    silent! execute 'wincmd ='
  endif
  silent! execute l:curwin . 'wincmd w'
endfunction

Where the important change is that I have defined a variable w:count that is either 0 or 1. The function is used with the following autocommands:

if has("gui_running")
  augroup vimrc_autocommands
    autocmd WinEnter    * let w:count = 1 | call <SID>ResizeSplits()
    autocmd BufEnter    * let w:count = 1 | call <SID>ResizeSplits()
    autocmd WinLeave    * call <SID>ResizeSplits()
    autocmd BufHidden   * let w:count = 0 | call <SID>ResizeSplits()
    autocmd BufWinLeave * let w:count = 0 | call <SID>ResizeSplits()
  augroup END
endif

This seems to work in almost every case I've tried. There is only one case where :only and <c-w>o still does not work: If the windows have the same buffer. A simple mapping remedies the <c-w>o:

nnoremap <c-w>o <c-w>o:call <sid>ResizeSplits()<cr>

I will of course be happy if anyone finds a better solution.

Upvotes: 2

Ingo Karkat
Ingo Karkat

Reputation: 172580

There's no exact event for :close / :quit; the closest is BufWinLeave, but that doesn't fire when the buffer is still visible in another buffer. You could combine that with BufLeave, but then have to check that the buffer is actually not visible any more.

To only handle unlisted buffers, you can add a condition checking 'buflisted' in the executed autocmd.

Edit after update of question

I don't think there's a way to intercept the corner cases you've described. Especially :only could be tricky. I can only propose to work around this with additional autocmds on CursorHold and CursorMoved. This way, the incorrect state will persist only for a short time.

Upvotes: 1

Related Questions