(Remove html character entities) |
No edit summary Tag: Visual edit |
||
(25 intermediate revisions by 13 users not shown) | |||
Line 1: | Line 1: | ||
− | {{review}} |
||
− | {{Duplicate|622}} |
||
− | |||
{{TipImported |
{{TipImported |
||
|id=165 |
|id=165 |
||
|previous=164 |
|previous=164 |
||
|next=166 |
|next=166 |
||
− | |created= |
+ | |created=2001 |
|complexity=intermediate |
|complexity=intermediate |
||
− | |author= |
+ | |author= |
− | |version= |
+ | |version=7.0 |
|rating=29/13 |
|rating=29/13 |
||
|category1= |
|category1= |
||
|category2= |
|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. |
||
⚫ | |||
− | " 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 <SID>BufcloseCloseIt() |
||
− | function! <SID>BufcloseCloseIt() |
||
⚫ | |||
⚫ | |||
− | if buflisted(l:alternateBufNum) |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | execute("bdelete ".l:currentBufNum) |
||
⚫ | |||
⚫ | |||
⚫ | |||
+ | One option is to '''first switch to another buffer''' (<code>:bp</code>, <code>:bn</code>, or <code>:b#</code>, for instance) and then '''delete the first''' (alternate) buffer with <code>:bd#</code>. |
||
⚫ | |||
⚫ | |||
+ | Alternatively, the '''script below''' defines the <code>:Bclose</code> command that deletes a buffer, while keeping the current window layout (no windows are closed). |
||
− | ---- |
||
− | 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 ;-(. |
||
+ | ==Usage== |
||
− | ---- |
||
+ | The <code>:Bclose</code> command deletes a buffer without changing the window layout. For each window where the buffer is currently displayed: |
||
− | I didn't try the tip, but this is the solution I came up with: |
||
+ | *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> |
||
− | 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 in current window. |
||
+ | ;<code>:Bclose ''N''</code> |
||
+ | :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 |
||
⚫ | |||
− | \ :call BufClose(expand('<args>'), expand('<bang>')) |
||
+ | |||
+ | ==Script== |
||
+ | 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. |
||
⚫ | |||
+ | " 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) |
||
⚫ | |||
+ | echomsg a:msg |
||
⚫ | |||
⚫ | |||
+ | " 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). |
||
⚫ | |||
+ | if empty(a:buffer) |
||
⚫ | |||
⚫ | |||
+ | let btarget = bufnr(str2nr(a:buffer)) |
||
else |
else |
||
− | let |
+ | let btarget = bufnr(a:buffer) |
endif |
endif |
||
− | if |
+ | if btarget < 0 |
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
return |
return |
||
endif |
endif |
||
⚫ | |||
⚫ | |||
⚫ | |||
− | 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. |
||
⚫ | |||
+ | let wnums = filter(range(1, winnr('$')), 'winbufnr(v:val) == btarget') |
||
− | echohl ErrorMsg |
||
+ | if !g:bclose_multiple && len(wnums) > 1 |
||
⚫ | |||
+ | call s:Warn('Buffer is in multiple windows (use ":let bclose_multiple=1")') |
||
− | \ buffer '(add ! to override)' |
||
− | echohl None |
||
return |
return |
||
endif |
endif |
||
⚫ | |||
− | if buffer_window >= 0 |
||
+ | for w in wnums |
||
− | exe 'norm ' . buffer_window . "\<C-w>w" |
||
− | + | execute w.'wincmd w' |
|
+ | let prevbuf = bufnr('#') |
||
− | exe 'norm ' . current_window . "\<C-w>w" |
||
+ | if prevbuf > 0 && buflisted(prevbuf) && prevbuf != btarget |
||
⚫ | |||
⚫ | |||
+ | bprevious |
||
⚫ | |||
+ | 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 |
||
⚫ | |||
+ | endfor |
||
+ | execute 'bdelete'.a:bang.' '.btarget |
||
+ | execute wcurrent.'wincmd w' |
||
+ | endfunction |
||
+ | command! -bang -complete=buffer -nargs=? Bclose call <SID>Bclose(<q-bang>, <q-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(&modified) |
||
+ | let answer = confirm("This buffer has been modified. Are you sure you want to delete it?", "&Yes\n&No", 2) |
||
+ | if(answer != 1) |
||
+ | return |
||
+ | endif |
||
⚫ | |||
+ | if(!buflisted(winbufnr(0))) |
||
+ | bd! |
||
+ | return |
||
+ | endif |
||
⚫ | |||
+ | let s:kwbdWinNum = winnr() |
||
+ | windo call s:Kwbd(2) |
||
+ | execute s:kwbdWinNum . 'wincmd w' |
||
+ | let s:buflistedLeft = 0 |
||
+ | let s:bufFinalJump = 0 |
||
⚫ | |||
+ | let l:i = 1 |
||
+ | while(l:i <= l:nBufs) |
||
+ | if(l:i != s:kwbdBufNum) |
||
⚫ | |||
+ | 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 |
||
⚫ | |||
⚫ | |||
+ | 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! |
||
− | > Using :bd <bufferName> 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) |
||
⚫ | |||
⚫ | |||
− | 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 |
||
+ | 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 |
||
− | ---- |
Latest revision as of 21:44, 18 February 2021
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.
One option is to first switch to another buffer (:bp
, :bn
, or :b#
, for instance) and then delete the first (alternate) buffer with :bd#
.
Alternatively, 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 != btarget 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 <SID>Bclose(<q-bang>, <q-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(&modified) let answer = confirm("This buffer has been modified. Are you sure you want to delete it?", "&Yes\n&No", 2) if(answer != 1) return endif endif 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