Comment j'ai configuré neovim pour écrire du LaTeX - Partie 2 : la completion

Ce billet fait suite à un premier billet sur la configuration du serveur LSP. Vous pouvez retrouver la partie 1 ici.

La complétion ?

Une fonctionnalité importante (selon moi) pour un éditeur de texte est sa capacité à compléter quelque chose. Par exemple, si vous avez une variable_avec_un_nom_tres_long, peut-être n'avez-vous pas envie de taper son nom complet manuellement à chaque fois ; un mécanisme de complétion le fera à votre place, pour peu que vous tapiez les premières lettres. Certains sont plus ou moins intelligents que d'autres, regardent simplement le contenu de votre fichier (comme neovim, nativement) ou tirent partie d'un serveur LSP pour regarder dans votre projet en entier, offrir une complétion contextuelle (vous proposer une référence si vous êtes dans un environnement \cite, par exemple)... Aujourd'hui, on va parler de complétion et de snippets, pour se simplifier la vie.

Liste des ingrédients

Cette fois on a besoin de moins d'ingrédients, puisque (en général), le plugin de complétion est autonome. J'utilise personnellement le plugin de la suite mini.nvim, notamment car j'utilise déjà d'autres plugins de cette suite et parce que je trouve que la complétion fonctionne bien par défaut. Donc, on a besoin :

  • neovim v0.10.0 ou supérieure ;
  • mini.completion, en tant que partie de mini.nvim ou en version autonome (ici, je vais utiliser mini.icons pour avoir de belles icônes dans le menu de sélection, mais c'est juste pour la frime).
  • mini.snippets, selon le même mécanisme.

J'utilise toujours lazy.nvim pour gérer mes plugins.

Récupérer le plugin

Puisque j'utilise la suite mini.nvim, la configuration n'est pas exactement la même en ce qui concerne l'URL de téléchargement, je vous invite à regarder la doc pour avoir toutes les infos. Cette fois, ça se passe dans ~/.config/nvim/plugin/mini.lua :

return {
    {
        "echasnovski/mini.nvim",
        version = false,
        dependencies = {
            { url = "https://codeberg.org/swytch/snippets.git" }
        },
        config = function()
            -- completion & snippets
            local gen_loader = require("mini.snippets").gen_loader
            local tex_patterns = { "tex/**/*.json", "**/tex.json" }
            local lang_patterns = {
                tex = tex_patterns,
                plaintex = tex_patterns,
                latex = tex_patterns,
            }
            require("mini.snippets").setup(
                {
                    snippets = {
                        -- Load snippets for all languages
                        gen_loader.from_runtime("global.json"),

                        -- Load snippets based on current language by reading files from
                        -- "snippets/" subdirectories from 'runtimepath' directories.
                        gen_loader.from_lang({ lang_patterns = lang_patterns }),
                    },
                    mappings = {
                        -- Expand snippet at cursor position. Created globally in Insert mode.
                        expand = "<C-l>",

                        -- Interact with default `expand.insert` session.
                        -- Created for the duration of active session(s)
                        jump_next = "<C-j>",
                        jump_prev = "<C-k>",
                        stop = "<C-h>",
                    },
                }
            )
            local MiniCompletion = require("mini.completion")
            local process_items = function(items, base)
                -- Don't show 'Text' suggestions
                items = vim.tbl_filter(function(x) return x.kind ~= 1 end, items)
                return MiniCompletion.default_process_items(items, base)
            end
            MiniCompletion.setup(
                {
                    -- Delay (debounce type, in ms) between certain Neovim event and action.
                    -- This can be used to (virtually) disable certain automatic actions by
                    -- setting very high delay time (like 10^7).
                    delay = { completion = 10 ^ 9 },

                    -- Way of how module does LSP completion
                    lsp_completion = {
                        source_func = 'omnifunc',
                        auto_setup = false,
                        process_items = process_items,
                    },

                    -- Fallback action as function/string. Executed in Insert mode.
                    -- To use built-in completion (`:h ins-completion`), set its mapping as
                    -- string. Example: set '<C-x><C-l>' for 'whole lines' completion.
                    fallback_action = '<C-n>',

                    -- Module mappings. Use `''` (empty string) to disable one. Some of them
                    -- might conflict with system mappings.
                    mappings = {
                        -- Force two-step/fallback completions
                        force_twostep = '<C-Space>',
                        force_fallback = '<A-Space>',

                        -- Scroll info/signature window down/up. When overriding, check for
                        -- conflicts with built-in keys for popup menu (like `<C-u>`/`<C-o>`
                        -- for 'completefunc'/'omnifunc' source function; or `<C-n>`/`<C-p>`).
                        scroll_down = '<C-n>',
                        scroll_up = '<C-p>',
                    },
                }
            )
        end,
    },
}

La configuration est assez simple à comprendre : puisque mini.snippets ne gère que l'utilisation de snippets (et pas les snippets eux-mêmes), on les récupères d'un dossier tiers (ici c'est un plugin perso que je passe via dependencies.[url = "https://codeberg.org/swytch/snippets.git"], mais il existe un plugin communautaire qui en donne beaucoup) et on les passe au plugin lors de son activation ; on peut ensuite personnaliser les commandes claviers qui activent le plugin pour étendre les snippets et naviguer à l'intérieur de ceux-ci, le cas échéant.

En ce qui concerne mini.completion c'est un peu pareil. D'abord, on filtre un peu les propositions pour ne pas avoir le texte d'un fichier, mais simplement les éléments « intéressants » (variables, fonctions...) ; ensuite, on passe tout ça au plugin, on lui donne un temps d'activation très long (pour une raison qui m'échappe, la complétion automatique n'est pas désactivable), on configure l'action « en cas d'échec » (fallback_action) pour retomber sur le mécanisme de complétion natif de neovim, on paramètre les raccourcis clavier, et voilà.

Et finalement, ça donne quoi ?

La complétion donne ça :

Capture d'écran du plugin de complétion en action

Par défaut, il affiche également une bulle d'info pour, dans notre cas, savoir exactement quelle référence on va citer.

Pour les snippets, ça donne ça :

Capture d'écran du plugin de gestion des snippets en action

Ici, le point doré indique le nœud sur lequel on se trouve actuellement, les points bleus sont les nœuds que l'on n'a pas encore visité et le carré rouge est le nœud de sortie. Si ce n'est pas clair, référez-vous à la démo du plugin qui est très bien fichue.

The deed is done.

Je pense que vous avez une bonne base pour la complétion et les snippets ; maintenant, à vous de trouver la configuration qui vous va le mieux.

À ciao !