The Last Statusline For Vim

Kade Killary · 2018.01.07 · 9 minutes until it's over

statusline-vim

For some Vimmers, their statusline is like the Lightsaber of a Jedi. Each similar, yet unique - the aesthetic cherry revealing a lurking identity. This notion has led to projects like Powerline, Airline, and Lightline. While all interesting, this tutorial is about doing it the old school way. We’ll be traveling to a metaphorical Dagobah to craft our statusline using the methods of the force - Vim script.

Before we get going, here is a preview of the syntax. The screenshot below is my current statusline, alongside the code used to generate it.

my-line

" Function: display errors from Ale in statusline
function! LinterStatus() abort
   let l:counts = ale#statusline#Count(bufnr(''))
   let l:all_errors = l:counts.error + l:counts.style_error
   let l:all_non_errors = l:counts.total - l:all_errors
   return l:counts.total == 0 ? '' : printf(
   \ 'W:%d E:%d',
   \ l:all_non_errors,
   \ l:all_errors
   \)
endfunction

set laststatus=2
set statusline=
set statusline+=\ %l
set statusline+=\ %*
set statusline+=\ ‹‹
set statusline+=\ %f\ %*
set statusline+=\ ››
set statusline+=\ %m
set statusline+=\ %F
set statusline+=%=
set statusline+=\ %{LinterStatus()}
set statusline+=\ ‹‹
set statusline+=\ %{strftime('%R', getftime(expand('%')))}
set statusline+=\ ::
set statusline+=\ %n
set statusline+=\ ››\ %*

The Basics

As with any exploration into Vim, we need to start from the ground floor - :help statusline.

When nonempty, this option determines the content of the statusline. The option consists of printf style ‘%’ items interspersed with normal text. Each statusline item is of the form:

%-0{minwid}.{maxwid}{item}

All fields except the {item} are optional. A single percent sign can be given as “%%”. Up to 80 items can be specified.

This may seem equal parts confusing and non-intuitive, but fear not…it’s not that bad.

The first thing is to tell Vim we want to see the statusline. You can do this by putting the following code in your .vimrc (vim) or init.vim (neovim).

set laststatus=2

Now that we have told Vim we are interested, we need to give it something to display! For a very basic example, type the following below:

set statusline=lightsaber!

When you reload Vim you should see:

hello-world

Clearly, this statusline isn’t incredibly useful, but baby steps!

Syntax

Within the galaxy of statuslines, a special syntax exists. Every line is derived from individual items, with each corresponding to a particular piece of information. For instance, %f will give the relative path to the file currently present in the buffer.

echo "Hello, this is a .jedi file" >> lightsaber.jedi

Insert the following into your Vim config.

set statusline=%f

Open the file we created earlier. You should see your statusline with the name of the file printed on it.

basic-line

This is, by and large, the formula for statuslines. You’ll be using these predefined mappings to create your weapon. There is one thing to note: how to combine multiple items.

" this
set statusline=%f%n

" is the same as this
" but this variant is cleaner and easier to maintain
set statusline=
set statusline+=%f
set statusline+=%n

The full list of mapings:

The second character in "item" is the type:
    N for number
    S for string
    F for flags as described below
    - not applicable

ITEM  MEANING ~

f S   Path to the file in the buffer, as typed or relative to current
        directory.
F S   Full path to the file in the buffer.
t S   File name (tail) of file in the buffer.
m F   Modified flag, text is "[+]"; "[-]" if 'modifiable' is off.
M F   Modified flag, text is ",+" or ",-".
r F   Readonly flag, text is "[RO]".
R F   Readonly flag, text is ",RO".
h F   Help buffer flag, text is "[help]".
H F   Help buffer flag, text is ",HLP".
w F   Preview window flag, text is "[Preview]".
W F   Preview window flag, text is ",PRV".
y F   Type of file in the buffer, e.g., "[vim]".  See 'filetype'.
Y F   Type of file in the buffer, e.g., ",VIM".  See 'filetype'.
q S   "[Quickfix List]", "[Location List]" or empty.
k S   Value of "b:keymap_name" or 'keymap' when |:lmap| mappings are
        being used: "<keymap>"
n N   Buffer number.
b N   Value of character under cursor.
B N   As above, in hexadecimal.
o N   Byte number in file of byte under cursor, first byte is 1.
        Mnemonic: Offset from start of file (with one added)
O N   As above, in hexadecimal.
N N   Printer page number.  (Only works in the 'printheader' option.)
l N   Line number.
L N   Number of lines in buffer.
c N   Column number.
v N   Virtual column number.
V N   Virtual column number as -{num}.  Not displayed if equal to 'c'.
p N   Percentage through file in lines as in |CTRL-G|.
P S   Percentage through file of displayed window.  This is like the
        percentage described for 'ruler'.  Always 3 in length, unless
        translated.
a S   Argument list status as in default title.  ({current} of {max})
        Empty if the argument file count is zero or one.

A minimal example that displays the file name, current line, total lines, and buffer number:

set laststatus=2
set statusline=
set statusline+=%f
set statusline+=%l
set statusline+=%L
set statusline+=%n

Formatting

We’re starting to get somewhere, a padawan no more. However, there are a few more tips, which are largely formatting quirks, to cover.

The first of these deals with controlling which side our items reside on. Specifically, the = symbol. An = denotes a separation point between sections.

set statusline=
set statusline+=%f
set statusline+=%m
" switching to right side
set statusline+=%=
set statusline+=%l
set statusline+=%L

Another formatting must-have is spacing. Spaces are escaped using backslashes - \. The following example has one other symbol of note - <. The < is used to tell Vim where to truncate the line if it is too long.

" from scrooloose's blog post
" https://got-ravings.blogspot.com/2008/08/vim-pr0n-making-statuslines-that-own.html
set statusline=
set statusline+=%<\                       " cut at start
set statusline+=%2*[%n%H%M%R%W]%*\        " flags and buf no
set statusline+=%-40f\                    " path
set statusline+=%=%1*%y%*%*\              " file type
set statusline+=%10((%l,%c)%)\            " line and column
set statusline+=%P                        " percentage of file

With the knowledge you now posess you should be able to throw together something half-decent. But the journey is not over yet!

Expressions

There may come a time when you’re interested in including something that isn’t previously defined. This is where expressions will come into play. Within Vim’s statusline syntax anything between %{...} is evaluated as an expression, and substituted with the result. So, let’s say you’re too cool for set showmode and want it directly on your line, this is how you would achieve such a task.

" Dictionary: take mode() input -> longer notation of current mode
" mode() is defined by Vim
let g:currentmode={ 'n' : 'Normal ', 'no' : 'N·Operator Pending ', 'v' : 'Visual ', 'V' : 'V·Line ', '^V' : 'V·Block ', 's' : 'Select ', 'S': 'S·Line ', '^S' : 'S·Block ', 'i' : 'Insert ', 'R' : 'Replace ', 'Rv' : 'V·Replace ', 'c' : 'Command ', 'cv' : 'Vim Ex ', 'ce' : 'Ex ', 'r' : 'Prompt ', 'rm' : 'More ', 'r?' : 'Confirm ', '!' : 'Shell ', 't' : 'Terminal '}


" Function: return current mode
" abort -> function will abort soon as error detected
function! ModeCurrent() abort
    let l:modecurrent = mode()
    " use get() -> fails safely, since ^V doesn't seem to register
    " 3rd arg is used when return of mode() == 0, which is case with ^V
    " thus, ^V fails -> returns 0 -> replaced with 'V Block'
    let l:modelist = toupper(get(g:currentmode, l:modecurrent, 'V·Block '))
    let l:current_status_mode = l:modelist
    return l:current_status_mode
endfunction

set statusline=
set statusline+=\ %{ModeCurrent()}

This is your first step towards the darkside. You can now add just about anything your heart desires to your line. For instance, if you’re a crypto-nut, you could add the prices of Bitcoin and ETH - the possibilities are endless. For a great introduction to Vim Script head here. I’ve also included a few more examples below.

File Size

function! FileSize() abort
    let l:bytes = getfsize(expand('%p'))
    if (l:bytes >= 1024)
        let l:kbytes = l:bytes / 1025
    endif
    if (exists('kbytes') && l:kbytes >= 1000)
        let l:mbytes = l:kbytes / 1000
    endif
 
    if l:bytes <= 0
        return '0'
    endif
  
    if (exists('mbytes'))
        return l:mbytes . 'MB '
    elseif (exists('kbytes'))
        return l:kbytes . 'KB '
    else
        return l:bytes . 'B '
    endif
endfunction

Read Only File

function! ReadOnly() abort
  if &readonly || !&modifiable
    return ''
  else
    return ''
endfunction

It is also worth noting that you can utilize Vim’s internal functions within expressions.

set statusline=
" file encoding
set statusline+=\ %{(&fenc!=''?&fenc:&enc)}
" current time, when buffer saved
set statusline+=\ %{strftime('%R', getftime(expand('%')))}
set statusline+=\ %{&fileformat}

Color

The time has come. Which side will you pick? The life of a gritty minimalist accepting what the Vim gods have to offer. Or, will you indulge in the lush ecstasy of the rainbow? Personally, I could care less…but, luckily, the choice is yours!

There are two ways to go about adding color to your statusline. One is by piggy backing off of your colorscheme. The other is by defining the colors yourself.

Highlight Groups

The first method for coloring borrows from your colorscheme. For example, #function# will utilize the defined highlighting for the function keyword. The next image is my statusline using this techinque:

highlight

set laststatus=2
set statusline=
set statusline+=%#function#\ %l
set statusline+=\ %*
set statusline+=\ ‹‹
set statusline+=\ %f\ %*
set statusline+=\ ››
set statusline+=\ %m
set statusline+=%#keyword#\ %F
set statusline+=%=
set statusline+=\ %{LinterStatus()}
set statusline+=\ ‹‹
set statusline+=\ %{strftime('%R',getftime(expand('%')))}
set statusline+=\ ::
set statusline+=\ %n
set statusline+=\ ››\ %*

User1..9 Colors

The second method relies on colors that you’ll define. If you want a high degree of control, this is your choice. We’ll briefly dive back into Vim’s help to get an overview before preceeding.

Set highlight group to User{N}, where {N} is taken from the minwid field, e.g. %1. Restore normal highlight with % or %0*. The difference between User{N} and StatusLine will be applied to StatusLineNC for the statusline of non-current windows. The number N must be between 1 and 9. See |hl-User1..9|

That may be slightly confusing, so let’s hop into an example. The first thing to do is specify our colors of interest. The syntax is akin to creating a custom colorscheme.

hi User1 ctermbg=green ctermfg=red   guibg=green guifg=red
hi User2 ctermbg=red   ctermfg=blue  guibg=red   guifg=blue
hi User3 ctermbg=blue  ctermfg=green guibg=blue  guifg=green

In order to apply it, you designate the User number followed by a * - %1* User1 colors. Below is a full blown example.

colors-user

set laststatus=2
set statusline=
set statusline+=%2*\ %l
set statusline+=\ %*
set statusline+=%1*\ ‹‹
set statusline+=%1*\ %f\ %*
set statusline+=%1*\ ››
set statusline+=%1*\ %m
set statusline+=%3*\ %F
set statusline+=%=
set statusline+=%3*\ %{LinterStatus()}
set statusline+=%3*\ ‹‹
set statusline+=%3*\ %{strftime('%R',getftime(expand('%')))}
set statusline+=%3*\ ::
set statusline+=%3*\ %n
set statusline+=%3*\ ››\ %*

hi User1 guifg=#FFFFFF guibg=#191f26 gui=BOLD
hi User2 guifg=#000000 guibg=#959ca6
hi User3 guifg=#000000 guibg=#4cbf99

A lightline-esque version:

lightline

Your options are only limited by your imagination and time.

Next Steps

Despite all that has been covered, there is still a lot to learn. I’ll briefly cover a few more topics before suggesting additional resources.

Special Characters

The ‹‹ ›› on my line are solely for aesthetics. You can go to unicode-table to find interesting characters. Additionally, and this is specific to Mac OS, however, I assume there is a solution for other platforms, you can hit a combination of or for a variety of symbols (mine derive from ).

Different Colors Per Mode

If you are determined to do this without using a plugin it is possible. The easiest path will be using augroup and Events. I’d start with a :help Events. Then you’ll set up an augroup for your user defined colors and switch them based off the event. I’m not going to give a solution because I’m guessing it’s the sort of thing that would incite some motivation. Thus, it would be a worthwhile task to start to learn some Vim script. It’s honestly not too bad. There are other ways, however at some point you end up re-inventing the wheel.

Other Resources