VIM: how to show folder name in tab, but only if two files have the same name

In GVIM, you can customize the tab labels with the 'guitablabel' option.

In terminal Vim; there's no 'guitablabel' equivalent; one has to render the entire 'tabline'. Fortunately, the Vim help has an example which delegates the label rendering to a separate function, so re-using your custom function is pretty easy.

The help pages for the mentioned options link to examples; you probably have to use fnamemodify() to canonicalize all buffers' paths to full absolute paths, find the common base directory, and then strip that off the paths.

On the other hand, if it's okay for you to :cd to the base directory, you'll get that kind of tab label pretty much out-of-the-box.


Here's my solution that makes the tabname the directory---which is usually a good proxy for the project that tab is meant to represent. This solution can be modified to show the filename if there is only one buffer (modification shown below).

This solution draws a tiny bit from Jerome's. I'm not doing anything as complex as they are, so mine is 5x shorter.

Also, this solution places the tab number alongside the name, making it easy to bounce around, meaning the tabs will look like this: 1:log 2:doc 3:vimfiles and 2gt will move to the second tab.

set tabline=%!TabLine()

function! TabLine()
    let line = ''
    for i in range(tabpagenr('$'))
        let line .= (i+1 == tabpagenr()) ? '%#TabLineSel#' : '%#TabLine#'
        let line .= '%' . (i + 1) . 'T'
        let line .= TabLabel(i + 1) . ' '
    endfor
    let line .= '%#TabLineFill#%T'
    return line
endfunction

function! TabLabel(n)
    " Return list of buffer numbers for each window pane open in tab.
    let panelist = tabpagebuflist(a:n)
    " See :help setting-tabline then search MyTabLabel if you want to
    " use use the active window. I use the topmost pane, which let's
    " me rename the tab just by putting a window from a different
    " directory in the first position.
    let filepath = bufname(panelist[0])
    let dirname = fnamemodify(filepath, ':p:h:t')
    return a:n . ':' . dirname
endfunction

The modification to show the filename if only one buffer is visible:

function! TabLabel(n)
    " Return list of buffer numbers for each window pane open in tab.
    let panelist = tabpagebuflist(a:n)
    " See :help setting-tabline then search MyTabLabel if you want to
    " use use the active window. I use the topmost pane, which let's
    " me rename the tab just by putting a window from a different
    " directory in the first position.
    let filepath = bufname(panelist[0])
    let dirname = fnamemodify(filepath, ':p:h:t')
    let filename = fnamemodify(filepath, ':t')
    let tabname = len(panelist) > 1 ? dirname : filename
    return a:n . ':' . tabname
endfunction

Assuming the following files:

z.txt
a/b/c/d.txt
a/b/f/d.txt

My current setup will make the tabline look like so (I reversed-engineered the behavior from Sublime Text 2):

z.txt | d.txt - c | d.txt - f

My code has a lot of extras like treating Nerdtree/FZF tabs specially, and naming tabs according to the left-most buffer when there are splits. You can remove these extras yourself if you don't want them, or change anything you don't like. I also assumed Unix only, and terminal VIM only (GVIM would need minor tweaking I guess).

I am providing the code below without guarantee, as a starting point for you to customize according to your needs.

set tabline=%!GetTabLine()

function! GetTabLine()
  let tabs = BuildTabs()
  let line = ''
  for i in range(len(tabs))
    let line .= (i+1 == tabpagenr()) ? '%#TabLineSel#' : '%#TabLine#'
    let line .= '%' . (i + 1) . 'T'
    let line .= ' ' . tabs[i].uniq_name . ' '
  endfor
  let line .= '%#TabLineFill#%T'
  return line
endfunction

function! BuildTabs()
  let tabs = []
  for i in range(tabpagenr('$'))
    let tabnum = i + 1
    let buflist = tabpagebuflist(tabnum)
    let file_path = ''
    let tab_name = bufname(buflist[0])
    if tab_name =~ 'NERD_tree' && len(buflist) > 1
      let tab_name = bufname(buflist[1])
    end
    let is_custom_name = 0
    if tab_name == ''
      let tab_name = '[No Name]'
      let is_custom_name = 1
    elseif tab_name =~ 'fzf'
      let tab_name = 'FZF'
      let is_custom_name = 1
    else
      let file_path = fnamemodify(tab_name, ':p')
      let tab_name = fnamemodify(tab_name, ':p:t')
    end
    let tab = {
      \ 'name': tab_name,
      \ 'uniq_name': tab_name,
      \ 'file_path': file_path,
      \ 'is_custom_name': is_custom_name
      \ }
    call add(tabs, tab)
  endfor
  call CalculateTabUniqueNames(tabs)
  return tabs
endfunction

function! CalculateTabUniqueNames(tabs)
  for tab in a:tabs
    if tab.is_custom_name | continue | endif
    let tab_common_path = ''
    for other_tab in a:tabs
      if tab.name != other_tab.name || tab.file_path == other_tab.file_path
        \ || other_tab.is_custom_name
        continue
      endif
      let common_path = GetCommonPath(tab.file_path, other_tab.file_path)
      if tab_common_path == '' || len(common_path) < len(tab_common_path)
        let tab_common_path = common_path
      endif
    endfor
    if tab_common_path == '' | continue | endif
    let common_path_has_immediate_child = 0
    for other_tab in a:tabs
      if tab.name == other_tab.name && !other_tab.is_custom_name
        \ && tab_common_path == fnamemodify(other_tab.file_path, ':h')
        let common_path_has_immediate_child = 1
        break
      endif
    endfor
    if common_path_has_immediate_child
      let tab_common_path = fnamemodify(common_path, ':h')
    endif
    let path = tab.file_path[len(tab_common_path)+1:-1]
    let path = fnamemodify(path, ':~:.:h')
    let dirs = split(path, '/', 1)
    if len(dirs) >= 5
      let path = dirs[0] . '/.../' . dirs[-1]
    endif
    let tab.uniq_name = tab.name . ' - ' . path
  endfor
endfunction

function! GetCommonPath(path1, path2)
  let dirs1 = split(a:path1, '/', 1)
  let dirs2 = split(a:path2, '/', 1)
  let i_different = 0
  for i in range(len(dirs1))
    if get(dirs1, i) != get(dirs2, i)
      let i_different = i
      break
    endif
  endfor
  return join(dirs1[0:i_different-1], '/')
endfunction

As Ingo suggests you can use guitablabel. On my installation its only configured to show the file name (:echo &guitablabel reports %M%t). To set this to show the relative path do :set guitablabel=%M%f. Like Ingo says, use :cd DIRECTORY to set the home directory, and :pwd to see where its currently set.

See :help statusline for (many) more formatting options.

Tags:

Vim