Drill 05: LSP Configuration

LSP configuration: nvim-lspconfig, mason, native API, and diagnostics.

Essential for understanding domus-nvim LSP setup.

Run This Drill

bash ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/examples/lua-drills/05-lsp.sh

Drill Script

#!/bin/bash
# LUA DRILL 05: LSP CONFIGURATION
# Topics: nvim-lspconfig, mason, keymaps, diagnostics

echo "=================================================================="
echo "             LUA DRILL 05: LSP CONFIGURATION                     "
echo "=================================================================="
echo ""
echo "Understanding LSP setup in modern Neovim"
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 5.1: LSP BASICS"
echo "What LSP provides and how it works"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- LSP = Language Server Protocol
-- Neovim is the CLIENT, language servers are SERVERS
--
-- Flow:
-- 1. Neovim opens file
-- 2. LSP client attaches to buffer
-- 3. Client sends document to server
-- 4. Server provides: completions, diagnostics, hover, goto definition, etc.

-- Check attached LSP clients
:lua print(vim.inspect(vim.lsp.get_active_clients()))

-- Check LSP for current buffer
:lua print(vim.inspect(vim.lsp.buf_get_clients()))

-- Common language servers:
-- lua_ls (Lua)
-- pyright (Python)
-- rust_analyzer (Rust)
-- tsserver (TypeScript/JavaScript)
-- gopls (Go)
-- clangd (C/C++)
NVIMEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 5.2: NVIM-LSPCONFIG SETUP"
echo "Traditional LSP configuration"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- lua/domus/plugins/config/lsp/init.lua

-- Define capabilities (what client supports)
local capabilities = vim.lsp.protocol.make_client_capabilities()

-- If using nvim-cmp, extend capabilities
-- capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)

-- Define on_attach (runs when LSP attaches to buffer)
local on_attach = function(client, bufnr)
    local keymap = vim.keymap.set
    local opts = {buffer = bufnr, silent = true}

    -- Navigation
    keymap('n', 'gd', vim.lsp.buf.definition, opts)
    keymap('n', 'gD', vim.lsp.buf.declaration, opts)
    keymap('n', 'gi', vim.lsp.buf.implementation, opts)
    keymap('n', 'gr', vim.lsp.buf.references, opts)
    keymap('n', 'K', vim.lsp.buf.hover, opts)

    -- Actions
    keymap('n', '<leader>rn', vim.lsp.buf.rename, opts)
    keymap('n', '<leader>ca', vim.lsp.buf.code_action, opts)
    keymap('n', '<leader>f', function()
        vim.lsp.buf.format({async = true})
    end, opts)

    -- Diagnostics
    keymap('n', '[d', vim.diagnostic.goto_prev, opts)
    keymap('n', ']d', vim.diagnostic.goto_next, opts)
    keymap('n', '<leader>d', vim.diagnostic.open_float, opts)
end

-- Setup individual servers
local lspconfig = require('lspconfig')

lspconfig.lua_ls.setup({
    capabilities = capabilities,
    on_attach = on_attach,
    settings = {
        Lua = {
            diagnostics = {globals = {'vim'}},
            workspace = {checkThirdParty = false},
        },
    },
})

lspconfig.pyright.setup({
    capabilities = capabilities,
    on_attach = on_attach,
})

lspconfig.rust_analyzer.setup({
    capabilities = capabilities,
    on_attach = on_attach,
})
NVIMEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 5.3: MASON + MASON-LSPCONFIG"
echo "Auto-install and manage LSP servers"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- Mason installs LSP servers, formatters, linters
-- Mason-lspconfig bridges mason and nvim-lspconfig

-- lua/domus/plugins/specs/lsp.lua
return {
    {
        "williamboman/mason.nvim",
        build = ":MasonUpdate",
        opts = {},
    },
    {
        "williamboman/mason-lspconfig.nvim",
        dependencies = {"williamboman/mason.nvim"},
        opts = {
            ensure_installed = {
                "lua_ls",
                "pyright",
                "rust_analyzer",
            },
            automatic_installation = true,
        },
    },
    {
        "neovim/nvim-lspconfig",
        dependencies = {"williamboman/mason-lspconfig.nvim"},
        config = function()
            require("domus.plugins.config.lsp")
        end,
    },
}

-- Mason-lspconfig can auto-setup servers:
require("mason-lspconfig").setup_handlers({
    -- Default handler
    function(server_name)
        require("lspconfig")[server_name].setup({
            capabilities = capabilities,
            on_attach = on_attach,
        })
    end,
    -- Custom handler for lua_ls
    ["lua_ls"] = function()
        require("lspconfig").lua_ls.setup({
            capabilities = capabilities,
            on_attach = on_attach,
            settings = {
                Lua = {diagnostics = {globals = {'vim'}}}
            }
        })
    end,
})
NVIMEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 5.4: NEOVIM 0.11+ NATIVE LSP"
echo "Using vim.lsp.config without lspconfig"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- Neovim 0.11+ has native LSP config API
-- No nvim-lspconfig needed for basic setup

-- Configure a server
vim.lsp.config.lua_ls = {
    cmd = {"lua-language-server"},
    filetypes = {"lua"},
    root_markers = {".git", ".luarc.json", "init.lua"},
    settings = {
        Lua = {
            diagnostics = {globals = {"vim"}},
        },
    },
}

vim.lsp.config.pyright = {
    cmd = {"pyright-langserver", "--stdio"},
    filetypes = {"python"},
    root_markers = {".git", "pyproject.toml", "setup.py"},
}

vim.lsp.config.rust_analyzer = {
    cmd = {"rust-analyzer"},
    filetypes = {"rust"},
    root_markers = {".git", "Cargo.toml"},
}

-- Enable configured servers
vim.lsp.enable({"lua_ls", "pyright", "rust_analyzer"})

-- This is useful for Termux where Mason doesn't work
-- Install LSPs via pkg/npm, then use vim.lsp.config
NVIMEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 5.5: DIAGNOSTICS"
echo "Configure error/warning display"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- Diagnostic configuration
vim.diagnostic.config({
    virtual_text = true,        -- Show inline text
    signs = true,               -- Show signs in gutter
    underline = true,           -- Underline errors
    update_in_insert = false,   -- Don't update while typing
    severity_sort = true,       -- Sort by severity
    float = {
        border = "rounded",
        source = "always",
    },
})

-- Custom signs
local signs = {Error = "󰅚 ", Warn = "󰀪 ", Hint = "󰌶 ", Info = " "}
for type, icon in pairs(signs) do
    local hl = "DiagnosticSign" .. type
    vim.fn.sign_define(hl, {text = icon, texthl = hl, numhl = hl})
end

-- Useful diagnostic commands in normal mode:
-- vim.diagnostic.open_float()     -- Show diagnostic in float
-- vim.diagnostic.goto_next()      -- Go to next diagnostic
-- vim.diagnostic.goto_prev()      -- Go to previous
-- vim.diagnostic.setloclist()     -- All diagnostics in location list
-- vim.diagnostic.hide()           -- Hide diagnostics
-- vim.diagnostic.show()           -- Show diagnostics
NVIMEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "YOUR TURN - TRY THESE:"
echo "------------------------------------------------------------------"
echo ""
echo "1. Check active LSP clients:"
echo "   :lua print(vim.inspect(vim.lsp.get_active_clients()))"
echo ""
echo "2. Check LSP capabilities:"
echo "   :lua print(vim.inspect(vim.lsp.get_active_clients()[1].server_capabilities))"
echo ""
echo "3. Manual LSP commands:"
echo "   :lua vim.lsp.buf.hover()"
echo "   :lua vim.lsp.buf.definition()"
echo ""
echo "4. Diagnostic commands:"
echo "   :lua vim.diagnostic.open_float()"
echo "   :lua print(vim.inspect(vim.diagnostic.get()))"
echo ""
echo "------------------------------------------------------------------"
echo "KEY TAKEAWAYS:"
echo "1. on_attach runs when LSP connects to buffer"
echo "2. capabilities tell server what client supports"
echo "3. Mason installs servers, mason-lspconfig bridges"
echo "4. Neovim 0.11+: vim.lsp.config + vim.lsp.enable()"
echo "5. vim.diagnostic.config() for display options"
echo "------------------------------------------------------------------"