Vim Tips Wiki
No edit summary
(→‎See also: Sort plugins alphabetically.)
(24 intermediate revisions by 11 users not shown)
Line 1: Line 1:
{{review}}
 
{{Duplicate|1078|622}}
 
 
 
{{TipImported
 
{{TipImported
 
|id=165
 
|id=165
 
|previous=164
 
|previous=164
 
|next=166
 
|next=166
|created=November 16, 2001
+
|created=2001
 
|complexity=intermediate
 
|complexity=intermediate
|author=Raymond Li
+
|author=
|version=6.0
+
|version=7.0
 
|rating=29/13
 
|rating=29/13
  +
|category1=
  +
|category2=
 
}}
 
}}
  +
You may use the <code>:split</code> or <code>:vsplit</code> commands to display several windows, with some windows showing different parts of one buffer, while other windows show other buffers.
I'm not sure if this functionality is already within Vim, but I sometimes I find it useful to keep a split window from closing when deleting a buffer. This has already been discussed on the vim@vim.org mailing list. However, I feel this solution is a little easier to use.
 
   
  +
When finished with a buffer, you can close it with the <code>:bdelete</code> command. However, that command will also close all windows currently showing the buffer.
<pre>
 
" Put this into .vimrc or make it a plugin.
 
" Mapping :Bclose to some keystroke would probably be more useful.
 
" I like the way buflisted() behaves, but some may like the behavior
 
" of other buffer testing functions.
 
command! Bclose call &lt;SID&gt;BufcloseCloseIt()
 
function! &lt;SID&gt;BufcloseCloseIt()
 
let l:currentBufNum = bufnr("%")
 
let l:alternateBufNum = bufnr("#")
 
if buflisted(l:alternateBufNum)
 
buffer #
 
else
 
bnext
 
endif
 
if bufnr("%") == l:currentBufNum
 
new
 
endif
 
if buflisted(l:currentBufNum)
 
execute("bdelete ".l:currentBufNum)
 
endif
 
endfunction
 
</pre>
 
   
  +
The script below defines the <code>:Bclose</code> command that deletes a buffer, while keeping the current window layout (no windows are closed).
==Comments==
 
The MiniBufExplorer.vim plugin (in the scripts area) provides this capability with a really simple and small user interface...
 
   
  +
==Usage==
----
 
  +
The <code>:Bclose</code> command deletes a buffer without changing the window layout. For each window where the buffer is currently displayed:
This tip didn't seem to work in gvim 6, I placed it in my _vimrc file as instructed, but deleting buffers still closes the windows they are in, as before ;-(.
 
  +
*Show the alternate buffer (<code>:buffer #</code>), if any.
  +
*Otherwise, show the previous buffer (<code>:bprevious</code>), if any.
  +
*Otherwise, show an empty buffer.
   
  +
;<code>:Bclose</code>
----
 
  +
:Close buffer in current window.
I didn't try the tip, but this is the solution I came up with:
 
   
  +
;<code>:Bclose ''N''</code>
You can use the :BufClose command with or without an argument. The argument can be the filename associated with a buffer, or a buffer number. You don't have to be in the window that has the buffer open if you use an argument.
 
  +
:Close buffer number ''N'' (as shown by <code>:ls</code>).
   
  +
;<code>:Bclose ''Name''</code>
  +
:Close buffer named ''Name'' (as shown by <code>:ls</code>).
  +
  +
Assuming the default backslash leader key, you can also press <code>\bd</code> to close (delete) the buffer in the current window (same as <code>:Bclose</code>).
  +
  +
Like the <code>:bdelete</code> command, <code>:Bclose</code> will fail if the buffer has been modified. You can append <code>!</code> to discard all changes (for example, <code>:Bclose!</code> will delete the buffer in the current window; any changes to the buffer are lost).
  +
  +
By default, <code>:Bclose</code> 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:
 
<pre>
 
<pre>
  +
:let bclose_multiple = 0
command! -nargs=? -complete=buffer -bang BufClose
 
 
</pre>
\ :call BufClose(expand('&lt;args&gt;'), expand('&lt;bang&gt;'))
 
   
  +
==Script==
function! BufClose(buffer, bang)
 
  +
Create file <code>~/.vim/plugin/bclose.vim</code> (Unix) or <code>$HOME/vimfiles/plugin/bclose.vim</code> (Windows) containing the script below, then restart Vim.
if a:buffer == ''
 
 
<pre>
let buffer = bufnr('%')
 
  +
" 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
 
else
let buffer = bufnr(a:buffer)
+
let btarget = bufnr(a:buffer)
 
endif
 
endif
if buffer == -1
+
if btarget < 0
 
call s:Warn('No matching buffer for '.a:buffer)
echohl ErrorMsg
 
echomsg "No matching buffer for" a:buffer
 
echohl None
 
 
return
 
return
 
endif
 
endif
 
if empty(a:bang) && getbufvar(btarget, '&modified')
let current_window = winnr()
 
 
call s:Warn('No write since last change for buffer '.btarget.' (use :Bclose!)')
let buffer_window = bufwinnr(buffer)
 
if buffer_window == -1
 
echohl ErrorMsg
 
echomsg "Buffer" buffer "isn't open in any windows."
 
echohl None
 
 
return
 
return
 
endif
 
endif
  +
" Numbers of windows that view target buffer which we will delete.
if a:bang == '' &amp;&amp; getbufvar(buffer, '&amp;modified')
 
  +
let wnums = filter(range(1, winnr('$')), 'winbufnr(v:val) == btarget')
echohl ErrorMsg
 
  +
if !g:bclose_multiple && len(wnums) > 1
echomsg 'No write since last change for buffer'
 
  +
call s:Warn('Buffer is in multiple windows (use ":let bclose_multiple=1")')
\ buffer '(add ! to override)'
 
echohl None
 
 
return
 
return
 
endif
 
endif
 
let wcurrent = winnr()
if buffer_window &gt;= 0
 
  +
for w in wnums
exe 'norm ' . buffer_window . "\&lt;C-w&gt;w"
 
exe 'enew' . a:bang
+
execute w.'wincmd w'
  +
let prevbuf = bufnr('#')
exe 'norm ' . current_window . "\&lt;C-w&gt;w"
 
  +
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>
  +
</pre>
  +
  +
==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 (<code>:Kwbd</code>) 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.
  +
  +
<pre>
  +
"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
 
endif
silent exe 'bdel' . a:bang . ' ' . buffer
 
 
endfunction
 
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
 
</pre>
 
</pre>
   
  +
==See also==
----
 
  +
* [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!
&gt; Using :bd &lt;bufferName&gt; works fine for me.
 
  +
*{{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=1147|text=bufkill}} unload/delete/wipe a buffer, keep its window(s), display last accessed buffer(s)
 
*{{script|id=159|text=MiniBufExplorer}} provides this capability with a simple user interface
   
 
==Comments==
The key part of this tip is deleting the buffer "without closing the window". Normally when you do a ":bd" it will close the window in the split.
 
  +
For getting asked what should be done, when a file has been modified I use normally <code>:confirm :bd</code>. So I changed the script in 2lines to achieve this:
  +
<pre>
  +
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'
  +
</pre>
   
  +
Just comment out the return and replace the command. greetings - Joe
----
 

Revision as of 22:18, 2 September 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

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

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