Saturday, August 30, 2008

Vim pr0n: Making statuslines that own

Many of vim's default options smoke boner hard enough to turn a man inside out. One such option is 'statusline'. By default this option is blank, causing the statusline to display only the tail of the filename for the current buffer. Most people like much more detailed information to be displayed. For example, check out my current statusline:


set statusline=%t[%{strlen(&fenc)?&fenc:'none'},%{&ff}]%h%m%r%y%=%c,%l/%L\ %P


If you don't have a statusline that owns, then read this raving right now.

Commented form


Before we get into actually putting information on your statusline, another way to express your statusline is like this:

 1 set statusline=%t       "tail of the filename
 2 set statusline+=[%{strlen(&fenc)?&fenc:'none'}, "file encoding
 3 set statusline+=%{&ff}] "file format
 4 set statusline+=%h      "help file flag
 5 set statusline+=%m      "modified flag
 6 set statusline+=%r      "read only flag
 7 set statusline+=%y      "filetype
 8 set statusline+=%=      "left/right separator
 9 set statusline+=%c,     "cursor column
10 set statusline+=%l/%L   "cursor line/total lines
11 set statusline+=\ %P    "percent through file


This format is way more useful if you are experimenting with all the statusline flags etc to find a good setting. You may want to use it all the time anyway as it is more readable and maintainable.

Statusline flags


The easiest way to put information on your statusline is with the built in flags. For example, %m displays a [+] if the current buffer is modified, while %F displays the full path to the current file. A full list of these flags can be found at :help 'statusline, there's tons of them so be sure to have a good read through.

Using expressions and functions calls


You are not restricted to using just these flags though. You can put anything you want on your statusline! Any expression enclosed by %{...} will be evaluated and displayed. For example: %{strlen(&ft)?&ft:'ZOMG'} will display the current filetype or "ZOMG" if none is set.

If you want to display something that requires any complex logic, you can extract it out into a function. For example:

 1 set statusline=......%{FileSize()}.....
 2 function! FileSize()
 3     let bytes = getfsize(expand("%:p"))
 4     if bytes <= 0
 5         return ""
 6     endif
 7     if bytes < 1024
 8         return bytes
 9     else
10         return (bytes / 1024) . "K"
11     endif
12 endfunction


In this example a function is used to display the size of the current file (in kilobytes if appropriate).

Controlling formatting and alignment


You can control the width and alignment of each item on the statusline as you specify it. The full syntax for specifying an item is:
%-0{minwid}.{maxwid}{item}

All of the parameters are optional except the {item} itself. A full description of the syntax is at :help 'statusline but basically:

  • The - forces the item to be left aligned, omitting it makes it right aligned.

  • The 0 forces numeric values to be left padded with zeros.

  • Then we have some width arguments and the item itself.


Examples:
%-.100F — displays the full filename of the current buffer, left aligned, 100 characters max.

%03.b — displays the ascii value of the current byte, at least 3 characters wide and left padded with zeros.

%20.20{'tabstop='.&tabstop} — displays "tabstop=<current tabstop>" 20 characters wide, right aligned.

You can also group several items together and apply formatting to the group as a whole. To do this, use the %(...%) syntax. For example: %20(%l/%L%) would display <current-line>/<total-lines> at a minimum width of 20 characters.

One more thing I will mention about formatting is the magic %= flag. If this flag appears in your statusline then everything after it will be shoved to the right as far as possible.

Using colors


There are two general approaches you can take with color. It depends on your needs and how hardcore you are. One way allows you to control exactly what colors are used, while the other way lets you use existing highlight groups which are controlled by your colorscheme.

Using existing highlight groups is easy, just use %#foo# to switch colors to the foo highlight group. Use %* to switch back to using the statusline highlight group. For example:


1 set statusline=
2 set statusline+=%#todo#  "switch to todo highlight
3 set statusline+=%F       "full filename
4 set statusline+=%#error# "switch to error highlight
5 set statusline+=%y       "filetype
6 set statusline+=%*       "switch back to normal statusline highlight
7 set statusline+=%l       "line number


The two disadvantages to doing color this way is that you don't have direct control over which colors are used, and if you change colorschemes then all the colors on your statusline will change.

The second method requires you to define up to nine highlight groups called User1,User2..User9. Then, vim provides shortcuts for you to switch between these colors on the statusline. For example:

 1 "define 3 custom highlight groups
 2 hi User1 ctermbg=green ctermfg=red   guibg=green guifg=red
 3 hi User2 ctermbg=red   ctermfg=blue  guibg=red   guifg=blue
 4 hi User3 ctermbg=blue  ctermfg=green guibg=blue  guifg=green
 5
 6 set statusline=
 7 set statusline+=%1*  "switch to User1 highlight
 8 set statusline+=%F   "full filename
 9 set statusline+=%2*  "switch to User2 highlight
10 set statusline+=%y   "filetype
11 set statusline+=%3*  "switch to User3 highlight
12 set statusline+=%l   "line number
13 set statusline+=%*   "switch back to statusline highlight
14 set statusline+=%P   "percentage thru file


Note that you must define your custom highlight groups after any :colorscheme in your vimrc otherwise your highlight groups will be cleared when :colorscheme is called.

OMG the big gotcha


Remember, any spaces is your statusline must be escaped, this includes any strings resulting from a function call as well. If you get any strange error messages when setting your statusline, check for spaces.

Some examples


While writing this raving I journeyed into the nerdfest that is #vim on freenode and asked (harassed) people there to give me their statusline settings.

I got some responses from some pretty high powered geeks. Take a look though these examples and steal bits and pieces. If you want to try one out, be sure to strip out any non-standard function calls as well as color settings.

 1 " spiiph's
 2 set statusline=
 3 set statusline+=%<\                       " cut at start
 4 set statusline+=%2*[%n%H%M%R%W]%*\        " flags and buf no
 5 set statusline+=%-40f\                    " path
 6 set statusline+=%=%1*%y%*%*\              " file type
 7 set statusline+=%10((%l,%c)%)\            " line and column
 8 set statusline+=%P                        " percentage of file
 9
10
11 " jamessan's
12 set statusline=   " clear the statusline for when vimrc is reloaded
13 set statusline+=%-3.3n\                      " buffer number
14 set statusline+=%f\                          " file name
15 set statusline+=%h%m%r%w                     " flags
16 set statusline+=[%{strlen(&ft)?&ft:'none'},  " filetype
17 set statusline+=%{strlen(&fenc)?&fenc:&enc}, " encoding
18 set statusline+=%{&fileformat}]              " file format
19 set statusline+=%=                           " right align
20 set statusline+=%{synIDattr(synID(line('.'),col('.'),1),'name')}\  " highlight
21 set statusline+=%b,0x%-8B\                   " current char
22 set statusline+=%-14.(%l,%c%V%)\ %<%P        " offset
23
24
25 " tpope's
26 set statusline=[%n]\ %<%.99f\ %h%w%m%r%{exists('*CapsLockStatusline')?CapsLockStatusline():''}%y%=%-16(\ %l,%c-%v\ %)%P
27
28
29 " frogonwheels'
30 set statusline=%f%w%m%h%1*%r%2*%{VarExists('b:devpath','<Rel>')}%3*%{VarExists('b:relpath','<Dev>')}%{XLockStat()}%=%-15(%l,%c%V%)%P
31
32
33 " godlygeek's
34 let &statusline='%<%f%{&mod?"[+]":""}%r%{&fenc !~ "^$\\|utf-8" || &bomb ? "[".&fenc.(&bomb?"-bom":"")."]" : ""}%=%15.(%l,%c%V %P%)'
35
36
37 " Another way to write godlygeeks:
38 set statusline=%<%f%m%r%{Fenc()}%=%15.(%l,%c%V\ %P%)
39 function! Fenc()
40     if &fenc !~ "^$\|utf-8" || &bomb
41         return "[" . &fenc . (&bomb ? "-bom" : "" ) . "]"
42     else
43         return ""
44     endif
45 endfunction

11 comments:

  1. Thanks--sweet post man, I had just turned off my statusline before but this has changed my mine. Very helpful.

    ReplyDelete
  2. Awesome - Thankyou! Keep up the great work!

    ReplyDelete
  3. Woah, definitely some sexy options in there I didn't know about, thanks for the vim pr0n! Picked up your idea on splitting it into seperate lines for each option and totally didn't think of the strlen trick :P

    And wow jamessan if you need triple digit buffer numbers o.0

    Here's mine: (Pastebinned 'cause site is a little thin xD)
    http://vim.pastey.net/139802

    ReplyDelete
  4. Thanks for the detailed overview! It was extremely helpful. :)

    ReplyDelete
  5. Thanks. Is there any way I can put the colorscheme name in the statusline?

    ReplyDelete
    Replies
    1. This is an old post, but to help out anyone wondering the same thing, %{colorscheme} should do the trick

      Delete
  6. There is a convention that colorschemes set the g:colors_name variable. So you could just have a function that returns that if it exists or an empty string (or maybe "default") otherwise, and call it from your statusline.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. where are the pretty screenshots?!

    ReplyDelete
  9. And here is mine:
    set statusline=
    set statusline+=%f%= " filename
    set statusline+=%< " folding left
    set statusline+=[%{GetBG()}\:%{GetCN()}]\ " background and colorscheme
    set statusline+=[%1*%M%*%n%R%W\,%{strlen(&ft)?&ft:'none'}]\ " flags and filetype
    set statusline+=%{synIDattr(synID(line('.'),col('.'),1),'name')}\ " highlight type on word
    set statusline+=%(%3l,%02c%03V%)\ " row,column,virtual-column
    set statusline+=\b\:%-04O\ " cursor hex offset from start of file
    set statusline+=\c\:%03b\ " char byte code under cursor
    set statusline+=%P " percentage of the file

    also, those functions have to be defined:
    " check if variable bg exists at all, and return it in a safe way
    fun! GetBG()
    if exists("&bg")|return &bg|else|return "-"|endif
    endfun

    " check is colorscheme name exists and return it
    fun! GetCN()
    if exists("g:colors_name") | return g:colors_name | else | return "-" | endif
    endfun

    ReplyDelete