Vim Tips Wiki
(Change <tt> to <code>, perhaps also minor tweak.)
(→‎See also: Add Bbye.)
Line 196: Line 196:
 
*{{script|id=1147|text=bufkill}} unload/delete/wipe a buffer, keep its window(s), display last accessed buffer(s)
 
*{{script|id=1147|text=bufkill}} unload/delete/wipe a buffer, keep its window(s), display last accessed buffer(s)
 
*{{script|id=559|text=BufClose}} not useful because it doesn't work as expected (if you close a buffer that's open in several windows, those windows close)
 
*{{script|id=559|text=BufClose}} not useful because it doesn't work as expected (if you close a buffer that's open in several windows, those windows close)
  +
* [https://github.com/moll/vim-bbye Bbye] Delete buffers and close files in Vim without messing up your window layout. Rewritten Bclose.vim to handle multiple windows. Comes with warranty!
   
 
==Comments==
 
==Comments==

Revision as of 23:41, 20 July 2013

Tip 165 Printable Monobook Previous Next

created 2001 · complexity intermediate · version 7.0


You may use the :split or :vsplit commands to display several windows, with some windows showing different parts of one buffer, while other windows show other buffers.

When finished with a buffer, you can close it with the :bdelete command. However, that command will also close all windows currently showing the buffer.

The script below defines the :Bclose command that deletes a buffer, while keeping the current window layout (no windows are closed).

Usage

The :Bclose command deletes a buffer without changing the window layout. For each window where the buffer is currently displayed:

  • Show the alternate buffer (:buffer #), if any.
  • Otherwise, show the previous buffer (:bprevious), if any.
  • Otherwise, show an empty buffer.
:Bclose
Close buffer in current window.
:Bclose N
Close buffer number N (as shown by :ls).
:Bclose Name
Close buffer named Name (as shown by :ls).

Assuming the default backslash leader key, you can also press \bd to close (delete) the buffer in the current window (same as :Bclose).

Like the :bdelete command, :Bclose will fail if the buffer has been modified. You can append ! to discard all changes (for example, :Bclose! will delete the buffer in the current window; any changes to the buffer are lost).

By default, :Bclose will close a buffer even if it is displayed in multiple windows (the windows are not closed). Put the following in your vimrc if you would prefer that a buffer is not closed if it is displayed more than once:

:let bclose_multiple = 0

Script

Create file ~/.vim/plugin/bclose.vim (Unix) or $HOME/vimfiles/plugin/bclose.vim (Windows) containing the script below, then restart Vim.

" Delete buffer while keeping window layout (don't close buffer's windows).
" Version 2008-11-18 from http://vim.wikia.com/wiki/VimTip165
if v:version < 700 || exists('loaded_bclose') || &cp
  finish
endif
let loaded_bclose = 1
if !exists('bclose_multiple')
  let bclose_multiple = 1
endif

" Display an error message.
function! s:Warn(msg)
  echohl ErrorMsg
  echomsg a:msg
  echohl NONE
endfunction

" Command ':Bclose' executes ':bd' to delete buffer in current window.
" The window will show the alternate buffer (Ctrl-^) if it exists,
" or the previous buffer (:bp), or a blank buffer if no previous.
" Command ':Bclose!' is the same, but executes ':bd!' (discard changes).
" An optional argument can specify which buffer to close (name or number).
function! s:Bclose(bang, buffer)
  if empty(a:buffer)
    let btarget = bufnr('%')
  elseif a:buffer =~ '^\d\+$'
    let btarget = bufnr(str2nr(a:buffer))
  else
    let btarget = bufnr(a:buffer)
  endif
  if btarget < 0
    call s:Warn('No matching buffer for '.a:buffer)
    return
  endif
  if empty(a:bang) && getbufvar(btarget, '&modified')
    call s:Warn('No write since last change for buffer '.btarget.' (use :Bclose!)')
    return
  endif
  " Numbers of windows that view target buffer which we will delete.
  let wnums = filter(range(1, winnr('$')), 'winbufnr(v:val) == btarget')
  if !g:bclose_multiple && len(wnums) > 1
    call s:Warn('Buffer is in multiple windows (use ":let bclose_multiple=1")')
    return
  endif
  let wcurrent = winnr()
  for w in wnums
    execute w.'wincmd w'
    let prevbuf = bufnr('#')
    if prevbuf > 0 && buflisted(prevbuf) && prevbuf != w
      buffer #
    else
      bprevious
    endif
    if btarget == bufnr('%')
      " Numbers of listed buffers which are not the target to be deleted.
      let blisted = filter(range(1, bufnr('$')), 'buflisted(v:val) && v:val != btarget')
      " Listed, not target, and not displayed.
      let bhidden = filter(copy(blisted), 'bufwinnr(v:val) < 0')
      " Take the first buffer, if any (could be more intelligent).
      let bjump = (bhidden + blisted + [-1])[0]
      if bjump > 0
        execute 'buffer '.bjump
      else
        execute 'enew'.a:bang
      endif
    endif
  endfor
  execute 'bdelete'.a:bang.' '.btarget
  execute wcurrent.'wincmd w'
endfunction
command! -bang -complete=buffer -nargs=? Bclose call s:Bclose('<bang>', '<args>')
nnoremap <silent> <Leader>bd :Bclose<CR>

Alternative Script

Below, I have a new, more complicated version of above script. The below script will actually create a scratch buffer if there are no listed buffers left. The script, in addition, takes care of a small annoyance. Before, if you 1) open vim, 2) :e a file, 3) :bd, 4) :e the same file, then there will be two buffers listed (that file and a [no name] buffer). The following script ensures this doesn't happen.

Everything in this tutorial assumes the user does "set hidden".

Using this Kwbd command (:Kwbd) will make Vim windows behave more like an IDE, or maybe even better. You can also setup a mapping, see the end of the script.

"here is a more exotic version of my original Kwbd script
"delete the buffer; keep windows; create a scratch buffer if no buffers left
function s:Kwbd(kwbdStage)
  if(a:kwbdStage == 1)
    if(!buflisted(winbufnr(0)))
      bd!
      return
    endif
    let s:kwbdBufNum = bufnr("%")
    let s:kwbdWinNum = winnr()
    windo call s:Kwbd(2)
    execute s:kwbdWinNum . 'wincmd w'
    let s:buflistedLeft = 0
    let s:bufFinalJump = 0
    let l:nBufs = bufnr("$")
    let l:i = 1
    while(l:i <= l:nBufs)
      if(l:i != s:kwbdBufNum)
        if(buflisted(l:i))
          let s:buflistedLeft = s:buflistedLeft + 1
        else
          if(bufexists(l:i) && !strlen(bufname(l:i)) && !s:bufFinalJump)
            let s:bufFinalJump = l:i
          endif
        endif
      endif
      let l:i = l:i + 1
    endwhile
    if(!s:buflistedLeft)
      if(s:bufFinalJump)
        windo if(buflisted(winbufnr(0))) | execute "b! " . s:bufFinalJump | endif
      else
        enew
        let l:newBuf = bufnr("%")
        windo if(buflisted(winbufnr(0))) | execute "b! " . l:newBuf | endif
      endif
      execute s:kwbdWinNum . 'wincmd w'
    endif
    if(buflisted(s:kwbdBufNum) || s:kwbdBufNum == bufnr("%"))
      execute "bd! " . s:kwbdBufNum
    endif
    if(!s:buflistedLeft)
      set buflisted
      set bufhidden=delete
      set buftype=
      setlocal noswapfile
    endif
  else
    if(bufnr("%") == s:kwbdBufNum)
      let prevbufvar = bufnr("#")
      if(prevbufvar > 0 && buflisted(prevbufvar) && prevbufvar != s:kwbdBufNum)
        b #
      else
        bn
      endif
    endif
  endif
endfunction

command! Kwbd call s:Kwbd(1)
nnoremap <silent> <Plug>Kwbd :<C-u>Kwbd<CR>

" Create a mapping (e.g. in your .vimrc) like this:
"nmap <C-W>! <Plug>Kwbd

See also

  • MiniBufExplorer provides this capability with a simple user interface
  • bufkill unload/delete/wipe a buffer, keep its window(s), display last accessed buffer(s)
  • BufClose not useful because it doesn't work as expected (if you close a buffer that's open in several windows, those windows close)
  • Bbye Delete buffers and close files in Vim without messing up your window layout. Rewritten Bclose.vim to handle multiple windows. Comes with warranty!

Comments

For getting asked what should be done, when a file has been modified I use normally :confirm :bd. So I changed the script in 2lines to achieve this:

if empty(a:bang) && getbufvar(btarget, '&modified')
    call s:Warn('No write since last change for buffer '.btarget.' (use :Bclose!)')
"    return
  endif

...

    endif
  endfor
"  execute 'bdelete'.a:bang.' '.btarget
  execute ':confirm :bdelete '.btarget
  execute wcurrent.'wincmd w'

Just comment out the return and replace the command. greetings - Joe