LSP natif dans Neovim 0.11 : zéro plugin, zéro compromis

LSP natif dans Neovim 0.11 : zéro plugin, zéro compromis

·6 min de lecture·Mis à jour le 16 janvier 2026

Ce qui a changé avec Neovim 0.11

Pendant des années, configurer le LSP dans Neovim passait par nvim-lspconfig : configs par défaut pour des centaines de serveurs, gestion du cycle de vie. Du solide, mais une dépendance de plus, avec sa propre logique et ses couches d'abstraction.

Neovim 0.11 a intégré deux fonctions au client LSP qui changent la donne :

  • vim.lsp.config() : déclarer la config d'un serveur LSP directement dans Neovim
  • vim.lsp.enable() : activer les serveurs pour tel ou tel filetype

C'est un changement philosophique autant que technique : le LSP devient une affaire de Neovim, pas d'un plugin.

L'architecture

Toute la config LSP tient dans un fichier, lua/plugins/lsp.lua, avec trois responsabilités : installer les serveurs via Mason, les configurer nativement, et attacher les keybindings.

Mason : le gestionnaire de serveurs

Mason est un package manager spécialisé pour les serveurs LSP, linters et formateurs de l'écosystème Neovim.

{
  "williamboman/mason.nvim",
  cmd = "Mason",
  opts = {
    ui = {
      border = "rounded",
      icons = {
        package_installed = "✓",
        package_pending = "➜",
        package_uninstalled = "✗",
      },
    },
  },
}

mason-tool-installer garantit que tous les serveurs sont présents au démarrage :

{
  "WhoIsSethDaniel/mason-tool-installer.nvim",
  dependencies = { "mason.nvim" },
  opts = {
    ensure_installed = {
      -- LSP servers
      "lua-language-server",
      "typescript-language-server",
      "pyright",
      "html-lsp",
      "css-lsp",
      "json-lsp",
      "yaml-language-server",
      "tailwindcss-language-server",
      "emmet-language-server",
      -- Formatters
      "stylua",
      "prettier",
      "black",
      "isort",
    },
  },
}

La commande :Mason ouvre une interface pour gérer visuellement les installations.

Configuration native des serveurs

Chaque serveur se déclare avec vim.lsp.config(), puis on les active tous d'un coup avec vim.lsp.enable() :

-- Capabilities enrichies par blink.cmp (autocomplétion)
local capabilities = require("blink.cmp").get_lsp_capabilities()

-- lua_ls : Lua avec LuaJIT
vim.lsp.config("lua_ls", {
  capabilities = capabilities,
  settings = {
    Lua = {
      runtime = { version = "LuaJIT" },
      diagnostics = { globals = { "vim" } },
      workspace = { checkThirdParty = false },
      telemetry = { enable = false },
    },
  },
})

-- ts_ls : TypeScript / JavaScript
vim.lsp.config("ts_ls", {
  capabilities = capabilities,
})

-- pyright : Python avec typage
vim.lsp.config("pyright", {
  capabilities = capabilities,
})

-- Serveurs web
vim.lsp.config("html", { capabilities = capabilities })
vim.lsp.config("cssls", { capabilities = capabilities })
vim.lsp.config("jsonls", { capabilities = capabilities })
vim.lsp.config("yamlls", { capabilities = capabilities })
vim.lsp.config("tailwindcss", { capabilities = capabilities })
vim.lsp.config("emmet_language_server", { capabilities = capabilities })

-- Activation de tous les serveurs
vim.lsp.enable({
  "lua_ls",
  "ts_ls",
  "pyright",
  "html",
  "cssls",
  "jsonls",
  "yamlls",
  "tailwindcss",
  "emmet_language_server",
})

Trois réglages de lua_ls méritent une explication :

  • runtime.version = "LuaJIT" parce que Neovim utilise LuaJIT, pas le Lua vanilla 5.1
  • diagnostics.globals = { "vim" } sinon lua_ls signale « variable vim not found » à chaque ligne
  • workspace.checkThirdParty = false supprime le popup qui demande de charger des types tiers à chaque ouverture de projet

Pas de table servers à wrapper, pas de boucle for, pas d'abstraction intermédiaire : tout est explicite.

Les diagnostics

Configurés globalement avec vim.diagnostic.config(), avec des icônes centralisées dans config/icons.lua :

local icons = require("config.icons")

vim.diagnostic.config({
  signs = {
    text = {
      [vim.diagnostic.severity.ERROR] = icons.diagnostics.Error,
      [vim.diagnostic.severity.WARN] = icons.diagnostics.Warn,
      [vim.diagnostic.severity.HINT] = icons.diagnostics.Hint,
      [vim.diagnostic.severity.INFO] = icons.diagnostics.Info,
    },
  },
  virtual_text = {
    spacing = 4,
    prefix = "■",
  },
  severity_sort = true,
  float = {
    border = "rounded",
    source = true,
  },
})

severity_sort = true garantit que les erreurs apparaissent avant les warnings dans la signcolumn, et prefix = "■" donne un marqueur discret pour le texte virtuel inline.

Les keybindings LSP

Attachés via l'autocmd LspAttach, ils ne sont disponibles que dans les buffers où un serveur LSP est actif :

vim.api.nvim_create_autocmd("LspAttach", {
  group = vim.api.nvim_create_augroup("lsp-attach", { clear = true }),
  callback = function(event)
    local map = function(keys, func, desc, mode)
      mode = mode or "n"
      vim.keymap.set(mode, keys, func, { buffer = event.buf, desc = "LSP: " .. desc })
    end

    local telescope = require("telescope.builtin")

    -- Navigation
    map("gd", telescope.lsp_definitions, "Go to definition")
    map("gr", telescope.lsp_references, "Go to references")
    map("gI", telescope.lsp_implementations, "Go to implementation")
    map("gD", vim.lsp.buf.declaration, "Go to declaration")

    -- Informations
    map("K", vim.lsp.buf.hover, "Hover documentation")
    map("<C-k>", vim.lsp.buf.signature_help, "Signature help", "i")

    -- Symboles
    map("<leader>ds", telescope.lsp_document_symbols, "Document symbols")
    map("<leader>ws", telescope.lsp_dynamic_workspace_symbols, "Workspace symbols")
    map("<leader>D", telescope.lsp_type_definitions, "Type definition")

    -- Actions
    map("<leader>rn", vim.lsp.buf.rename, "Rename symbol")
    map("<leader>ca", vim.lsp.buf.code_action, "Code action")
  end,
})

Les commandes du quotidien :

  • gd (go to definition) : via Telescope, avec preview du fichier destination et choix entre plusieurs définitions.
  • gr (references) : tous les endroits où un symbole est appelé, indispensable avant un refactor.
  • K (hover) : doc du symbole sous le curseur dans une floating window — types, signatures, JSDoc.
  • <leader>rn (rename) : renommage sémantique dans tout le projet. Le LSP comprend le code, ce n'est pas un find-and-replace.
  • <leader>ca (code action) : fixes automatiques, imports manquants, refactorings proposés par le serveur.

Trouble.nvim pour les diagnostics

Trouble.nvim ajoute un panneau dédié qui agrège toutes les erreurs et warnings du projet dans une vue unique, triée par sévérité :

{
  "folke/trouble.nvim",
  cmd = "Trouble",
  keys = {
    { "<leader>xx", "<cmd>Trouble diagnostics toggle<cr>", desc = "Diagnostics (Trouble)" },
    { "<leader>xX", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>", desc = "Buffer diagnostics" },
  },
  opts = {
    use_diagnostic_signs = true,
  },
}

Particulièrement utile sur un projet à nombreux fichiers, où naviguer fichier par fichier pour trouver les erreurs devient vite pénible.

Pourquoi abandonner lspconfig

  • Moins de dépendances : un plugin de moins à mettre à jour quand quelque chose casse.
  • Forward-compatible : on utilise l'API officielle de Neovim, pas une abstraction tierce.
  • Explicite : chaque serveur est déclaré clairement, sans magie.
  • Simple : plus besoin de savoir comment lspconfig résout les noms de serveurs ou merge les configs.

Pour 9 serveurs, la différence en lignes est minime — une quinzaine. C'est la clarté qui compense.

Conclusion

Le LSP natif de Neovim 0.11 donne un environnement complet — go-to-definition, autocomplétion, diagnostics, rename, code actions — avec juste Mason pour installer les serveurs et quelques appels à l'API native. Plus simple, plus transparent, et chaque morceau du setup reste compréhensible parce qu'il n'est pas caché derrière une couche d'abstraction.

PartagerLinkedInXBluesky

Articles similaires