Skip to content

Advanced LSP Setup

LSP configuration is mostly done through the help of AstroLSP, the AstroNvim language server configuration engine plugin. This provides a simple to use interface for configuration while handling the complex integration between the AstroNvim features, nvim-lspconfig, mason.nvim and mason related tooling, and none-ls.nvim. For full documentation on how to use and configure this plugin you can check out the plugin’s README or run :h astrolsp in AstroNvim. All of the following recipes are provided through lazy.nvim plugin specifications and can be added to your own plugins.

Our main tool for configuring and setting up language servers is with the nvim-lspconfig plugin. This plugin provides configurations for many common language servers (A full list can be found in nvim-lspconfig server configurations documentation or :h lspconfig-all). These baseline configuration options are not always sufficient to meet everyone’s needs. For the complete set of options that can be used when configuraing language servers, check out :h lsp-config in your editor.

AstroLSP automatically enables language servers installed through Mason and for servers specified manually (See LSP Setup Without Installer). Neovim allows for easy language server customization within the after/lsp/ folder in the root of your configuration (Check out :h lsp-config). While the after/lsp/ directory is the recommended way to configure language servers, AstroLSP also provides a simple config table in the plugin’s options for extending the built in server configurations provided by nvim-lspconfig. This can be helpful if you want to conditionally make modifications in your plugin configuration.

lsp/clangd.lua
return {
capabilities = {
offsetEncoding = "utf-8",
},
}
lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
config = {
-- the key is the server name to configure
-- the value is the configuration table
clangd = {
capabilities = {
offsetEncoding = "utf-8",
},
},
},
},
}

nvim-lspconfig is great, but it doesn’t support all language servers that exist. You may want to set up a custom server where you manually define the cmd and the root_markers. This can be achieved by putting a new file in your lsp/ folder at the root of your configuration with the necessary information. This can also be done completely through servers and config tables similar to configuring servers that are supported by nvim-lspconfig! For these custom servers, the minimum requirement is defining a cmd in the config entry, but to get automatic starting of language servers you also need to set filetypes and root_markers. Here is a simple example setting up a Prolog LSP with swipl:

lsp/prolog_lsp.lua
return {
cmd = {
"swipl",
"-g",
"use_module(library(lsp_server)).",
"-g",
"lsp_server:main",
"-t",
"halt",
"--",
"stdio",
},
filetypes = { "prolog" },
root_markers = { "pack.pl" },
}
lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
servers = { "prolog_lsp" }, -- always necessary to tell AstroNvim to enable the language server
config = { -- only necessary if not configured through `lsp/`
prolog_lsp = {
cmd = {
"swipl",
"-g",
"use_module(library(lsp_server)).",
"-g",
"lsp_server:main",
"-t",
"halt",
"--",
"stdio",
},
filetypes = { "prolog" },
root_markers = { "pack.pl" },
},
},
},
}

AstroNvim comes with mason-lspconfig as an easy interface for setting up language servers installed with Mason, but this might not be adequate for all users. The LSP installer doesn’t support all of the language servers that Neovim’s LSP config supports and some users may already have the language servers installed on their machine and don’t want to reinstall it separately. In these cases we have added an easy interface for enabling these servers. The following plugin specification for AstroLSP simply sets up pyright language server for a user with pyright already available on their system:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
servers = { "pyright" },
},
}

AstroNvim has made formatting on save part of the default functionality out of the box. If you don’t want your code to get auto formatted on save, you can disable it in the AstroLSP configuration:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
format_on_save = false, -- enable or disable automatic formatting on save
},
},
}

We have also added an extension to just true or false for this option to give more the user the ability to disable the auto formatting for specific filetypes. For example:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
format_on_save = {
enabled = true, -- enable or disable
ignore_filetypes = { -- disable format on save for specified filetypes
"markdown",
"python",
},
},
},
},
}

If you would rather use a whitelist of filetypes for formatting on save rather than a blacklist type model, you can do that as well with the allow_filetypes table. If you have allow_filetypes it will take precedence over ignore_filetypes. So please only use one of these options at a time. Here is an example:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
format_on_save = {
enabled = true, -- enable or disable
allow_filetypes = { -- only allow formatting on save for these filetypes
"lua",
"python",
},
},
},
},
}

For even more control, you can provide a filter function with the key filter. This function takes a single parameter of the current buffer number and returns a boolean value of whether you want to format on save or not (true means format, false means do not format). This function will run on each save to calculate if it should format.

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
enabled = true, -- enable format on save
filter = function(bufnr)
-- any lua logic...
return true -- return boolean whether or not to format
end,
},
},
}

With the formatting on save enabled, we have also provided the mapping <Leader>uf and <Leader>uF to toggle the auto formatting temporarily for either the current buffer or globally (Note: Format on save must be enabled in the AstroLSP formatting options for this option and keybinding to do anything).

Since Neovim v0.8 there have been improvements to how language servers are used for formatting files. Previously Neovim could only use a single language server to format files at a time and would prompt on each format if multiple were available. This led to users disabling formatting capabilities for different language servers and losing that functionality all together for convenience. Now you are able to format with many formatters at the same time and filter them with a function. To empower this, AstroNvim has a configuration option for controlling what formatters are used. This can be done either with a filter function or a list of disabled clients.

Disabling formatting for a filter function

Section titled “Disabling formatting for a filter function”

Using the filter option you can supply filter function to be run on each client that has formatting capabilities and if it returns true then it will be used for formatting and if it returns false then it will not be used. This applies to whenever you format your code either on save, with <Leader>lf, or with :Format.

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
filter = function(client)
-- disable formatting for lua_ls
if client.name == "lua_ls" then
return false
end
-- only enable null-ls for javascript files
if vim.bo.filetype == "javascript" then
return client.name == "null-ls"
end
-- enable all other clients
return true
end,
},
},
}

Disabling formatting for a list of language servers

Section titled “Disabling formatting for a list of language servers”

Using the disabled option you can supply an array like list of language server client names and those clients will be disabled with you format your code either on save, with <Leader>lf, or with :Format.

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
disabled = {
"lua_ls",
"rust_analyzer",
},
},
},
}

Using both filter function and disabled list

Section titled “Using both filter function and disabled list”

When using the options together, a client listed in the disabled list will always be disabled and then all other clients will then be passed into the filter function. For example, we can simplify our previous filter function by just disabling the lua_ls client in the disabled table:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
disabled = { "lua_ls" },
filter = function(client)
-- only enable null-ls for javascript files
if vim.bo.filetype == "javascript" then
return client.name == "null-ls"
end
-- enable all other clients
return true
end,
},
},
}

The formatting options also allows you to specify other parameters for the vim.lsp.buf.format() call. Any of the other formatting options are allowed to be used here to be used as the default options. This means being able to easily adjust the default timeout_ms for formatting in AstroNvim or making asynchronous formatting the default. For example you can do the following to increase the formatting timeout along with adjust the filtering:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
formatting = {
format_on_save = true, -- enable or disable automatic formatting on save
timeout_ms = 3200, -- adjust the timeout_ms variable for formatting
disabled = { "lua_ls" },
filter = function(client)
-- only enable null-ls for javascript files
if vim.bo.filetype == "javascript" then
return client.name == "null-ls"
end
-- enable all other clients
return true
end,
},
},
}

AstroLSP provides a complete implementation of the file manipulation features provided by the LSP Specification through the astrolsp.file_operations module. These operations are useful when language servers provide functionality such as automatically updating import statements when files are renamed. Currently this includes:

  • workspace/willCreateFiles
  • workspace/didCreateFiles
  • workspace/willRenameFiles
  • workspace/didRenameFiles
  • workspace/willDeleteFiles
  • workspace/didDeleteFiles

This functionality was introduced in AstroNvim v4 disabled by default and will be enabled by default starting in AstroNvim v5. The module is used to integrate with the Neo-tree file explorer out of the box as well as the file renaming capability provided by AstroCore (require("astrocore").rename_file() which is bound to <Leader>R).

To enable the file operations in AstroNvim v4, you can configure them through the AstroLSP plugin opts:

lua/plugins/astrolsp.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
file_operations = {
timeout = 10000, -- default timeout in ms for completing LSP operations
operations = { -- enable all of the file operations
willCreate = true,
didCreate = true,
willRename = true,
didRename = true,
willDelete = true,
didDelete = true,
},
},
},
}

The astrolsp.file_operations module is meant to provide a simple Lua API to allow easy integration with any file management plugins/workflows that users may have. Some example integration code can be found in the AstroLSP Documentation. Many of the AstroCommunity plugins such as mini.files already provide the necessary integration to execute the LSP file operations when appropriate.

There are some plugins available for doing advanced setup of language servers that require the user to not use the lspconfig setup call and instead use their own plugin setup for handling this. AstroNvim provides a nice way to do this while still using mason.nvim for installing the language servers. You can use the handlers table for specifying how language servers should be setup such as using a language specific plugin. This function for each handler can take a single parameter which is the name of the server. The current lsp configuration settings can be retried with vim.lsp.config[server_name]. These options include things such as our built in capabilities, on_attach, as well as the user defined options in and lsp/<server_name>.lua and after/lsp/<server_name>.lua files the config table. Here are a couple examples for some common LSP plugins:

lua/plugins/deno.lua
return {
{ "sigmasd/deno-nvim", lazy = true }, -- add lsp plugin
{
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
handlers = {
-- add custom handler
denols = function()
require("deno-nvim").setup({ server = vim.lsp.config.denols })
end,
},
},
},
{
"WhoIsSethDaniel/mason-tool-installer.nvim",
opts = {
ensure_installed = { "deno" }, -- automatically install lsp
},
},
}

tsserver + denols

Since both tsserver and denols (and others such as eslint and prettier) attach to TypeScript/JavaScript files, some extra configuration may be required if both are installed.

To conditionally enable tsserver/denols based on the presence of package.json/deno.json, put the following LSP definitions in your after/lsp/ directory:

after/lsp/denols.lua
---@type vim.lsp.Config
return {
root_markers = { "deno.json", "deno.jsonc" },
}
after/lsp/tsserver.lua
---@type vim.lsp.Config
return {
root_markers = { "package.json" },
}
after/lsp/eslint.lua
---@type vim.lsp.Config
return {
root_markers = { "package.json", ".eslintrc.json", ".eslintrc.js" },
}

For none-ls packages (such as prettier, prettierd, or eslint_d), set the following to handlers in the mason-null-ls options:

lua/plugins/typescript_deno_null_ls.lua
return {
"jay-babu/mason-null-ls.nvim",
opts = {
handlers = {
-- for prettier
prettier = function()
require("null-ls").register(
require("null-ls").builtins.formatting.prettier.with({
condition = function(utils)
return utils.root_has_file("package.json")
or utils.root_has_file(".prettierrc")
or utils.root_has_file(".prettierrc.json")
or utils.root_has_file(".prettierrc.js")
end,
})
)
end,
-- for prettierd
prettierd = function()
require("null-ls").register(
require("null-ls").builtins.formatting.prettierd.with({
condition = function(utils)
return utils.root_has_file("package.json")
or utils.root_has_file(".prettierrc")
or utils.root_has_file(".prettierrc.json")
or utils.root_has_file(".prettierrc.js")
end,
})
)
end,
-- For eslint_d:
eslint_d = function()
require("null-ls").register(
require("null-ls").builtins.diagnostics.eslint_d.with({
condition = function(utils)
return utils.root_has_file("package.json")
or utils.root_has_file(".eslintrc.json")
or utils.root_has_file(".eslintrc.js")
end,
})
)
end,
},
},
}
after/lsp/clangd.lua
---@type vim.lsp.Config
return {
capabilities = {
offsetEncoding = "utf-8",
},
}
lua/plugins/clangd.lua
return {
{
"p00f/clangd_extensions.nvim", -- install lsp plugin
lazy = true,
init = function()
-- load clangd extensions when clangd attaches
vim.api.nvim_create_autocmd("LspAttach", {
desc = "Load clangd_extensions with clangd",
callback = function(args)
if
assert(vim.lsp.get_client_by_id(args.data.client_id)).name
== "clangd"
then
require("clangd_extensions")
-- add more `clangd` setup here as needed such as loading autocmds
return true -- delete the autocommand once the plugin is loaded
end
end,
})
end,
},
{
"WhoIsSethDaniel/mason-tool-installer.nvim",
opts = {
ensure_installed = { "clangd" }, -- automatically install lsp
},
},
}

Requires dart to be available on the system.

after/lsp/dartls.lua
---@type vim.lsp.Config
return {
config = {
dartls = {
-- any changes you want to make to the LSP setup, for example
color = {
enabled = true,
},
settings = {
showTodos = true,
completeFunctionCalls = true,
},
},
},
}
lua/plugins/flutter-tools.lua
return {
{ "akinsho/flutter-tools.nvim", lazy = true }, -- add lsp plugin
{
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
servers = { "dartls" },
handlers = {
dartls = function()
require("flutter-tools").setup({ lsp = vim.lsp.config.dartls })
end,
},
},
},
}
lua/plugins/rustaceanvim.lua
return {
{
"mrcjkb/rustaceanvim", -- add lsp plugin
version = "^5",
lazy = false, -- This plugin is already lazy
opts = function(_, opts)
local server_opts = vim.lsp.config.rust_analyzer
local server = {
---@type table | (fun(project_root:string|nil, default_settings: table|nil):table) -- The rust-analyzer settings or a function that creates them.
settings = function(project_root, default_settings)
local merge_table = require("astrocore").extend_tbl(
default_settings or {},
server_opts.settings or {}
)
local ra = require("rustaceanvim.config.server")
-- load_rust_analyzer_settings merges any found settings with the passed in default settings table and then returns that table
return ra.load_rust_analyzer_settings(project_root, {
settings_file_pattern = "rust-analyzer.json",
default_settings = merge_table,
})
end,
}
return { server = require("astrocore").extend_tbl(server_opts, server) }
end,
-- configure `rustaceanvim` by setting the `vim.g.rustaceanvim` variable
config = function(_, opts)
vim.g.rustaceanvim =
require("astrocore").extend_tbl(opts, vim.g.rustaceanvim)
end,
},
{
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
handlers = { rust_analyzer = false }, -- Let rustaceanvim setup `rust_analyzer`
},
},
{
"WhoIsSethDaniel/mason-tool-installer.nvim",
opts = {
ensure_installed = { "rust-analyzer" }, -- automatically install lsp
},
},
}
lua/plugins/jdtls.lua
return {
{
"AstroNvim/astrolsp",
dependencies = {
{ "mfussenegger/nvim-jdtls" }, -- load jdtls before loading language servers
},
},
{
"WhoIsSethDaniel/mason-tool-installer.nvim",
opts = {
ensure_installed = { "jdtls" },
},
},
}

Some users may want to have automatic pop ups of function signatures while editing with a language server similar to that of functionality provided by noice.nvim and lsp_signature.nvim. By default this behavior is disabled, but can easily be enabled by modifying features.signature_help in the AstroLSP opts:

lua/plugins/signature_help.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
features = {
signature_help = true, -- enable automatic signature help popup globally on startup
},
},
}

Neovim has the ability to handle and render inline virtual text and therefore first class support of inlay hints. AstroLSP comes with a built in feature for enabling inlay hints easily as well as toggling them during runtime. By default inlay hints are disabled, but they can easily be enabled by default by modifying features.inlay_hints in the AstroLSP opts:

lua/plugins/inlay_hints.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
features = {
inlay_hints = true, -- enable inlay hints globally on startup
},
},
}

Some users may like the look of inlay hints but prefer for them to be disabled with actively editing text in insert mode. This recipe also respects the current state of inlay hints and will only toggle them if inlay hints are enabled in a given buffer. This can easily be achieved with the AstroLSP autocmds feature:

lua/plugins/no_insert_inlay_hints.lua
return {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
autocmds = {
no_insert_inlay_hints = {
-- only create for language servers that support inlay hints
cond = "textDocument/inlayHint",
{
-- when going into insert mode
event = "InsertEnter",
desc = "disable inlay hints on insert",
callback = function(args)
local filter = { bufnr = args.buf }
-- if the inlay hints are currently enabled
if vim.lsp.inlay_hint.is_enabled(filter) then
-- disable the inlay hints
vim.lsp.inlay_hint.enable(false, filter)
-- create a single use autocommand to turn the inlay hints back on
-- when leaving insert mode
vim.api.nvim_create_autocmd("InsertLeave", {
buffer = args.buf,
once = true,
callback = function()
vim.lsp.inlay_hint.enable(true, filter)
end,
})
end
end,
},
},
},
},
}