Setting up Neovim Tree-sitter and Built-in LSP

2023-09-04

In this article we will go into setting up tree-sitter and the built-in LSP in Neovim.

It won't cover some of the extra plugins for completion, but I will go over some of the more common plugins associated with the LSP such as mason.

I hope you enjoy!

Tree-sitter and Syntax Highlighting

Tree-sitter already comes built-in with Neovim, and is a parsing tool/library. It’s very easy to set up and definitely worth doing. It’s written in pure C so it’s fast, efficient, and has no external dependencies.

To keep it simple, tree-sitter improves syntax highlighting in Neovim, although sometimes you won’t see any noticeable difference until you install a language server (which we will do in the LSP section).

The configuration code is as simple as writing the following:

-- .config/nvim/lua/plugins/treesitter-setup.lua

require("nvim-treesitter.configs").setup({
  ensure_installed = { "c", "lua", "vim", "vimdoc", "query" },
})

In wherever this fits into your own config setup. Just replace the languages in ensure_installed with the languages you want parsers for.

The full list of languages and additional configuration options are available here.

Tree-sitter and Colorschemes

When picking a Neovim colorscheme make sure that it’s tree-sitter supported/compatible - Awesome Neovim provides a list of themes which you can use.

Introduction to The LSP

The LSP is what you can use to give Neovim extra features that you might associate with VSCode or other ide-like code/text editors.

LSP stands for Language Server Protocol. Neovim ships with a built-in LSP client, and it uses the language server protocol to communicate with these different servers for each language.

These servers make it possible to get features such as go-to-definition, auto-complete and syntax errors (like if you spell something wrong or miss a semi-colon - so you don’t rip your hair out trying to find it).

It’s worth noting that these features don’t technically ship directly with Neovim when the language server is configured. It enables the use of them, and you have to get plugins for each feature you want (unix philosophy, baby).

lsp-config

The first plugin is called lsp-config and is provided by the Neovim team.

This is just an abstraction for you to be able to quickly enable language servers you have installed. mason (optional)

Remember those language servers we just talked about? Well the lsp-config enables them, but it’s still up to you to install them.

Mason is an easy way to install these language servers for the programming languages of your choice if you don’t want to install and maintain them yourself.

Each language server can be installed via a package manager - but often in a different way. For example, the language servers for HTML/CSS/JS are provided by Microsoft, and are installed via npm. Whereas the language server for say, Rust, might be managed by rustup (or Rust itself).

Keep this in mind, as you will need to have the relevant package manager installed based on what language servers you need. Microsoft keeps a list of LSP servers where you can check the repositories to see which package manager (if any) you will need to install it.

If you’re using mason, once you open Neovim simply type :Mason as a command to list the language servers available. You can use the standard vim keybindings to navigate the menu and use i to install the language servers you desire.

mason-lspconfig (optional)

You can extend this with another plugin called mason-lspconfig, which just gives you some QoL stuff like ensuring servers are installed, automatic installation if they aren't etc.

It’s more just for error handling and isn’t strictly necessary; I personally use it alongside mason though.

Assuming you have installed both mason and mason-lspconfig with your plugin manager, the following code runs both on startup.

require("mason").setup()
require("mason-lspconfig").setup {
    ensure_installed = { "lua_ls", "rust_analyzer" },
}

The extra setup options for mason-lspconfig are the extra features such as ensuring language servers are installed and configured correctly, and automatically installing any that don’t exist on your system.

Following this, just add the line require("lspconfig").rust_analyzer.setup {} for each language server you want to set up (replace rust_analyzer with the language server of your choice). And that’s it! wq and re-open Neovim, and the language server should already be working.

Troubleshooting

If you load up a file and aren’t seeing any errors come up where they should be, there’s a couple things to initially check for:

I personally think it’s pretty cool that out of the box, on setup, no “real” third-party LSP plugins that you start getting syntax errors, warnings, etc.

Go-to-definition, Hover definitions, and Keybindings

In the documentation for lspconfig it also provides a list of example keybindings you can use for other LSP features - including diagnostics (i.e. warnings/errors) and definitions.

TJ (a core member of the Neovim team) has a video on this which goes into much more detail.

Regardless, I’ll explain in more detail below.

Diagnostic Keybindings

The first of these keybindings are pulled straight from lspconfig readme on github. The only adjustments I made was changing space to the leader key (which happens to still be space for me anyway).

vim.keymap.set('n', '<leader>do', vim.diagnostic.open_float)
vim.keymap.set('n', '<leader>dp', vim.diagnostic.goto_prev)
vim.keymap.set('n', '<leader>dn', vim.diagnostic.goto_next)
vim.keymap.set('n', '<leader>dl', "<cmd>Telescope diagnostics<cr>")

All of these keybinds make use of Neovim’s built-in diagnostic feature to open up warnings or errors raised by the LSP.

Most of these are self-explanatory (goto_prev goes to the previous error, goto_next goes to the next error, etc.)

open_float opens the error is a small pop-up window in full, and you can repeat the keymap to bring your cursor “inside” the pop-up (use q to quit).

I’ve also set a keymap using the Telescope plugin, which shows a list of all the errors in the file and you can navigate through them.

LSP Keybindings

LSP keymaps are a little more complex, only because they require a little more Lua and Neovim magic. Once again, all of the code for this is in the lspconfig docs.

I’ve provided a stripped down version below.

vim.api.nvim_create_autocmd('LspAttach', {
  group = vim.api.nvim_create_augroup('UserLspConfig', {}),
  callback = function(ev)
    local opts = { buffer = ev.buf }
    vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
    vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, opts)
    vim.keymap.set('n', '<space>f', function()
      vim.lsp.buf.format { async = true }
    end, opts)
  end,
})

The autocmd created in the example code here is so the keybindings only activate when you are in a buffer which has an LSP client attached. The reason for this is so you can re-use keybindings which may do similar things outside of LSP buffers, for example exploring Neovim documentation with K.

In vim, K takes you to the documentation of the word you are hovering over. The LSP follows a similar kind of thing, where K will instead show you a documentation window of whatever symbol you are hovering over.

So there is a lot of stuff built into Neovim - both the LSP client and just standard Vim features which make the vanilla experience way better than you might expect.

The most common functions are:

And you can see the full list by typing :h vim.lsp.buf.*. Remember, this is your Neovim config, so set it up the way that you want!

Caveats

Each language server is different, so you might find for the languages you want to code in that some of the functions you want to use don’t work or work in a different manner.

What About Completions? And Conclusion

Neovim does actually have some form of built-in completions with omnifunc, which is actually activated if you copy and pasted the code from the lspconfig docs. However in my opinion I think manual completion isn’t really necessary, and auto-completion works a lot better if you do happen to want it.

Auto-complete is typically setup through a plugin called nvim-cmp and isn’t part of the built-in LSP which I wanted to focus on today. I am also personally a big believer in using an IDE for IDE work - debugging etc, so I’m not interested in setting up the debugger for Neovim.

Regardless of this, I hope you learned something about Neovim’s built-in LSP and how many features it actually has. If you want to learn how to install nvim-cmp, TJ goes over it in this video (same video I linked previously).

I hope you enjoyed!