Unambiguous, switchable tab page labels for Vim/Neovim.
Vim's tab line shows only the filename tail, so tabs holding a/index.ts
and b/index.ts look identical. tablabel disambiguates them the way Emacs
uniquify and VSCode do: only colliding labels grow, by just enough
trailing directories to tell them apart — and shrink back automatically when
the conflict disappears.
tail: index.ts | index.ts | main.go ← which index.ts is which?
unique: a/index.ts | b/index.ts | main.go ← default
" vim-plug
Plug 'kis9a/tablabel'Zero configuration needed: the plugin sets 'tabline' / 'guitablabel' and
duplicate names are disambiguated automatically. Vim 8.0+ / Neovim 0.6+, no
external dependencies, no autocmds, no timers.
If you already have a hand-rolled tabline, note that installing replaces it
(plugin files load after the vimrc); set g:tablabel_set_tabline = 0 to keep
yours and feed it with tablabel#labels() instead.
One command only:
| Command | Description |
|---|---|
:TabLabel |
Cycle through g:tablabel_formats. |
:TabLabel {format} |
Switch to a format directly (with completion). |
:TabLabel! |
Toggle abbreviated directories (src/components/index.ts → s/c/index.ts). |
The active format is echoed after each invocation.
For a buffer at src/components/index.ts:
| Format | Label | |
|---|---|---|
tail |
index.ts |
|
dir |
components/index.ts |
always parent + tail |
unique |
index.ts, growing only on collision |
default |
relative |
src/components/index.ts |
relative to :pwd (outside it: ~-relative) |
Any format can be combined with g:tablabel_shorten to abbreviate directory
segments (s/c/index.ts). Abbreviation never merges labels the format kept
distinct: src/x.ts vs spec/x.ts widens to sr/x.ts / sp/x.ts instead
of colliding at s/x.ts.
| Function | Description |
|---|---|
tablabel#label(tabnr) |
Label for one tab, modified flag included. Plugs directly into g:tabset_label_func and lightline's tab_component_function. |
tablabel#match_label(tabnr) |
Bare label for one tab (no modified flag) — for pattern-matching consumers like g:tabset_match_label_func. |
tablabel#labels() |
Bare labels for all tabs (for custom tabline renderers). |
tablabel#modified(tabnr) |
Modified state of a tab's active buffer. |
tablabel#tabline() |
Complete 'tabline' string (click regions + highlights). |
tablabel#guitablabel() |
Label for 'guitablabel' (GUI Vim). |
| Option | Default | Description |
|---|---|---|
g:tablabel_enabled |
1 |
0 disables the command and falls back to tail labels. |
g:tablabel_format |
'unique' |
Active format. |
g:tablabel_formats |
['unique', 'dir', 'relative', 'tail'] |
:TabLabel cycle order. |
g:tablabel_shorten |
0 |
Abbreviate directory segments to their first N characters. |
g:tablabel_max_width |
0 |
Max display width per label (0 = unlimited). |
g:tablabel_modified_flag |
'+' |
Marker appended to modified buffers. |
g:tablabel_noname |
'[No Name]' |
Label for unnamed buffers. |
g:tablabel_set_tabline |
1 |
0 keeps your own 'tabline'. |
g:tablabel_set_guitablabel |
1 |
0 keeps your own 'guitablabel'. |
g:tablabel_format_func |
unset | Presentation callback receiving precomputed parts ({'tail':, 'extra':, ...}); the disambiguation algorithm stays inside the plugin. |
" Match a hand-rolled '!'-style modified marker
let g:tablabel_modified_flag = '!'
" Abbreviated directories by default
let g:tablabel_shorten = 1nnoremap <Leader>tt :TabLabel<CR>" tabset: :TabJump / :TabSortByName match what the tabline shows
" (string form: Vim rejects Funcrefs in lowercase g: variables, :h E704)
let g:tabset_label_func = 'tablabel#label'
" keep the modified marker out of tabset's pattern matching
let g:tabset_match_label_func = 'tablabel#match_label'
" lightline
let g:tablabel_set_tabline = 0
let g:lightline = {'tab_component_function': {'filename': 'tablabel#label'}}See :help tablabel for details, including using tablabel#labels() from a
hand-rolled tabline.
MIT — see LICENSE.