Comment j'ai configuré neovim pour écrire du LaTeX - Partie 1 : le LSP

Il arrive un moment dans la vie d'un⋅e thésard⋅e où on doit écrire son manuscrit. Personnellement, c'est maintenant. Évidemment il y a pléthore d'outils pour faciliter le processus (je pense notamment aux outils de gestion bibliographique), mais certains sont moins connus que d'autres.

Si vous faites (ou avez déjà fait) une bibliographie, il y a de bonnes chances pour que vous ayez déjà utilisé un logiciel comme Zotero (utilisez autre chose par pitié, Elsevier c'est le mal) ou Jabref. Mais avez-vous réfléchi à vos outils d'écriture ? Pour écrire du LaTeX, utilisez-vous un logiciel clef-en-main comme TeXstudio ?

Pas n'importe quel éditeur de code

Un éditeur de texte est un outil qui, comme son nom l'indique, sert à modifier un fichier texte. Il y en a des brouettes, les plus connus étant Notepad, Emacs et vim. Personnellement, j'utilise neovim, une version modifiée de vim qui ajoute des fonctionnalités, comme la possibilité d'être configuré en Lua ou d'écrire des plugins dans n'importe quel langage informatique.

Ce qui suit sera donc étroitement lié à neovim, mais devrait en théorie être adaptable à n'importe quel éditeur de texte pouvant gérer le Language Server Protocol.

Language Server Protocol ?

Un LSP (Language Server Protocol (server), ou Protocole de serveur de langage) est une spécification permettant à un logiciel de "discuter avec un langage". Dit simplement, un serveur implémentant le LSP pour un langage donné va être capable d'analyser le code (dans le langage en question) qu'on lui fournit, et de retourner une liste d'erreur, d'informations syntaxiques, voire de correctifs concernant votre code. Cela permet aux éditeurs de code d'afficher les erreurs dans votre code, sans que vous ayez besoin de manuellement le compiler, et dans votre interface de développement. C'est un peu magique, c'est très utile, et ce n'est pas très difficile à mettre en place.

La mise en place

Liste des ingrédients

Un peu comme une recette de cuisine, il y a plusieurs choses dont on va avoir besoin. Je vais me concentrer sur LaTeX et neovim, mais comme je l'ai déjà dit c'est adaptable à tout éditeur de texte supportant les LSPs et tout langage disposant d'un serveur implémentant le protocole---c'est l'avantage des spécifications, tout le monde peut faire sa propre implémentation.

On a donc besoin de :

  • neovim v0.10.0 ou supérieure ;
  • nvim-lspconfig, le plugin officiel proposant des configurations de base pour lap lupart des serveurs LSP ;
  • texlab, le serveur LSP le plus connu pour LaTeX.

Bien qu'on n'ait pas besoin de plus pour que tout fonctionne, on va quand même utiliser d'autres plugins pour nous rendre la vie plus facile. Notamment :

Je ne pense pas parler de complétion automatique aujourd'hui, ce sera peut-être l'occasion d'un deuxième article sur le sujet.

Tout récupérer

Si vous utilisez neovim vous ne serez probablement pas trop étonné⋅e par la suite (même si, dans le cas où vous utilisez un autre gestionnaire de plugin, ce ne sera peut-être pas la même syntaxe).

neovim se paramètre via des fichiers de configuration placés dans $HOME/.config/nvim par défaut. Par la suite, je vais utiliser la variable $NVIM pour référencer ce dossier. On commence donc par créer (ou modifier) $NVIM/init.lua, et on y écrit :

require("plugin")
require("lazy").setup("plugin")

Vous pouvez changer les deux occurences de plugin par le terme de votre choix, tant que vous utilisez le même à chaque fois.

On crée un dossier $NVIM/lua/plugin qui va accueillir la configuration de nos plugins :

$ > cd ~/.config/nvim/
$ > mkdir -p lua/plugin

Dans $NVIM/lua/plugin, on crée un fichier init.lua, qui contient

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

return {}

lazy.nvim est un grand plugin, il est propre et se gère tout seul. Ce bout de code est simplement là pour que neovim vérifie qu'il soit là, le télécharge au besoin, puis passe le contenu de la variable lazypath à lazy pour qu'il voit les plugins ainsi téléchargés.

Maintenant, on spécifie le serveur qu'on veut récupérer par son nom (pas celui du langage). Ça se passe dans $NVIM/lua/plugin/lsp/init.lua :

_G.lsp_root_dir = vim.fn.stdpath("data") .. "/mason/bin"

local servers = {
    "texlab",
}

Enfin, il reste à récupérer nvim-lspconfig et mason pour télécharger texlab et le configurer. Toujours dans $NVIM/lua/plugin/lsp/init.lua :

return {
    {
        "neovim/nvim-lspconfig",
        dependencies = {
            "saghen/blink.cmp",
            "williamboman/mason.nvim",
            "williamboman/mason-lspconfig.nvim",
        },
        config = function()
            vim.diagnostic.config(
                {
                    virtual_text = false,
                    severity_sort = { true, reverse = true },
                }
            )

            local lspconfig = require("lspconfig")

            local capabilities = vim.lsp.protocol.make_client_capabilities()

            for _, server in ipairs(servers) do
                local opts = {
                    capabilities = capabilities,
                }

                local plugin = string.format("plugin.lsp.%s", server)
                require(plugin).setup(opts)

                lspconfig[server].setup(opts)
            end
        end
    },
    {
        "williamboman/mason.nvim",
        cmd = "Mason",
        opts = { ensure_installed = servers },
    },
}

C'est tout pour le moment ! Ce bout de code est assez simple à comprendre : mason.nvim s'assure que tous les serveurs de la liste servers soient installés lorsqu'il est configuré, et au besoin les télécharge. Ensuite, on appelle nvim-lspconfig qui regroupe une configuraton de base pour la plupart des serveurs LSP, on désactive le texte virtuel (préférence personnelle, je trouve que ça pollue mon interface -- mais je peux afficher les messages avec un raccourci clavier, j'y reviendrai si je fais un article sur la complétion) et on affiche en priorité les choses les moins sévères (pour savoir que le LSP peut nous aider au lieu de simplement savoir qu'il y a une erreur).

On récupère ensuite les infos sur neovim et ce qu'il pourrait obtenir du serveur via make_client_capabilities, puis on configure le serveur en lui passant ces capacités au moment d'appeler texlab.setup().

On peut passer à la dernière petite étape, qui est de modifier un tout petit peu la configuration de base pour texlab. Dans $NVIM/lua/plugin/lsp/texlab.lua :

local M = {}

M.setup = function(opts)
    opts.settings = {
        build = { executable = "pdflatex" },
        texlab = {
            diagnostics = { ignoredPatterns = { "Unused label" } }
        },
    }
end

return M

On spécifie qu'on utilise pdflatex (et pas mklatex, valeur par défaut) et qu'on veut ignorer les warnings concernant un \label non-utilisé (je mets très souvent un label sur mes environnement LaTeX, il y en a donc beaucoup que je me retrouve à ne pas utiliser), et voilà.

It's done, Sam! It's done!

Il reste à configurer les plugins de complétion. On verra ça dans un prochain billet. :)