/ src / content / posts / neovim-lua-statusline.mdx
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  ![gitstatus](/assets/posts/neovim-lua-statusline/gitstatus.png)
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  ![result](/assets/posts/neovim-lua-statusline/result.png)
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! :)