neovim-lua-statusline.mdx
1 --- 2 title: How I made my Neovim statusline in Lua 3 date: 2020-11-29 4 description: A post where I explain how I made my custom statusline in Lua 5 tags: 6 - neovim 7 --- 8 9 import Update from "~/components/Update.astro"; 10 11 [gitsigns-link]: https://github.com/lewis6991/gitsigns.nvim 12 [nerdfont-link]: https://nerdfonts.com 13 [alacritty-link]: https://github.com/alacritty/alacritty 14 [short-circuit]: https://en.m.wikipedia.org/wiki/Short-circuit_evaluation 15 [nvim-web-devicons]: https://github.com/kyazdani42/nvim-web-devicons 16 [lsp-status]: https://github.com/nvim-lua/lsp-status.nvim 17 [bufferline-lua]: https://github.com/elianiva/dotfiles/blob/950ba38bda8230da8071fc72cf3d8617d6288565/config/nvim/lua/plugins/nvim-bufferline.lua 18 [autocmd]: https://github.com/neovim/neovim/pull/12378 19 [appearances-lua]: https://github.com/elianiva/dotfiles/blob/934fe3dd54aab909c396bf0fafae285946fa7fb5/nvim/.config/nvim/lua/modules/_appearances.lua 20 [expressline]: https://github.com/tjdevries/express_line.nvim 21 [galaxyline]: https://github.com/glepnir/galaxyline.nvim 22 [neoline]: https://github.com/adelarsq/neoline.vim 23 [spec-file]: https://github.com/elianiva/icy.nvim/blob/d946e6d783c1903e92cfe46a7a955732ee6d9988/lua/lush_theme/icy.lua 24 [new-line]: https://github.com/elianiva/dotfiles/blob/950ba38bda8230da8071fc72cf3d8617d6288565/config/nvim/lua/modules/statusline.lua 25 26 # Introduction 27 28 Hello there! So, I've been playing around with the latest Neovim feature and 29 that is it can now use Lua for its config. Quite a while ago I wrote [this post](https://elianiva.my.id/posts/vim-statusline) where I explain how I made my statusline. Now, it's time to update that post using Lua :) 30 31 # Prerequisite 32 33 If you want to follow along, then these are the prerequisite. 34 35 - Neovim 0.5 (we need this version for lua support) 36 - [gitsigns.nvim][gitsigns-link] 37 - [nerdfont][nerdfont-link] 38 - [nvim-web-devicons][nvim-web-devicons] 39 - Terminal that supports true colour (I use [Alacritty][alacritty-link]) 40 - Patience 41 - Googling skills in case something doesn't work correctly :p 42 43 # Creating The Statusline 44 45 ## Initial Setup 46 47 I wrote my statusline on `~/.config/nvim/lua/modules/_statusline.lua` along with my other lua modules so it will get picked up by Neovim and I can import it by using `require('modules._statusline')` 48 49 ## First Function 50 51 I create an empty table for my statusline and alias for `vim.fn` and `vim.api` to make it shorter. You can call it whatever you want, I call it `M` since this variable is just a 'temporary' table that I'm going to use for a metatable. My current file now looks something like this. 52 53 ```lua 54 local fn = vim.fn 55 local api = vim.api 56 local M = {} 57 ``` 58 59 This first function is going to be a helper function that will return `true` of `false` based on the current window width. I use this to decide whether or not a component should display a full or a truncated version of it. 60 61 ```lua 62 M.trunc_width = setmetatable({ 63 -- You can adjust these values to your liking, if you want 64 -- I promise this will all makes sense later :) 65 mode = 80, 66 git_status = 90, 67 filename = 140, 68 line_col = 60, 69 }, { 70 __index = function() 71 return 80 -- handle edge cases, if there's any 72 end 73 }) 74 75 M.is_truncated = function(_, width) 76 local current_width = api.nvim_win_get_width(0) 77 return current_width < width 78 end 79 ``` 80 81 This function calls `vim.api.nvim_win_get_width` for the current active window which will return its width. This function will return `true` if the current window width is less than the passed argument thus telling a component to truncate its content. 82 83 <Update date="2021-02-26"> 84 85 Thanks @Evgeni for the suggestion on creating a table for each section truncation width, it's easier to keep track of which component has how many width. 86 87 </Update> 88 89 ## Highlight groups 90 91 I have this table that contains a string for the highlight group. I can then concatenate one of its items with a component and apply the highlight group for that component. 92 93 ```lua 94 M.colors = { 95 active = '%#StatusLine#', 96 inactive = '%#StatuslineNC#', 97 mode = '%#Mode#', 98 mode_alt = '%#ModeAlt#', 99 git = '%#Git#', 100 git_alt = '%#GitAlt#', 101 filetype = '%#Filetype#', 102 filetype_alt = '%#FiletypeAlt#', 103 line_col = '%#LineCol#', 104 line_col_alt = '%#LineColAlt#', 105 } 106 ``` 107 108 I made the highlight groups on my `~/.config/nvim/lua/modules/_appearances.lua` along with my other hl-group definitions, but here's the important snippet. 109 110 <Update date="2021-07-23"> 111 112 Now since I made my own colourscheme using Lush, I defined them directly in the [spec file][lush-theme] 113 114 </Update> 115 116 ```lua 117 local set_hl = function(group, options) 118 local bg = options.bg == nil and '' or 'guibg=' .. options.bg 119 local fg = options.fg == nil and '' or 'guifg=' .. options.fg 120 local gui = options.gui == nil and '' or 'gui=' .. options.gui 121 122 vim.cmd(string.format('hi %s %s %s %s', group, bg, fg, gui)) 123 end 124 125 -- you can of course pick whatever colour you want, I picked these colours 126 -- because I use Gruvbox and I like them 127 local highlights = { 128 {'StatusLine', { fg = '#3C3836', bg = '#EBDBB2' }}, 129 {'StatusLineNC', { fg = '#3C3836', bg = '#928374' }}, 130 {'Mode', { bg = '#928374', fg = '#1D2021', gui="bold" }}, 131 {'LineCol', { bg = '#928374', fg = '#1D2021', gui="bold" }}, 132 {'Git', { bg = '#504945', fg = '#EBDBB2' }}, 133 {'Filetype', { bg = '#504945', fg = '#EBDBB2' }}, 134 {'Filename', { bg = '#504945', fg = '#EBDBB2' }}, 135 {'ModeAlt', { bg = '#504945', fg = '#928374' }}, 136 {'GitAlt', { bg = '#3C3836', fg = '#504945' }}, 137 {'LineColAlt', { bg = '#504945', fg = '#928374' }}, 138 {'FiletypeAlt', { bg = '#3C3836', fg = '#504945' }}, 139 } 140 141 for _, highlight in ipairs(highlights) do 142 set_hl(highlight[1], highlight[2]) 143 end 144 ``` 145 146 You can define this using VimL but I prefer doing it in Lua because 99% of my config is in Lua and I don't really like using VimL. 147 148 ## Separators 149 150 Since I use [nerdfont](https://nerdfonts.com), I have fancy symbols that I can use. I use these symbols as a separator. 151 152 ```lua 153 -- I keep this here just in case I changed my mind so I don't have to find these icons again when I need them 154 -- you can of course just store one of them if you want 155 M.separators = { 156 arrow = { '', '' }, 157 rounded = { '', '' }, 158 blank = { '', '' }, 159 } 160 161 local active_sep = 'blank' 162 ``` 163 164 I use the arrow separator, either one is fine. It will look empty here because my website doesn't use Nerdfont. 165 166 <Update date="2021-01-30"> 167 168 I now use the blank separator. 169 170 </Update> 171 172 ## Mode Component 173 174 The first component for my statusline is the one that shows the current mode. 175 176 ```lua 177 M.modes = setmetatable({ 178 ['n'] = {'Normal', 'N'}; 179 ['no'] = {'N·Pending', 'N·P'} ; 180 ['v'] = {'Visual', 'V' }; 181 ['V'] = {'V·Line', 'V·L' }; 182 [''] = {'V·Block', 'V·B'}; 183 ['s'] = {'Select', 'S'}; 184 ['S'] = {'S·Line', 'S·L'}; 185 [''] = {'S·Block', 'S·B'}; 186 ['i'] = {'Insert', 'I'}; 187 ['ic'] = {'Insert', 'I'}; 188 ['R'] = {'Replace', 'R'}; 189 ['Rv'] = {'V·Replace', 'V·R'}; 190 ['c'] = {'Command', 'C'}; 191 ['cv'] = {'Vim·Ex ', 'V·E'}; 192 ['ce'] = {'Ex ', 'E'}; 193 ['r'] = {'Prompt ', 'P'}; 194 ['rm'] = {'More ', 'M'}; 195 ['r?'] = {'Confirm ', 'C'}; 196 ['!'] = {'Shell ', 'S'}; 197 ['t'] = {'Terminal ', 'T'}; 198 }, { 199 __index = function() 200 return {'Unknown', 'U'} -- handle edge cases 201 end 202 }) 203 204 M.get_current_mode = function() 205 local current_mode = api.nvim_get_mode().mode 206 207 if self:is_truncated(self.trunc_width.mode) then 208 return string.format(' %s ', modes[current_mode][2]):upper() 209 end 210 211 return string.format(' %s ', modes[current_mode][1]):upper() 212 end 213 ``` 214 215 You probably notice that `V·Block` and `S·Block` look empty but they're not. It's a special character of `C-V` and `C-S`. If you go to (Neo)vim and press `C-V` in insert mode twice, it will insert something like `^V`. It's not the same as `^V`, I thought they're the same but they're not. 216 217 What that code does is creates a key-value pair table with string as a key and a table as its value. I use the table's key to match what `vim.api.nvim_get_mode().mode` returns. 218 219 Depending on the current window width, it will return different output. For example, if my current window isn't wide enough, it will return `N` instead of `Normal`. If you want to change when it will start to change then adjust the argument that is passed to the `is_truncated` function. Remember that `trunc_width` table from earlier? We use `mode` value here so that my Mode component will get truncated if my window width is less than `80`. 220 221 <Update date="2021-02-26"> 222 223 Thanks to @Evgeni for pointing me out, I moved the `mode` table outside of the function because previously I was putting it inside a function which will get created every time the function is executed. 224 225 <br /> 226 Also, since I moved from `vim.fn.mode` to `vim.api.nvim_get_mode().mode`, there are *a lot* of missing keys on my `mode` 227 table; Hence a metatable is used so it will give me an `Unknown` mode instead of throwing an error when there's no matching 228 key on the table. (Also thanks @Evgeni :) 229 230 </Update> 231 232 ## Git Status Component 233 234 I use [gitsigns.nvim][gitsigns-link] to show the git hunk status on `signcolumn`. It provides some details like how many lines have been changed, added, or removed. It also provides the branch name. So, I'd like to integrate this functionality into my statusline. 235 236 ```lua 237 M.get_git_status = function(self) 238 -- use fallback because it doesn't set this variable on the initial `BufEnter` 239 local signs = vim.b.gitsigns_status_dict or {head = '', added = 0, changed = 0, removed = 0} 240 local is_head_empty = signs.head ~= '' 241 242 if self:is_truncated(self.trunc_width.git_status) then 243 return is_head_empty and string.format(' %s ', signs.head or '') or '' 244 end 245 246 return is_head_empty 247 and string.format( 248 ' +%s ~%s -%s | %s ', 249 signs.added, signs.changed, signs.removed, signs.head 250 ) 251 or '' 252 end 253 ``` 254 255 What that code does is it gets the git hunk status from [gitsigns.nvim][gitsigns-link] and store it on a variable. I use fallback here because it doesn't get set on initial `BufEnter` so I'll get a `nil` error if I don't do that. 256 257 The next bit is it checks if the branch name exists or not (basically checking if we're in a git repo or not), if it exists then it will return a formatted status that will look something like this. 258 259  260 261 If the current window isn't wide enough, it will remove the git hunk summary and just display the branch name. 262 263 If you get confused with `and` and `or`, it's similar to ternary operator. `cond and true or false` is the same as `cond ? true : false` because `and` and `or` is a [short circuit][short-circuit] in Lua. 264 265 ## Filename Component 266 267 My next component is a filename component. I'd like to be able to see the filename without having to press `<C-G>` every time I want to check the filename and its full path. 268 269 ```lua 270 M.get_filename = function(self) 271 if self:is_truncated(self.trunc_width.filename) then return " %<%f " end 272 return " %<%F " 273 end 274 ``` 275 276 Depending on the current window width, it will display an absolute path, relative path to our `$CWD`, or just the current filename. 277 278 The `%<` is to tell the statusline to truncate this component if it's too long or doesn't have enough space instead of truncating the first component. 279 280 ## Filetype Component 281 282 I want to see the filetype of the current buffer, so I'd like to include this on my statusline as well. 283 284 ```lua 285 M.get_filetype = function() 286 local file_name, file_ext = fn.expand("%:t"), fn.expand("%:e") 287 local icon = require'nvim-web-devicons'.get_icon(file_name, file_ext, { default = true }) 288 local filetype = vim.bo.filetype 289 290 if filetype == '' then return '' end 291 return string.format(' %s %s ', icon, filetype):lower() 292 end 293 ``` 294 295 It gets a value from `vim.bo.filetype` which will return a filetype and I transform it to lowercase using the `lower()` method. If the current buffer doesn't have a filetype, it will return nothing. 296 297 I also use [nvim-web-devicons][nvim-web-devicons] to get the fancy icon for the current filetype. 298 299 ## Line Component 300 301 Even though I have `number` and `relativenumber` turned on, I'd like to have this on my statusline as well. 302 303 ```lua 304 M.get_line_col = function(self) 305 if self:is_truncated(self.trunc_width.line_col) then return ' %l:%c ' end 306 return ' Ln %l, Col %c ' 307 end 308 ``` 309 310 It will display something like `Ln 12, Col 2` which means the cursor is at Line 12 and Column 2. This component also depends on the current window width, if it's not wide enough then it will display something like `12:2`. 311 312 ## LSP Diagnostic 313 314 I use the built-in LSP client and it has the diagnostic capability. I can get the diagnostic summary using `vim.lsp.diagnostic.get_count(bufnr, severity)`. 315 316 ```lua 317 M.get_lsp_diagnostic = function(self) 318 local result = {} 319 local levels = { 320 errors = 'Error', 321 warnings = 'Warning', 322 info = 'Information', 323 hints = 'Hint' 324 } 325 326 for k, level in pairs(levels) do 327 result[k] = vim.lsp.diagnostic.get_count(0, level) 328 end 329 330 if self:is_truncated(self.trunc_width.diagnostic) then 331 return '' 332 else 333 return string.format( 334 "| :%s :%s :%s :%s ", 335 result['errors'] or 0, result['warnings'] or 0, 336 result['info'] or 0, result['hints'] or 0 337 ) 338 end 339 end 340 ``` 341 342 I got this section from [this repo][lsp-status] with some modification. It will be hidden when the current window width is less than `120`. I don't personally use this because I use a small monitor. 343 344 <Update date="2021-07-23"> 345 346 I display this at my `tabline` instead since nvim-bufferline now supports custom section. [Here's][bufferline-lua] the relevant file for that. It will show the available diagnostics at the top right corner of the screen and update them in real-time. 347 348 </Update> 349 350 # Different Statusline 351 352 I want to have 3 different statusline for different states which are _Active_ for the currently active window, _Inactive_ for the inactive window, and _Explorer_ for the file explorer window. 353 354 ## Active Statusline 355 356 I combine all of my components as follows. 357 358 ```lua 359 M.set_active = function(self) 360 local colors = self.colors 361 362 local mode = colors.mode .. self:get_current_mode() 363 local mode_alt = colors.mode_alt .. self.separators[active_sep][1] 364 local git = colors.git .. self:get_git_status() 365 local git_alt = colors.git_alt .. self.separators[active_sep][1] 366 local filename = colors.inactive .. self:get_filename() 367 local filetype_alt = colors.filetype_alt .. self.separators[active_sep][2] 368 local filetype = colors.filetype .. self:get_filetype() 369 local line_col = colors.line_col .. self:get_line_col() 370 local line_col_alt = colors.line_col_alt .. self.separators[active_sep][2] 371 372 return table.concat({ 373 colors.active, mode, mode_alt, git, git_alt, 374 "%=", filename, "%=", 375 filetype_alt, filetype, line_col_alt, line_col 376 }) 377 end 378 ``` 379 380 The `%=` acts like a separator. It will place all of the next components to the right, since I want my filename indicator to be in the middle, I put 2 of them around my filename indicator. It will basically center it. You can play around with it and find which one you like. 381 382 ## Inactive Statusline 383 384 I want this inactive statusline to be as boring as possible so it won't distract me. 385 386 ```lua 387 M.set_inactive = function(self) 388 return self.colors.inactive .. '%= %F %=' 389 end 390 ``` 391 392 It's just displaying the full path of the file with a dimmed colour, super simple. 393 394 ## Inactive Statusline 395 396 I have [nvim-tree.lua][nvim-tree-lua] as my file explorer and I want to have different statusline for it, so I made this simple statusline. 397 398 ```lua 399 M.set_explorer = function(self) 400 local title = self.colors.mode .. ' ' 401 local title_alt = self.colors.mode_alt .. self.separators[active_sep][2] 402 403 return table.concat({ self.colors.active, title, title_alt }) 404 end 405 ``` 406 407 ## Dynamic statusline 408 409 I use metatable to set the statusline from autocmd because the `:` symbol conflicts with VimL syntax. I'm probably going to change this once Neovim has the ability to define autocmd using Lua natively. 410 411 ```lua 412 Statusline = setmetatable(M, { 413 __call = function(statusline, mode) 414 return self["set_" .. mode](self) 415 end 416 }) 417 418 api.nvim_exec([[ 419 augroup Statusline 420 au! 421 au WinEnter,BufEnter * setlocal statusline=%!v:lua.Statusline('active') 422 au WinLeave,BufLeave * setlocal statusline=%!v:lua.Statusline('inactive') 423 au WinEnter,BufEnter,FileType NvimTree setlocal statusline=%!v:lua.Statusline('explorer') 424 augroup END 425 ]], false) 426 ``` 427 428 This auto command runs every time we enter or leave a buffer and set the corresponding statusline. It needs to be done using VimL because it doesn't have lua version _yet_. It's currently a [work in progress][autocmd] at the time of writing this post. 429 430 # Result 431 432 Here's how the entire file looks. 433 434 ```lua 435 local fn = vim.fn 436 local api = vim.api 437 438 local M = {} 439 440 -- possible values are 'arrow' | 'rounded' | 'blank' 441 local active_sep = 'blank' 442 443 -- change them if you want to different separator 444 M.separators = { 445 arrow = { '', '' }, 446 rounded = { '', '' }, 447 blank = { '', '' }, 448 } 449 450 -- highlight groups 451 M.colors = { 452 active = '%#StatusLine#', 453 inactive = '%#StatuslineNC#', 454 mode = '%#Mode#', 455 mode_alt = '%#ModeAlt#', 456 git = '%#Git#', 457 git_alt = '%#GitAlt#', 458 filetype = '%#Filetype#', 459 filetype_alt = '%#FiletypeAlt#', 460 line_col = '%#LineCol#', 461 line_col_alt = '%#LineColAlt#', 462 } 463 464 M.trunc_width = setmetatable({ 465 mode = 80, 466 git_status = 90, 467 filename = 140, 468 line_col = 60, 469 }, { 470 __index = function() 471 return 80 472 end 473 }) 474 475 M.is_truncated = function(_, width) 476 local current_width = api.nvim_win_get_width(0) 477 return current_width < width 478 end 479 480 M.modes = setmetatable({ 481 ['n'] = {'Normal', 'N'}; 482 ['no'] = {'N·Pending', 'N·P'} ; 483 ['v'] = {'Visual', 'V' }; 484 ['V'] = {'V·Line', 'V·L' }; 485 [''] = {'V·Block', 'V·B'}; -- this is not ^V, but it's , they're different 486 ['s'] = {'Select', 'S'}; 487 ['S'] = {'S·Line', 'S·L'}; 488 [''] = {'S·Block', 'S·B'}; -- same with this one, it's not ^S but it's 489 ['i'] = {'Insert', 'I'}; 490 ['ic'] = {'Insert', 'I'}; 491 ['R'] = {'Replace', 'R'}; 492 ['Rv'] = {'V·Replace', 'V·R'}; 493 ['c'] = {'Command', 'C'}; 494 ['cv'] = {'Vim·Ex ', 'V·E'}; 495 ['ce'] = {'Ex ', 'E'}; 496 ['r'] = {'Prompt ', 'P'}; 497 ['rm'] = {'More ', 'M'}; 498 ['r?'] = {'Confirm ', 'C'}; 499 ['!'] = {'Shell ', 'S'}; 500 ['t'] = {'Terminal ', 'T'}; 501 }, { 502 __index = function() 503 return {'Unknown', 'U'} -- handle edge cases 504 end 505 }) 506 507 M.get_current_mode = function(self) 508 local current_mode = api.nvim_get_mode().mode 509 510 if self:is_truncated(self.trunc_width.mode) then 511 return string.format(' %s ', self.modes[current_mode][2]):upper() 512 end 513 return string.format(' %s ', self.modes[current_mode][1]):upper() 514 end 515 516 M.get_git_status = function(self) 517 -- use fallback because it doesn't set this variable on the initial `BufEnter` 518 local signs = vim.b.gitsigns_status_dict or {head = '', added = 0, changed = 0, removed = 0} 519 local is_head_empty = signs.head ~= '' 520 521 if self:is_truncated(self.trunc_width.git_status) then 522 return is_head_empty and string.format(' %s ', signs.head or '') or '' 523 end 524 525 return is_head_empty and string.format( 526 ' +%s ~%s -%s | %s ', 527 signs.added, signs.changed, signs.removed, signs.head 528 ) or '' 529 end 530 531 M.get_filename = function(self) 532 if self:is_truncated(self.trunc_width.filename) then return " %<%f " end 533 return " %<%F " 534 end 535 536 M.get_filetype = function() 537 local file_name, file_ext = fn.expand("%:t"), fn.expand("%:e") 538 local icon = require'nvim-web-devicons'.get_icon(file_name, file_ext, { default = true }) 539 local filetype = vim.bo.filetype 540 541 if filetype == '' then return '' end 542 return string.format(' %s %s ', icon, filetype):lower() 543 end 544 545 M.get_line_col = function(self) 546 if self:is_truncated(self.trunc_width.line_col) then return ' %l:%c ' end 547 return ' Ln %l, Col %c ' 548 end 549 550 551 M.set_active = function(self) 552 local colors = self.colors 553 554 local mode = colors.mode .. self:get_current_mode() 555 local mode_alt = colors.mode_alt .. self.separators[active_sep][1] 556 local git = colors.git .. self:get_git_status() 557 local git_alt = colors.git_alt .. self.separators[active_sep][1] 558 local filename = colors.inactive .. self:get_filename() 559 local filetype_alt = colors.filetype_alt .. self.separators[active_sep][2] 560 local filetype = colors.filetype .. self:get_filetype() 561 local line_col = colors.line_col .. self:get_line_col() 562 local line_col_alt = colors.line_col_alt .. self.separators[active_sep][2] 563 564 return table.concat({ 565 colors.active, mode, mode_alt, git, git_alt, 566 "%=", filename, "%=", 567 filetype_alt, filetype, line_col_alt, line_col 568 }) 569 end 570 571 M.set_inactive = function(self) 572 return self.colors.inactive .. '%= %F %=' 573 end 574 575 M.set_explorer = function(self) 576 local title = self.colors.mode .. ' ' 577 local title_alt = self.colors.mode_alt .. self.separators[active_sep][2] 578 579 return table.concat({ self.colors.active, title, title_alt }) 580 end 581 582 Statusline = setmetatable(M, { 583 __call = function(statusline, mode) 584 if mode == "active" then return statusline:set_active() end 585 if mode == "inactive" then return statusline:set_inactive() end 586 if mode == "explorer" then return statusline:set_explorer() end 587 end 588 }) 589 590 -- set statusline 591 -- TODO: replace this once we can define autocmd using lua 592 api.nvim_exec([[ 593 augroup Statusline 594 au! 595 au WinEnter,BufEnter * setlocal statusline=%!v:lua.Statusline('active') 596 au WinLeave,BufLeave * setlocal statusline=%!v:lua.Statusline('inactive') 597 au WinEnter,BufEnter,FileType NvimTree setlocal statusline=%!v:lua.Statusline('explorer') 598 augroup END 599 ]], false) 600 601 ----[[ 602 -- NOTE: I don't use this since the statusline already has 603 -- so much stuff going on. Feel free to use it! 604 -- credit: https://github.com/nvim-lua/lsp-status.nvim 605 -- 606 -- I now use `tabline` to display these errors, go to `_bufferline.lua` if you 607 -- want to check that out 608 ----]] 609 -- Statusline.get_lsp_diagnostic = function(self) 610 -- local result = {} 611 -- local levels = { 612 -- errors = 'Error', 613 -- warnings = 'Warning', 614 -- info = 'Information', 615 -- hints = 'Hint' 616 -- } 617 618 -- for k, level in pairs(levels) do 619 -- result[k] = vim.lsp.diagnostic.get_count(0, level) 620 -- end 621 622 -- if self:is_truncated(120) then 623 -- return '' 624 -- else 625 -- return string.format( 626 -- "| :%s :%s :%s :%s ", 627 -- result['errors'] or 0, result['warnings'] or 0, 628 -- result['info'] or 0, result['hints'] or 0 629 -- ) 630 -- end 631 -- end 632 ``` 633 634 And here's the result. 635 636  637 638 Also a [preview video](https://streamable.com/arzm3q) for a better demonstration. As you can see in the video, they change their appearance based on the window width. 639 640 That's the active statusline, I don't think I need to put a screenshot for the inactive one because nothing is interesting going on there :p. 641 642 Here's [my statusline file](https://github.com/elianiva/dotfiles/blob/master/nvim/.config/nvim/lua/modules/_statusline.lua) for a reference. 643 644 <Update date="2021-06-17"> 645 646 I've changed [my statusline][new-line] quite a bit so it won't look the same as the one you see in this post. 647 648 </Update> 649 650 There are also some great statusline plugins written in lua if you want to get started quickly such as [tjdevries/express_line.nvim][expressline], [glepnir/galaxyline.nvim][galaxyline], [adelarsq/neoline.vim][neoline] and so on. 651 652 # Closing Note 653 654 I really like how it turned out, Lua support on Neovim is probably the best update I've ever experienced. It makes me want to play around with Neovim's API even more. Kudos to all of Neovim contributors! 655 656 Anyway, thanks for reading, and gave a great day! :)