Structure

L’organisation des fichiers

init.lua                     amorçage de lazy.nvim, ordre de chargement
lua/config/options.lua       options Vim
lua/config/keymaps.lua       keymaps globaux et autocommandes
lua/config/languages.lua     source unique du support par langage
lua/plugins/lsp.lua          mason, nvim-lspconfig, lazydev
lua/plugins/format.lua       conform
lua/plugins/completion.lua   blink.cmp
lua/plugins/treesitter.lua   coloration, indentation, objets texte
lua/plugins/rust.lua         rustaceanvim
lua/plugins/colorscheme.lua  tokyonight
lua/plugins/ui.lua           lualine, indent-blankline, render-markdown
lua/plugins/fileexplorer.lua neo-tree
lua/plugins/picker.lua       fzf-lua
lua/plugins/motion.lua       flash
lua/plugins/git.lua          gitsigns
stylua.toml                  style de formatage du Lua de ce dépôt

Le découpage de lua/plugins/ est par rôle, pas par plugin : lsp.lua contient trois plugins parce qu’ils forment un ensemble, et motion.lua un seul. Le critère est de pouvoir répondre à « où est-ce que je règle X » sans chercher.

init.lua, ligne à ligne

Trente lignes, et chacune a une raison d’être à sa place.

vim.g.mapleader = ' '
vim.g.maplocalleader = ' '

Le leader d’abord, avant tout le reste. Un plugin qui déclare keys = { '<leader>p', … } capture la valeur du leader au moment où lazy.nvim lit sa spécification : le définir après laisserait ces raccourcis sur l’ancienne valeur, sans le moindre message d’erreur.

require('config.options')
require('config.keymaps')

Ensuite les options et les raccourcis, avant les plugins. Certains plugins lisent des options pendant leur initialisation — clipboard, signcolumn, expandtab — et un plugin initialisé avant que l’option soit posée verrait l’ancienne valeur.

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',
    'https://github.com/folke/lazy.nvim.git', '--branch=stable', lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

L’amorçage : lazy.nvim s’installe lui-même au premier lancement. --filter=blob:none évite de télécharger l’historique complet, --branch=stable évite de suivre la branche de développement. vim.uv est la nouvelle API de boucle d’événements — les configurations plus anciennes écrivent (vim.uv or vim.loop) pour rester compatibles avec Neovim 0.9, ce qui n’est pas nécessaire ici.

require('lazy').setup('plugins')

Une chaîne plutôt qu’une table : lazy.nvim importe tous les fichiers de lua/plugins/ et concatène leurs spécifications. Ajouter un plugin ne demande donc aucune déclaration ailleurs — on crée un fichier, il est pris en compte.

Le chargement différé

C’est le sujet qui décide du temps de démarrage. Chaque plugin déclare son déclencheur :

PluginCharge surPourquoi
nvim-lspconfigBufReadPre, BufNewFileinutile tant qu’aucun fichier n’est ouvert
gitsignsBufReadPre, BufNewFileidem
indent-blanklineBufReadPost, BufNewFileidem
conformBufWritePre, :ConformInfone sert qu’à l’enregistrement
blink.cmpInsertEnterne sert qu’en mode insertion
masonses commandes :Mason…l’installation est pilotée par lspconfig
neo-treeCtrl + f, Ctrl + tà la première ouverture
fzf-luases cinq raccourcisà la première recherche
treesitter-textobjectsses dix mappingsà la première utilisation
render-markdownft = markdownseulement sur du markdown
lazydevft = luaseulement sur du Lua
lualine, flashVeryLazyjuste après le démarrage, hors du chemin critique

VeryLazy est un événement que lazy.nvim ajoute : il se déclenche une fois l’interface affichée. C’est le bon choix pour ce qui doit finir par être là mais n’a pas besoin d’y être à la milliseconde zéro.

Vérifier plutôt que supposer

:Lazy profile mesure le coût réel de chaque plugin et signale ceux qui chargent au démarrage. C’est le seul moyen fiable de savoir si un event fait ce qu’on croit — il est facile d’écrire un déclencheur qui se déclenche tout de suite.

Les quatre exceptions

Quatre plugins chargent au démarrage. Chacune de ces exceptions est un problème résolu, pas une négligence.

tokyonight — priority = 1000. Un thème doit s’appliquer avant tout ce qui définit des couleurs, sinon on voit l’interface changer d’apparence pendant le démarrage. La priorité élevée le place en tête de la file de chargement.

nvim-treesitter — lazy = false. Le plugin enregistre une autocommande FileType qui démarre la coloration. Or il n’existe aucun événement qui précède de façon fiable le premier FileType de tous les buffers : chargé plus tard, l’autocommande arriverait après coup et le premier fichier ouvert ne serait pas coloré. Il dépend en outre de mason, dont le répertoire bin doit être dans le $PATH avant la compilation des parsers — sans quoi chaque build échoue sur ENOENT: 'tree-sitter'.

rustaceanvim — lazy = false. Le plugin s’installe lui-même sur le type de fichier Rust ; c’est lui qui gère son propre report de chargement.

mason, enfin, est déclaré comme sa propre spécification et pas seulement comme dépendance de lspconfig. La raison : les commandes :Mason doivent exister avant qu’un fichier soit ouvert, alors que lspconfig ne charge que sur BufReadPre. Sans ça, :Mason serait introuvable sur un Neovim démarré à vide.

Ce que ça donne à l’usage

L’ordre effectif au premier lancement, sur une machine neuve :

  1. leader, options, raccourcis
  2. lazy.nvim s’installe, puis installe les 21 plugins
  3. tokyonight s’applique
  4. treesitter installe tree-sitter-cli via mason, puis compile les parsers en arrière-plan
  5. l’interface s’affiche ; lualine et flash chargent sur VeryLazy
  6. à l’ouverture du premier fichier : lspconfig charge, mason installe les serveurs et formateurs manquants, treesitter colore le buffer

Les étapes 4 et 6 ne se produisent qu’une fois. Ensuite, le démarrage tient dans les quelques dizaines de millisecondes que :Lazy profile affiche.