Custom Neovim Statusline
Table of Contents
1. Introduction
The statusline is an integral part of (neo)vim's interface. I'd always fancied making my own statusline, and galaxyline becoming unmaintained pushed me to make the switch.
This is what it looks like right now:
2. Prerequisites
- Neovim 0.6 or above (0.5 might work too, but I can't guarantee it)
- A Nerd Font
…and that's it.
Let's get started.
3. Statusline Modules
We define each module individually, and then build the statusline at the end. It's more manageable this way.
3.1. Mode Indicator
The code below creates a table that defines the string name (which will be displayed in the statusline) for each output of vim.api.nvim_get_mode().mode
.
local modes = { ["n"] = "NORMAL", ["no"] = "NORMAL", ["v"] = "VISUAL", ["V"] = "VISUAL LINE", [""] = "VISUAL BLOCK", ["s"] = "SELECT", ["S"] = "SELECT LINE", [""] = "SELECT BLOCK", ["i"] = "INSERT", ["ic"] = "INSERT", ["R"] = "REPLACE", ["Rv"] = "VISUAL REPLACE", ["c"] = "COMMAND", ["cv"] = "VIM EX", ["ce"] = "EX", ["r"] = "PROMPT", ["rm"] = "MOAR", ["r?"] = "CONFIRM", ["!"] = "SHELL", ["t"] = "TERMINAL", }
Now, to actually get the mode and match it with modes
:
local function mode() local current_mode = vim.api.nvim_get_mode().mode return string.format(" %s ", modes[current_mode]):upper() end
I'd like to have different colors for different modes. The function below returns the color for the current mode. I have set these highlights in another part of my configuration, for each theme.
If you don't want different colors for each mode, simply remove this function and remove all occurences of it below. Everything should still work.
local function update_mode_colors() local current_mode = vim.api.nvim_get_mode().mode local mode_color = "%#StatusLineAccent#" if current_mode == "n" then mode_color = "%#StatuslineAccent#" elseif current_mode == "i" or current_mode == "ic" then mode_color = "%#StatuslineInsertAccent#" elseif current_mode == "v" or current_mode == "V" or current_mode == "" then mode_color = "%#StatuslineVisualAccent#" elseif current_mode == "R" then mode_color = "%#StatuslineReplaceAccent#" elseif current_mode == "c" then mode_color = "%#StatuslineCmdLineAccent#" elseif current_mode == "t" then mode_color = "%#StatuslineTerminalAccent#" end return mode_color end
3.2. File
What is a statusline without the filename?
Here, we use a few modifiers with vim.fn.fnamemodify
to customize what the filepath looks like:
:~
- reduces filename to be relative to the home directory, i.e. replace/home/<user>
with~
.:.
- reduces filename to be relative to the current directory.:h
- reduces filename to only the head (without this it would be printed twice.)
Refer to :h filename-modifiers
for more.
local function filepath() local fpath = vim.fn.fnamemodify(vim.fn.expand "%", ":~:.:h") if fpath == "" or fpath == "." then return " " end return string.format(" %%<%s/", fpath) end
Here, ~
refers to the current file name, and :t
modifies it to only show the tail part
local function filename() local fname = vim.fn.expand "%:t" if fname == "" then return "" end return fname .. " " end
3.3. LSP
LSP is cool.
The function below returns the icon and count for each level if the count is not zero. i.e. the statusline doesnt show the number of errors (or warnings, info and hints) if there aren't any.
local function lsp() local count = {} local levels = { errors = "Error", warnings = "Warn", info = "Info", hints = "Hint", } for k, level in pairs(levels) do count[k] = vim.tbl_count(vim.diagnostic.get(0, { severity = level })) end local errors = "" local warnings = "" local hints = "" local info = "" if count["errors"] ~= 0 then errors = " %#LspDiagnosticsSignError# " .. count["errors"] end if count["warnings"] ~= 0 then warnings = " %#LspDiagnosticsSignWarning# " .. count["warnings"] end if count["hints"] ~= 0 then hints = " %#LspDiagnosticsSignHint# " .. count["hints"] end if count["info"] ~= 0 then info = " %#LspDiagnosticsSignInformation# " .. count["info"] end return errors .. warnings .. hints .. info .. "%#Normal#" end
3.4. Filetype
Takes the filetype and makes it uppercase
local function filetype() return string.format(" %s ", vim.bo.filetype):upper() end
3.5. Line Info
%P
- Percentage through the file.%l
- Line Number%c
- Column Number
local function lineinfo() if vim.bo.filetype == "alpha" then return "" end return " %P %l:%c " end
4. Building the Statusline
Let us build the statusline using the modules defined above. Here, we take a table of strings (returned by the functions above), and concatenate them into one string (which is what the statusline wil be set to.)
Statusline = {} Statusline.active = function() return table.concat { "%#Statusline#", update_mode_colors(), mode(), "%#Normal# ", filepath(), filename(), "%#Normal#", lsp(), "%=%#StatusLineExtra#", filetype(), lineinfo(), } end function Statusline.inactive() return " %F" end function Statusline.short() return "%#StatusLineNC# NvimTree" end
5. Showing the Statusline
I use autocommands to show the statusline. This lets me show different statuslines for different buffers and add an inactive statusline. Credit to elianiva for the concept.
vim.api.nvim_exec([[ augroup Statusline au! au WinEnter,BufEnter * setlocal statusline=%!v:lua.Statusline.active() au WinLeave,BufLeave * setlocal statusline=%!v:lua.Statusline.inactive() au WinEnter,BufEnter,FileType NvimTree setlocal statusline=%!v:lua.Statusline.short() augroup END ]], false)
6. Bonus modules
Some extra modules that I don't use, but you may be interested in.
6.1. Git Info
Many people like to have git info in their statusline.
To use it, chuck this function into your file and add it to Statusline.active()
.
(requires gitsigns.nvim)
local vcs = function() local git_info = vim.b.gitsigns_status_dict if not git_info or git_info.head == "" then return "" end local added = git_info.added and ("%#GitSignsAdd#+" .. git_info.added .. " ") or "" local changed = git_info.changed and ("%#GitSignsChange#~" .. git_info.changed .. " ") or "" local removed = git_info.removed and ("%#GitSignsDelete#-" .. git_info.removed .. " ") or "" if git_info.added == 0 then added = "" end if git_info.changed == 0 then changed = "" end if git_info.removed == 0 then removed = "" end return table.concat { " ", added, changed, removed, " ", "%#GitSignsAdd# ", git_info.head, " %#Normal#", } end
7. More Resources
:h 'statusline'
- https://elianiva.my.id/post/neovim-lua-statusline
- https://learnvimscriptthehardway.stevelosh.com/chapters/17.html
- https://github.com/elenapan/dotfiles/blob/master/config/nvim/statusline.vim
- https://got-ravings.blogspot.com/2008/08/vim-pr0n-making-statuslines-that-own.html
- https://gabri.me/blog/diy-vim-statusline