Guide to Make Your Own Vim/Neovim Statusline

cd .. || cd

August 20, 2021 · 6 mins · Robertus Chris

Table of Contents

We don’t have to install vim plugin to get a statusline if we don’t want to.

Introduction

Hi everyone! In this post I will talk about making your custom statusline in vim. There are a lot of plugins out there that makes vim statusline way better and works out of the box.

“Why would someone going through all the trouble while there’s a plugin for that?”, you might ask. Well, for me personally, having one less plugin is a good thing. I don’t really want to depend on the plugin for something simple such as statusline. If I can build it myself, then I will build it myself rather than using a plugin. Also, it save me a lot of time to figure out which plugin causes the issue if I have less plugin! Oh, another thing, some vim statusline plugin can slow down your startup time (I’m looking at you vim-airline) so be careful about that. Alright then, let’s get into it!

Requirements

Before we start, we need to prepare a few things:

If all is set, then let’s get started!

Disable Old Statusline

Before we move further, you need to disable or remove your old statusline plugin and the config. If you don’t want to remove your old statusline plugin config, you can commented out those line/config in your vimrc or init.vim.

Different statusline for active and inactive window

You can have different statusline for active and inactive window by using autocmd event. “What is autocmd?” you might ask, go take a look at :help autocmd for more info. To make it simple, autocmd is to automatically execute a command on certain event which you can check on :help autocmd-events for more info.

So, how can we have different statusline for active and inactive window? First, we need to make a function to define our active or inactive statusline component. You can create the function similar to this:

" component for active window
function! StatuslineActive()
  " the component goes here
endfunction

" component for inactive window
function! StatuslineInactive()
  " the component goes here
endfunction

" load statusline using `autocmd` event with this function
function! StatuslineLoad(mode)
  if a:mode ==# 'active'
    " to make it simple, %! is to evaluate the current changes in the window
    " it can be useful for evaluate current mode in statusline. For more info:
    " :help statusline.
    setlocal statusline=%!StatuslineActive()
  else
    setlocal statusline=%!StatuslineInactive()
  endif
endfunction

and also the autocmd similar to this:

" so that autocmd didn't stack up and slow down vim
augroup statusline_startup
  autocmd!
  " for more info :help WinEnter and :help BufWinEnter
  autocmd WinEnter,BufWinEnter * call StatuslineLoad('active')
  autocmd WinLeave * call StatuslineLoad('inactive')
augroup END

Now we can compose our statusline component. We can take a look at :help statusline for supported items like for example f for relative path to the file in the buffer. You can choose whatever item you like in your statusline, and put it in the previous function similar to this:

function! StatuslineActive()
  " if we want to add `f` items in our statusline
  let l:filename = '%f'
  " if we want to add 'm' items in our statusline
  let l:mod = '%m'
  " the `.` is basically to ignore whitespace before and put it right after the previous component
  return l:filename.l:mod
endfunction

Why we need to do that? well, I’ll explain it in next section

Current Mode in Statusline

If you want to put your current mode in your statusline, you can do it with a function similar to this:

function! StatuslineMode() abort

    let l:currentmode={
        \ 'n':  'N',
        \ 'v':  'V',
        \ 'V':  'VL',
        \ '^V': 'VB',
        \ 's':  'S',
        \ 'S':  'SL',
        \ '^S': 'SB',
        \ 'i':  'I',
        \ 'R':  'R',
        \ 'c':  'C',
        \ 't':  'T'}

    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 'VB'
    let l:modelist = toupper(get(l:currentmode, l:modecurrent, 'VB'))
    let l:current_status_mode = l:modelist
    return l:current_status_mode
endfunction

and put it inside of your statusline function like this:

function! StatuslineActive()
  let l:filename = '%f'
  let l:mod = '%m'
  " `w:` is basically local variable to current window
  " and `l:` is basically local variable to function. For more info :help E121
  let w:mode = '%{StatuslineMode()}'
  return w:mode.l:filename.l:mod
endfunction

Now, if we want to change the current mode background based on the current mode, we can do something like this:

" define Normal mode color, Insert mode color, and so on
hi NormalModeColor ctermbg=... ctermfg=... guifg=#... guibg=#...
hi InsertModeColor ctermbg=... ctermfg=... guifg=#... guibg=#...

function! StatuslineActive()
  let l:filename = '%f'
  let l:mod = '%m'
  if mode() ==# 'n'
    let w:mode = '%#NormalModeColor#%{StatuslineMode()}'
  elseif mode() ==# v:insertmode
    let w:mode = '%#InsertModeColor#%{StatuslineMode()}'
  endif
  " %* is basically to restore highlight to StatusLine highlight group
  return w:mode.'%* '.l:filename.l:mod
endfunction

Git Branch in Statusline

If you install vim-fugitive plugin, then you can use fugitive#head() in your statusline like this:

function! StatuslineActive()
  let l:filename = '%f'
  let l:mod = '%m'
  if mode() ==# 'n'
    let w:mode = '%#NormalModeColor#%{StatuslineMode()}'
  elseif mode() ==# v:insertmode
    let w:mode = '%#InsertModeColor#%{StatuslineMode()}'
  endif
  " make sure it doesn't throw an error if `vim-fugitive` is not installed
  let l:git = "%{exists('*FugitiveHead') ? fugitive#head() : ''}"
  " to separate left and right side
  let l:sep = '%='
  return w:mode.'%* '.l:filename.l:mod.l:sep.l:git
endfunction

alternatively, you can use system() command to get the current git branch (for more info :help system()) like this:

function! StatuslineActive()
  let l:filename = '%f'
  let l:mod = '%m'
  if mode() ==# 'n'
    let w:mode = '%#NormalModeColor#%{StatuslineMode()}'
  elseif mode() ==# v:insertmode
    let w:mode = '%#InsertModeColor#%{StatuslineMode()}'
  endif
  " for more info :help E121
  let g:gitbranchcmd = "git branch --show-current 2>/dev/null | tr -d '\n'"
  " use system() if vim-fugitive not installed
  let l:git = "%{exists('*FugitiveHead') ? fugitive#head() : system(g:gitbranchcmd)}"
  let l:sep = '%='
  return w:mode.'%* '.l:filename.l:mod.l:sep.l:git
endfunction

Same Statusline for active and inactive window

Now, if we want our statusline to be the same whether in active or inactive window. We can make simplify it by only make one function and not using autocmd. It will look something like this:

function! StatuslineComponent()
  " your component goes here
endfunction

set statusline=%!StatuslineComponent()

and you can use some tips from previous section too!

Conclusion

This guide is for do-it-yourself kind of people, so it’s only giving some pointer you can use to make your own statusline. I don’t want to tell you what to put in your statusline, it is your OWN statusline after all, so you need to know what you want in it. Also, I’m not a vim script expert so please forgive me if I miss something. If you have any question regarding this post, feel free to hit me up on twitter (@diawanchris)! See you later!

References