buftabline.vim (8659B)
1 " Vim global plugin for rendering the buffer list in the tabline 2 " Licence: The MIT License (MIT) 3 " Commit: $Format:%H$ 4 " {{{ Copyright (c) 2015 Aristotle Pagaltzis <pagaltzis@gmx.de> 5 " 6 " Permission is hereby granted, free of charge, to any person obtaining a copy 7 " of this software and associated documentation files (the "Software"), to deal 8 " in the Software without restriction, including without limitation the rights 9 " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 " copies of the Software, and to permit persons to whom the Software is 11 " furnished to do so, subject to the following conditions: 12 " 13 " The above copyright notice and this permission notice shall be included in 14 " all copies or substantial portions of the Software. 15 " 16 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 " THE SOFTWARE. 23 " }}} 24 25 if v:version < 700 26 echoerr printf('Vim 7 is required for buftabline (this is only %d.%d)',v:version/100,v:version%100) 27 finish 28 endif 29 30 scriptencoding utf-8 31 32 hi default link BufTabLineCurrent TabLineSel 33 hi default link BufTabLineActive PmenuSel 34 hi default link BufTabLineHidden TabLine 35 hi default link BufTabLineFill TabLineFill 36 hi default link BufTabLineModifiedCurrent BufTabLineCurrent 37 hi default link BufTabLineModifiedActive BufTabLineActive 38 hi default link BufTabLineModifiedHidden BufTabLineHidden 39 40 let g:buftabline_numbers = get(g:, 'buftabline_numbers', 0) 41 let g:buftabline_indicators = get(g:, 'buftabline_indicators', 0) 42 let g:buftabline_separators = get(g:, 'buftabline_separators', 0) 43 let g:buftabline_show = get(g:, 'buftabline_show', 1) 44 let g:buftabline_plug_max = get(g:, 'buftabline_plug_max', 10) 45 46 function! buftabline#user_buffers() " help buffers are always unlisted, but quickfix buffers are not 47 return filter(range(1,bufnr('$')),'buflisted(v:val) && "quickfix" !=? getbufvar(v:val, "&buftype")') 48 endfunction 49 50 function! s:switch_buffer(bufnum, clicks, button, mod) 51 execute 'buffer' a:bufnum 52 endfunction 53 54 function s:SID() 55 return matchstr(expand('<sfile>'), '<SNR>\d\+_') 56 endfunction 57 58 let s:dirsep = fnamemodify(getcwd(),':p')[-1:] 59 let s:centerbuf = winbufnr(0) 60 let s:tablineat = has('tablineat') 61 let s:sid = s:SID() | delfunction s:SID 62 function! buftabline#render() 63 let show_num = g:buftabline_numbers == 1 64 let show_ord = g:buftabline_numbers == 2 65 let show_mod = g:buftabline_indicators 66 let lpad = g:buftabline_separators ? nr2char(0x23B8) : ' ' 67 68 let bufnums = buftabline#user_buffers() 69 let centerbuf = s:centerbuf " prevent tabline jumping around when non-user buffer current (e.g. help) 70 71 " pick up data on all the buffers 72 let tabs = [] 73 let path_tabs = [] 74 let tabs_per_tail = {} 75 let currentbuf = winbufnr(0) 76 let screen_num = 0 77 for bufnum in bufnums 78 let screen_num = show_num ? bufnum : show_ord ? screen_num + 1 : '' 79 let tab = { 'num': bufnum, 'pre': '' } 80 let tab.hilite = currentbuf == bufnum ? 'Current' : bufwinnr(bufnum) > 0 ? 'Active' : 'Hidden' 81 if currentbuf == bufnum | let [centerbuf, s:centerbuf] = [bufnum, bufnum] | endif 82 let bufpath = bufname(bufnum) 83 if strlen(bufpath) 84 let tab.path = fnamemodify(bufpath, ':p:~:.') 85 let tab.sep = strridx(tab.path, s:dirsep, strlen(tab.path) - 2) " keep trailing dirsep 86 let tab.label = tab.path[tab.sep + 1:] 87 let pre = screen_num 88 if getbufvar(bufnum, '&mod') 89 let tab.hilite = 'Modified' . tab.hilite 90 if show_mod | let pre = '+' . pre | endif 91 endif 92 if strlen(pre) | let tab.pre = pre . ' ' | endif 93 let tabs_per_tail[tab.label] = get(tabs_per_tail, tab.label, 0) + 1 94 let path_tabs += [tab] 95 elseif -1 < index(['nofile','acwrite'], getbufvar(bufnum, '&buftype')) " scratch buffer 96 let tab.label = ( show_mod ? '!' . screen_num : screen_num ? screen_num . ' !' : '!' ) 97 else " unnamed file 98 let tab.label = ( show_mod && getbufvar(bufnum, '&mod') ? '+' : '' ) 99 \ . ( screen_num ? screen_num : '*' ) 100 endif 101 let tabs += [tab] 102 endfor 103 104 " disambiguate same-basename files by adding trailing path segments 105 while len(filter(tabs_per_tail, 'v:val > 1')) 106 let [ambiguous, tabs_per_tail] = [tabs_per_tail, {}] 107 for tab in path_tabs 108 if -1 < tab.sep && has_key(ambiguous, tab.label) 109 let tab.sep = strridx(tab.path, s:dirsep, tab.sep - 1) 110 let tab.label = tab.path[tab.sep + 1:] 111 endif 112 let tabs_per_tail[tab.label] = get(tabs_per_tail, tab.label, 0) + 1 113 endfor 114 endwhile 115 116 " now keep the current buffer center-screen as much as possible: 117 118 " 1. setup 119 let lft = { 'lasttab': 0, 'cut': '.', 'indicator': '<', 'width': 0, 'half': &columns / 2 } 120 let rgt = { 'lasttab': -1, 'cut': '.$', 'indicator': '>', 'width': 0, 'half': &columns - lft.half } 121 122 " 2. sum the string lengths for the left and right halves 123 let currentside = lft 124 let lpad_width = strwidth(lpad) 125 for tab in tabs 126 let tab.width = lpad_width + strwidth(tab.pre) + strwidth(tab.label) + 1 127 let tab.label = lpad . tab.pre . substitute(strtrans(tab.label), '%', '%%', 'g') . ' ' 128 if centerbuf == tab.num 129 let halfwidth = tab.width / 2 130 let lft.width += halfwidth 131 let rgt.width += tab.width - halfwidth 132 let currentside = rgt 133 continue 134 endif 135 let currentside.width += tab.width 136 endfor 137 if currentside is lft " centered buffer not seen? 138 " then blame any overflow on the right side, to protect the left 139 let [lft.width, rgt.width] = [0, lft.width] 140 endif 141 142 " 3. toss away tabs and pieces until all fits: 143 if ( lft.width + rgt.width ) > &columns 144 let oversized 145 \ = lft.width < lft.half ? [ [ rgt, &columns - lft.width ] ] 146 \ : rgt.width < rgt.half ? [ [ lft, &columns - rgt.width ] ] 147 \ : [ [ lft, lft.half ], [ rgt, rgt.half ] ] 148 for [side, budget] in oversized 149 let delta = side.width - budget 150 " toss entire tabs to close the distance 151 while delta >= tabs[side.lasttab].width 152 let delta -= remove(tabs, side.lasttab).width 153 endwhile 154 " then snip at the last one to make it fit 155 let endtab = tabs[side.lasttab] 156 while delta > ( endtab.width - strwidth(strtrans(endtab.label)) ) 157 let endtab.label = substitute(endtab.label, side.cut, '', '') 158 endwhile 159 let endtab.label = substitute(endtab.label, side.cut, side.indicator, '') 160 endfor 161 endif 162 163 if len(tabs) | let tabs[0].label = substitute(tabs[0].label, lpad, ' ', '') | endif 164 165 let swallowclicks = '%'.(1 + tabpagenr('$')).'X' 166 return s:tablineat 167 \ ? join(map(tabs,'"%#BufTabLine".v:val.hilite."#" . "%".v:val.num."@'.s:sid.'switch_buffer@" . strtrans(v:val.label)'),'') . '%#BufTabLineFill#' . swallowclicks 168 \ : swallowclicks . join(map(tabs,'"%#BufTabLine".v:val.hilite."#" . strtrans(v:val.label)'),'') . '%#BufTabLineFill#' 169 endfunction 170 171 function! buftabline#update(zombie) 172 set tabline= 173 if tabpagenr('$') > 1 | set guioptions+=e showtabline=2 | return | endif 174 set guioptions-=e 175 if 0 == g:buftabline_show 176 set showtabline=1 177 return 178 elseif 1 == g:buftabline_show 179 " account for BufDelete triggering before buffer is actually deleted 180 let bufnums = filter(buftabline#user_buffers(), 'v:val != a:zombie') 181 let &g:showtabline = 1 + ( len(bufnums) > 1 ) 182 elseif 2 == g:buftabline_show 183 set showtabline=2 184 endif 185 set tabline=%!buftabline#render() 186 endfunction 187 188 augroup BufTabLine 189 autocmd! 190 autocmd VimEnter * call buftabline#update(0) 191 autocmd TabEnter * call buftabline#update(0) 192 autocmd BufAdd * call buftabline#update(0) 193 autocmd FileType qf call buftabline#update(0) 194 autocmd BufDelete * call buftabline#update(str2nr(expand('<abuf>'))) 195 augroup END 196 197 for s:n in range(1, g:buftabline_plug_max) + ( g:buftabline_plug_max > 0 ? [-1] : [] ) 198 let s:b = s:n == -1 ? -1 : s:n - 1 199 execute printf("noremap <silent> <Plug>BufTabLine.Go(%d) :<C-U>exe 'b'.get(buftabline#user_buffers(),%d,'')<cr>", s:n, s:b) 200 endfor 201 unlet! s:n s:b 202 203 if v:version < 703 204 function s:transpile() 205 let [ savelist, &list ] = [ &list, 0 ] 206 redir => src 207 silent function buftabline#render 208 redir END 209 let &list = savelist 210 let src = substitute(src, '\n\zs[0-9 ]*', '', 'g') 211 let src = substitute(src, 'strwidth(strtrans(\([^)]\+\)))', 'strlen(substitute(\1, ''\p\|\(.\)'', ''x\1'', ''g''))', 'g') 212 return src 213 endfunction 214 exe "delfunction buftabline#render\n" . s:transpile() 215 delfunction s:transpile 216 endif