Configuration

Palier5 Cette page décrit les mécaniques : la langue dans laquelle une configuration Neovim s’écrit. Ma configuration est la lecture commentée d’un cas réel écrit dans cette langue.

L’ordre compte. Configurer avant de savoir éditer est le piège principal du parcours — les paliers 0 à 4 se franchissent avec un Neovim nu.

Structure des fichiers

Le point d’entrée est ~/.config/nvim/init.lua, et tout ce qui se trouve dans le sous-répertoire lua/ est importable par require.

Rien n’impose une organisation particulière, mais celle-ci est devenue la convention — c’est aussi celle qu’attendent les distributions :

~/.config/nvim
├── init.lua              point d'entrée
├── lsp/                  configurations de serveurs de langage
└── lua/
    ├── config/
    │   ├── options.lua   les options de l'éditeur
    │   ├── keymaps.lua   les raccourcis personnels
    │   └── autocmds.lua  les automatismes
    └── plugins/
        ├── lsp.lua       un fichier par plugin ou par thème
        └── ui.lua

require("config.options") charge lua/config/options.lua : le point remplace le séparateur de répertoire, et l’extension est implicite.

Options

vim.opt donne accès aux options de l’éditeur, avec une écriture proche de celle de :set.

vim.opt.number = true          -- numéros de ligne
vim.opt.relativenumber = true  -- numérotation relative au curseur
vim.opt.expandtab = true       -- des espaces plutôt que des tabulations
vim.opt.shiftwidth = 2         -- largeur d'une indentation
vim.opt.signcolumn = "yes"     -- évite que le texte saute quand un signe apparaît
vim.opt.undofile = true        -- historique d'annulation persistant
vim.opt.scrolloff = 8          -- contexte conservé autour du curseur

vim.opt renvoie un objet qui sait manipuler les options-listes, ce que vim.o ne fait pas :

vim.opt.shortmess:append("I")
vim.opt.wildignore:remove("*.o")

vim.opt_local fait la même chose sur le seul buffer ou la seule fenêtre courante — c’est ce qu’on utilise dans une autocommande. Les variables globales, elles, passent par vim.g :

vim.g.mapleader = " "
vim.g.maplocalleader = " "
Le leader avant les plugins

mapleader doit être défini avant que le gestionnaire de plugins ne charge quoi que ce soit. Les raccourcis déclarés par un plugin dans sa clé keys capturent la valeur du leader au moment où ils sont créés : le changer ensuite laisse une partie des raccourcis sur l’ancienne touche, sans aucun message d’erreur.

Raccourcis

local map = vim.keymap.set

map("n", "<leader>w", "<cmd>write<cr>", { desc = "Enregistrer" })
map("n", "<Esc>", "<cmd>nohlsearch<cr>", { desc = "Éteindre le surlignage" })
map("x", "<", "<gv", { desc = "Désindenter en gardant la sélection" })
map("x", ">", ">gv", { desc = "Indenter en gardant la sélection" })
map("t", "<Esc><Esc>", "<C-\\><C-n>", { desc = "Quitter le mode terminal" })

Le premier argument est le mode — "n" normal, "i" insertion, "v" visuel et sélection, "x" visuel strict, "o" en attente d’un opérateur, "t" terminal, ou une table pour plusieurs. Les options utiles :

  • desc : la description affichée par which-key.nvim et par :map
  • buffer = 0 : limite le raccourci au buffer courant — c’est ce qu’on utilise dans un LspAttach, pour que le raccourci n’existe que là où un serveur répond
  • silent = true : n’affiche pas la commande dans la ligne de statut
  • expr = true : le second argument est une expression évaluée à la volée

Le troisième argument peut être une fonction Lua plutôt qu’une chaîne de touches, ce qui est le cas dès qu’on appelle une API :

map("n", "<leader>f", function()
  require("conform").format({ async = true })
end, { desc = "Formater le buffer" })
Vérifier avant d’ajouter

Neovim fournit nativement une bonne partie des raccourcis que les configurations redéfinissent encore par habitude : gcc et gc pour commenter, ]d / [d pour les diagnostics, ]q / [q pour la quickfix, grn, gra, grr pour le LSP. Lancer nvim --clean et tester la touche évite d’écrire une ligne inutile — ou d’écraser un comportement qu’on aurait aimé garder. :verbose map {touche} dit d’où vient un mapping existant.

Automatismes

vim.api.nvim_create_autocmd("TextYankPost", {
  desc = "Surligner brièvement le texte copié",
  callback = function() vim.hl.on_yank() end,
})

vim.api.nvim_create_autocmd("FileType", {
  pattern = { "markdown", "gitcommit" },
  callback = function()
    vim.opt_local.wrap = true
    vim.opt_local.spell = true
  end,
})

Regrouper ses autocommandes dans un groupe évite qu’elles ne s’empilent à chaque rechargement :

local group = vim.api.nvim_create_augroup("config", { clear = true })

Les événements les plus utilisés sont FileType (un type de fichier est déterminé), BufReadPre et BufWritePre (avant lecture, avant écriture), LspAttach (un serveur vient de s’attacher à un buffer), TermOpen, et le VeryLazy que lazy.nvim ajoute pour « juste après le démarrage ».

Gestionnaire de plugins

lazy.nvim est le standard actuel. Il s’installe lui-même depuis init.lua :

vim.g.mapleader = " "

require("config.options")
require("config.keymaps")

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
  vim.fn.system({
    "git", "clone", "--filter=blob:none", "--branch=stable",
    "https://github.com/folke/lazy.nvim.git", lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup("plugins")

setup("plugins") importe tous les fichiers de lua/plugins/. Chacun renvoie une spécification :

return {
  {
    "lewis6991/gitsigns.nvim",
    event = { "BufReadPre", "BufNewFile" },
    opts = {
      signs = { add = { text = "+" } },
    },
  },
}

opts est passé au setup() du plugin, que lazy.nvim appelle lui-même — il n’y a donc pas besoin d’écrire require("gitsigns").setup(...). Quand la mise en place demande plus qu’une table, config = function() … end remplace opts.

Le chargement différé

C’est l’intérêt principal de lazy.nvim, et ce qui décide du temps de démarrage. Quatre clés déclenchent le chargement :

CléLe plugin charge…
eventsur un événement — BufReadPre, InsertEnter, VeryLazy
ftà l’ouverture d’un type de fichier
cmdau premier appel d’une de ses commandes
keysà la première frappe d’un de ses raccourcis

lazy = false force le chargement au démarrage, ce qui est parfois nécessaire : un thème doit s’appliquer avant tout le reste (avec priority = 1000), et un plugin qui doit enregistrer une autocommande FileType doit le faire avant que le premier FileType ne se déclenche.

:Lazy profile mesure le coût de chaque plugin et montre lesquels chargent inutilement au démarrage.

Serveurs de langage

Depuis Neovim 0.11, configurer un serveur ne demande plus de framework. Les fichiers déposés dans lsp/ du répertoire de configuration sont découverts automatiquement, et il suffit d’activer les serveurs voulus.

-- ~/.config/nvim/lsp/lua_ls.lua
return {
  cmd = { "lua-language-server" },
  filetypes = { "lua" },
  root_markers = { ".luarc.json", ".git" },
}
vim.lsp.enable({ "lua_ls", "ts_ls", "basedpyright" })

vim.lsp.config("nom", { … }) complète ou surcharge la configuration d’un serveur, et vim.lsp.config("*", { … }) s’applique à tous — c’est là qu’on déclare une fois pour toutes les capacités du moteur de complétion.

nvim-lspconfig reste très répandu, mais son rôle a changé : il ne sert plus de framework, il fournit précisément ces fichiers lsp/ pour des centaines de serveurs.

Une fois un serveur attaché, Neovim installe ses propres raccourcis :

RaccourciAction
Kdocumentation au survol
grnrenommer le symbole
graactions de code
grrréférences
griimplémentations
grtdéfinition du type
gOsymboles du document
Ctrl + s (insertion)aide sur la signature
]d / [ddiagnostic suivant / précédent
Ctrl + w ddiagnostic sous le curseur, en flottant

vim.lsp.buf.definition n’est pas mappé par défaut : c’est la seule brique de navigation à ajouter soi-même, en général sur gd. vim.diagnostic.config({ … }) règle l’affichage des diagnostics — signes, texte virtuel, tri par sévérité.

Diagnostiquer sa configuration

  • :checkhealth : vérifie l’installation, les dépendances externes, les fournisseurs — le premier réflexe quand quelque chose ne marche pas. :checkhealth lsp et :checkhealth vim.treesitter ciblent un sous-système
  • :Lazy : état des plugins, mises à jour, profil de démarrage
  • :messages : les messages passés trop vite
  • :verbose set shiftwidth? : la valeur d’une option et le fichier qui l’a définie en dernier
  • :verbose map {touche} : idem pour un raccourci
  • nvim --clean : démarre sans configuration ni plugin, pour savoir si un problème vient de Neovim ou de vous
  • :h {sujet} : la documentation intégrée, qui reste la meilleure référence