Saturday, May 30, 2015

My approach to a custom status line

Hi,
I have recently re-implemented my status line from scratch. Since getting
to my (hopefully final) solution took a while (and help from this group) I'm
giving back my approach, in the hope that it may be useful to other
non-expert Vim users, and maybe to get feedback to improve it even further.
If you don't feel like reading this somewhat lengthy post, my code is here:

https://github.com/lifepillar/lifepillar-vim-config/blob/master/vimrc#L480

My requirements for a status line are as follows:

1. show slightly different information for active and inactive status lines;
2. display the current mode in the active status line;
3. color some parts of the active status line with a second color (besides
the background) that depends on the current mode;
4. do not render everything slow as hell :)

After trying at least three different ways to reach such goals, with varying
amounts of bugs, and following some advice from the group, I have
decided to keep it simple, which means:

1. set g:statusline once, and never redefine it;
2. do not use l:statusline, so that plugins (e.g., CtrlP) can override my
status line without the need for me to add custom logic depending on
the plugin;
3. try to avoid Ex-mode for performance;
4. do not maintain buffer or window variables;
5. do not use autocommands.

Avoiding 4 and 5 almost surely means avoiding subtle bugs that are hard
to fix (at least for me).

The main problem is: how does a function that builds a status line know
whether it is building the status line for the active window? This is
not trivial
because (see :h statusline), winnr() is evaluated differently depending on the
context. Instead of fighting against Vim, I have decided to follow its logic,
which inevitably leads to define:

set statusline=%!BuildStatusLine(winnr())

Here, winnr() is always the number of the currently *active* window.
As far as I know, there is no way for BuildStatusLine() to know which window
it is building a status line for, but it can put the information it knows
(the number of the active window) into the status line's string. So,
the minimal
BuildStatusLine() is:

func! BuildStatusLine(nr)
return '%{ReallyBuildStatusLine(' . a:nr . ')}'
endfunc

Since ReallyBuildStatusLine() is evaluated in %{} context, winnr() returns the
number of the window *that the status line belongs to*. So, a minimal
implementation is:

func! ReallyBuildStatusLine(nr)
if (winnr() == a:nr)
" return active status line
else
" return inactive status line
endif
endfunc

Discovering that things were this simple was a "a-ha" moment for me. Of course,
things are not that simple :) If all you want is putting different
strings in different
status lines, this is all you need, as far as I can see. But if you want to use
'%' items, say highlight groups, you'll soon discover that you cannot use them
in the latter function, because they will be taken as literal strings and not
interpolated.

To solve this problem, at first I have used a trick like this:

func! SomeText(nr, flag)
return (winnr() == a:nr) ? (flag ? 'text' : '') : (flag ? '' : 'text')
endfunc

func! BuildStatusLine(nr)
return '%#MyHighlight#%{SomeText(' . a:nr . ',1)}%*%{SomeText(' .
a:nr . ',0)}...'
endfunc

It works, but it's ugly. Eventually, I decided to redefine a highlight
group on-the-fly:

func! SetHighlight(nr)
if (winnr() == a:nr)
hi! link StlHighlight MyHighlight
else
hi! link StlHighlight StatusLineNC
endif
return ''
endfunc

func! BuildStatusLine(nr)
return '%{SetHighlight(' . a:nr . ')}%#StlHighlight#%{SomeText()}%*...'
endfunc

I thought this would be slow, but in my (limited) benchmarks this seems
to perform
quite well.

So, in the end, I have a bi-colored collapsible status line highlighting the
current mode (and showing other information), which can be drawn in 1.5x-2x
time compared to a basic status line. My approach may not be general enough
to cover all use cases, but I'm pretty satisfied with it! If you know
how I can do
better than this, please let me know!

Nicola


--
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_use+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments: