I am not a big user of the tabline in Neovim, but when i occasionally reach for it, I want it to look like the user interfaces of the rest of my tools, namely a (dark) foreground color on a (light) background color with reverse video for highlight. For my Neovim setup, this means that the windows show the text in the buffers using whatever dark color my terminal has as its foreground color on whatever light color my terminal has as its background color. The same goes for the command line. The status lines, on the other hand, show reverse video.
To stay in line with this look and to make the tabline stand out from the editing window, I want my whole tabline, save for the active tab label, to be displayed in reverse video. To make the active tab stand out from the rest of the tabline, in turn, I want it to be displayed in normal video.
Furthermore, since I find that the default tab labels can be a bit messy, I want the labels to be a single number, where the label of the first tab is simply 1, the label of the second tab is 2 and so on. According to the Neovim docs this isn't easy. However, the hard part about defining my preferred way to tab pages is not in the tabline option. The provided example is clear an easy to follow. Rather, the hard part is in getting the highlights to work. Let us have a look!
But first,
here is what Neovim looks like when using an empty configuration file
and running the command
vi -p foo bar baz /usr/local/share/nvim/runtime/filetype.lua
, where vi is my alias for nvim.
I added the last file to show what
a label with a long path name
looks like in the default tabline.
First naive attempts at highlighting
Let me start with configuring the highlight groups:
highlight TabLine cterm=reverse
highlight clear TabLineSel
highlight TabLineFill cterm=reverse
The second line is not strictly necessary,
but I include it to be explicit.
It could also be set to cterm=NONE.
As far as highlighting goes,
the result is exactly what I want:
Let me then use the provided example functions from the documentation as a starting point for my desired tab labels.
highlight TabLine cterm=reverse
highlight clear TabLineSel
highlight TabLineFill cterm=reverse
set tabline=%!MyTabLine()
function MyTabLine()
let s = ''
for i in range(tabpagenr('$'))
" select the highlighting
if i + 1 == tabpagenr()
let s ..= '%#TabLineSel#'
else
let s ..= '%#TabLine#'
endif
" set the tab page number (for mouse clicks)
let s ..= '%' .. (i + 1) .. 'T'
" the label is made by MyTabLabel()
let s ..= ' %{MyTabLabel(' .. (i + 1) .. ')} '
endfor
" after the last tab page fill with TabLineFill and reset tab page nr
let s ..= '%#TabLineFill#%T'
" right-align the label to close the current tab page
if tabpagenr('$') > 1
let s ..= '%=%#TabLine#%999Xclose'
endif
return s
endfunction
function MyTabLabel(n)
let buflist = tabpagebuflist(a:n)
let winnr = tabpagewinnr(a:n)
return bufname(buflist[winnr - 1])
endfunction
Strangely, adding these functions
and setting the tabline
causes the whole tabline to become reversed:
The problem
Having experimented a bit with this,
trying out different configurations for both
cterm and gui
written in both Vimscript and
Lua,
I have come to the conclusion that
the attributes of TabLineFill
are established before the tabline,
that they will remain in effect for all elements of the tabline,
and that the they cannot be changed in the tabline.
This, however, does only apply when using a custom tabline.
When using the
default tabline
(by letting the tabline option be empty),
the attributes are working as expected.
To see this in action,
let me change the setting for the inactive labels
from reversed
to blue text on yellow background,
still using the tabline example from the Neovim documentation
and still having TabLineFill set to reverse.
highlight TabLine ctermbg=yellow ctermfg=blue
TabLine,
with the reverse attribute of
TabLineFill.
Since TabLine has no reverse attribute,
one would expect blue text one yellow background for the inactive tabs,
and one would certainly expect normal video for the active tab.
This is indeed how it works when
the same highlight settings
are used with the default (empty) tabline option:
Workarounds
To get around this apparent limitation,
one can imagine a few workarounds.
One workaround would be to not put
reverse
among the attributes of TabLineFill.
highlight TabLine cterm=reverse
highlight TabLineFill cterm=NONE
Obviously, this wont give me the desired tabline,
since the part containing no tabs will be in normal video:
Another possible workaround would be to use explicit colors instead of attributes for normal and reverse video.
highlight TabLine ctermfg=white ctermbg=black
highlight TabLineSel ctermfg=black ctermbg=white
highlight TabLineFill ctermfg=white ctermbg=black
This looks somewhat like my desired highlight,
but wont quite cut it
since the active tab now is displayed with a white background,
whereas I want it to be displayed using whatever color
my terminal program has as its background color.
This background color is most likely some very, very light gray
rather than totally white:
The Solution
The solution that I have found and that will highlight
the tabline properly
while still allowing a custom tabline to be used is a variation
of the first workaround.
By leaving attributes out of TabLineFill altogether
and instead putting them in a custom highlight group,
one can get the inactive tabs and the fill part to be
displayed in reverse video,
and the active tab to be displayed in normal video.
Again, using the example tabline from the Neovim documentation:
highlight TabLine cterm=reverse
highlight TabLineFill cterm=None
highlight MyTabLineFill cterm=reverse
set tabline=%!MyTabLine()
function MyTabLine()
let s = ''
for i in range(tabpagenr('$'))
" select the highlighting
if i + 1 == tabpagenr()
let s ..= '%#TabLineSel#'
else
let s ..= '%#TabLine#'
endif
" set the tab page number (for mouse clicks)
let s ..= '%' .. (i + 1) .. 'T'
" the label is made by MyTabLabel()
let s ..= ' %{MyTabLabel(' .. (i + 1) .. ')} '
endfor
" after the last tab page fill with TabLineFill and reset tab page nr
let s ..= '%#MyTabLineFill#%T'
" right-align the label to close the current tab page
if tabpagenr('$') > 1
let s ..= '%=%#TabLine#%999Xclose'
endif
return s
endfunction
function MyTabLabel(n)
let buflist = tabpagebuflist(a:n)
let winnr = tabpagewinnr(a:n)
return bufname(buflist[winnr - 1])
endfunction
Wrapping up
With a way of properly highlighting a custom tabline, it is now time to rewrite the tabline function and make it display numbers instead of filenames or long path names:
highlight TabLine cterm=reverse
highlight TabLineFill cterm=None
highlight MyTabLineFill cterm=reverse
set tabline=%!MyTabLine()
function MyTabLine()
let s = ''
for i in range(tabpagenr('$'))
if i + 1 == tabpagenr()
let s ..= '%#TabLineSel#'
else
let s ..= '%#TabLine#'
endif
let s ..= ' ' .. (i + 1) .. ' '
endfor
let s ..= '%#MyTabLineFill#'
return s
endfunction
While changing the tab line labels,
I also removed the mouse related %T and %X:
From here on,
it is trivial to convert everything to Lua
and to make it work for termguicolors.
That is actually how I have it setup in my init.lua,
but since the example in the Neovim documentation
is given in Vimscript,
all the configuration above is also in Vimscript.
While working on my tabline configuration, it struck me that the documentation is silent regarding this limitation, and also that not much is said about it online. As for the latter, I can only speculate, and I would guess that most users who configure their own tabline will do so by using colors and hence be unaware of the difficulties of using video attributes.