" Vim global plugin for rendering the buffer list in the tabline " Licence: The MIT License (MIT) " Commit: $Format:%H$ " {{{ Copyright (c) 2015 Aristotle Pagaltzis " " Permission is hereby granted, free of charge, to any person obtaining a copy " of this software and associated documentation files (the "Software"), to deal " in the Software without restriction, including without limitation the rights " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell " copies of the Software, and to permit persons to whom the Software is " furnished to do so, subject to the following conditions: " " The above copyright notice and this permission notice shall be included in " all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN " THE SOFTWARE. " }}} if v:version < 700 echoerr printf('Vim 7 is required for buftabline (this is only %d.%d)',v:version/100,v:version%100) finish endif scriptencoding utf-8 hi default link BufTabLineCurrent TabLineSel hi default link BufTabLineActive PmenuSel hi default link BufTabLineHidden TabLine hi default link BufTabLineFill TabLineFill hi default link BufTabLineModifiedCurrent BufTabLineCurrent hi default link BufTabLineModifiedActive BufTabLineActive hi default link BufTabLineModifiedHidden BufTabLineHidden let g:buftabline_numbers = get(g:, 'buftabline_numbers', 0) let g:buftabline_indicators = get(g:, 'buftabline_indicators', 0) let g:buftabline_separators = get(g:, 'buftabline_separators', 0) let g:buftabline_show = get(g:, 'buftabline_show', 2) let g:buftabline_plug_max = get(g:, 'buftabline_plug_max', 10) function! buftabline#user_buffers() " help buffers are always unlisted, but quickfix buffers are not return filter(range(1,bufnr('$')),'buflisted(v:val) && "quickfix" !=? getbufvar(v:val, "&buftype")') endfunction function! s:switch_buffer(bufnum, clicks, button, mod) execute 'buffer' a:bufnum endfunction function s:SID() return matchstr(expand(''), '\d\+_') endfunction let s:dirsep = fnamemodify(getcwd(),':p')[-1:] let s:centerbuf = winbufnr(0) let s:tablineat = has('tablineat') let s:sid = s:SID() | delfunction s:SID function! buftabline#render() let show_num = g:buftabline_numbers == 1 let show_ord = g:buftabline_numbers == 2 let show_mod = g:buftabline_indicators let lpad = g:buftabline_separators ? nr2char(0x23B8) : ' ' let bufnums = buftabline#user_buffers() let centerbuf = s:centerbuf " prevent tabline jumping around when non-user buffer current (e.g. help) " pick up data on all the buffers let tabs = [] let path_tabs = [] let tabs_per_tail = {} let currentbuf = winbufnr(0) let screen_num = 0 for bufnum in bufnums let screen_num = show_num ? bufnum : show_ord ? screen_num + 1 : '' let tab = { 'num': bufnum, 'pre': '' } let tab.hilite = currentbuf == bufnum ? 'Current' : bufwinnr(bufnum) > 0 ? 'Active' : 'Hidden' if currentbuf == bufnum | let [centerbuf, s:centerbuf] = [bufnum, bufnum] | endif let bufpath = bufname(bufnum) if strlen(bufpath) let tab.path = fnamemodify(bufpath, ':p:~:.') let tab.sep = strridx(tab.path, s:dirsep, strlen(tab.path) - 2) " keep trailing dirsep let tab.label = tab.path[tab.sep + 1:] let pre = screen_num if getbufvar(bufnum, '&mod') let tab.hilite = 'Modified' . tab.hilite if show_mod | let pre = '+' . pre | endif endif if strlen(pre) | let tab.pre = pre . ' ' | endif let tabs_per_tail[tab.label] = get(tabs_per_tail, tab.label, 0) + 1 let path_tabs += [tab] elseif -1 < index(['nofile','acwrite'], getbufvar(bufnum, '&buftype')) " scratch buffer let tab.label = ( show_mod ? '!' . screen_num : screen_num ? screen_num . ' !' : '!' ) else " unnamed file let tab.label = ( show_mod && getbufvar(bufnum, '&mod') ? '+' : '' ) \ . ( screen_num ? screen_num : '*' ) endif let tabs += [tab] endfor " disambiguate same-basename files by adding trailing path segments while len(filter(tabs_per_tail, 'v:val > 1')) let [ambiguous, tabs_per_tail] = [tabs_per_tail, {}] for tab in path_tabs if -1 < tab.sep && has_key(ambiguous, tab.label) let tab.sep = strridx(tab.path, s:dirsep, tab.sep - 1) let tab.label = tab.path[tab.sep + 1:] endif let tabs_per_tail[tab.label] = get(tabs_per_tail, tab.label, 0) + 1 endfor endwhile " now keep the current buffer center-screen as much as possible: " 1. setup let lft = { 'lasttab': 0, 'cut': '.', 'indicator': '<', 'width': 0, 'half': &columns / 2 } let rgt = { 'lasttab': -1, 'cut': '.$', 'indicator': '>', 'width': 0, 'half': &columns - lft.half } " 2. sum the string lengths for the left and right halves let currentside = lft let lpad_width = strwidth(lpad) for tab in tabs let tab.width = lpad_width + strwidth(tab.pre) + strwidth(tab.label) + 1 let tab.label = lpad . tab.pre . substitute(strtrans(tab.label), '%', '%%', 'g') . ' ' if centerbuf == tab.num let halfwidth = tab.width / 2 let lft.width += halfwidth let rgt.width += tab.width - halfwidth let currentside = rgt continue endif let currentside.width += tab.width endfor if currentside is lft " centered buffer not seen? " then blame any overflow on the right side, to protect the left let [lft.width, rgt.width] = [0, lft.width] endif " 3. toss away tabs and pieces until all fits: if ( lft.width + rgt.width ) > &columns let oversized \ = lft.width < lft.half ? [ [ rgt, &columns - lft.width ] ] \ : rgt.width < rgt.half ? [ [ lft, &columns - rgt.width ] ] \ : [ [ lft, lft.half ], [ rgt, rgt.half ] ] for [side, budget] in oversized let delta = side.width - budget " toss entire tabs to close the distance while delta >= tabs[side.lasttab].width let delta -= remove(tabs, side.lasttab).width endwhile " then snip at the last one to make it fit let endtab = tabs[side.lasttab] while delta > ( endtab.width - strwidth(strtrans(endtab.label)) ) let endtab.label = substitute(endtab.label, side.cut, '', '') endwhile let endtab.label = substitute(endtab.label, side.cut, side.indicator, '') endfor endif if len(tabs) | let tabs[0].label = substitute(tabs[0].label, lpad, ' ', '') | endif let swallowclicks = '%'.(1 + tabpagenr('$')).'X' return s:tablineat \ ? join(map(tabs,'"%#BufTabLine".v:val.hilite."#" . "%".v:val.num."@'.s:sid.'switch_buffer@" . strtrans(v:val.label)'),'') . '%#BufTabLineFill#' . swallowclicks \ : swallowclicks . join(map(tabs,'"%#BufTabLine".v:val.hilite."#" . strtrans(v:val.label)'),'') . '%#BufTabLineFill#' endfunction function! buftabline#update(zombie) set tabline= " silent! is used here because neovim lacks guioptions and v0.11.0 broke backcompat " by making setting it an E519 unsupported error instead of the previous no-op " N.B. :set processes options in order and will abort on error even under silent! if tabpagenr('$') > 1 | silent! set showtabline=2 guioptions+=e | return | endif silent! set guioptions-=e if 0 == g:buftabline_show set showtabline=1 return elseif 1 == g:buftabline_show " account for BufDelete triggering before buffer is actually deleted let bufnums = filter(buftabline#user_buffers(), 'v:val != a:zombie') let &g:showtabline = 1 + ( len(bufnums) > 1 ) elseif 2 == g:buftabline_show set showtabline=2 endif set tabline=%!buftabline#render() endfunction augroup BufTabLine autocmd! autocmd VimEnter * call buftabline#update(0) autocmd TabEnter * call buftabline#update(0) autocmd BufAdd * call buftabline#update(0) autocmd FileType qf call buftabline#update(0) autocmd BufDelete * call buftabline#update(str2nr(expand(''))) augroup END for s:n in range(1, g:buftabline_plug_max) + ( g:buftabline_plug_max > 0 ? [-1] : [] ) let s:b = s:n == -1 ? -1 : s:n - 1 execute printf("noremap BufTabLine.Go(%d) :exe 'b'.get(buftabline#user_buffers(),%d,'')", s:n, s:b) endfor unlet! s:n s:b if v:version < 703 function s:transpile() let [ savelist, &list ] = [ &list, 0 ] redir => src silent function buftabline#render redir END let &list = savelist let src = substitute(src, '\n\zs[0-9 ]*', '', 'g') let src = substitute(src, 'strwidth(strtrans(\([^)]\+\)))', 'strlen(substitute(\1, ''\p\|\(.\)'', ''x\1'', ''g''))', 'g') return src endfunction exe "delfunction buftabline#render\n" . s:transpile() delfunction s:transpile endif