├── .gitignore ├── release.bat ├── how_to_release.txt ├── LICENSE ├── syntax └── bufexplorer.vim ├── README.md ├── plugin └── bufexplorer.vim └── doc └── bufexplorer.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore generated tags 2 | /doc/tags 3 | dist.bat 4 | *.zip 5 | tags 6 | *.sw[a-p] 7 | 8 | # Github token. 9 | github_token 10 | 11 | # goreleaser dist directory. 12 | dist/ 13 | -------------------------------------------------------------------------------- /release.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if [%1]==[] goto :help 4 | 5 | setlocal enableDelayedExpansion 6 | set /p "GH_TOKEN=" be` normal open 10 | 11 | `\bt` toggle open / close 12 | 13 | `\bs` force horizontal split open 14 | 15 | `\bv` force vertical split open 16 | 17 | Once the bufexplorer window is open you can use the normal movement keys (hjkl) to move around and then use `` or `` to select the buffer you would like to open. If you would like to have the selected buffer opened in a new tab, simply press either `` or `t`. Please note that when opening a buffer in a tab, that if the buffer is already in another tab, bufexplorer can switch to that tab automatically for you if you would like. More about that in the supplied VIM help. 18 | 19 | Bufexplorer also offers various options including: 20 | 21 | - Display the list of buffers in various sort orders including: 22 | - Most Recently Used (MRU) which is the default 23 | - Buffer number 24 | - File name 25 | - File extension 26 | - Full file path name 27 | - Delete buffer from list 28 | 29 | For more about options, sort orders, configuration options, etc. please see the supplied VIM help. 30 | 31 | In this example, the `` key is assigned to [Space]. 32 | 33 | ![ScreenToGif](https://github.com/user-attachments/assets/ae5422b9-59ac-4657-aab5-30e6eb8a3243) 34 | 35 | ## vim.org 36 | 37 | This plugin can also be found at http://www.vim.org/scripts/script.php?script_id=42. 38 | 39 | ## Installation 40 | 41 | ### Manually 42 | 43 | 1. If you do not want to use one of the the bundle handlers, you can take the 44 | zip file from vim.org and unzip it and copy the plugin to your vimfiles\plugin 45 | directory and the txt file to your vimfiles\doc directory. If you do that, 46 | make sure you generate the help by executing 47 | 48 | `:helptag /doc` 49 | 50 | Once help tags have been generated, you can view the manual with 51 | `:help bufexplorer`. 52 | 53 | ### Vundle (https://github.com/gmarik/Vundle.vim) 54 | 55 | 1. Add the following configuration to your `.vimrc`. 56 | 57 | Plugin 'jlanzarotta/bufexplorer' 58 | 59 | 2. Install with `:BundleInstall`. 60 | 61 | ### NeoBundle (https://github.com/Shougo/neobundle.vim) 62 | 63 | 1. Add the following configuration to your `.vimrc`. 64 | 65 | NeoBundle 'jlanzarotta/bufexplorer' 66 | 67 | 2. Install with `:NeoBundleInstall`. 68 | 69 | ### Plug (https://github.com/junegunn/vim-plug) 70 | 71 | 1. Add the following configuration to your `.vimrc`. 72 | 73 | Plug 'jlanzarotta/bufexplorer' 74 | 75 | 2. Install with `:PlugInstall`. 76 | 77 | ### Pathogen 78 | 79 | 1. Install with the following command. 80 | 81 | git clone https://github.com/jlanzarotta/bufexplorer.git ~/.vim/bundle/bufexplorer.vim 82 | 83 | ## License 84 | 85 | Copyright (c) 2001-2025, Jeff Lanzarotta 86 | 87 | All rights reserved. 88 | 89 | Redistribution and use in source and binary forms, with or without modification, 90 | are permitted provided that the following conditions are met: 91 | 92 | - Redistributions of source code must retain the above copyright notice, this 93 | list of conditions and the following disclaimer. 94 | 95 | - Redistributions in binary form must reproduce the above copyright notice, this 96 | list of conditions and the following disclaimer in the documentation and/or 97 | other materials provided with the distribution. 98 | 99 | - Neither the name of the {organization} nor the names of its 100 | contributors may be used to endorse or promote products derived from 101 | this software without specific prior written permission. 102 | 103 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 104 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 105 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 106 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 107 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 108 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 109 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 110 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 111 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 112 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 113 | -------------------------------------------------------------------------------- /plugin/bufexplorer.vim: -------------------------------------------------------------------------------- 1 | "============================================================================ 2 | " Copyright: Copyright (c) 2001-2025, Jeff Lanzarotta 3 | " All rights reserved. 4 | " 5 | " Redistribution and use in source and binary forms, with or 6 | " without modification, are permitted provided that the 7 | " following conditions are met: 8 | " 9 | " * Redistributions of source code must retain the above 10 | " copyright notice, this list of conditions and the following 11 | " disclaimer. 12 | " 13 | " * Redistributions in binary form must reproduce the above 14 | " copyright notice, this list of conditions and the following 15 | " disclaimer in the documentation and/or other materials 16 | " provided with the distribution. 17 | " 18 | " * Neither the name of the {organization} nor the names of its 19 | " contributors may be used to endorse or promote products 20 | " derived from this software without specific prior written 21 | " permission. 22 | " 23 | " THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 24 | " CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 25 | " INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 26 | " MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | " DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 28 | " CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 | " SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 30 | " NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 | " LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 | " HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 | " CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 34 | " OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 35 | " EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | " Name Of File: bufexplorer.vim 37 | " Description: Buffer Explorer Vim Plugin 38 | " Maintainer: Jeff Lanzarotta (my name at gmail dot com) 39 | " Last Changed: Tuesday, 19 August 2025 40 | " Version: See g:bufexplorer_version for version number. 41 | " Usage: This file should reside in the plugin directory and be 42 | " automatically sourced. 43 | " 44 | " You may use the default keymappings of 45 | " 46 | " be - Opens BufExplorer 47 | " bt - Toggles BufExplorer open or closed 48 | " bs - Opens horizontally split window BufExplorer 49 | " bv - Opens vertically split window BufExplorer 50 | " 51 | " Or you can override the defaults and define your own mapping 52 | " in your vimrc file, for example: 53 | " 54 | " nnoremap :BufExplorer 55 | " nnoremap :ToggleBufExplorer 56 | " nnoremap :BufExplorerHorizontalSplit 57 | " nnoremap :BufExplorerVerticalSplit 58 | " 59 | " Or you can use 60 | " 61 | " ":BufExplorer" - Opens BufExplorer 62 | " ":ToggleBufExplorer" - Opens/Closes BufExplorer 63 | " ":BufExplorerHorizontalSplit" - Opens horizontally window BufExplorer 64 | " ":BufExplorerVerticalSplit" - Opens vertically split window BufExplorer 65 | " 66 | " For more help see supplied documentation. 67 | " History: See supplied documentation. 68 | "============================================================================= 69 | 70 | " Exit quickly if already running or when 'compatible' is set. {{{1 71 | if exists("g:bufexplorer_version") || &cp 72 | finish 73 | endif 74 | "1}}} 75 | 76 | " Version number. 77 | let g:bufexplorer_version = "7.13.0" 78 | 79 | " Plugin Code {{{1 80 | " Check for Vim version {{{2 81 | if !exists("g:bufExplorerVersionWarn") 82 | let g:bufExplorerVersionWarn = 1 83 | endif 84 | 85 | " Make sure we are using the correct version of Vim. If not, do not load the 86 | " plugin. 87 | if v:version < 704 88 | if g:bufExplorerVersionWarn 89 | echohl WarningMsg 90 | echo "Sorry, bufexplorer ".g:bufexplorer_version." required Vim 7.4 or greater." 91 | echohl None 92 | endif 93 | finish 94 | endif 95 | 96 | " Command actions {{{2 97 | let s:actions = [ 98 | \ 'current', 99 | \ 'close', 100 | \ 'split', 101 | \ 'vsplit', 102 | \ 'above', 103 | \ 'below', 104 | \ 'left', 105 | \ 'right', 106 | \ ] 107 | 108 | " Command-line completion function for `s:actions`. 109 | function! s:ActionArgs(ArgLead, CmdLine, CursorPos) 110 | return join(s:actions, "\n") 111 | endfunction 112 | 113 | " Create commands {{{2 114 | command! -nargs=? -complete=custom,ActionArgs 115 | \ BufExplorer :call BufExplorer() 116 | command! -nargs=? -complete=custom,ActionArgs 117 | \ ToggleBufExplorer :call ToggleBufExplorer() 118 | command! BufExplorerHorizontalSplit :call BufExplorerHorizontalSplit() 119 | command! BufExplorerVerticalSplit :call BufExplorerVerticalSplit() 120 | 121 | " Set {{{2 122 | function! s:Set(var, default) 123 | if !exists(a:var) 124 | if type(a:default) 125 | execute "let" a:var "=" string(a:default) 126 | else 127 | execute "let" a:var "=" a:default 128 | endif 129 | 130 | return 1 131 | endif 132 | 133 | return 0 134 | endfunction 135 | 136 | " Naming conventions for file paths. 137 | " Conventionally a `path` is the string of characters used to identify a file 138 | " (ref. https://en.wikipedia.org/wiki/Path_(computing)). 139 | " An absolute or `full` path starts from the root directory and consists of 140 | " parent directories (if any) and a final `name` component. 141 | " A file's `dir` (directory) is the path to the parent directory of the file. 142 | " In general: 143 | " 144 | " fullpath = dir / name 145 | " 146 | " Paths below the user's home directory may be abbreviated, replacing the home 147 | " directory with `~`, e.g.: 148 | " 149 | " /home/user/some/file 150 | " -> 151 | " ~/some/file 152 | " 153 | " `homerel` refers to paths with home-directory-relative abbreviation. 154 | " 155 | " `relative` refers to paths computed relative to the current working directory; 156 | " these also include the home-directory-relative abbreviation. 157 | " 158 | " `rawpath` is the path as returned from `:buffers`; as such, buffers lacking 159 | " any name are represented as `[No Name]`. 160 | " 161 | " Thus, for a buffer: 162 | " - `rawpath` is the path as returned from `:buffers`. 163 | " - `fullpath` is the absolute path to the buffer. 164 | " - `homerelpath` is `fullpath` with the `~/` abbreviation. 165 | " - `relativepath` is `homerelpath` with relative abbreviation. 166 | " - `fulldir` is the absolute path to the buffer's parent directory. 167 | " - `homereldir` is `fulldir` with the `~/` abbreviation. 168 | " - `relativedir` is `homereldir` with relative abbreviation. 169 | 170 | " Script variables {{{2 171 | let s:MRU_Exclude_List = ["[BufExplorer]","__MRU_Files__","[Buf\ List]"] 172 | let s:name = '[BufExplorer]' 173 | " Buffer number of the BufExplorer window. 174 | let s:bufExplorerBuffer = 0 175 | let s:running = 0 176 | let s:sort_by = ["number", "name", "fullpath", "mru", "extension"] 177 | let s:didSplit = 0 178 | 179 | " Setup the autocommands that handle stuff. {{{2 180 | augroup BufExplorer 181 | autocmd! 182 | autocmd WinEnter * call s:DoWinEnter() 183 | autocmd BufEnter * call s:DoBufEnter() 184 | autocmd BufDelete * call s:DoBufDelete() 185 | if exists('##TabClosed') 186 | autocmd TabClosed * call s:DoTabClosed() 187 | endif 188 | autocmd BufWinEnter \[BufExplorer\] call s:Initialize() 189 | autocmd BufWinLeave \[BufExplorer\] call s:Cleanup() 190 | augroup END 191 | 192 | " AssignTabId {{{2 193 | " Assign a `tabId` to the given tab. 194 | function! s:AssignTabId(tabNbr) 195 | " Create a unique `tabId` based on the current time and an incrementing 196 | " counter value that helps ensure uniqueness. 197 | let tabId = reltimestr(reltime()) . ':' . s:tabIdCounter 198 | call settabvar(a:tabNbr, 'bufexp_tabId', tabId) 199 | let s:tabIdCounter = (s:tabIdCounter + 1) % 1000000000 200 | return tabId 201 | endfunction 202 | 203 | let s:tabIdCounter = 0 204 | 205 | " GetTabId {{{2 206 | " Retrieve the `tabId` for the given tab (or '' if the tab has no `tabId`). 207 | function! s:GetTabId(tabNbr) 208 | return gettabvar(a:tabNbr, 'bufexp_tabId', '') 209 | endfunction 210 | 211 | " MRU data structure {{{2 212 | " An MRU data structure is a dictionary that holds a circular doubly linked list 213 | " of `item` values. The dictionary contains three keys: 214 | " 'head': a sentinel `item` representing the head of the list. 215 | " 'next': a dictionary mapping an `item` to the next `item` in the list. 216 | " 'prev': a dictionary mapping an `item` to the previous `item` in the list. 217 | " E.g., an MRU holding buffer numbers will use `0` (an invalid buffer number) as 218 | " `head`. With the buffer numbers `1`, `2`, and `3`, an example MRU would be: 219 | " 220 | " +--<---------<---------<---------<---------<+ 221 | " `next` | | 222 | " +--> +---+ --> +---+ --> +---+ --> +---+ -->+ 223 | " `head` | 0 | | 1 | | 2 | | 3 | 224 | " +<-- +---+ <-- +---+ <-- +---+ <-- +---+ <--+ 225 | " `prev` | | 226 | " +->-------->--------->--------->--------->--+ 227 | " 228 | " `head` allows the chosen sentinel item to differ in value and type; for 229 | " example, `head` could be the string '.', allowing an MRU of strings (such as 230 | " for `TabId` values). 231 | " 232 | " Note that dictionary keys are always strings. Integers may be used, but they 233 | " are converted to strings when used (and `keys(theDictionary)` will be a 234 | " list of strings, not of integers). 235 | 236 | " MRUNew {{{2 237 | function! s:MRUNew(head) 238 | let [next, prev] = [{}, {}] 239 | let next[a:head] = a:head 240 | let prev[a:head] = a:head 241 | return { 'head': a:head, 'next': next, 'prev': prev } 242 | endfunction 243 | 244 | " MRULen {{{2 245 | function! s:MRULen(mru) 246 | " Do not include the always-present `mru.head` item. 247 | return len(a:mru.next) - 1 248 | endfunction 249 | 250 | " MRURemoveMustExist {{{2 251 | " `item` must exist in `mru`. 252 | function! s:MRURemoveMustExist(mru, item) 253 | let [next, prev] = [a:mru.next, a:mru.prev] 254 | let prevItem = prev[a:item] 255 | let nextItem = next[a:item] 256 | let next[prevItem] = nextItem 257 | let prev[nextItem] = prevItem 258 | unlet next[a:item] 259 | unlet prev[a:item] 260 | endfunction 261 | 262 | " MRURemove {{{2 263 | " `item` need not exist in `mru`. 264 | function! s:MRURemove(mru, item) 265 | if has_key(a:mru.next, a:item) 266 | call s:MRURemoveMustExist(a:mru, a:item) 267 | endif 268 | endfunction 269 | 270 | " MRUAdd {{{2 271 | function! s:MRUAdd(mru, item) 272 | let [next, prev] = [a:mru.next, a:mru.prev] 273 | let prevItem = a:mru.head 274 | let nextItem = next[prevItem] 275 | if a:item != nextItem 276 | call s:MRURemove(a:mru, a:item) 277 | let next[a:item] = nextItem 278 | let prev[a:item] = prevItem 279 | let next[prevItem] = a:item 280 | let prev[nextItem] = a:item 281 | endif 282 | endfunction 283 | 284 | " MRUGetItems {{{2 285 | " Return list of up to `maxItems` items in MRU order. 286 | " `maxItems == 0` => unlimited. 287 | function! s:MRUGetItems(mru, maxItems) 288 | let [head, next] = [a:mru.head, a:mru.next] 289 | let items = [] 290 | let item = next[head] 291 | while item != head 292 | if a:maxItems > 0 && len(items) >= a:maxItems 293 | break 294 | endif 295 | call add(items, item) 296 | let item = next[item] 297 | endwhile 298 | return items 299 | endfunction 300 | 301 | " MRUGetOrdering {{{2 302 | " Return dictionary mapping up to `maxItems` from `item` to MRU order. 303 | " `maxItems == 0` => unlimited. 304 | function! s:MRUGetOrdering(mru, maxItems) 305 | let [head, next] = [a:mru.head, a:mru.next] 306 | let items = {} 307 | let order = 0 308 | let item = next[head] 309 | while item != head 310 | if a:maxItems > 0 && order >= a:maxItems 311 | break 312 | endif 313 | let items[item] = order 314 | let order = order + 1 315 | let item = next[item] 316 | endwhile 317 | return items 318 | endfunction 319 | 320 | " MRU trackers {{{2 321 | " `.head` value for tab MRU: 322 | let s:tabIdHead = '.' 323 | 324 | " Track MRU buffers globally (independent of tabs). 325 | let s:bufMru = s:MRUNew(0) 326 | 327 | " Track MRU buffers for each tab, indexed by `tabId`. 328 | " `s:bufMruByTab[tabId] -> MRU structure`. 329 | let s:bufMruByTab = {} 330 | 331 | " Track MRU tabs for each buffer, indexed by `bufNbr`. 332 | " `s:tabMruByBuf[burNbr] -> MRU structure`. 333 | let s:tabMruByBuf = {} 334 | 335 | " MRURemoveBuf {{{2 336 | function! s:MRURemoveBuf(bufNbr) 337 | call s:MRURemove(s:bufMru, a:bufNbr) 338 | if has_key(s:tabMruByBuf, a:bufNbr) 339 | let mru = s:tabMruByBuf[a:bufNbr] 340 | let [head, next] = [mru.head, mru.next] 341 | let tabId = next[head] 342 | while tabId != head 343 | call s:MRURemoveMustExist(s:bufMruByTab[tabId], a:bufNbr) 344 | let tabId = next[tabId] 345 | endwhile 346 | unlet s:tabMruByBuf[a:bufNbr] 347 | endif 348 | endfunction 349 | 350 | " MRURemoveTab {{{2 351 | function! s:MRURemoveTab(tabId) 352 | if has_key(s:bufMruByTab, a:tabId) 353 | let mru = s:bufMruByTab[a:tabId] 354 | let [head, next] = [mru.head, mru.next] 355 | let bufNbr = next[head] 356 | while bufNbr != head 357 | call s:MRURemoveMustExist(s:tabMruByBuf[bufNbr], a:tabId) 358 | let bufNbr = next[bufNbr] 359 | endwhile 360 | unlet s:bufMruByTab[a:tabId] 361 | endif 362 | endfunction 363 | 364 | " MRUAddBufTab {{{2 365 | function! s:MRUAddBufTab(bufNbr, tabId) 366 | if s:ShouldIgnore(a:bufNbr) 367 | return 368 | endif 369 | call s:MRUAdd(s:bufMru, a:bufNbr) 370 | if !has_key(s:bufMruByTab, a:tabId) 371 | let s:bufMruByTab[a:tabId] = s:MRUNew(0) 372 | endif 373 | let bufMru = s:bufMruByTab[a:tabId] 374 | call s:MRUAdd(bufMru, a:bufNbr) 375 | if !has_key(s:tabMruByBuf, a:bufNbr) 376 | let s:tabMruByBuf[a:bufNbr] = s:MRUNew(s:tabIdHead) 377 | endif 378 | let tabMru = s:tabMruByBuf[a:bufNbr] 379 | call s:MRUAdd(tabMru, a:tabId) 380 | endfunction 381 | 382 | " MRUTabForBuf {{{2 383 | " Return `tabId` most recently used by `bufNbr`. 384 | " If no `tabId` is found for `bufNbr`, return `s:tabIdHead`. 385 | function! s:MRUTabForBuf(bufNbr) 386 | let tabMru = get(s:tabMruByBuf, a:bufNbr, s:alwaysEmptyTabMru) 387 | return tabMru.next[tabMru.head] 388 | endfunction 389 | 390 | " An always-empty MRU for tabs as a default when looking up 391 | " `s:tabMruByBuf[bufNbr]` for an unknown `bufNbr`. 392 | let s:alwaysEmptyTabMru = s:MRUNew(s:tabIdHead) 393 | 394 | " MRUTabHasSeenBuf {{{2 395 | " Return true if `tabId` has ever seen `bufNbr`. 396 | function! s:MRUTabHasSeenBuf(tabId, bufNbr) 397 | let mru = get(s:bufMruByTab, a:tabId, s:alwaysEmptyBufMru) 398 | return has_key(mru.next, a:bufNbr) 399 | endfunction 400 | 401 | " MRUTabShouldShowBuf {{{2 402 | " Return true if `tabId` should show `bufNbr`. 403 | " This is a function of current display modes. 404 | function! s:MRUTabShouldShowBuf(tabId, bufNbr) 405 | if !g:bufExplorerShowTabBuffer 406 | " We are showing buffers from all tabs. 407 | return 1 408 | elseif g:bufExplorerOnlyOneTab 409 | " We are showing buffers that were most recently seen in this tab. 410 | return s:MRUTabForBuf(a:bufNbr) == a:tabId 411 | else 412 | " We are showing buffers that have ever been seen in this tab. 413 | return s:MRUTabHasSeenBuf(a:tabId, a:bufNbr) 414 | endif 415 | endfunction 416 | 417 | " MRUListedBuffersForTab {{{2 418 | " Return list of up to `maxBuffers` listed buffers in MRU order for the tab. 419 | " `maxBuffers == 0` => unlimited. 420 | function! s:MRUListedBuffersForTab(tabId, maxBuffers) 421 | let bufNbrs = [] 422 | let mru = get(s:bufMruByTab, a:tabId, s:alwaysEmptyBufMru) 423 | let [head, next] = [mru.head, mru.next] 424 | let bufNbr = next[head] 425 | while bufNbr != head 426 | if a:maxBuffers > 0 && len(bufNbrs) >= a:maxBuffers 427 | break 428 | endif 429 | if buflisted(bufNbr) && s:MRUTabShouldShowBuf(a:tabId, bufNbr) 430 | call add(bufNbrs, bufNbr) 431 | endif 432 | let bufNbr = next[bufNbr] 433 | endwhile 434 | return bufNbrs 435 | endfunction 436 | 437 | " An always-empty MRU for buffers as a default when looking up 438 | " `s:bufMruByTab[tabId]` for an unknown `tabId`. 439 | let s:alwaysEmptyBufMru = s:MRUNew(0) 440 | 441 | " MRUOrderForBuf {{{2 442 | " Return the position of `bufNbr` in the current MRU ordering. 443 | " This is a function of the current display mode. When showing buffers from all 444 | " tabs, it's the global MRU order; otherwise, it the MRU order for the tab at 445 | " BufExplorer launch. The latter includes all buffers seen in this tab, which 446 | " is sufficient whether `g:bufExplorerOnlyOneTab` is true or false. 447 | function! s:MRUOrderForBuf(bufNbr) 448 | if !exists('s:mruOrder') 449 | if g:bufExplorerShowTabBuffer 450 | let mru = get(s:bufMruByTab, s:tabIdAtLaunch, s:alwaysEmptyBufMru) 451 | else 452 | let mru = s:bufMru 453 | endif 454 | let s:mruOrder = s:MRUGetOrdering(mru, 0) 455 | endif 456 | return get(s:mruOrder, a:bufNbr, len(s:mruOrder)) 457 | endfunction 458 | 459 | " MRUEnsureTabId {{{2 460 | function! s:MRUEnsureTabId(tabNbr) 461 | let tabId = s:GetTabId(a:tabNbr) 462 | if tabId == '' 463 | let tabId = s:AssignTabId(a:tabNbr) 464 | for bufNbr in tabpagebuflist(a:tabNbr) 465 | call s:MRUAddBufTab(bufNbr, tabId) 466 | endfor 467 | endif 468 | return tabId 469 | endfunction 470 | 471 | " MRUGarbageCollectBufs {{{2 472 | " Requires `s:raw_buffer_listing`. 473 | function! s:MRUGarbageCollectBufs() 474 | for bufNbr in values(s:bufMru.next) 475 | if bufNbr != 0 && !has_key(s:raw_buffer_listing, bufNbr) 476 | call s:MRURemoveBuf(bufNbr) 477 | endif 478 | endfor 479 | endfunction 480 | 481 | " MRUGarbageCollectTabs {{{2 482 | function! s:MRUGarbageCollectTabs() 483 | let numTabs = tabpagenr('$') 484 | let liveTabIds = {} 485 | for tabNbr in range(1, numTabs) 486 | let tabId = s:GetTabId(tabNbr) 487 | if tabId != '' 488 | let liveTabIds[tabId] = 1 489 | endif 490 | endfor 491 | for tabId in keys(s:bufMruByTab) 492 | if tabId != s:tabIdHead && !has_key(liveTabIds, tabId) 493 | call s:MRURemoveTab(tabId) 494 | endif 495 | endfor 496 | endfunction 497 | 498 | " DoWinEnter {{{2 499 | function! s:DoWinEnter() 500 | let bufNbr = str2nr(expand("")) 501 | let tabNbr = tabpagenr() 502 | let tabId = s:GetTabId(tabNbr) 503 | " Ignore `WinEnter` for a newly created tab; this event comes when creating 504 | " a new tab, and the buffer at that moment is one that is about to be 505 | " replaced by the buffer to which we are switching; this latter buffer will 506 | " be handled by the forthcoming `BufEnter` event. 507 | if tabId != '' 508 | call s:MRUAddBufTab(bufNbr, tabId) 509 | endif 510 | endfunction 511 | 512 | " DoBufEnter {{{2 513 | function! s:DoBufEnter() 514 | let bufNbr = str2nr(expand("")) 515 | let tabNbr = tabpagenr() 516 | let tabId = s:MRUEnsureTabId(tabNbr) 517 | call s:MRUAddBufTab(bufNbr, tabId) 518 | endfunction 519 | 520 | " DoBufDelete {{{2 521 | function! s:DoBufDelete() 522 | let bufNbr = str2nr(expand("")) 523 | call s:MRURemoveBuf(bufNbr) 524 | endfunction 525 | 526 | " DoTabClosed {{{2 527 | function! s:DoTabClosed() 528 | call s:MRUGarbageCollectTabs() 529 | endfunction 530 | 531 | " ShouldIgnore {{{2 532 | function! s:ShouldIgnore(buf) 533 | " Ignore temporary buffers with buftype set. 534 | if empty(getbufvar(a:buf, "&buftype")) == 0 535 | return 1 536 | endif 537 | 538 | " Ignore the BufExplorer buffer. 539 | if fnamemodify(bufname(a:buf), ":t") == s:name 540 | return 1 541 | endif 542 | 543 | " Ignore any buffers in the exclude list. 544 | if index(s:MRU_Exclude_List, bufname(a:buf)) >= 0 545 | return 1 546 | endif 547 | 548 | " Else return 0 to indicate that the buffer was not ignored. 549 | return 0 550 | endfunction 551 | 552 | " Initialize {{{2 553 | function! s:Initialize() 554 | call s:SetLocalSettings() 555 | let s:running = 1 556 | endfunction 557 | 558 | " Cleanup {{{2 559 | function! s:Cleanup() 560 | if exists("s:_insertmode") 561 | let &insertmode = s:_insertmode 562 | endif 563 | 564 | if exists("s:_showcmd") 565 | let &showcmd = s:_showcmd 566 | endif 567 | 568 | if exists("s:_cpo") 569 | let &cpo = s:_cpo 570 | endif 571 | 572 | if exists("s:_report") 573 | let &report = s:_report 574 | endif 575 | 576 | let s:running = 0 577 | let s:didSplit = 0 578 | 579 | delmarks! 580 | endfunction 581 | 582 | " SetLocalSettings {{{2 583 | function! s:SetLocalSettings() 584 | let s:_insertmode = &insertmode 585 | set noinsertmode 586 | 587 | let s:_showcmd = &showcmd 588 | set noshowcmd 589 | 590 | let s:_cpo = &cpo 591 | set cpo&vim 592 | 593 | let s:_report = &report 594 | let &report = 10000 595 | 596 | setlocal nonumber 597 | setlocal foldcolumn=0 598 | setlocal nofoldenable 599 | setlocal cursorline 600 | setlocal nospell 601 | setlocal nobuflisted 602 | setlocal filetype=bufexplorer 603 | endfunction 604 | 605 | " BufExplorerHorizontalSplit {{{2 606 | function! BufExplorerHorizontalSplit() 607 | call BufExplorer('split') 608 | endfunction 609 | 610 | " BufExplorerVerticalSplit {{{2 611 | function! BufExplorerVerticalSplit() 612 | call BufExplorer('vsplit') 613 | endfunction 614 | 615 | " ToggleBufExplorer {{{2 616 | " Args: `([action])` 617 | " Optional `action` argument must be taken from `s:actions`. If not present, 618 | " `action` defaults to `g:bufExplorerDefaultAction`. 619 | function! ToggleBufExplorer(...) 620 | if a:0 >= 1 621 | let action = a:1 622 | else 623 | let action = g:bufExplorerDefaultAction 624 | endif 625 | if a:0 >= 2 626 | echoerr 'Too many arguments' 627 | return 628 | endif 629 | 630 | if index(s:actions, action) < 0 631 | echoerr 'Invalid action ' . action 632 | return 633 | endif 634 | 635 | if s:running && bufnr('%') == s:bufExplorerBuffer 636 | let action = 'close' 637 | endif 638 | 639 | call BufExplorer(action) 640 | endfunction 641 | 642 | " BufExplorer {{{2 643 | " Args: `([action])` 644 | " Optional `action` argument must be taken from `s:actions`. If not present, 645 | " `action` defaults to `g:bufExplorerDefaultAction`. 646 | function! BufExplorer(...) 647 | if a:0 >= 1 648 | let action = a:1 649 | else 650 | let action = g:bufExplorerDefaultAction 651 | endif 652 | if a:0 >= 2 653 | echoerr 'Too many arguments' 654 | return 655 | endif 656 | 657 | if index(s:actions, action) < 0 658 | echoerr 'Invalid action ' . action 659 | return 660 | endif 661 | 662 | if action == 'close' 663 | call s:Close() 664 | return 665 | endif 666 | 667 | let [tabNbr, winNbr] = s:FindBufExplorer() 668 | if tabNbr > 0 669 | execute 'keepjumps ' . tabNbr . 'tabnext' 670 | execute 'keepjumps ' . winNbr . 'wincmd w' 671 | return 672 | endif 673 | 674 | let name = s:name 675 | 676 | if !has("win32") 677 | " On non-Windows boxes, escape the name so that is shows up correctly. 678 | let name = escape(name, "[]") 679 | endif 680 | 681 | let s:bufNbrAtLaunch = bufnr('%') 682 | let s:tabIdAtLaunch = s:MRUEnsureTabId(tabpagenr()) 683 | let s:windowAtLaunch = winnr() 684 | 685 | " Forget any cached MRU ordering from previous invocations. 686 | unlet! s:mruOrder 687 | 688 | let s:raw_buffer_listing = s:GetBufferInfo(0) 689 | 690 | call s:MRUGarbageCollectBufs() 691 | call s:MRUGarbageCollectTabs() 692 | 693 | let [splitbelow, splitright] = [g:bufExplorerSplitBelow, g:bufExplorerSplitRight] 694 | " `{ action: [splitMode, botRight] }`. 695 | let actionMap = { 696 | \ 'split' : ['split', splitbelow, splitright], 697 | \ 'vsplit' : ['vsplit', splitbelow, splitright], 698 | \ 'above' : ['split', 0, splitright], 699 | \ 'below' : ['split', 1, splitright], 700 | \ 'left' : ['vsplit', splitbelow, 0], 701 | \ 'right' : ['vsplit', splitbelow, 1], 702 | \ 'current' : ['', splitbelow, splitright], 703 | \} 704 | let [splitMode, splitbelow, splitright] = actionMap[action] 705 | 706 | " We may have to split the current window. 707 | if splitMode != '' 708 | " Save off the original settings. 709 | let [_splitbelow, _splitright] = [&splitbelow, &splitright] 710 | 711 | " Set the setting to ours. 712 | let [&splitbelow, &splitright] = [splitbelow, splitright] 713 | let size = splitMode == 'split' ? g:bufExplorerSplitHorzSize : g:bufExplorerSplitVertSize 714 | let cmd = 'keepalt ' 715 | if size > 0 716 | let cmd .= size 717 | endif 718 | let cmd .= splitMode 719 | execute cmd 720 | 721 | " Restore the original settings. 722 | let [&splitbelow, &splitright] = [_splitbelow, _splitright] 723 | 724 | " Remember that a split was triggered 725 | let s:didSplit = 1 726 | endif 727 | 728 | if !exists("b:displayMode") || b:displayMode != "winmanager" 729 | " Do not use keepalt when opening bufexplorer to allow the buffer that 730 | " we are leaving to become the new alternate buffer 731 | execute "silent keepjumps hide edit".name 732 | endif 733 | 734 | " Record BufExplorer's buffer number. 735 | let s:bufExplorerBuffer = bufnr('%') 736 | 737 | call s:DisplayBufferList() 738 | 739 | " Position the cursor in the newly displayed list on the line representing 740 | " the active buffer at BufExplorer launch (assuming it is displayed). 741 | let activeBufIndex = index(s:displayedBufNbrs, s:bufNbrAtLaunch) 742 | if activeBufIndex >= 0 743 | let activeBufLineNbr = s:firstBufferLine + activeBufIndex 744 | keepjumps execute 'normal! ' . string(activeBufLineNbr) . 'G' 745 | endif 746 | 747 | if exists('#User#BufExplorer_Started') 748 | " Notify that BufExplorer has started. This is an opportunity to make 749 | " custom buffer-local mappings and the like. 750 | doautocmd User BufExplorer_Started 751 | endif 752 | endfunction 753 | 754 | " Tracks buffer number at BufExplorer launch. 755 | let s:bufNbrAtLaunch = 0 756 | 757 | " Tracks `tabId` at BufExplorer launch. 758 | let s:tabIdAtLaunch = '' 759 | 760 | " Tracks window number at BufExplorer launch. 761 | let s:windowAtLaunch = 0 762 | 763 | " DisplayBufferList {{{2 764 | function! s:DisplayBufferList() 765 | setlocal buftype=nofile 766 | setlocal modifiable 767 | setlocal noreadonly 768 | setlocal noswapfile 769 | setlocal nowrap 770 | setlocal bufhidden=wipe 771 | 772 | call s:MapKeys() 773 | 774 | " Wipe out any existing lines in case BufExplorer buffer exists and the 775 | " user had changed any global settings that might reduce the number of 776 | " lines needed in the buffer. 777 | silent keepjumps 1,$d _ 778 | 779 | call setline(1, s:CreateHelp()) 780 | call s:BuildBufferList() 781 | call cursor(s:firstBufferLine, 1) 782 | 783 | if !g:bufExplorerResize 784 | normal! zz 785 | endif 786 | 787 | setlocal nomodifiable 788 | endfunction 789 | 790 | " BufExplorer_redisplay {{{2 791 | function! BufExplorer_redisplay() 792 | if s:running && bufnr('%') == s:bufExplorerBuffer 793 | call s:RedisplayBufferList() 794 | endif 795 | endfunction 796 | 797 | " RedisplayBufferList {{{2 798 | function! s:RedisplayBufferList() 799 | call s:RebuildBufferList() 800 | call s:UpdateHelpStatus() 801 | endfunction 802 | 803 | " MapKeys {{{2 804 | function! s:MapKeys() 805 | nnoremap (BufExplorer_BufferDelete) :call RemoveBuffer("delete") 806 | nnoremap (BufExplorer_BufferDeleteForced) :call RemoveBuffer("force_delete") 807 | nnoremap (BufExplorer_BufferWipe) :call RemoveBuffer("wipe") 808 | nnoremap (BufExplorer_BufferWipeForced) :call RemoveBuffer("force_wipe") 809 | nnoremap (BufExplorer_Close) :call Close() 810 | nnoremap (BufExplorer_OpenBuffer) :call SelectBuffer() 811 | nnoremap (BufExplorer_OpenBufferAsk) :call SelectBuffer("ask") 812 | nnoremap (BufExplorer_OpenBufferOriginalWindow) :call SelectBuffer("original_window") 813 | nnoremap (BufExplorer_OpenBufferSplitAbove) :call SelectBuffer("split", "st") 814 | nnoremap (BufExplorer_OpenBufferSplitBelow) :call SelectBuffer("split", "sb") 815 | nnoremap (BufExplorer_OpenBufferSplitLeft) :call SelectBuffer("split", "vl") 816 | nnoremap (BufExplorer_OpenBufferSplitRight) :call SelectBuffer("split", "vr") 817 | nnoremap (BufExplorer_OpenBufferTab) :call SelectBuffer("tab") 818 | nnoremap (BufExplorer_SortByNext) :call SortSelect() 819 | nnoremap (BufExplorer_SortByPrev) :call ReverseSortSelect() 820 | nnoremap (BufExplorer_ToggleFindActive) :call ToggleFindActive() 821 | nnoremap (BufExplorer_ToggleHelp) :call ToggleHelp() 822 | nnoremap (BufExplorer_ToggleOnlyOneTab) :call ToggleOnlyOneTab() 823 | nnoremap (BufExplorer_ToggleReverseSort) :call SortReverse() 824 | nnoremap (BufExplorer_ToggleShowRelativePath) :call ToggleShowRelativePath() 825 | nnoremap (BufExplorer_ToggleShowTabBuffer) :call ToggleShowTabBuffer() 826 | nnoremap (BufExplorer_ToggleShowTerminal) :call ToggleShowTerminal() 827 | nnoremap (BufExplorer_ToggleShowUnlisted) :call ToggleShowUnlisted() 828 | nnoremap (BufExplorer_ToggleSplitOutPathName) :call ToggleSplitOutPathName() 829 | 830 | if exists("b:displayMode") && b:displayMode == "winmanager" 831 | nnoremap :call SelectBuffer() 832 | endif 833 | 834 | nmap <2-leftmouse> (BufExplorer_OpenBuffer) 835 | nmap (BufExplorer_OpenBuffer) 836 | nmap (BufExplorer_ToggleHelp) 837 | nmap (BufExplorer_OpenBufferTab) 838 | nmap a (BufExplorer_ToggleFindActive) 839 | nmap b (BufExplorer_OpenBufferAsk) 840 | nmap B (BufExplorer_ToggleOnlyOneTab) 841 | nmap d (BufExplorer_BufferDelete) 842 | nmap D (BufExplorer_BufferWipe) 843 | nmap f (BufExplorer_OpenBufferSplitBelow) 844 | nmap F (BufExplorer_OpenBufferSplitAbove) 845 | nmap o (BufExplorer_OpenBuffer) 846 | nmap O (BufExplorer_OpenBufferOriginalWindow) 847 | nmap p (BufExplorer_ToggleSplitOutPathName) 848 | nmap q (BufExplorer_Close) 849 | nmap r (BufExplorer_ToggleReverseSort) 850 | nmap R (BufExplorer_ToggleShowRelativePath) 851 | nmap s (BufExplorer_SortByNext) 852 | nmap S (BufExplorer_SortByPrev) 853 | nmap t (BufExplorer_OpenBufferTab) 854 | nmap T (BufExplorer_ToggleShowTabBuffer) 855 | nmap u (BufExplorer_ToggleShowUnlisted) 856 | nmap v (BufExplorer_OpenBufferSplitRight) 857 | nmap V (BufExplorer_OpenBufferSplitLeft) 858 | nmap X (BufExplorer_ToggleShowTerminal) 859 | 860 | for k in ["G", "n", "N", "L", "M", "H"] 861 | execute "nnoremap " k ":keepjumps normal!" k."" 862 | endfor 863 | endfunction 864 | 865 | " ToggleHelp {{{2 866 | function! s:ToggleHelp() 867 | let g:bufExplorerDetailedHelp = !g:bufExplorerDetailedHelp 868 | 869 | setlocal modifiable 870 | 871 | " Save position. 872 | normal! ma 873 | 874 | " Remove old header. 875 | if s:firstBufferLine > 1 876 | execute "keepjumps 1,".(s:firstBufferLine - 1) "d _" 877 | endif 878 | 879 | call append(0, s:CreateHelp()) 880 | 881 | silent! normal! g`a 882 | delmarks a 883 | 884 | setlocal nomodifiable 885 | 886 | if exists("b:displayMode") && b:displayMode == "winmanager" 887 | call WinManagerForceReSize("BufExplorer") 888 | endif 889 | endfunction 890 | 891 | " GetHelpStatus {{{2 892 | function! s:GetHelpStatus() 893 | let ret = '" Sorted by '.((g:bufExplorerReverseSort == 1) ? "reverse " : "").g:bufExplorerSortBy 894 | let ret .= ' | '.((g:bufExplorerFindActive == 0) ? "Don't " : "")."Locate buffer" 895 | let ret .= ((g:bufExplorerShowUnlisted == 0) ? "" : " | Show unlisted") 896 | let ret .= ((g:bufExplorerShowTabBuffer == 0) ? "" : " | Show buffers/tab") 897 | let ret .= ((g:bufExplorerOnlyOneTab == 0) ? "" : " | One tab/buffer") 898 | let ret .= ' | ' 899 | if g:bufExplorerShowRelativePath 900 | let ret .= "Relative " 901 | endif 902 | let ret .= ((g:bufExplorerSplitOutPathName == 0) ? "Whole" : "Split")." path" 903 | let ret .= ((g:bufExplorerShowTerminal == 0) ? "" : " | Show terminal") 904 | 905 | return ret 906 | endfunction 907 | 908 | " CreateHelp {{{2 909 | function! s:CreateHelp() 910 | if g:bufExplorerDefaultHelp == 0 && g:bufExplorerDetailedHelp == 0 911 | let s:firstBufferLine = 1 912 | return [] 913 | endif 914 | 915 | let header = [] 916 | 917 | if g:bufExplorerDetailedHelp == 1 918 | call add(header, '" Buffer Explorer ('.g:bufexplorer_version.')') 919 | call add(header, '" --------------------------') 920 | call add(header, '" : toggle this help') 921 | call add(header, '" or o or Mouse-Double-Click : open buffer under cursor') 922 | call add(header, '" or t : open buffer in another tab') 923 | call add(header, '" a : toggle find active buffer') 924 | call add(header, '" b : Fast buffer switching with b') 925 | call add(header, '" B : toggle showing buffers only on their MRU tabs') 926 | call add(header, '" d : delete buffer') 927 | call add(header, '" D : wipe buffer') 928 | call add(header, '" F : open buffer in another window above the current') 929 | call add(header, '" f : open buffer in another window below the current') 930 | call add(header, '" O : open buffer in original window') 931 | call add(header, '" p : toggle splitting of path into name + dir') 932 | call add(header, '" q : quit') 933 | call add(header, '" r : reverse sort') 934 | call add(header, '" R : toggle showing relative paths') 935 | call add(header, '" s : cycle thru "sort by" fields '.string(s:sort_by).'') 936 | call add(header, '" S : reverse cycle thru "sort by" fields') 937 | call add(header, '" T : toggle showing all buffers/only buffers used on this tab') 938 | call add(header, '" u : toggle showing unlisted buffers') 939 | call add(header, '" V : open buffer in another window on the left of the current') 940 | call add(header, '" v : open buffer in another window on the right of the current') 941 | call add(header, '" X : toggle showing terminal buffers') 942 | else 943 | call add(header, '" Press for Help') 944 | endif 945 | 946 | if (!exists("b:displayMode") || b:displayMode != "winmanager") || (b:displayMode == "winmanager" && g:bufExplorerDetailedHelp == 1) 947 | call add(header, s:GetHelpStatus()) 948 | call add(header, '"=') 949 | endif 950 | 951 | let s:firstBufferLine = len(header) + 1 952 | 953 | return header 954 | endfunction 955 | 956 | " CalculateBufferDetails {{{2 957 | " Calculate `buf`-related details. 958 | " Only these fields of `buf` must be defined on entry: 959 | " - `.bufNbr` 960 | " - `.numberindicators` 961 | " - `.line` 962 | function! s:CalculateBufferDetails(buf) 963 | let buf = a:buf 964 | let buf.number = string(buf.bufNbr) 965 | let buf.indicators = substitute(buf.numberindicators, '^\s*\d*', '', '') 966 | let rawpath = bufname(buf.bufNbr) 967 | let buf["hasNoName"] = empty(rawpath) 968 | if buf.hasNoName 969 | let rawpath = "[No Name]" 970 | let buf.isdir = 0 971 | else 972 | let buf.isdir = getftype(rawpath) == 'dir' 973 | endif 974 | let buf.rawpath = rawpath 975 | let buf.isterminal = getbufvar(buf.bufNbr, '&buftype') == 'terminal' 976 | if buf.isterminal 977 | " Neovim uses paths with `term://` prefix, where the provided dir path 978 | " is the current working directory when the terminal was launched, e.g.: 979 | " - Unix: 980 | " term://~/tmp/sort//1464953:/bin/bash 981 | " - Windows: 982 | " term://C:\apps\nvim-win64\bin//6408:C:\Windows\system32\cmd.exe 983 | " Vim uses paths starting with `!`, with no provided dir path, e.g.: 984 | " - Unix: 985 | " !/bin/bash 986 | " - Windows: 987 | " !C:\Windows\system32\cmd.exe 988 | 989 | " Use the terminal's current working directory as `fulldir`. 990 | " For `name`, use `!PID:shellName`, prefixed with `!` as Vim does, 991 | " and without the shell's dir path for brevity, e.g.: 992 | " `/bin/bash` -> `!bash` 993 | " `1464953:/bin/bash` -> `!1464953:bash` 994 | " `C:\Windows\system32\cmd.exe` -> `!cmd.exe` 995 | " `6408:C:\Windows\system32\cmd.exe` -> `!6408:cmd.exe` 996 | 997 | " Neovim-style path format: 998 | " term://(cwd)//(pid):(shellPath) 999 | " e.g.: 1000 | " term://~/tmp/sort//1464953:/bin/bash 1001 | " `cwd` is the directory at terminal launch. 1002 | let termNameParts = matchlist(rawpath, '\v\c^term://(.*)//(\d+):(.*)$') 1003 | if len(termNameParts) > 0 1004 | let [cwd, pidStr, shellPath] = termNameParts[1:3] 1005 | let pid = str2nr(pidStr) 1006 | let shellName = fnamemodify(shellPath, ':t') 1007 | else 1008 | " Default to Vim's current working directory. 1009 | let cwd = '.' 1010 | let shellName = fnamemodify(rawpath, ':t') 1011 | let pid = -1 1012 | if exists('*term_getjob') && exists('*job_info') 1013 | let job = term_getjob(buf.bufNbr) 1014 | if job != v:null 1015 | let pid = job_info(job).process 1016 | endif 1017 | endif 1018 | endif 1019 | 1020 | if pid < 0 1021 | let name = '!' . shellName 1022 | else 1023 | let name = '!' . pid . ':' . shellName 1024 | " On some systems having a `/proc` filesystem (e.g., Linux, *BSD, 1025 | " Solaris), each process has a `cwd` symlink for the current working 1026 | " directory. `resolve()` will return the actual current working 1027 | " directory if possible; otherwise, it will return the symlink path 1028 | " unchanged. 1029 | let cwd_symlink = '/proc/' . pid . '/cwd' 1030 | let resolved_cwd = resolve(cwd_symlink) 1031 | if resolved_cwd != cwd_symlink 1032 | let cwd = resolved_cwd 1033 | endif 1034 | endif 1035 | 1036 | let slashed_cwd = fnamemodify(cwd, ':p') 1037 | let buf.fullpath = slashed_cwd . name 1038 | let buf.fulldir = fnamemodify(slashed_cwd, ':h') 1039 | let buf.name = name 1040 | let buf.homereldir = fnamemodify(slashed_cwd, ':~:h') 1041 | let buf.homerelpath = fnamemodify(buf.fullpath, ':~') 1042 | let buf.relativedir = fnamemodify(slashed_cwd, ':~:.:h') 1043 | let buf.relativepath = fnamemodify(buf.fullpath, ':~:.') 1044 | return 1045 | endif 1046 | 1047 | let fullpath = simplify(fnamemodify(rawpath, ':p')) 1048 | if buf.isdir 1049 | " `fullpath` ends with a path separator; this will be 1050 | " removed via the first `:h` applied to `fullpath` (except 1051 | " for the root directory, where the path separator will remain). 1052 | 1053 | " Must perform shortening (`:~`, `:.`) before `:h`. 1054 | let buf.homerelpath = fnamemodify(fullpath, ':~:h') 1055 | let buf.relativepath = fnamemodify(fullpath, ':~:.:h') 1056 | " Remove trailing slash. 1057 | let fullpath = fnamemodify(fullpath, ':h') 1058 | let parent = fnamemodify(fullpath, ':h') 1059 | let buf.name = fnamemodify(fullpath, ':t') 1060 | " Special case for root directory: fnamemodify('/', ':h:t') == '' 1061 | if buf.name == '' 1062 | let buf.name = '.' 1063 | endif 1064 | else 1065 | let parent = fnamemodify(fullpath, ':h') 1066 | let buf.name = fnamemodify(fullpath, ':t') 1067 | let buf.homerelpath = fnamemodify(fullpath, ':~') 1068 | let buf.relativepath = fnamemodify(fullpath, ':~:.') 1069 | endif 1070 | let buf.fullpath = fullpath 1071 | let buf.fulldir = parent 1072 | " `:p` on `parent` adds back the path separator which permits more 1073 | " effective shortening (`:~`, `:.`), but `:h` is required afterward 1074 | " to trim this separator. 1075 | let buf.homereldir = fnamemodify(parent, ':p:~:h') 1076 | let buf.relativedir = fnamemodify(parent, ':p:~:.:h') 1077 | endfunction 1078 | 1079 | " GetBufferInfo {{{2 1080 | " Return dictionary `{ bufNbr : buf }`. 1081 | " - If `onlyBufNbr > 0`, dictionary will contain at most that buffer. 1082 | " On return, only these fields are set for each `buf`: 1083 | " - `.bufNbr` 1084 | " - `.numberindicators` 1085 | " - `.line` 1086 | " Other fields will be populated by `s:CalculateBufferDetails()`. 1087 | function! s:GetBufferInfo(onlyBufNbr) 1088 | redir => bufoutput 1089 | 1090 | " Below, `:silent buffers` allows capturing the output via `:redir` but 1091 | " prevents display to the user. 1092 | 1093 | if a:onlyBufNbr > 0 && buflisted(a:onlyBufNbr) 1094 | " We care only about the listed buffer `a:onlyBufNbr`, so no need to 1095 | " enumerate unlisted buffers. 1096 | silent buffers 1097 | else 1098 | " Use `!` to show all buffers including the unlisted ones. 1099 | silent buffers! 1100 | endif 1101 | redir END 1102 | 1103 | if a:onlyBufNbr > 0 1104 | " Since we are only interested in this specified buffer remove the 1105 | " other buffers listed. 1106 | " Use a very-magic pattern starting with a newline and a run of zero or 1107 | " more spaces/tabs: 1108 | let onlyLinePattern = '\v\n\s*' 1109 | " Continue with the buffer number followed by a non-digit character 1110 | " (which will be a buffer indicator character such as `u` or ` `). 1111 | let onlyLinePattern .= a:onlyBufNbr . '\D' 1112 | " Finish with a run of zero or more non-newline characters plus newline: 1113 | let onlyLinePattern .= '[^\n]*\n' 1114 | let bufoutput = matchstr("\n" . bufoutput . "\n", onlyLinePattern) 1115 | endif 1116 | 1117 | let all = {} 1118 | 1119 | " Loop over each line in the buffer. 1120 | for line in split(bufoutput, '\n') 1121 | let bits = split(line, '"') 1122 | 1123 | " Use first and last components after the split on '"', in case a 1124 | " filename with an embedded '"' is present. 1125 | let buf = { 1126 | \ "numberindicators": bits[0], 1127 | \ "line": substitute(bits[-1], 1128 | \ '\s*', '', '') 1129 | \} 1130 | let buf.bufNbr = str2nr(buf.numberindicators) 1131 | let all[buf.bufNbr] = buf 1132 | endfor 1133 | 1134 | return all 1135 | endfunction 1136 | 1137 | " BuildBufferList {{{2 1138 | function! s:BuildBufferList() 1139 | if exists('#User#BufExplorer_PreDisplay') 1140 | " Notify that BufExplorer is about to display the buffer list. This is 1141 | " an opportunity to make last-minute changes to `g:bufExplorerColumns`. 1142 | doautocmd User BufExplorer_PreDisplay 1143 | endif 1144 | 1145 | let columns = s:GetColumns() 1146 | let table = [] 1147 | let s:displayedBufNbrs = [] 1148 | 1149 | " Loop through every buffer. 1150 | for buf in values(s:raw_buffer_listing) 1151 | " `buf.numberindicators` must exist, but we defer the expensive work of 1152 | " calculating other buffer details (e.g., `buf.fullpath`) until we know 1153 | " the user wants to view this buffer. 1154 | 1155 | " Skip BufExplorer's buffer. 1156 | if buf.bufNbr == s:bufExplorerBuffer 1157 | continue 1158 | endif 1159 | 1160 | " Skip unlisted buffers if we are not to show them. 1161 | if !g:bufExplorerShowUnlisted && buf.numberindicators =~ "u" 1162 | " Skip unlisted buffers if we are not to show them. 1163 | continue 1164 | endif 1165 | 1166 | " Ensure buffer details are computed for this buffer. 1167 | if !has_key(buf, 'fullpath') 1168 | call s:CalculateBufferDetails(buf) 1169 | endif 1170 | 1171 | " Skip 'No Name' buffers if we are not to show them. 1172 | if g:bufExplorerShowNoName == 0 && buf.hasNoName 1173 | continue 1174 | endif 1175 | 1176 | " Should we show this buffer in this tab? 1177 | if !s:MRUTabShouldShowBuf(s:tabIdAtLaunch, buf.bufNbr) 1178 | continue 1179 | endif 1180 | 1181 | " Skip terminal buffers if we are not to show them. 1182 | if !g:bufExplorerShowTerminal && buf.isterminal 1183 | continue 1184 | endif 1185 | 1186 | " Skip directory buffers if we are not to show them. 1187 | if !g:bufExplorerShowDirectories && buf.isdir 1188 | continue 1189 | endif 1190 | 1191 | let row = [] 1192 | for column in columns 1193 | if has_key(buf, column) 1194 | let row += [buf[column]] 1195 | elseif column == 'icon' 1196 | " Support must exist or 'icon' would have been removed. 1197 | let row += [WebDevIconsGetFileTypeSymbol(buf.fullpath, buf.isdir)] 1198 | else 1199 | " Must be of the form `=literal`. 1200 | let row += [column[1:]] 1201 | endif 1202 | endfor 1203 | 1204 | call add(table, row) 1205 | call add(s:displayedBufNbrs, buf.bufNbr) 1206 | endfor 1207 | 1208 | let lines = s:MakeLines(table) 1209 | call setline(s:firstBufferLine, lines) 1210 | let firstMissingLine = s:firstBufferLine + len(lines) 1211 | if line('$') >= firstMissingLine 1212 | " Clear excess lines starting with `firstMissingLine`. 1213 | execute "silent keepjumps ".firstMissingLine.',$d _' 1214 | endif 1215 | call s:SortListing() 1216 | endfunction 1217 | 1218 | " Buffer numbers for buffers displayed in the BufExplorer window. 1219 | let s:displayedBufNbrs = [] 1220 | 1221 | " GetColumns {{{2 1222 | function! s:GetColumns() 1223 | let validColumns = [ 1224 | \ 'dir', 1225 | \ 'fulldir', 1226 | \ 'fullpath', 1227 | \ 'homereldir', 1228 | \ 'homerelpath', 1229 | \ 'icon', 1230 | \ 'indicators', 1231 | \ 'line', 1232 | \ 'name', 1233 | \ 'number', 1234 | \ 'numberindicators', 1235 | \ 'path', 1236 | \ 'rawpath', 1237 | \ 'relativedir', 1238 | \ 'relativepath', 1239 | \ 'splittablepath', 1240 | \ ] 1241 | let columns = [] 1242 | for column in g:bufExplorerColumns 1243 | if column == 'splittablepath' 1244 | if g:bufExplorerSplitOutPathName 1245 | let columns += ['name'] 1246 | let column = 'dir' 1247 | else 1248 | let column = 'path' 1249 | endif 1250 | endif 1251 | if column == 'dir' || column == 'path' 1252 | if g:bufExplorerShowRelativePath 1253 | let column = 'relative' . column 1254 | else 1255 | let column = 'homerel' . column 1256 | endif 1257 | endif 1258 | if column == 'icon' 1259 | " 'icon' is always valid, but omitted unless support is loaded. 1260 | if exists("g:loaded_webdevicons") 1261 | let columns += [column] 1262 | endif 1263 | elseif index(validColumns, column) >= 0 || column =~# '^=' 1264 | let columns += [column] 1265 | else 1266 | let columns += ['=[bad column name "' . column . '"]'] 1267 | endif 1268 | endfor 1269 | if len(columns) == 0 1270 | let columns = ['=[`g:bufExplorerColumns` is empty]'] 1271 | endif 1272 | return columns 1273 | endfunction 1274 | 1275 | " BufExplorer_defaultColumns {{{2 1276 | " User-accessible default value for `g:bufExplorerColumns`. 1277 | " Implemented as a function so that users may modify the returned list. 1278 | function! BufExplorer_defaultColumns() 1279 | let defaultColumns = [ 1280 | \ 'numberindicators', 1281 | \ 'icon', 1282 | \ 'splittablepath', 1283 | \ 'line', 1284 | \ ] 1285 | return defaultColumns 1286 | endfunction 1287 | 1288 | " MakeLines {{{2 1289 | function! s:MakeLines(table) 1290 | if len(a:table) == 0 1291 | return [] 1292 | endif 1293 | let lines = [] 1294 | " To avoid trailing whitespace, do not pad the final column. 1295 | let numColumnsToPad = len(a:table[0]) - 1 1296 | " Ensure correctness even if `table` has no columns. 1297 | if numColumnsToPad < 0 1298 | let numColumnsToPad = 0 1299 | endif 1300 | let maxWidths = repeat([0], numColumnsToPad) 1301 | for row in a:table 1302 | let i = 0 1303 | while i < numColumnsToPad 1304 | let maxWidths[i] = max([maxWidths[i], s:StringWidth(row[i])]) 1305 | let i = i + 1 1306 | endwhile 1307 | endfor 1308 | 1309 | let pads = [] 1310 | for w in maxWidths 1311 | call add(pads, repeat(' ', w)) 1312 | endfor 1313 | 1314 | for row in a:table 1315 | let i = 0 1316 | while i < numColumnsToPad 1317 | let row[i] .= strpart(pads[i], s:StringWidth(row[i])) 1318 | let i = i + 1 1319 | endwhile 1320 | call add(lines, join(row, ' ')) 1321 | endfor 1322 | return lines 1323 | endfunction 1324 | 1325 | " SelectBuffer {{{2 1326 | " Valid arguments: 1327 | " `()` Open in current window. 1328 | " `("ask")` Prompt for buffer, then open in current window. 1329 | " `("original_window")` Open in original window. 1330 | " `("split", "st")` Open in horizontal split above current window. 1331 | " `("split", "sb")` Open in horizontal split below current window. 1332 | " `("split", "vl")` Open in vertical split left of current window. 1333 | " `("split", "vr")` Open in vertical split right of current window. 1334 | " `("tab")` Open in a new tab. 1335 | function! s:SelectBuffer(...) 1336 | " Sometimes messages are not cleared when we get here so it looks like an 1337 | " error has occurred when it really has not. 1338 | "echo "" 1339 | 1340 | let bufNbr = -1 1341 | 1342 | if (a:0 == 1) && (a:1 == "ask") 1343 | " Ask the user for input. 1344 | call inputsave() 1345 | let cmd = input("Enter buffer number to switch to: ") 1346 | call inputrestore() 1347 | 1348 | " Clear the message area from the previous prompt. 1349 | redraw | echo 1350 | 1351 | if strlen(cmd) > 0 1352 | let bufNbr = str2nr(cmd) 1353 | else 1354 | call s:Error("Invalid buffer number, try again.") 1355 | return 1356 | endif 1357 | else 1358 | let bufNbr = s:GetBufNbrAtCursor() 1359 | if bufNbr == 0 1360 | return 1361 | endif 1362 | 1363 | " Check and see if we are running BufferExplorer via WinManager. 1364 | if exists("b:displayMode") && b:displayMode == "winmanager" 1365 | let _bufName = expand("#".bufNbr.":p") 1366 | 1367 | if (a:0 == 1) && (a:1 == "tab") 1368 | call WinManagerFileEdit(_bufName, 1) 1369 | else 1370 | call WinManagerFileEdit(_bufName, 0) 1371 | endif 1372 | 1373 | return 1374 | endif 1375 | endif 1376 | 1377 | if bufexists(bufNbr) 1378 | " Get the tab number where this buffer is located in. 1379 | let tabNbr = s:GetTabNbr(bufNbr) 1380 | if exists("g:bufExplorerChgWin") && g:bufExplorerChgWin <=winnr("$") 1381 | execute g:bufExplorerChgWin."wincmd w" 1382 | execute "keepjumps keepalt silent b!" bufNbr 1383 | 1384 | " Are we supposed to open the selected buffer in a tab? 1385 | elseif (a:0 == 1) && (a:1 == "tab") 1386 | call s:Close() 1387 | 1388 | " Open a new tab with the selected buffer in it. 1389 | if v:version > 704 || ( v:version == 704 && has('patch2237') ) 1390 | " new syntax for last tab as of 7.4.2237 1391 | execute "$tab split +buffer" . bufNbr 1392 | else 1393 | execute "999tab split +buffer" . bufNbr 1394 | endif 1395 | " Are we supposed to open the selected buffer in a split? 1396 | elseif (a:0 == 2) && (a:1 == "split") 1397 | call s:Close() 1398 | if (a:2 == "vl") 1399 | execute "vert topleft sb ".bufNbr 1400 | elseif (a:2 == "vr") 1401 | execute "vert belowright sb ".bufNbr 1402 | elseif (a:2 == "st") 1403 | execute "topleft sb ".bufNbr 1404 | else " = sb 1405 | execute "belowright sb ".bufNbr 1406 | endif 1407 | " Are we supposed to open the selected buffer in the original window? 1408 | elseif (a:0 == 1) && (a:1 == "original_window") 1409 | call s:Close() 1410 | execute s:windowAtLaunch . "wincmd w" 1411 | execute "keepjumps keepalt silent b!" bufNbr 1412 | else 1413 | " Request to open in current (BufExplorer) window. 1414 | if g:bufExplorerFindActive && tabNbr > 0 1415 | " Close BufExplorer window and switch to existing tab/window. 1416 | call s:Close() 1417 | execute tabNbr . "tabnext" 1418 | execute bufwinnr(bufNbr) . "wincmd w" 1419 | else 1420 | " Use BufExplorer window for the buffer. 1421 | execute "keepjumps keepalt silent b!" bufNbr 1422 | endif 1423 | endif 1424 | 1425 | " Make the buffer 'listed' again. 1426 | call setbufvar(bufNbr, "&buflisted", "1") 1427 | 1428 | " Call any associated function references. g:bufExplorerFuncRef may be 1429 | " an individual function reference or it may be a list containing 1430 | " function references. It will ignore anything that's not a function 1431 | " reference. 1432 | " 1433 | " See :help FuncRef for more on function references. 1434 | if exists("g:BufExplorerFuncRef") 1435 | if type(g:BufExplorerFuncRef) == 2 1436 | keepj call g:BufExplorerFuncRef() 1437 | elseif type(g:BufExplorerFuncRef) == 3 1438 | for FncRef in g:BufExplorerFuncRef 1439 | if type(FncRef) == 2 1440 | keepj call FncRef() 1441 | endif 1442 | endfor 1443 | endif 1444 | endif 1445 | else 1446 | call s:Error("Sorry, that buffer no longer exists, please select another") 1447 | call s:DeleteBuffer(bufNbr, "wipe") 1448 | endif 1449 | endfunction 1450 | 1451 | " RemoveBuffer {{{2 1452 | " Valid `mode` values: 1453 | " - "delete" 1454 | " - "force_delete" 1455 | " - "wipe" 1456 | " - "force_wipe" 1457 | function! s:RemoveBuffer(mode) 1458 | " Are we on a line with a file name? 1459 | if line('.') < s:firstBufferLine 1460 | return 1461 | endif 1462 | 1463 | let mode = a:mode 1464 | let forced = mode =~# '^force_' 1465 | 1466 | " These commands are to temporarily suspend the activity of winmanager. 1467 | if exists("b:displayMode") && b:displayMode == "winmanager" 1468 | call WinManagerSuspendAUs() 1469 | end 1470 | 1471 | let bufNbr = s:GetBufNbrAtCursor() 1472 | if bufNbr == 0 1473 | return 1474 | endif 1475 | let buf = s:raw_buffer_listing[bufNbr] 1476 | 1477 | if !forced && (buf.isterminal || getbufvar(bufNbr, '&modified')) 1478 | if buf.isterminal 1479 | let msg = "Buffer " . bufNbr . " is a terminal" 1480 | else 1481 | let msg = "No write since last change for buffer " . bufNbr 1482 | endif 1483 | " Calling confirm() requires Vim built with dialog option. 1484 | if !has("dialog_con") && !has("dialog_gui") 1485 | call s:Error(msg . "; cannot remove without 'force'") 1486 | return 1487 | endif 1488 | 1489 | let answer = confirm(msg . "; Remove anyway?", "&Yes\n&No", 2) 1490 | 1491 | if answer == 1 1492 | let mode = 'force_' . mode 1493 | else 1494 | return 1495 | endif 1496 | 1497 | endif 1498 | 1499 | " Okay, everything is good, delete or wipe the buffer. 1500 | call s:DeleteBuffer(bufNbr, mode) 1501 | 1502 | " Reactivate winmanager autocommand activity. 1503 | if exists("b:displayMode") && b:displayMode == "winmanager" 1504 | call WinManagerForceReSize("BufExplorer") 1505 | call WinManagerResumeAUs() 1506 | end 1507 | endfunction 1508 | 1509 | " DeleteBuffer {{{2 1510 | " Valid `mode` values: 1511 | " - "delete" 1512 | " - "force_delete" 1513 | " - "wipe" 1514 | " - "force_wipe" 1515 | function! s:DeleteBuffer(bufNbr, mode) 1516 | " This routine assumes that the buffer to be removed is on the current line. 1517 | if a:mode =~# 'delete$' && bufexists(a:bufNbr) && !buflisted(a:bufNbr) 1518 | call s:Error('Buffer ' . a:bufNbr 1519 | \ . ' is unlisted; must `wipe` to remove') 1520 | return 1521 | endif 1522 | try 1523 | " Wipe/Delete buffer from Vim. 1524 | if a:mode == "wipe" 1525 | execute "silent bwipe" a:bufNbr 1526 | elseif a:mode == "force_wipe" 1527 | execute "silent bwipe!" a:bufNbr 1528 | elseif a:mode == "force_delete" 1529 | execute "silent bdelete!" a:bufNbr 1530 | else 1531 | execute "silent bdelete" a:bufNbr 1532 | endif 1533 | catch 1534 | call s:Error(v:exception) 1535 | endtry 1536 | 1537 | if bufexists(a:bufNbr) 1538 | " Buffer is still present. We may have failed to wipe it, or it may 1539 | " have changed indicators (as `:bd` only makes a buffer unlisted). 1540 | " Regather information on this buffer, update the buffer list, and 1541 | " redisplay. 1542 | let info = s:GetBufferInfo(a:bufNbr) 1543 | let s:raw_buffer_listing[a:bufNbr] = info[a:bufNbr] 1544 | call s:RedisplayBufferList() 1545 | else 1546 | " Delete the buffer from the list on screen. 1547 | setlocal modifiable 1548 | normal! "_dd 1549 | setlocal nomodifiable 1550 | 1551 | " Delete the buffer from the raw buffer list. 1552 | unlet s:raw_buffer_listing[a:bufNbr] 1553 | " Remove buffer number from list of displayed buffer numbers. 1554 | call remove(s:displayedBufNbrs, index(s:displayedBufNbrs, a:bufNbr)) 1555 | endif 1556 | endfunction 1557 | 1558 | " Close {{{2 1559 | function! s:Close() 1560 | let [tabNbr, winNbr] = s:FindBufExplorer() 1561 | if tabNbr == 0 1562 | return 1563 | endif 1564 | let [curTabNbr, curWinNbr] = [tabpagenr(), winnr()] 1565 | if [tabNbr, winNbr] != [curTabNbr, curWinNbr] 1566 | " User has switched away from the original BufExplorer window. 1567 | " It's unclear how to do better than simply wiping out the 1568 | " BufExplorer buffer. 1569 | execute 'bwipeout ' . s:bufExplorerBuffer 1570 | return 1571 | endif 1572 | " Get only the listed buffers associated with the current tab (up to 2). 1573 | let listed = s:MRUListedBuffersForTab(s:tabIdAtLaunch, 2) 1574 | 1575 | " If we needed to split the main window, close the split one. 1576 | if s:didSplit 1577 | execute "wincmd c" 1578 | " After closing the BufExplorer split, we expect to be back on the 1579 | " tab from which we launched; if so, make sure we also return to the 1580 | " window from which we launched. 1581 | if s:MRUEnsureTabId(tabpagenr()) == s:tabIdAtLaunch 1582 | execute s:windowAtLaunch . "wincmd w" 1583 | endif 1584 | endif 1585 | 1586 | " Check to see if there are anymore buffers listed. 1587 | if len(listed) == 0 1588 | " Since there are no buffers left to switch to, open a new empty 1589 | " buffers. 1590 | execute "enew" 1591 | else 1592 | " Since there are buffers left to switch to, switch to the previous and 1593 | " then the current. 1594 | for b in reverse(listed[0:1]) 1595 | execute "keepjumps silent b ".b 1596 | endfor 1597 | endif 1598 | 1599 | " Clear any messages. 1600 | echo 1601 | endfunction 1602 | 1603 | " FindBufExplorer {{{2 1604 | " Return `[tabNbr, winNbr]`; both numbers will be zero if not found. 1605 | function! s:FindBufExplorer() 1606 | let result = [0, 0] 1607 | if s:running 1608 | let numTabs = tabpagenr('$') 1609 | for tabNbr in range(1, numTabs) 1610 | let winNbr = index(tabpagebuflist(tabNbr), s:bufExplorerBuffer) + 1 1611 | if winNbr > 0 1612 | let result = [tabNbr, winNbr] 1613 | break 1614 | endif 1615 | endfor 1616 | endif 1617 | return result 1618 | endfunction 1619 | 1620 | " ToggleShowTerminal {{{2 1621 | function! s:ToggleShowTerminal() 1622 | let g:bufExplorerShowTerminal = !g:bufExplorerShowTerminal 1623 | call s:RedisplayBufferList() 1624 | endfunction 1625 | 1626 | " ToggleSplitOutPathName {{{2 1627 | function! s:ToggleSplitOutPathName() 1628 | let g:bufExplorerSplitOutPathName = !g:bufExplorerSplitOutPathName 1629 | call s:RedisplayBufferList() 1630 | endfunction 1631 | 1632 | " ToggleShowRelativePath {{{2 1633 | function! s:ToggleShowRelativePath() 1634 | let g:bufExplorerShowRelativePath = !g:bufExplorerShowRelativePath 1635 | call s:RedisplayBufferList() 1636 | endfunction 1637 | 1638 | " ToggleShowTabBuffer {{{2 1639 | function! s:ToggleShowTabBuffer() 1640 | " Forget any cached MRU ordering, as it depends on 1641 | " `g:bufExplorerShowTabBuffer`. 1642 | unlet! s:mruOrder 1643 | let g:bufExplorerShowTabBuffer = !g:bufExplorerShowTabBuffer 1644 | call s:RedisplayBufferList() 1645 | endfunction 1646 | 1647 | " ToggleOnlyOneTab {{{2 1648 | function! s:ToggleOnlyOneTab() 1649 | let g:bufExplorerOnlyOneTab = !g:bufExplorerOnlyOneTab 1650 | call s:RedisplayBufferList() 1651 | endfunction 1652 | 1653 | " ToggleShowUnlisted {{{2 1654 | function! s:ToggleShowUnlisted() 1655 | let g:bufExplorerShowUnlisted = !g:bufExplorerShowUnlisted 1656 | call s:RedisplayBufferList() 1657 | endfunction 1658 | 1659 | " ToggleFindActive {{{2 1660 | function! s:ToggleFindActive() 1661 | let g:bufExplorerFindActive = !g:bufExplorerFindActive 1662 | call s:UpdateHelpStatus() 1663 | endfunction 1664 | 1665 | " RebuildBufferList {{{2 1666 | function! s:RebuildBufferList() 1667 | setlocal modifiable 1668 | 1669 | let curPos = getpos('.') 1670 | 1671 | let num_bufs = s:BuildBufferList() 1672 | 1673 | call setpos('.', curPos) 1674 | 1675 | setlocal nomodifiable 1676 | 1677 | return num_bufs 1678 | endfunction 1679 | 1680 | " UpdateHelpStatus {{{2 1681 | function! s:UpdateHelpStatus() 1682 | setlocal modifiable 1683 | 1684 | let text = s:GetHelpStatus() 1685 | call setline(s:firstBufferLine - 2, text) 1686 | 1687 | setlocal nomodifiable 1688 | endfunction 1689 | 1690 | " Key_number {{{2 1691 | function! s:Key_number(buf) 1692 | let key = [printf('%020d', a:buf.bufNbr)] 1693 | return key 1694 | endfunction 1695 | 1696 | " Key_name {{{2 1697 | function! s:Key_name(buf) 1698 | let key = [a:buf.name, a:buf.fullpath] 1699 | return key 1700 | endfunction 1701 | 1702 | " Key_fullpath {{{2 1703 | function! s:Key_fullpath(buf) 1704 | let key = [a:buf.fullpath] 1705 | return key 1706 | endfunction 1707 | 1708 | " Key_extension {{{2 1709 | function! s:Key_extension(buf) 1710 | let extension = fnamemodify(a:buf.name, ':e') 1711 | let key = [extension, a:buf.name, a:buf.fullpath] 1712 | return key 1713 | endfunction 1714 | 1715 | " Key_mru {{{2 1716 | function! s:Key_mru(buf) 1717 | let pos = s:MRUOrderForBuf(a:buf.bufNbr) 1718 | return [printf('%9d', pos), a:buf.fullpath] 1719 | endfunction 1720 | 1721 | " SortByKeyFunc {{{2 1722 | function! s:SortByKeyFunc(keyFunc) 1723 | let lastLineNbr = s:firstBufferLine + len(s:displayedBufNbrs) - 1 1724 | " `s:firstBufferLine >= 1`, `lastLineNbr >= 0`; `getline(1, 0) -> []`. 1725 | let displayedLines = getline(s:firstBufferLine, lastLineNbr) 1726 | let bufLineIndex = 0 1727 | let keyedLines = [] 1728 | for bufNbr in s:displayedBufNbrs 1729 | let buf = s:raw_buffer_listing[bufNbr] 1730 | let key = eval(a:keyFunc . '(buf)') 1731 | let parts = key + [string(bufNbr), string(bufLineIndex)] 1732 | call add(keyedLines, join(parts, "\1")) 1733 | let bufLineIndex += 1 1734 | endfor 1735 | 1736 | " Ignore case when sorting by passing `1`: 1737 | call sort(keyedLines, 1) 1738 | 1739 | if g:bufExplorerReverseSort 1740 | call reverse(keyedLines) 1741 | endif 1742 | 1743 | let s:displayedBufNbrs = [] 1744 | let lines = [] 1745 | for keyedLine in keyedLines 1746 | let parts = split(keyedLine, "\1") 1747 | let [bufNbrStr, bufLineIndexStr] = parts[-2:-1] 1748 | call add(s:displayedBufNbrs, str2nr(bufNbrStr)) 1749 | call add(lines, displayedLines[str2nr(bufLineIndexStr)]) 1750 | endfor 1751 | 1752 | call setline(s:firstBufferLine, lines) 1753 | endfunction 1754 | 1755 | " SortReverse {{{2 1756 | function! s:SortReverse() 1757 | let g:bufExplorerReverseSort = !g:bufExplorerReverseSort 1758 | call s:ReSortListing() 1759 | endfunction 1760 | 1761 | " SortSelect {{{2 1762 | function! s:SortSelect() 1763 | let g:bufExplorerSortBy = get(s:sort_by, index(s:sort_by, g:bufExplorerSortBy) + 1, s:sort_by[0]) 1764 | call s:ReSortListing() 1765 | endfunction 1766 | 1767 | " ReverseSortSelect {{{2 1768 | function! s:ReverseSortSelect() 1769 | let g:bufExplorerSortBy = get(s:sort_by, index(s:sort_by, g:bufExplorerSortBy) - 1, s:sort_by[-1]) 1770 | call s:ReSortListing() 1771 | endfunction 1772 | 1773 | " ReSortListing {{{2 1774 | function! s:ReSortListing() 1775 | setlocal modifiable 1776 | 1777 | let curPos = getpos('.') 1778 | 1779 | call s:SortListing() 1780 | call s:UpdateHelpStatus() 1781 | 1782 | call setpos('.', curPos) 1783 | 1784 | setlocal nomodifiable 1785 | endfunction 1786 | 1787 | " SortListing {{{2 1788 | function! s:SortListing() 1789 | call s:SortByKeyFunc("Key_" . g:bufExplorerSortBy) 1790 | endfunction 1791 | 1792 | " GetBufNbrAtCursor {{{2 1793 | " Return `bufNbr` at cursor; return 0 if no buffer on that line. 1794 | function! s:GetBufNbrAtCursor() 1795 | return s:GetBufNbrAtLine(line('.')) 1796 | endfunction 1797 | 1798 | " GetBufNbrAtLine {{{2 1799 | " Return `bufNbr` at `lineNbr`; return 0 if no buffer on that line. 1800 | function! s:GetBufNbrAtLine(lineNbr) 1801 | let bufIndex = a:lineNbr - s:firstBufferLine 1802 | if bufIndex < 0 || bufIndex >= len(s:displayedBufNbrs) 1803 | return 0 1804 | endif 1805 | return s:displayedBufNbrs[bufIndex] 1806 | endfunction 1807 | 1808 | " BufferNumLines {{{2 1809 | " Return number of lines in the BufExplorer buffer. 1810 | function! s:BufferNumLines() 1811 | " `line('$')` returns the line number of the last line in a buffer. 1812 | " Normally, this is the same as the number of lines in the buffer. When 1813 | " there are no lines in the buffer, logically `line('$')` should return 1814 | " zero, but Vim unfortunately returns 1 for this case. This is because 1815 | " there must always be a valid line number for the cursor. 1816 | " 1817 | " When `line('$') == 1`, we detect an empty buffer by seeing if the first 1818 | " line itself is empty. Technically, this cannot distinguish a completely 1819 | " empty buffer from a one-line buffer with no characters on the first line, 1820 | " but BufExplorer doesn't create empty lines in the buffer. 1821 | let numLines = line('$') 1822 | if numLines == 1 && getline(1) == '' 1823 | let numLines = 0 1824 | endif 1825 | return numLines 1826 | endfunction 1827 | 1828 | " Error {{{2 1829 | " Display a message using ErrorMsg highlight group. 1830 | function! s:Error(msg) 1831 | echohl ErrorMsg 1832 | echomsg a:msg 1833 | echohl None 1834 | endfunction 1835 | 1836 | " Warning {{{2 1837 | " Display a message using WarningMsg highlight group. 1838 | function! s:Warning(msg) 1839 | echohl WarningMsg 1840 | echomsg a:msg 1841 | echohl None 1842 | endfunction 1843 | 1844 | " GetTabNbr {{{2 1845 | function! s:GetTabNbr(bufNbr) 1846 | " Prefer current tab. 1847 | if bufwinnr(a:bufNbr) > 0 1848 | return tabpagenr() 1849 | endif 1850 | " Searching buffer bufno, in tabs. 1851 | for i in range(tabpagenr("$")) 1852 | if index(tabpagebuflist(i + 1), a:bufNbr) != -1 1853 | return i + 1 1854 | endif 1855 | endfor 1856 | 1857 | return 0 1858 | endfunction 1859 | 1860 | " GetWinNbr" {{{2 1861 | function! s:GetWinNbr(tabNbr, bufNbr) 1862 | " window number in tabpage. 1863 | let tablist = tabpagebuflist(a:tabNbr) 1864 | " Number: 0 1865 | " String: 1 1866 | " Funcref: 2 1867 | " List: 3 1868 | " Dictionary: 4 1869 | " Float: 5 1870 | if type(tablist) == 3 1871 | return index(tabpagebuflist(a:tabNbr), a:bufNbr) + 1 1872 | else 1873 | return 1 1874 | endif 1875 | endfunction 1876 | 1877 | " StringWidth" {{{2 1878 | if exists('*strwidth') 1879 | function s:StringWidth(s) 1880 | return strwidth(a:s) 1881 | endfunction 1882 | else 1883 | function s:StringWidth(s) 1884 | return len(a:s) 1885 | endfunction 1886 | endif 1887 | 1888 | " Winmanager Integration {{{2 1889 | let g:BufExplorer_title = "\[Buf\ List\]" 1890 | call s:Set("g:bufExplorerResize", 1) 1891 | call s:Set("g:bufExplorerMaxHeight", 25) " Handles dynamic resizing of the window. 1892 | 1893 | " Evaluate a Vimscript expression in the context of this file. 1894 | " This enables debugging of script-local variables and functions from outside 1895 | " the plugin, e.g.: 1896 | " :echo BufExplorer_eval('s:bufMru') 1897 | function! BufExplorer_eval(expr) 1898 | return eval(a:expr) 1899 | endfunction 1900 | 1901 | " Execute a Vimscript statement in the context of this file. 1902 | " This enables setting script-local variables from outside the plugin, e.g.: 1903 | " :call BufExplorer_execute('let s:bufMru = s:MRUNew(0)') 1904 | function! BufExplorer_execute(statement) 1905 | execute a:statement 1906 | endfunction 1907 | 1908 | " function! to start display. Set the mode to 'winmanager' for this buffer. 1909 | " This is to figure out how this plugin was called. In a standalone fashion 1910 | " or by winmanager. 1911 | function! BufExplorer_Start() 1912 | let b:displayMode = "winmanager" 1913 | call s:SetLocalSettings() 1914 | call BufExplorer() 1915 | endfunction 1916 | 1917 | " Returns whether the display is okay or not. 1918 | function! BufExplorer_IsValid() 1919 | return 0 1920 | endfunction 1921 | 1922 | " Handles dynamic refreshing of the window. 1923 | function! BufExplorer_Refresh() 1924 | let b:displayMode = "winmanager" 1925 | call s:SetLocalSettings() 1926 | call BufExplorer() 1927 | endfunction 1928 | 1929 | function! BufExplorer_ReSize() 1930 | if !g:bufExplorerResize 1931 | return 1932 | end 1933 | 1934 | let nlines = min([line("$"), g:bufExplorerMaxHeight]) 1935 | 1936 | execute nlines." wincmd _" 1937 | 1938 | " The following lines restore the layout so that the last file line is also 1939 | " the last window line. Sometimes, when a line is deleted, although the 1940 | " window size is exactly equal to the number of lines in the file, some of 1941 | " the lines are pushed up and we see some lagging '~'s. 1942 | let pres = getpos(".") 1943 | 1944 | normal! $ 1945 | 1946 | let _scr = &scrolloff 1947 | let &scrolloff = 0 1948 | 1949 | normal! z- 1950 | 1951 | let &scrolloff = _scr 1952 | 1953 | call setpos(".", pres) 1954 | endfunction 1955 | 1956 | " Default values {{{2 1957 | call s:Set("g:bufExplorerColumns", BufExplorer_defaultColumns()) " Configurable list of column names for the buffer list. 1958 | call s:Set("g:bufExplorerDisableDefaultKeyMapping", 0) " Do not disable default key mappings. 1959 | call s:Set("g:bufExplorerDefaultAction", 'current') " Default action for `:BufExplorer` with no args. 1960 | call s:Set("g:bufExplorerDefaultHelp", 1) " Show default help? 1961 | call s:Set("g:bufExplorerDetailedHelp", 0) " Show detailed help? 1962 | call s:Set("g:bufExplorerFindActive", 1) " When selecting an active buffer, take you to the window where it is active? 1963 | call s:Set("g:bufExplorerOnlyOneTab", 1) " Show buffer only on MRU tab? (Applies when `g:bufExplorerShowTabBuffer` is true.) 1964 | call s:Set("g:bufExplorerReverseSort", 0) " Sort in reverse order by default? 1965 | call s:Set("g:bufExplorerShowDirectories", 1) " (Dir's are added by commands like ':e .') 1966 | call s:Set("g:bufExplorerShowRelativePath", 0) " Show listings with relative or absolute paths? 1967 | call s:Set("g:bufExplorerShowTabBuffer", 0) " Show only buffer(s) for this tab? 1968 | call s:Set("g:bufExplorerShowUnlisted", 0) " Show unlisted buffers? 1969 | call s:Set("g:bufExplorerShowNoName", 0) " Show 'No Name' buffers? 1970 | call s:Set("g:bufExplorerSortBy", "mru") " Sorting methods are in s:sort_by: 1971 | call s:Set("g:bufExplorerSplitBelow", &splitbelow) " Should horizontal splits be below or above current window? 1972 | call s:Set("g:bufExplorerSplitOutPathName", 1) " Split out path and file name? 1973 | call s:Set("g:bufExplorerSplitRight", &splitright) " Should vertical splits be on the right or left of current window? 1974 | call s:Set("g:bufExplorerSplitVertSize", 0) " Height for a vertical split. If <=0, default Vim size is used. 1975 | call s:Set("g:bufExplorerSplitHorzSize", 0) " Height for a horizontal split. If <=0, default Vim size is used. 1976 | call s:Set("g:bufExplorerShowTerminal", 1) " Show terminal buffers? 1977 | 1978 | " Default key mapping {{{2 1979 | if !hasmapto('BufExplorer') && g:bufExplorerDisableDefaultKeyMapping == 0 1980 | nnoremap