├── .gitignore ├── LICENSE ├── plugin └── buffet.vim ├── README.md └── autoload └── buffet.vim /.gitignore: -------------------------------------------------------------------------------- 1 | test-fixtures/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bagrat Aznauryan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugin/buffet.vim: -------------------------------------------------------------------------------- 1 | if exists("g:buffet_loaded") 2 | finish 3 | endif 4 | 5 | let g:buffet_loaded = 1 6 | 7 | let g:buffet_always_show_tabline = get(g:, "buffet_always_show_tabline", 1) 8 | 9 | augroup buffet_show_tabline 10 | autocmd! 11 | autocmd VimEnter,BufAdd,TabEnter * set showtabline=2 12 | augroup END 13 | 14 | if has("gui") || has("termguicolors") 15 | if !get(g:, "buffet_use_gui_tablne", 0) 16 | set guioptions-=e 17 | endif 18 | endif 19 | 20 | if get(g:, "buffet_powerline_separators", 0) 21 | let g:buffet_powerline_separators = 1 22 | let g:buffet_noseparator = "\ue0b0" 23 | let g:buffet_separator = "\ue0b1" 24 | else 25 | let g:buffet_powerline_separators = 0 26 | let g:buffet_noseparator = get(g:, "buffet_noseparator", " ") 27 | let g:buffet_separator = get(g:, "buffet_separator", "|") 28 | endif 29 | 30 | let g:buffet_show_index = get(g:, "buffet_show_index", 0) 31 | 32 | let g:buffet_max_plug = get(g:, "buffet_max_plug", 10) 33 | 34 | if get(g:, "buffet_use_devicons", 1) 35 | if !exists("*WebDevIconsGetFileTypeSymbol") 36 | let g:buffet_use_devicons = 0 37 | else 38 | let g:buffet_use_devicons = 1 39 | endif 40 | else 41 | let g:buffet_use_devicons = 0 42 | endif 43 | 44 | if !exists("g:buffet_modified_icon") 45 | let g:buffet_modified_icon = "+" 46 | endif 47 | 48 | if !exists("g:buffet_left_trunc_icon") 49 | let g:buffet_left_trunc_icon = "<" 50 | endif 51 | 52 | if !exists("g:buffet_right_trunc_icon") 53 | let g:buffet_right_trunc_icon = ">" 54 | endif 55 | 56 | if !exists("g:buffet_new_buffer_name") 57 | let g:buffet_new_buffer_name = "*" 58 | endif 59 | 60 | if !exists("g:buffet_tab_icon") 61 | let g:buffet_tab_icon = "#" 62 | endif 63 | 64 | if !exists("g:buffet_hidden_buffers") 65 | let g:buffet_hidden_buffers = ["terminal", "quickfix"] 66 | endif 67 | 68 | let g:buffet_prefix = "Buffet" 69 | let g:buffet_has_separator = { 70 | \ "Tab": { 71 | \ "Tab": g:buffet_separator, 72 | \ "LeftTrunc": g:buffet_separator, 73 | \ "End" : g:buffet_separator, 74 | \ }, 75 | \ "LeftTrunc": { 76 | \ "Buffer": g:buffet_separator, 77 | \ "CurrentBuffer": g:buffet_separator, 78 | \ "ActiveBuffer": g:buffet_separator, 79 | \ "ModBuffer": g:buffet_separator, 80 | \ "ModActiveBuffer": g:buffet_separator, 81 | \ "ModCurrentBuffer": g:buffet_separator, 82 | \ }, 83 | \ "RightTrunc": { 84 | \ "Tab": g:buffet_separator, 85 | \ "End": g:buffet_separator, 86 | \ }, 87 | \ } 88 | 89 | let g:buffet_buffer_types = [ 90 | \ "Buffer", 91 | \ "ActiveBuffer", 92 | \ "CurrentBuffer", 93 | \ "ModBuffer", 94 | \ "ModActiveBuffer", 95 | \ "ModCurrentBuffer", 96 | \ ] 97 | 98 | for s:type in g:buffet_buffer_types 99 | let g:buffet_has_separator["Tab"][s:type] = g:buffet_separator 100 | let g:buffet_has_separator[s:type] = { 101 | \ "RightTrunc": g:buffet_separator, 102 | \ "Tab": g:buffet_separator, 103 | \ "End": g:buffet_separator, 104 | \ } 105 | 106 | for s:t in g:buffet_buffer_types 107 | let g:buffet_has_separator[s:type][s:t] = g:buffet_separator 108 | endfor 109 | endfor 110 | 111 | function! s:GetHiAttr(name, attr) 112 | let vim_mode = "cterm" 113 | let attr_suffix = "" 114 | if has("gui") || has('termguicolors') 115 | let vim_mode = "gui" 116 | let attr_suffix = "#" 117 | endif 118 | 119 | let value = synIDattr(synIDtrans(hlID(a:name)), a:attr . attr_suffix, vim_mode) 120 | 121 | return value 122 | endfunction 123 | 124 | function! s:SetHi(name, fg, bg) 125 | let vim_mode = "cterm" 126 | if has("gui") || has("termguicolors") 127 | let vim_mode = "gui" 128 | endif 129 | 130 | let spec = "" 131 | if a:fg != "" 132 | let fg_spec = vim_mode . "fg=" . a:fg 133 | let spec = fg_spec 134 | endif 135 | 136 | if a:bg != "" 137 | let bg_spec = vim_mode . "bg=" . a:bg 138 | 139 | if spec != "" 140 | let bg_spec = " " . bg_spec 141 | endif 142 | 143 | let spec = spec . bg_spec 144 | endif 145 | 146 | if spec != "" 147 | exec "silent hi! " . a:name . " " . spec 148 | endif 149 | endfunction 150 | 151 | function! s:LinkHi(name, target) 152 | exec "silent hi! link " . a:name . " " . a:target 153 | endfunction 154 | 155 | function! s:SetColors() 156 | " TODO: try to match user's colorscheme 157 | " Issue: https://github.com/bagrat/vim-buffet/issues/5 158 | " if get(g:, "buffet_match_color_scheme", 1) 159 | 160 | hi! BuffetCurrentBuffer cterm=NONE ctermbg=2 ctermfg=8 guibg=#00FF00 guifg=#000000 161 | hi! BuffetActiveBuffer cterm=NONE ctermbg=10 ctermfg=2 guibg=#999999 guifg=#00FF00 162 | hi! BuffetBuffer cterm=NONE ctermbg=10 ctermfg=8 guibg=#999999 guifg=#000000 163 | 164 | hi! link BuffetModCurrentBuffer BuffetCurrentBuffer 165 | hi! link BuffetModActiveBuffer BuffetActiveBuffer 166 | hi! link BuffetModBuffer BuffetBuffer 167 | 168 | hi! BuffetTrunc cterm=bold ctermbg=11 ctermfg=8 guibg=#999999 guifg=#000000 169 | hi! BuffetTab cterm=NONE ctermbg=4 ctermfg=8 guibg=#0000FF guifg=#000000 170 | 171 | hi! link BuffetLeftTrunc BuffetTrunc 172 | hi! link BuffetRightTrunc BuffetTrunc 173 | hi! link BuffetEnd BuffetBuffer 174 | 175 | if exists("*g:BuffetSetCustomColors") 176 | call g:BuffetSetCustomColors() 177 | endif 178 | 179 | for left in keys(g:buffet_has_separator) 180 | for right in keys(g:buffet_has_separator[left]) 181 | let vim_mode = "cterm" 182 | if has("gui") || has("termguicolors") 183 | let vim_mode = "gui" 184 | endif 185 | 186 | let left_hi = g:buffet_prefix . left 187 | let right_hi = g:buffet_prefix . right 188 | let left_bg = s:GetHiAttr(left_hi, 'bg') 189 | let right_bg = s:GetHiAttr(right_hi, 'bg') 190 | 191 | if left_bg == "" 192 | let left_bg = "NONE" 193 | endif 194 | 195 | if right_bg == "" 196 | let right_bg = "NONE" 197 | endif 198 | 199 | let sep_hi = g:buffet_prefix . left . right 200 | if left_bg != right_bg 201 | let g:buffet_has_separator[left][right] = g:buffet_noseparator 202 | 203 | call s:SetHi(sep_hi, left_bg, right_bg) 204 | else 205 | let g:buffet_has_separator[left][right] = g:buffet_separator 206 | 207 | call s:LinkHi(sep_hi, left_hi) 208 | endif 209 | endfor 210 | endfor 211 | endfunction 212 | 213 | augroup buffet_set_colors 214 | autocmd! 215 | autocmd ColorScheme * call s:SetColors() 216 | augroup end 217 | 218 | " Set solors also at the startup 219 | call s:SetColors() 220 | 221 | if has("nvim") 222 | function! SwitchToBuffer(buffer_id, clicks, btn, flags) 223 | exec "silent buffer " . a:buffer_id 224 | endfunction 225 | endif 226 | 227 | function! buffet#bwipe_nerdtree_filter(bang, buffer) 228 | let is_in_nt = 0 229 | if exists("t:NERDTreeBufName") 230 | let ntwinnr = bufwinnr(t:NERDTreeBufName) 231 | 232 | if ntwinnr == winnr() 233 | let is_in_nt = 1 234 | endif 235 | endif 236 | 237 | if is_in_nt 238 | return 1 239 | endif 240 | endfunction 241 | 242 | let g:buffet_bwipe_filters = ["buffet#bwipe_nerdtree_filter"] 243 | 244 | for s:n in range(1, g:buffet_max_plug) 245 | execute printf("noremap BuffetSwitch(%d) :call buffet#bswitch(%d)", s:n, s:n) 246 | endfor 247 | 248 | command! -bang -complete=buffer -nargs=? Bw call buffet#bwipe(, ) 249 | command! -bang -complete=buffer -nargs=? Bonly call buffet#bonly(, ) 250 | 251 | set tabline=%!buffet#render() 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Markdownify 4 |
5 | 𝓿𝓲𝓶 -𝓫𝓾𝓯𝓯𝓮𝓽 6 |
7 |

8 | 9 |

10 | Brings you the IDE-like tabs into Vim, for easy navigation, and a nice, customizable look 11 |

12 | 13 |

14 | 15 | 16 | 17 | 22 |

23 | 24 |

25 | Installation • 26 | Usage • 27 | Recommendations • 28 | FAQ • 29 | Credits • 30 | License 31 |

32 | 33 | **Note:** This plugin has been renamed from `vim-workspace` and thus has also 34 | different prefix for the configuration and commands. Please revisit the README 35 | and use the new names of the configuration parameters, highlight groups and 36 | commands. Sorry for inconvenience. 37 | 38 | 39 | 40 | ## Introduction 41 |

42 | vim-buffet Screenshot 46 |

47 | 48 | Vim-buffet takes your buffers and tabs, and shows them combined in the 49 | tabline. With this you always have your list of buffers visible, at the same 50 | time not losing visibility into tabs. Moreover, `vim-buffet` provides handy 51 | commands to boost navigation as well as a list of options to customize how the 52 | tabline appears. 53 | 54 | If you are new to the Vim world, then welcome, and start by learning Vim's 55 | notions of [buffers, windows](http://vimdoc.sourceforge.net/htmldoc/windows.html) 56 | and [tabpages](http://vimdoc.sourceforge.net/htmldoc/tabpage.html). 57 | 58 | But if you are an experienced Vim user, you might have got tired of `bn/bp/ls/Ctrl-^`. 59 | 60 | Take a look at the screenshot. The blue cuties are the tabpages. The tabpage 61 | that has the buffers list coming next, is the current tabpage. The gray items 62 | with names are the hidden/inactive buffers, and obviously, the green one is the 63 | current buffer. The brighter items on both ends with the little arrows and numbers 64 | are the truncation indicators. If all the buffers do not fit the screen, 65 | `vim-buffet` truncates the tabline, and shows the number of truncated buffers on 66 | both ends. 67 | 68 | *__Note__: The instance of Vim in the screenshot is configured to use powerline 69 | symbols and dev-icons. The default interface is only text and will work without 70 | requiring any patched fonts. The default interface looks like in the screenshot 71 | below.* 72 | 73 |

74 | vim-buffet Screenshot 78 |

79 | 80 | 81 | 82 | ## Installation 83 | 84 | Use your favourite plugin manager to install `vim-buffet`. If you do not have any 85 | preference or have not decided yet, I would recommend [Plug](https://github.com/junegunn/vim-plug). 86 | 87 | ```vim 88 | Plug 'bagrat/vim-buffet' 89 | ``` 90 | 91 | After installation, `vim-buffet` is enabled by default, so whenever you restart 92 | Vim, you will see the new tabline! 93 | 94 | 95 | 96 | ## Usage 97 | ### Commands 98 | 99 | Apart from listing the buffers in the tabline, `vim-buffet` also provides some 100 | handy commands to manipulate the buffers: 101 | 102 | * `Bw[!]` - wipe the current buffer without closing the window. If it has unsaved 103 | changes, an error will be shown, and the buffer will stay open. To ignore any 104 | changes and forcibly wipe the buffer, use `Bw!`. 105 | * `Bonly[!]` - wipe all the buffers but the current one. If there are any buffers 106 | in the list that has unsaved changes, those will not be wiped. To ignore any 107 | changes and forcibly wipe all buffers except the current one, use `Bonly!`. 108 | 109 | ### Mappings 110 | 111 | Mappings for switching buffers are also provided. You just need to add the following 112 | mappings to your Vimrc file: 113 | 114 | ```vim 115 | nmap 1 BuffetSwitch(1) 116 | nmap 2 BuffetSwitch(2) 117 | nmap 3 BuffetSwitch(3) 118 | nmap 4 BuffetSwitch(4) 119 | nmap 5 BuffetSwitch(5) 120 | nmap 6 BuffetSwitch(6) 121 | nmap 7 BuffetSwitch(7) 122 | nmap 8 BuffetSwitch(8) 123 | nmap 9 BuffetSwitch(9) 124 | nmap 0 BuffetSwitch(10) 125 | ``` 126 | 127 | This will allow you to switch between buffers 1 - 10. You can get more `` 128 | mappings, or disable it completely, by configuring the `g:buffet_max_plug` option. 129 | 130 | ### Configuration 131 | 132 | There are some configuration options that make it possible to customize how the 133 | tabline works and looks like. 134 | 135 | #### 🔩 Options 136 | 137 | | Options | Default | Descriptions | 138 | | --- | --- | --- | 139 | | `g:buffet_always_show_tabline` | `1` | Set to `0`, the tabline will only be shown if there is more than one buffer or tab open | 140 | | `g:buffet_powerline_separators` | `0` | Set to `1`, use powerline separators in between buffers and tabs in the tabline (see the first screenshot) | 141 | | `g:buffet_separator` | `''` | The character to be used for separating items in the tabline | 142 | | `g:buffet_show_index` | `0` | Set to `1`, show index before each buffer name. Index is useful for switching between buffers quickly | 143 | | `g:buffet_max_plug` | `10` | The maximum number of `BuffetSwitch` provided. Mapping will be disabled if the option is set to `0` | 144 | | `g:buffet_use_devicons` | `1` | If set to `1` and [`vim-devicons`](https://github.com/ryanoasis/vim-devicons) plugin is installed, show file type icons for each buffer in the tabline. If the `vim-devicons` plugin is not present, the option will automatically default to `0` (*Note: you need to have `vim-devicons` loaded before `vim-buffet` in order to make this work*) | 145 | | `g:buffet_tab_icon` | `'#'` | The character to be used as an icon for the tab items in the tabline | 146 | | `g:buffet_new_buffer_name` | `'*'` | The character to be shown as the name of a new buffer | 147 | | `g:buffet_modified_icon` | `'+'` | The character to be shown by the name of a modified buffer | 148 | | `g:buffet_left_trun_icon` | `'<'` | The character to be shown by the count of truncated buffers on the left | 149 | | `g:buffet_right_trun_icon` | `'>'` | The character to be shown by the count of truncated buffers on the right | 150 | | `g:buffet_hidden_buffers` | `['terminal', 'quickfix']` | The types of buffers to hide from the tabline (*Note: This has the side effect of making all matching buffers unlisted)* | 151 | 152 | #### 🎨 Colors 153 | Of course, you can customize the colors of your tabline, to make it awesome and 154 | yours. To get your custom colors set, define a function with name 155 | `g:BuffetSetCustomColors` and place your highlight group definitions inside 156 | the function. 157 | 158 | ```vim 159 | " Note: Make sure the function is defined before `vim-buffet` is loaded. 160 | function! g:BuffetSetCustomColors() 161 | hi! BuffetCurrentBuffer cterm=NONE ctermbg=5 ctermfg=8 guibg=#00FF00 guifg=#000000 162 | endfunction 163 | ``` 164 | 165 | The following is the list of highlight groups, with self-explanatory 166 | names: 167 | 168 | | Highlights | Descriptions | 169 | | --- | --- | 170 | | `BuffetCurrentBuffer` | The current buffer. 171 | | `BuffetActiveBuffer` | An active buffer (a non-current buffer visible in a non-current window) | 172 | | `BuffetBuffer` | A non-current and non-active buffer. 173 | | `BuffetModCurrentBuffer` | The current buffer when modified. 174 | | `BuffetModActiveBuffer` | A modified active buffer (a non-current buffer visible in a non-current window). | 175 | | `BuffetModBuffer` | A modified non-current and non-active buffer. 176 | | `BuffetTrunc` | The truncation indicator (count of truncated buffers from the left or right) | 177 | | `BuffetTab` | A tab | 178 | 179 | 180 | 181 | ## Recommendations 182 | 183 | Here are some recommended mappings to boost your navigation experience: 184 | 185 | ```vim 186 | noremap :bn 187 | noremap :bp 188 | noremap :Bw 189 | noremap :Bw! 190 | noremap :tabnew split 191 | ``` 192 | 193 | 194 | 195 | ## FAQ 196 |
How do I get the look like in the screenshot? 197 |

198 | 199 | First you will need a patched font, extended with `powerline` and `font-awesome` 200 | symbols. Also, you will need the 201 | [`vim-devicons`](https://github.com/ryanoasis/vim-devicons) installed, which 202 | also has great guides on how to patch fonts, as well as some pre-patched fonts. 203 | As soon as you have the patched font, setting the following options, will give 204 | you exactly the same tabline as you see in the first demo screenshot: 205 | 206 | ```vim 207 | let g:buffet_powerline_separators = 1 208 | let g:buffet_tab_icon = "\uf00a" 209 | let g:buffet_left_trunc_icon = "\uf0a8" 210 | let g:buffet_right_trunc_icon = "\uf0a9" 211 | ``` 212 | 213 | *Note: you need to have `vim-devicons` loaded before `vim-buffet` in order to 214 | make this work.* 215 |

216 |
217 | 218 | 219 |
How to have the current buffer open in a new tab instead of a new one? 220 |

221 | 222 | Just add this mapping to your Vimrc: 223 | 224 | ```vim 225 | map :tab split 226 | ``` 227 |

228 |
229 | 230 | 231 |
I can only see the current active buffer in the tabline 232 |

233 | 234 | The reason is that you probably use some statusline plugin (e.g. lightline, 235 | airline) that also has tabline support, which overrides vim-buffet. All you need 236 | to do is disable the tabline of the statusline plugin. 237 | 238 | #### For [lightline.vim](https://github.com/itchyny/lightline.vim) 239 | It should be something like this: 240 | 241 | ```vim 242 | let g:lightline.enable.tabline = 0 243 | ``` 244 | 245 | If that's not working, checkout this [issue](https://github.com/bagrat/vim-buffet/issues/20) especially this [comment](https://github.com/bagrat/vim-buffet/issues/20#issuecomment-706075930). 246 |

247 |
248 | 249 | 250 | 251 | ## Credits 252 |
The icon in the header is made by Freepik from www.flaticon.com
253 | 254 | 255 | 256 | ## License 257 | 258 | See [LICENSE](https://github.com/bagrat/vim-buffet/blob/master/LICENS://github.com/bagrat/vim-buffet/blob/master/LICENSE). 259 | -------------------------------------------------------------------------------- /autoload/buffet.vim: -------------------------------------------------------------------------------- 1 | let s:buffers = {} 2 | let s:buffer_ids = [] 3 | 4 | " when the focus switches to another *unlisted* buffer, it does not appear in 5 | " the tabline, thus the tabline will list starting from the first buffer. For 6 | " this, we keep track of the last current buffer to keep the tabline "position" 7 | " in the same place. 8 | let s:last_current_buffer_id = -1 9 | 10 | " when you delete a buffer with the highest ID, we will never loop up there and 11 | " it will always stay in the buffers list, so we need to remember the largest 12 | " buffer ID. 13 | let s:largest_buffer_id = 1 14 | 15 | " either a slash or backslash 16 | let s:path_separator = fnamemodify(getcwd(),':p')[-1:] 17 | 18 | function! buffet#update() 19 | let largest_buffer_id = max([bufnr('$'), s:largest_buffer_id]) 20 | 21 | for buffer_id in range(1, largest_buffer_id) 22 | " Check if we already keep track of this buffer 23 | let is_present = 0 24 | if has_key(s:buffers, buffer_id) 25 | let is_present = 1 26 | endif 27 | 28 | " Skip if a buffer with this id does not exist 29 | if !buflisted(buffer_id) 30 | if is_present 31 | if buffer_id == s:last_current_buffer_id 32 | let s:last_current_buffer_id = -1 33 | endif 34 | 35 | " forget about this buffer 36 | call remove(s:buffers, buffer_id) 37 | call remove(s:buffer_ids, index(s:buffer_ids, buffer_id)) 38 | let s:largest_buffer_id = max(s:buffer_ids) 39 | endif 40 | 41 | continue 42 | endif 43 | 44 | " If this buffer is already tracked and listed, we're good. 45 | " In case if it is the only buffer, still update, because an empty new 46 | " buffer id is being replaced by a buffer for an existing file. 47 | if is_present && len(s:buffers) > 1 48 | continue 49 | endif 50 | 51 | " hide terminal and quickfix buffers 52 | let buffer_type = getbufvar(buffer_id, "&buftype", "") 53 | if index(g:buffet_hidden_buffers, buffer_type) >= 0 54 | call setbufvar(buffer_id, "&buflisted", 0) 55 | continue 56 | endif 57 | 58 | let buffer_name = bufname(buffer_id) 59 | let buffer_head = fnamemodify(buffer_name, ':p:h') 60 | let buffer_tail = fnamemodify(buffer_name, ':t') 61 | 62 | " Initialize the buffer object 63 | let buffer = {} 64 | let buffer.head = split(buffer_head, s:path_separator) 65 | let buffer.not_new = len(buffer_tail) 66 | let buffer.tail = buffer.not_new ? buffer_tail : g:buffet_new_buffer_name 67 | 68 | " Update the buffers map 69 | let s:buffers[buffer_id] = buffer 70 | 71 | if !is_present 72 | " Update the buffer IDs list 73 | call add(s:buffer_ids, buffer_id) 74 | let s:largest_buffer_id = max([s:largest_buffer_id, buffer_id]) 75 | endif 76 | endfor 77 | 78 | let buffer_name_count = {} 79 | 80 | " Set initial buffer name, and record occurrences 81 | for buffer in values(s:buffers) 82 | let buffer.index = -1 83 | let buffer.name = buffer.tail 84 | let buffer.length = len(buffer.name) 85 | 86 | if buffer.not_new 87 | let current_count = get(buffer_name_count, buffer.name, 0) 88 | let buffer_name_count[buffer.name] = current_count + 1 89 | endif 90 | endfor 91 | 92 | " Disambiguate buffer names with multiple occurrences 93 | while len(filter(buffer_name_count, 'v:val > 1')) 94 | let ambiguous = buffer_name_count 95 | let buffer_name_count = {} 96 | 97 | for buffer in values(s:buffers) 98 | if has_key(ambiguous, buffer.name) 99 | let buffer_path = buffer.head[buffer.index:] 100 | call add(buffer_path, buffer.tail) 101 | 102 | let buffer.index -= 1 103 | let buffer.name = join(buffer_path, s:path_separator) 104 | let buffer.length = len(buffer.name) 105 | endif 106 | 107 | if buffer.not_new 108 | let current_count = get(buffer_name_count, buffer.name, 0) 109 | let buffer_name_count[buffer.name] = current_count + 1 110 | endif 111 | endfor 112 | endwhile 113 | 114 | let current_buffer_id = bufnr('%') 115 | if has_key(s:buffers, current_buffer_id) 116 | let s:last_current_buffer_id = current_buffer_id 117 | elseif s:last_current_buffer_id == -1 && len(s:buffer_ids) > 0 118 | let s:last_current_buffer_id = s:buffer_ids[0] 119 | endif 120 | 121 | " Hide tabline if only one buffer and tab open 122 | if !g:buffet_always_show_tabline && len(s:buffer_ids) == 1 && tabpagenr("$") == 1 123 | set showtabline=0 124 | endif 125 | endfunction 126 | 127 | function! s:GetVisibleRange(length_limit, buffer_padding) 128 | let current_buffer_id = s:last_current_buffer_id 129 | 130 | if current_buffer_id == -1 131 | return [-1, -1] 132 | endif 133 | 134 | let current_buffer_id_i = index(s:buffer_ids, current_buffer_id) 135 | 136 | let current_buffer = s:buffers[current_buffer_id] 137 | let capacity = a:length_limit - current_buffer.length - a:buffer_padding 138 | let left_i = current_buffer_id_i 139 | let right_i = current_buffer_id_i 140 | 141 | for left_i in range(current_buffer_id_i - 1, 0, -1) 142 | let buffer = s:buffers[s:buffer_ids[left_i]] 143 | if (buffer.length + a:buffer_padding) <= capacity 144 | let capacity = capacity - buffer.length - a:buffer_padding 145 | else 146 | let left_i = left_i + 1 147 | break 148 | endif 149 | endfor 150 | 151 | for right_i in range(current_buffer_id_i + 1, len(s:buffers) - 1) 152 | let buffer = s:buffers[s:buffer_ids[right_i]] 153 | if (buffer.length + a:buffer_padding) <= capacity 154 | let capacity = capacity - buffer.length - a:buffer_padding 155 | else 156 | let right_i = right_i - 1 157 | break 158 | endif 159 | endfor 160 | 161 | return [left_i, right_i] 162 | endfunction 163 | 164 | function! s:GetBufferElements(capacity, buffer_padding) 165 | let [left_i, right_i] = s:GetVisibleRange(a:capacity, a:buffer_padding) 166 | " TODO: evaluate if calling this ^ twice will get better visuals 167 | 168 | if left_i < 0 || right_i < 0 169 | return [] 170 | endif 171 | 172 | let buffer_elems = [] 173 | 174 | let trunced_left = left_i 175 | if trunced_left 176 | let left_trunc_elem = {} 177 | let left_trunc_elem.type = "LeftTrunc" 178 | let left_trunc_elem.value = g:buffet_left_trunc_icon . " " . trunced_left 179 | call add(buffer_elems, left_trunc_elem) 180 | endif 181 | 182 | for i in range(left_i, right_i) 183 | let buffer_id = s:buffer_ids[i] 184 | let buffer = s:buffers[buffer_id] 185 | 186 | if buffer_id == winbufnr(0) 187 | let type_prefix = "Current" 188 | elseif bufwinnr(buffer_id) > 0 189 | let type_prefix = "Active" 190 | else 191 | let type_prefix = "" 192 | endif 193 | 194 | let elem = {} 195 | let elem.index = i + 1 196 | let elem.value = buffer.name 197 | let elem.buffer_id = buffer_id 198 | let elem.is_modified = getbufvar(buffer_id, '&mod') 199 | 200 | if elem.is_modified 201 | let type_prefix = "Mod" . type_prefix 202 | endif 203 | 204 | let elem.type = type_prefix . "Buffer" 205 | 206 | call add(buffer_elems, elem) 207 | endfor 208 | 209 | let trunced_right = (len(s:buffers) - right_i - 1) 210 | if trunced_right > 0 211 | let right_trunc_elem = {} 212 | let right_trunc_elem.type = "RightTrunc" 213 | let right_trunc_elem.value = trunced_right . " " . g:buffet_right_trunc_icon 214 | call add(buffer_elems, right_trunc_elem) 215 | endif 216 | 217 | return buffer_elems 218 | endfunction 219 | 220 | function! s:GetAllElements(capacity, buffer_padding) 221 | let last_tab_id = tabpagenr('$') 222 | let current_tab_id = tabpagenr() 223 | let buffer_elems = s:GetBufferElements(a:capacity, a:buffer_padding) 224 | let tab_elems = [] 225 | 226 | for tab_id in range(1, last_tab_id) 227 | let elem = {} 228 | let elem.value = tab_id 229 | let elem.type = "Tab" 230 | call add(tab_elems, elem) 231 | 232 | if tab_id == current_tab_id 233 | let tab_elems = tab_elems + buffer_elems 234 | endif 235 | endfor 236 | 237 | let end_elem = {"type": "End", "value": ""} 238 | call add(tab_elems, end_elem) 239 | 240 | return tab_elems 241 | endfunction 242 | 243 | function! s:IsBufferElement(element) 244 | if index(g:buffet_buffer_types, a:element.type) >= 0 245 | return 1 246 | endif 247 | 248 | return 0 249 | endfunction 250 | 251 | function! s:Len(string) 252 | let visible_singles = substitute(a:string, '[^\d0-\d127]', "-", "g") 253 | 254 | return len(visible_singles) 255 | endfunction 256 | 257 | function! s:GetTypeHighlight(type) 258 | return "%#" . g:buffet_prefix . a:type . "#" 259 | endfunction 260 | 261 | function! s:Render() 262 | let sep_len = s:Len(g:buffet_separator) 263 | 264 | let tabs_count = tabpagenr("$") 265 | let tabs_len = (1 + s:Len(g:buffet_tab_icon) + 1 + sep_len) * tabs_count 266 | 267 | let left_trunc_len = 1 + s:Len(g:buffet_left_trunc_icon) + 1 + 2 + 1 + sep_len 268 | let right_trunc_len = 1 + 2 + 1 + s:Len(g:buffet_right_trunc_icon) + 1 + sep_len 269 | let trunc_len = left_trunc_len + right_trunc_len 270 | 271 | let capacity = &columns - tabs_len - trunc_len - 5 272 | let buffer_padding = 1 + (g:buffet_use_devicons ? 1+1 : 0) + 1 + sep_len 273 | 274 | let elements = s:GetAllElements(capacity, buffer_padding) 275 | 276 | let render = "" 277 | for i in range(0, len(elements) - 2) 278 | let left = elements[i] 279 | let elem = left 280 | let right = elements[i + 1] 281 | 282 | if elem.type == "Tab" 283 | let render = render . "%" . elem.value . "T" 284 | elseif s:IsBufferElement(elem) && has("nvim") 285 | let render = render . "%" . elem.buffer_id . "@SwitchToBuffer@" 286 | endif 287 | 288 | let highlight = s:GetTypeHighlight(elem.type) 289 | let render = render . highlight 290 | 291 | if g:buffet_show_index && s:IsBufferElement(elem) 292 | let render = render . " " . elem.index 293 | endif 294 | 295 | let icon = "" 296 | if g:buffet_use_devicons && s:IsBufferElement(elem) 297 | let icon = " " . WebDevIconsGetFileTypeSymbol(elem.value) 298 | elseif elem.type == "Tab" 299 | let icon = " " . g:buffet_tab_icon 300 | endif 301 | 302 | let render = render . icon 303 | 304 | if elem.type != "Tab" 305 | let render = render . " " . elem.value 306 | endif 307 | 308 | if s:IsBufferElement(elem) 309 | if elem.is_modified && g:buffet_modified_icon != "" 310 | let render = render . g:buffet_modified_icon 311 | endif 312 | endif 313 | 314 | let render = render . " " 315 | 316 | let separator = g:buffet_has_separator[left.type][right.type] 317 | let separator_hi = s:GetTypeHighlight(left.type . right.type) 318 | let render = render . separator_hi . separator 319 | 320 | if elem.type == "Tab" && has("nvim") 321 | let render = render . "%T" 322 | elseif s:IsBufferElement(elem) && has("nvim") 323 | let render = render . "%T" 324 | endif 325 | endfor 326 | 327 | if !has("nvim") 328 | let render = render . "%T" 329 | endif 330 | 331 | let render = render . s:GetTypeHighlight("Buffer") 332 | 333 | return render 334 | endfunction 335 | 336 | function! buffet#render() 337 | call buffet#update() 338 | return s:Render() 339 | endfunction 340 | 341 | function! s:GetBuffer(buffer) 342 | if empty(a:buffer) && s:last_current_buffer_id >= 0 343 | let btarget = s:last_current_buffer_id 344 | elseif a:buffer =~ '^\d\+$' 345 | let btarget = bufnr(str2nr(a:buffer)) 346 | else 347 | let btarget = bufnr(a:buffer) 348 | endif 349 | 350 | return btarget 351 | endfunction 352 | 353 | function! buffet#bswitch(index) 354 | let i = str2nr(a:index) - 1 355 | if i < 0 || i > len(s:buffer_ids) - 1 356 | echohl ErrorMsg 357 | echom "Invalid buffer index" 358 | echohl None 359 | return 360 | endif 361 | let buffer_id = s:buffer_ids[i] 362 | execute 'silent buffer ' . buffer_id 363 | endfunction 364 | 365 | " inspired and based on https://vim.fandom.com/wiki/Deleting_a_buffer_without_closing_the_window 366 | function! buffet#bwipe(bang, buffer) 367 | let btarget = s:GetBuffer(a:buffer) 368 | 369 | let filters = get(g:, "buffet_bwipe_filters", []) 370 | if type(filters) == type([]) 371 | for f in filters 372 | if function(f)(a:bang, btarget) > 0 373 | return 374 | endif 375 | endfor 376 | endif 377 | 378 | if btarget < 0 379 | echohl ErrorMsg 380 | call 'No matching buffer for ' . a:buffer 381 | echohl None 382 | 383 | return 384 | endif 385 | 386 | if empty(a:bang) && getbufvar(btarget, '&modified') 387 | echohl ErrorMsg 388 | echom 'No write since last change for buffer ' . btarget . " (add ! to override)" 389 | echohl None 390 | return 391 | endif 392 | 393 | " IDs of windows that view target buffer which we will delete. 394 | let wnums = filter(range(1, winnr('$')), 'winbufnr(v:val) == btarget') 395 | 396 | let wcurrent = winnr() 397 | for w in wnums 398 | " switch to window with ID 'w' 399 | execute 'silent ' . w . 'wincmd w' 400 | 401 | let prevbuf = bufnr('#') 402 | " if the previous buffer is another listed buffer, switch to it... 403 | if prevbuf > 0 && buflisted(prevbuf) && prevbuf != btarget 404 | buffer # 405 | " ...otherwise just go to the previous buffer in the list. 406 | else 407 | bprevious 408 | endif 409 | 410 | " if the 'bprevious' did not work, then just open a new buffer 411 | if btarget == bufnr("%") 412 | execute 'silent enew' . a:bang 413 | endif 414 | endfor 415 | 416 | " finally wipe the tarbet buffer 417 | execute 'silent bwipe' . a:bang . " " . btarget 418 | " switch back to original window 419 | execute 'silent ' . wcurrent . 'wincmd w' 420 | endfunction 421 | 422 | function! buffet#bonly(bang, buffer) 423 | let btarget = s:GetBuffer(a:buffer) 424 | 425 | for b in s:buffer_ids 426 | if b == btarget 427 | continue 428 | endif 429 | 430 | call buffet#bwipe(a:bang, b) 431 | endfor 432 | endfunction 433 | --------------------------------------------------------------------------------