Posted on :: Source Code neovim declarative

I don't install and uninstall things. I declare things installed. My treesitter parsers in neovim should be no different!

In neovim, the treesitter parsers can (and frequently are), managed by nvim-treesitter. Its model of operation is rather simple: install some parsers, uninstall some parsers and you're good to go.

What if you update the plugin itself and the installed parsers need updating? Well, there's :TSUpdate, which you can run manually or bind to your package manager's plugin update event. For example in lazy.nvim, it's as simple as build = ':TSUpdate' in the plugin spec. This setup is almost perfect, except I am a nixos user, and nothing is ever perfect for me unless it is declarative.

"Today" (a couple days ago, really), I made it declarative because my states must be disposable and I want my list of parsers committed and tracked by git.

nvim-treesitter has everything needed to achieve this, so the solution is as simple as (open in context):

local function declare_parsers(parsers)
  local timeout_ms = 5 * 60 * 1000

  local treesitter = require('nvim-treesitter')
  treesitter.install(parsers):wait(timeout_ms)

  local stale_parsers = vim.tbl_filter(function(p)
    return not vim.list_contains(parsers, p)
  end, treesitter.get_installed())
  treesitter.uninstall(stale_parsers):wait(timeout_ms)

  treesitter.update():wait(timeout_ms)
end

Calling require'nvim-treesitter'.update() seems to be safe and free when parsers are already up to date - no need to trust any events.

Then, it's just a matter of:

declare_parsers({
  "nix",
  "rust",
  -- enough is enough, what else could one possibly want?
})

I also see no reason to not use a parser I declare installed, so as a bonus here's what I also do (open in context):

local group = vim.api.nvim_create_augroup("TreesitterHighlight", { clear = true })
vim.api.nvim_create_autocmd("FileType", {
  group = group,
  callback = function(args)
    local lang = vim.treesitter.language.get_lang(args.match)
    if not lang then
      return
    end
    local parser_available = vim.treesitter.language.add(lang)
    if not parser_available then
      return
    end
    vim.treesitter.start(args.buf, lang)
  end,
})