LSP Integration
For decades, if you wanted “Go to Definition” or “Rename Symbol” in Vim, you needed complex, language-specific plugins (like vim-go or jedi-vim). Each one worked differently, and many were slow.
Then Microsoft created the Language Server Protocol (LSP).
Neovim now includes a native LSP client. This means you can connect to the same language servers that VS Code uses, getting the exact same intelligence, but with Vim’s speed.
1. The Client-Server Model
LSP decouples the editor from the language intelligence.
LSP Communication Flow
2. Setting up LSP
While Neovim has a client, it doesn’t come with the servers (like gopls or pyright). You need to install those separately.
The ecosystem standard for managing this is:
- nvim-lspconfig: A collection of common configurations for various servers.
- mason.nvim: A package manager to easily install LSP servers, linters, and formatters.
Basic Configuration
First, install the plugins (using your plugin manager, e.g., lazy.nvim):
-- In your plugins module
return {
"neovim/nvim-lspconfig",
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
}
Then, configure them in your init.lua (or a dedicated lsp.lua file):
require("mason").setup()
require("mason-lspconfig").setup({
ensure_installed = { "lua_ls", "pyright", "gopls" }
})
local lspconfig = require("lspconfig")
-- Setup Python
lspconfig.pyright.setup({})
-- Setup Lua (with special settings for Neovim)
lspconfig.lua_ls.setup({
settings = {
Lua = {
diagnostics = {
globals = { "vim" }, -- Don't warn about 'vim' global
},
},
},
})
3. Interactive: RPC Explorer
See exactly what happens when you interact with code. Select an action to see the JSON-RPC message exchange.
User Action
JSON-RPC Log
4. Autocompletion (nvim-cmp)
LSP provides the candidates, but you need an autocompletion plugin to show the dropdown menu. nvim-cmp is the standard choice.
It’s modular: you need the core plugin, plus sources (LSP, snippets, buffer words, path).
local cmp = require("cmp")
cmp.setup({
mapping = cmp.mapping.preset.insert({
['<C-b>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-Space>'] = cmp.mapping.complete(),
['<CR>'] = cmp.mapping.confirm({ select = true }),
}),
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'luasnip' }, -- Snippets
}, {
{ name = 'buffer' },
})
})
[!IMPORTANT] Capabilities: You must tell the LSP server that you support completion snippets. When setting up
lspconfig, pass the capabilities:local capabilities = require('cmp_nvim_lsp').default_capabilities() lspconfig.pyright.setup({ capabilities = capabilities })
5. Essential Keymaps
LSP functions are not mapped by default. You should create an on_attach function to set them only when an LSP connects to a buffer.
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('UserLspConfig', {}),
callback = function(ev)
local opts = { buffer = ev.buf }
-- Go to definition
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
-- Hover documentation
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
-- Rename symbol
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
-- Code actions
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
end,
})
6. Next Steps
With LSP, your editor understands code. But how do you manage all these plugins? In the next chapter, we will master lazy.nvim, the modern package manager that keeps your config fast.