Chapter 10. Programming Language Support

Visual Studio Code brought the world the concept of language servers, and all other text editors jumped on the idea. Early incarnations of language server protocol in Vim were frustrating and clunky and required plugins that tended to be fragile and complicated.

Then Neovim decided to build support for language servers into the editor itself. Neovim’s built-in support is still frustrating and clunky, but over time, robust and simple plugins have evolved to make the language server experience almost automatic. LazyVim represents the pinnacle of that evolution.

In addition, Neovim also has built-in support for TreeSitter, a powerful library for parsing and identifying abstract syntax trees in source code while it is being edited, and LazyVim is configured with the plugins needed to make TreeSitter Just Work™.

Language Server protocol gives us support for things like code navigation, signature help, auto-completion, certain highlighting and formatting behaviours, diagnostics, and more. TreeSitter gives us better syntax highlighting, code folding, and syntax based navigation such as provided by the S command you already know.

There are two main tools for working with language servers in LazyVim: various language Lazy Extras, and the Mason.nvim plugin. We’ll get to know both of these and then learn how to better use some of the tooling they provide.

10.1. The lang.* Lazy Extras

We’ve already used LazyVim extras for plugin configuration, and I told you to install the extras for whichever languages you use regularly. These extras include preconfigured plugins that give best-in-class support for common programming languages. Most ship with preconfigured language servers and many include additional Neovim plugins that are useful with those languages.

Once you install these extras things will usually work out of the box, and you won’t have to learn any new keybindings for the commands each language provides. However, it wouldn’t hurt to read the Readmes for the plugins the extra installs (accessible by looking up the Extra’s documentation on the LazyVim website and clicking the headings) to make sure you aren’t missing out on any commands the language provides. For example, the Python extra ships with the venv-selector.nvim plugin that allows you to activate many types of Python virtual environments either automatically or on demand. LazyVim installs a keybinding to open the virtualenv selector using <Space>cs where <Space>c is the “Code” submode.

10.2. Mason.nvim

The Lazy Extras may not install everything you need. For example, instead of the default Typescript formatting and linting tools, I prefer to use a new hyper-fast up-and-comer called Biome.

To install things like this, you can use the Mason.nvim plugin, which is pre-installed with LazyVim. To open Mason, use the <Space>cm keybinding. The window that pops up looks similar to the Lazy.nvim and Lazy Extras floating window, although it ships with annoyingly unrelated keybindings.

Mason is a very large database of programming language support tooling, including language servers, formatters, and linters, along with the instructions to install them.

Mason.nvim does assume a certain baseline is already installed on your system; for example if you are going to install something that is Rust-based, you better have a cargo binary, and if you are going to install something that requires Python support, Python and pip need to be available. In most cases, if you are coding in a given language, you already have the tools Mason needs to do its thing. The main thing that Mason takes care of is ensuring that the tools are installed in such a way that other Neovim plugins can find them.

The hardest task with Mason is knowing what tool you want to install. I was already using Biome when I set up LazyVim, so I knew I was going to need to install editor support for it. That was no problem; just find biome in the Mason list (like any window, it is scrollable, searchable, and seekable, and Mason helpfully puts everything in alphabetical order).

But when I started working on this book, I decided I needed an advanced Markdown formatter, and I had no idea which one to use. I could search the window for markdown and then press Enter on any matching lines, which gives a description and some other information, but I had to do some research with a web browser, (along with a little trial and error) before I found the right tool for me.

Unfortunately, I can’t help you with figuring out what is right for you, but once you find the tool in Mason, just use i to install the package under the cursor. The only other command you will use frequently in Mason is Shift-U to update all installed tools, and you can look up the rest with g?.

10.3. Validating Things Installed Cleanly

As good as both LazyExtras and Mason are at installing language servers, linting, and formatting tools, setting them up is one of the places most likely to go wrong, no matter which editor you are using. So now is a good time to introduce several commands to validate that things are working as expected.

First, LazyVim pops up notifications in the upper right corner, as you have seen with the plugin updates. These disappear after a few seconds. Every once in a while, you need to be able to refer back to them.

The secret is to use the keybinding <Space>sn to open the “Noice” search menu. Noice is the plugin that provides those little popups. Most often, you’ll want to follow this with either an a or an l to see all recent Noice messages, or just the last one.

You can also use <Space>snd to dismiss any currently open notifications, but honestly, by the time you’ve completed those four keystrokes, they notifications have probably disappeared themselves already!

The second command you’ll need for debugging LSPs is <Space>cl, which runs the command :LspInfo. It displays information about any language servers that are currently running and which buffers they are attached to. For example, while editing a Markdown document, my LSPInfo window looks like this:

lsp info dark
Figure 44. Lspinfo Window

In this case, everything looks fine (though I’m surprised the tailwind server is associated with Markdown), but if your LSP isn’t behaving correctly, this window might give you a hint as to what the problem might be.

If your LSP is having temporary problems—​like showing incorrect diagnostics or unable to find a file you know is there—​sometimes it just needs to be given a good kick with :LspRestart. The Svelte language server has a nasty habit of not picking up new files, so I’ve been using this one often enough lately to add a keybinding for it.

Two other super useful commands are :checkhealth and :LazyHealth. Both provide information about the health of various installed plugins. The former is a Neovim command that plugins can register themselves with to provide plugin health information, while the latter provides LazyVim-specific information. There is a lot of overlap in the output, but I find the :LazyHealth output is easier to read, and the :checkhealth output to be a bit more comprehensive. So I usually use :LazyHealth first and switch to checkhealth only if :LazyHealth didn’t yield the answer I need.

Don’t expect to see green check marks across the board; you’ll make yourself crazy. For example, my checkhealth output contains a bunch of warnings from Mason:

lazy health dark
Figure 45. Lazy Health Warnings

Tools that I have used recently (and also Ruby for some reason) are installed, and I have warnings for languages that I don’t generally need to edit files in. So if you don’t code in Java, there’s no reason to waste cycles trying to make the java warning go away.

10.4. Diagnostics

Language Servers fulfill several useful functions, including identifying code problems, linting, formatting, context-aware code navigation, and documentation. We’ll discuss all of these between this and the next chapter.

We already got a peek at diagnostics in Chapter 7, when we discussed jumping between error messages with the unimpaired keybindings [d, [w, [e, ]d, ]w, and ]e. Diagnostics show up as little squiggles under specific sections of text and when you jump to them, you usually get a small overlay window telling you what is wrong at that location. For example, I have a simple typo causing an error in this screenshot:

diagnostic dark
Figure 46. Diagnostic Overlay

I misspelled “tracingMiddleware”, and I get a helpful error message on that line in the virtual text, and a window pops up when I navigate to that error with ]d. This window sometimes has more information than the virtual text. In addition, the line that imports the correctly spelled variable is showing a hint telling me that it isn’t used.

The colour of the diagnostic conveys the severity—​whether it is a hint, a warning, or an error—​so you can decide whether it is valuable to fix. I generally try to either fix or silence all diagnostics, as they become less useful if there is much noise.

If the window doesn’t pop up when you navigate to the diagnostic, you can use the <Space>cd keybinding to invoke it as long as your cursor is positioned somewhere within the underlined text. You can make the window disappear by moving your cursor with any motion key.

10.4.1. Trouble and Quick Fix

You can also navigate diagnostics using the Trouble menu. Trouble is a LazyVim plugin that provides an “enhanced quick fix” experience. Which is probably meaningless to you if you are new to Vim and don’t know what “quick fix” means!

The Quick Fix window is essentially a list of files and line numbers that have been tagged as “interesting” for some reason, where that reason depends on context. It can be used to represent multi-file search results, diagnostics, compiler error messages, and more, depending on how you open it. You can easily hop between the targeted locations, making changes or corrections without losing the context of what you were originally searching for.

In its simpler form, Trouble is the same thing, just a little bit prettier to look at, with colours, icons, and nice groupings when fix locations are in multiple files.

The contents of the Quick Fix and Trouble windows depend entirely on how you open them. Most of them are accessible from the <space>x (I assume the x stands for “fiX”) menu, which looks something like this:

diagnostics menu dark
Figure 47. Diagnostic Menu

Let’s take to-dos as an example, as I have a lot of them in this book. It’s weird saying that because they’ll be gone by the time you see it, but this screenshot will live on:

todos dark
Figure 48. To-Dos List

The cool thing about this list of locations is that they are not all in the same file. Without Trouble, I could navigate between to-dos in the current file using the [t and ]t keys. However, using Trouble, I can navigate between to-dos in multiple files by moving my cursor to the appropriate line in the Trouble window and hitting Enter. It will open the file and move the cursor right to the “troubling” line.

Or you can use the commands [q and ]q, which will navigate between Quick Fix OR Trouble locations, no matter which file they are in, without ever focusing the Trouble window.

For diagnostics, open the Trouble menu with <Space>xx or <Space>xX. The lowercase version shows the diagnostics in the current file for a quick overview while the “but bigger” uppercase X shows all the diagnostics in the current workspace (although it depends a bit on the language servers; some language servers only show you diagnostics for all currently open buffers, not the whole project).

If you’re wondering what the “Location List” is, it’s a Quick Fix window that is associated with the current window (NOT buffer). I never use it; my brain can only handle fixing one problem at a time, even if it is in hundreds of files!

10.5. Code Actions

One of the things that made VS Code seem magical when it came out was code actions. Not that they existed, as the concept has been around for a long time, but that they WORKED. Nowadays, I kind of take them for granted.

You may be used to accessing code actions by moving your hands to the mouse and clicking a light bulb or right clicking a diagnostic. In LazyVim it is (of course) a keybinding. Navigate to a diagnostic using whatever keybindings work for you (I live by ]d) and then invoke the <Space>ca menu where c and a mean “code action.” A picker menu will pop up with a list of any actions you can take. You can use the arrow keys or <Escape> followed by j and k to navigate between them, or you can enter a number or any text from the line to filter. Hit <Enter> to perform the action, or <Escape><Escape> to cancel the menu (just one escape allows you to enter Normal mode in the search box so you can use the many LazyVim navigation keystrokes that you are, by now, accustomed to).

10.6. Linting

Linting is mostly handled using the nvim-lint plugin instead of the LSP. This was a major pain point in my pre-LazyVim days because getting the LSP and linter cooperating often required some serious troubleshooting. And then throw formatting into the mix and I’d lose a day or two. To be fair, this was true when I used VS Code, too.

Using LazyVim, it is actually likely that you don’t know who is doing the linting for you. I honestly don’t. Some of my diagnostics come from the LSP and others come from the linter. I don’t bother to question the source of the errors; I just fix them.

The hard part with linting (at least, when it doesn’t work automatically) will be making sure that the appropriate linter is installed (Mason has your back here), and configured correctly. If you are lucky and the languages you love to work in have Lazy Extras, then it is probably already configured correctly. Otherwise, you may have to do a little tweaking. The tweaking involved is, sadly, language-dependent, but you’ll probably need something like this in a (for example) extend-nvim-lint.lua file in your plugins directory:

Listing 26. Nvim-lint Customization
return {
  "mfussenegger/nvim-lint",
  opts = {
    linters_by_ft = {
      typescript = {
        -- lint settings for Typescript
      }
    },
  },
}

Read :help nvim-lint for more information and refer to the LazyVim documentation for this configuration if you need further clarification.

The nice thing is that once you have your linting configured, the errors will show up using the same diagnostics described above and you can engage with them using the same keybindings, Trouble window, code actions, etc.

10.7. Formatting

Similar to linting, code formatting can be handled by some LSPs, but people have realized that using the language server is often more complicated than just invoking a formatter directly. So LazyVim ships with the conform.nvim plugin.

Also similar to linting, if you are lucky, it will Just Work™ after you install the appropriate Lazy Extra and/or Mason tool. However, if you don’t like the default formatter (or it’s not working), you will have to familiarize yourself with the LazyVim and conform.nvim documentation to figure out the exact incantation required.

The only formatter I’ve had to manually configure is using Prettier for Markdown. It looks eerily similar to the nvim-lint configuration:

Listing 27. Conform Customization
return {
  "stevearc/conform.nvim",
  opts = {
    formatters_by_ft = {
      ["markdown"] = { "prettier" },
    },
  },
}

Once it’s set up (I acknowledge this may be no mean feat), formatting in LazyVim is typically fire and forget: save your file and it formats. If you want to invoke it manually without saving, use the <Space>cf keybinding. I can’t stress how lucky you are that this is the case; without LazyVim, countless hours have been wasted trying to get the autocommands for “format on save” to work!

10.8. Configuring Non-standard LSPs

If you have installed an LSP that LazyVim isn’t aware of, you may need to tweak the nvim-lspconfig plugin. You will minimally need to let it know that your language server is available, and possibly to configure it to your needs. For example, one of my favourite programming languages is Rescript, which doesn’t have a huge ecosystem and therefore, has no LazyVim extra. I was able to install the language server with Mason easily enough, but I also needed to add the following to my extend-lspconfig.lua file for LazyVim to pick it up:

Listing 28. Third-party LSP server
return {
  "neovim/nvim-lspconfig",
  opts = {
    servers = {
      rescriptls = {},
    },
  },
}

As a second example, the css_variables language server, which I use with the excellent open-props CSS framework, works out of the box for css files, but I needed to use a different configuration to activate it in svelte files:

Listing 29. Css Variable LSP Config
return {
  {
    "neovim/nvim-lspconfig",
    opts = {
      servers = {
        css_variables = {
          filetypes = { "css", "scss", "less", "svelte" },
        },
      },
    },
  },
}

10.9. Summary

In this chapter, we learned how LazyVim integrates the language server protocol that VS Code brought to the world. It is usually quick and painless, which is more than can be said for manually configuring LSPs. However, there may be some headaches especially around linting and formatting. This is true in any editor, sometimes they hold your hand and sometimes they get in your way. If you get stuck, hit us up in the LazyVim discussions group on GitHub (but search it first; you’re probably not the first person to have trouble).

In the next chapter, we’ll learn more about navigating code using LSPs, TreeSitter, and several plugins.

Reminder

This book is freely available. It contains a textbook's worth of material. If you are deriving value from this content, I really appreciate any support you can give (both to me, and to those who can't afford to give it) by purchasing a copy of the book or through donation. Click here to see various purchase and donation options.

Copyright © 2024 Dusty Phillips