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

Neovim (Client) User hovers 'func' Language Server Analyzes Code textDocument/hover (JSON-RPC) Result: "Signature: func(a, b)"

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:

  1. nvim-lspconfig: A collection of common configurations for various servers.
  2. 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

Select an action to see the protocol messages...

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.