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

Wednesday, August 13, 2008

Vim pr0n: A simple template engine

I frequently hang out in #vim on freenode. It's a funny place to be in sometimes. People come and go, asking all kinds of random questions, but occasionally someone will ask an interesting question that requires vimscript hacks. Its times like these where, if you listen closely, you can hear the collective *click* of all available nerds simultaneously pausing their pornography. This is closely followed by the furious stampeding of fingers over keyboards as said nerds race each other to hack and pastebin the solution.

A while ago, someone asked how to write a simple template engine, i.e. something like "when I create a new java file, how can I choose a code skeleton to prefill it with". I could practically hear the starter pistol fire. I cant remember if I won or not, but I wrote some dynamite hacks and kept them in my vimrc so that the next time someone asked I could pastebin them straight away and pretend I wrote them on the spot.

Anyway, a few days ago I revisited the code and cleaned it up a bit. Here it is:


 1 "define the Template command
 2 command! -complete=customlist,AvailableTemplates -n=1
 3     \ Template :call InsertTemplate('<args>')
 4
 5 function! InsertTemplate(name)
 6
 7     "read in the template
 8     execute 'read ~/.vim/templates/' . &filetype . '/' . a:name
 9
10     "if the cursor was previously on a blank line, delete it
11     if getline(line(".")-1) =~ '^\s*$'
12         exec line(".")-1 . 'd'
13     endif
14 endfunction
15
16 function! AvailableTemplates(lead, cmdline, cursorpos)
17     let templateDir = expand('~/.vim/templates/' . &filetype . '/')
18     let files = split(globpath(templateDir, a:lead . '*'), '\n')
19
20     "chop off the templateDir from each file
21     return map(files, 'strpart(v:val,strlen(templateDir))')
22 endfunction


The idea is that you store all your template files in:

~/.vim/templates/<filetype>/

So, for example, you might have three html templates and a java template:

~/.vim/templates/html/first.html
~/.vim/templates/html/second.html
~/.vim/templates/html/third.html
~/.vim/templates/java/foo.java

Then you edit an html file and apply a template with:

:Template first.html

You can also tab complete template names.

Limitations: No error handling. No clever variable expansion etc. Assumes you are working in *nix style os, but could easily be hacked for MF Windows.