"============================================================================ " Copyright: Copyright (c) 2001-2018, Jeff Lanzarotta " All rights reserved. " " Redistribution and use in source and binary forms, with or " without modification, are permitted provided that the " following conditions are met: " " * Redistributions of source code must retain the above " copyright notice, this list of conditions and the following " disclaimer. " " * Redistributions in binary form must reproduce the above " copyright notice, this list of conditions and the following " disclaimer in the documentation and/or other materials " provided with the distribution. " " * Neither the name of the {organization} nor the names of its " contributors may be used to endorse or promote products " derived from this software without specific prior written " permission. " " THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND " CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, " INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF " MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE " DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR " CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, " SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT " NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; " LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) " HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN " CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR " OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, " EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. " Name Of File: bufexplorer.vim " Description: Buffer Explorer Vim Plugin " Maintainer: Jeff Lanzarotta (delux256-vim at outlook dot com) " Last Changed: Saturday, 08 December 2018 " Version: See g:bufexplorer_version for version number. " Usage: This file should reside in the plugin directory and be " automatically sourced. " " You may use the default keymappings of " " be - Opens BufExplorer " bt - Toggles BufExplorer open or closed " bs - Opens horizontally split window BufExplorer " bv - Opens vertically split window BufExplorer " " Or you can override the defaults and define your own mapping " in your vimrc file, for example: " " nnoremap :BufExplorer " nnoremap :ToggleBufExplorer " nnoremap :BufExplorerHorizontalSplit " nnoremap :BufExplorerVerticalSplit " " Or you can use " " ":BufExplorer" - Opens BufExplorer " ":ToggleBufExplorer" - Opens/Closes BufExplorer " ":BufExplorerHorizontalSplit" - Opens horizontally window BufExplorer " ":BufExplorerVerticalSplit" - Opens vertically split window BufExplorer " " For more help see supplied documentation. " History: See supplied documentation. "============================================================================= " Exit quickly if already running or when 'compatible' is set. {{{1 if exists("g:bufexplorer_version") || &cp finish endif "1}}} " Version number let g:bufexplorer_version = "7.4.21" " Plugin Code {{{1 " Check for Vim version {{{2 if !exists("g:bufExplorerVersionWarn") let g:bufExplorerVersionWarn = 1 endif if v:version < 700 if g:bufExplorerVersionWarn echohl WarningMsg echo "Sorry, bufexplorer ".g:bufexplorer_version." required Vim 7.0 or greater." echohl None endif finish endif " Check to see if the version of Vim has the correct patch applied, if not, do " not used . if v:version > 703 || v:version == 703 && has('patch1261') && has('patch1264') " We are good to go. else if g:bufExplorerVersionWarn echohl WarningMsg echo "Sorry, bufexplorer ".g:bufexplorer_version." required Vim 7.3 or greater with patch1261 and patch1264." echohl None endif finish endif " Create commands {{{2 command! BufExplorer :call BufExplorer() command! ToggleBufExplorer :call ToggleBufExplorer() command! BufExplorerHorizontalSplit :call BufExplorerHorizontalSplit() command! BufExplorerVerticalSplit :call BufExplorerVerticalSplit() " Set {{{2 function! s:Set(var, default) if !exists(a:var) if type(a:default) execute "let" a:var "=" string(a:default) else execute "let" a:var "=" a:default endif return 1 endif return 0 endfunction " Script variables {{{2 let s:MRU_Exclude_List = ["[BufExplorer]","__MRU_Files__","[Buf\ List]"] let s:MRUList = [] let s:name = '[BufExplorer]' let s:originBuffer = 0 let s:running = 0 let s:sort_by = ["number", "name", "fullpath", "mru", "extension"] let s:splitMode = "" let s:types = {"fullname": ':p', "path": ':p:h', "relativename": ':~:.', "relativepath": ':~:.:h', "shortname": ':t'} " Setup the autocommands that handle the MRUList and other stuff. {{{2 autocmd VimEnter * call s:Setup() " Setup {{{2 function! s:Setup() call s:Reset() " Now that the MRUList is created, add the other autocmds. augroup BufExplorer autocmd! autocmd BufEnter,BufNew * call s:ActivateBuffer() autocmd BufWipeOut * call s:DeactivateBuffer(1) autocmd BufDelete * call s:DeactivateBuffer(0) autocmd BufWinEnter \[BufExplorer\] call s:Initialize() autocmd BufWinLeave \[BufExplorer\] call s:Cleanup() augroup END endfunction " Reset {{{2 function! s:Reset() " Build initial MRUList. This makes sure all the files specified on the " command line are picked up correctly. let s:MRUList = range(1, bufnr('$')) " Initialize the association of buffers to tabs for any buffers " that have been created prior to now, e.g., files specified as " vim command line arguments call s:CatalogBuffers() endfunction " CatalogBuffers {{{2 " Create tab associations for any existing buffers function! s:CatalogBuffers() let ct = tabpagenr() for tab in range(1, tabpagenr('$')) silent execute 'normal! ' . tab . 'gt' for buf in tabpagebuflist() call s:UpdateTabBufData(buf) endfor endfor silent execute 'normal! ' . ct . 'gt' endfunction " AssociatedTab {{{2 " Return the number of the tab associated with the specified buffer. " If the buffer is associated with more than one tab, the first one " found is returned. If the buffer is not associated with any tabs, " -1 is returned. function! s:AssociatedTab(bufnr) for tab in range(1, tabpagenr('$')) let list = gettabvar(tab, 'bufexp_buf_list', []) let idx = index(list, a:bufnr) if idx != -1 return tab endif endfor return -1 endfunction " RemoveBufFromOtherTabs {{{2 " Remove the specified buffer from the buffer lists of all tabs " except the current tab. function! s:RemoveBufFromOtherTabs(bufnr) for tab in range(1, tabpagenr('$')) if tab == tabpagenr() continue endif let list = gettabvar(tab, 'bufexp_buf_list', []) let idx = index(list, a:bufnr) if idx == -1 continue endif call remove(list, idx) call settabvar(tab, 'bufexp_buf_list', list) endfor endfunction " AddBufToCurrentTab {{{2 " Add the specified buffer to the list of buffers associated " with the current tab function! s:AddBufToCurrentTab(bufnr) if index(t:bufexp_buf_list, a:bufnr) == -1 call add(t:bufexp_buf_list, a:bufnr) endif endfunction " IsInCurrentTab {{{2 " Returns whether the specified buffer is associated " with the current tab function! s:IsInCurrentTab(bufnr) " It shouldn't happen that the list of buffers is " not defined but if it does, play it safe and " include the buffer if !exists('t:bufexp_buf_list') return 1 endif return (index(t:bufexp_buf_list, a:bufnr) != -1) endfunction " UpdateTabBufData {{{2 " Update the tab buffer data for the specified buffer " " The current tab's list is updated. If a buffer is only " allowed to be associated with one tab, it is removed " from the lists of any other tabs with which it may have " been associated. " " The associations between tabs and buffers are maintained " in separate lists for each tab, which are stored in tab- " specific variables 't:bufexp_buf_list'. function! s:UpdateTabBufData(bufnr) " The first time we add a tab, Vim uses the current buffer " as its starting page even though we are about to edit a " new page, and another BufEnter for the new page is triggered " later. Use this first BufEnter to initialize the list of " buffers, but don't add the buffer number to the list if " it is already associated with another tab " " Unfortunately, this doesn't work right when the first " buffer opened in the tab should be associated with it, " such as when 'tab split +buffer N' is used if !exists("t:bufexp_buf_list") let t:bufexp_buf_list = [] if s:AssociatedTab(a:bufnr) != -1 return endif endif call s:AddBufToCurrentTab(a:bufnr) if g:bufExplorerOnlyOneTab call s:RemoveBufFromOtherTabs(a:bufnr) endif endfunction " ActivateBuffer {{{2 function! s:ActivateBuffer() let _bufnr = bufnr("%") call s:UpdateTabBufData(_bufnr) call s:MRUPush(_bufnr) endfunction " DeactivateBuffer {{{2 function! s:DeactivateBuffer(remove) let _bufnr = str2nr(expand("")) call s:MRUPop(_bufnr) endfunction " MRUPop {{{2 function! s:MRUPop(bufnr) call filter(s:MRUList, 'v:val != '.a:bufnr) endfunction " MRUPush {{{2 function! s:MRUPush(buf) " Skip temporary buffer with buftype set. Don't add the BufExplorer window " to the list. if s:ShouldIgnore(a:buf) == 1 return endif " Remove the buffer number from the list if it already exists. call s:MRUPop(a:buf) " Add the buffer number to the head of the list. call insert(s:MRUList, a:buf) endfunction " ShouldIgnore {{{2 function! s:ShouldIgnore(buf) " Ignore temporary buffers with buftype set. if empty(getbufvar(a:buf, "&buftype") == 0) return 1 endif " Ignore buffers with no name. if empty(bufname(a:buf)) == 1 return 1 endif " Ignore the BufExplorer buffer. if fnamemodify(bufname(a:buf), ":t") == s:name return 1 endif " Ignore any buffers in the exclude list. if index(s:MRU_Exclude_List, bufname(a:buf)) >= 0 return 1 endif " Else return 0 to indicate that the buffer was not ignored. return 0 endfunction " Initialize {{{2 function! s:Initialize() call s:SetLocalSettings() let s:running = 1 endfunction " Cleanup {{{2 function! s:Cleanup() if exists("s:_insertmode") let &insertmode = s:_insertmode endif if exists("s:_showcmd") let &showcmd = s:_showcmd endif if exists("s:_cpo") let &cpo = s:_cpo endif if exists("s:_report") let &report = s:_report endif let s:running = 0 let s:splitMode = "" delmarks! endfunction " SetLocalSettings {{{2 function! s:SetLocalSettings() let s:_insertmode = &insertmode set noinsertmode let s:_showcmd = &showcmd set noshowcmd let s:_cpo = &cpo set cpo&vim let s:_report = &report let &report = 10000 setlocal nonumber setlocal foldcolumn=0 setlocal nofoldenable setlocal cursorline setlocal nospell setlocal nobuflisted setlocal filetype=bufexplorer endfunction " BufExplorerHorizontalSplit {{{2 function! BufExplorerHorizontalSplit() let s:splitMode = "sp" execute "BufExplorer" endfunction " BufExplorerVerticalSplit {{{2 function! BufExplorerVerticalSplit() let s:splitMode = "vsp" execute "BufExplorer" endfunction " ToggleBufExplorer {{{2 function! ToggleBufExplorer() if exists("s:running") && s:running == 1 && bufname(winbufnr(0)) == s:name call s:Close() else call BufExplorer() endif endfunction " BufExplorer {{{2 function! BufExplorer() let name = s:name if !has("win32") " On non-Windows boxes, escape the name so that is shows up correctly. let name = escape(name, "[]") endif " Make sure there is only one explorer open at a time. if s:running == 1 " Go to the open buffer. if has("gui") execute "drop" name endif return endif " Add zero to ensure the variable is treated as a number. let s:originBuffer = bufnr("%") + 0 silent let s:raw_buffer_listing = s:GetBufferInfo(0) " We may have to split the current window. if s:splitMode != "" " Save off the original settings. let [_splitbelow, _splitright] = [&splitbelow, &splitright] " Set the setting to ours. let [&splitbelow, &splitright] = [g:bufExplorerSplitBelow, g:bufExplorerSplitRight] let _size = (s:splitMode == "sp") ? g:bufExplorerSplitHorzSize : g:bufExplorerSplitVertSize " Split the window either horizontally or vertically. if _size <= 0 execute 'keepalt ' . s:splitMode else execute 'keepalt ' . _size . s:splitMode endif " Restore the original settings. let [&splitbelow, &splitright] = [_splitbelow, _splitright] endif if !exists("b:displayMode") || b:displayMode != "winmanager" " Do not use keepalt when opening bufexplorer to allow the buffer that " we are leaving to become the new alternate buffer execute "silent keepjumps hide edit".name endif call s:DisplayBufferList() " Position the cursor in the newly displayed list on the line representing " the active buffer. The active buffer is the line with the '%' character " in it. execute search("%") endfunction " DisplayBufferList {{{2 function! s:DisplayBufferList() " Do not set bufhidden since it wipes out the data if we switch away from " the buffer using CTRL-^. setlocal buftype=nofile setlocal modifiable setlocal noswapfile setlocal nowrap call s:SetupSyntax() call s:MapKeys() " Wipe out any existing lines in case BufExplorer buffer exists and the " user had changed any global settings that might reduce the number of " lines needed in the buffer. silent keepjumps 1,$d _ call setline(1, s:CreateHelp()) call s:BuildBufferList() call cursor(s:firstBufferLine, 1) if !g:bufExplorerResize normal! zz endif setlocal nomodifiable endfunction " MapKeys {{{2 function! s:MapKeys() if exists("b:displayMode") && b:displayMode == "winmanager" nnoremap :call SelectBuffer() endif nnoremap