Adding types to your Neovim configuration
Once again I have started customizing my Neovim’s configuration, so that I can improve my productivity and enjoy every single click on my keyboard at work. After spending hours updating my init.lua
and checked out some plugins I have never heard of, I noticed quite a few plugins now include an annotation comment @type
for typing their configuration’s arguments.
require("lazy").setup({
spec = {
{
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
opts = {
},
}
};
})
As Lua is a weakly-typed language, just like Javascript, adding type support to it is a non-trivial task. Instead of replacing Lua with a typed superset, for example Teal, using annotations is a much more straight-forward approach, as users do not need to rewrite their code and adapt a building process that compiles codes from a typed variant back to Lua. They only need to set up LSP diagnostics support with the editor to enjoy those typings. I believe that is the right approach for improving Lua, given its relative small community.
Rather than getting type suggestions from lua-language-server
after adding the annotation, I got the following LSP warning.
Undefined type or alias `snacks.Config`. Lua Diagnostics. (undefined-doc-name)
I tried to require snacks
before that type annotation, but lua-language-server
still refused to pick up the type. At the end, I found out that I have to update the configuration of lua-language-server
, so it can analyze all libraries that are imported in a workspace. As I am using lazy.nvim
to manage my plugins, all plugins are installed at $XDG_DATA_HOME/nvim/lazy
, so I just need to add that path to the configuration.
require("lazy").setup({
spec = {
{
"neovim/nvim-lspconfig",
version = "1.6.0",
event = { "BufReadPre", "BufNewFile" },
config = function()
local lspconfig = require("lspconfig")
lspconfig.lua_ls.setup({
capabilities = capabilities,
on_init = function(client)
client.server_capabilities.semanticTokensProvider = nil
if client.workspace_folders then
local path = client.workspace_folders[1].name
if
path ~= vim.fn.stdpath("config")
and (
vim.loop.fs_stat(path .. "/.luarc.json")
or vim.loop.fs_stat(path .. "/.luarc.jsonc")
)
then
return
end
end
client.config.settings.Lua = vim.tbl_deep_extend(
"force",
client.config.settings.Lua,
{
runtime = {
version = "LuaJIT",
},
workspace = {
checkThirdParty = false,
library = {
"$VIMRUNTIME",
"$XDG_DATA_HOME/nvim/lazy",
"${3rd}/luv/library",
},
},
})
end,
})
end,
},
},
})
After updating my configuration, the LSP is able to pick up types and give suggestions, even without an explicit type annotation.
Avoid global configuration with .luarc.json
Configuration for lua-language-server
in init.lua
is global, and it will load and analyze types from $XDG_DATA_HOME/nvim/lazy
, even if your project does not require any module from it, slowing down the LSP server start up time. A better option is to use .luarc.json
to customize lua-language-server
. By creating this configuration file at the root level of your project, only libraries specific to that project will be analyzed.
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"workspace.library": ["$VIMRUNTIME", "$XDG_DATA_HOME/nvim/lazy", "${3rd}/luv/library"]
}