Récente découverte pour moi: neovim expose une partie de la libuv.

Cette bibliothèque permet de gérer des évènements asynchrones. Parmi eux sont envisageables la gestion de requêtes externes en TCP ou UDP ou bien l’appel de commandes lorsqu’un fichier est modifié sur le disque. C’est cette seconde possibilité que je vais aborder dans cet article.

J’écris de plus en plus de choses en markdown avec pandoc pour la conversion en html et/ou pdf. J’ai récemment rédigé un article sur comment configurer neovim pour la gestion du markdown pour pandoc ou revealjs. Ce que je présente ici va plus loin en automatisant plus encore les taches de générations en reposant sur la libuv et, ce faisant, sans bloquer l’interface de l’éditeur à chaque sauvegarde.

Le besoin

Dans mon précédent article, je déclenchais un appel à :make<CR> à chaque sauvegarde du buffer sur disque. En ayant préalablement redéfini makeprg, un document html était généré à chaque :w<CR>.

Ça fonctionne, pas de doute là-dessus. Cependant, il y a une petite gêne: l’interface s’arrête une fraction de seconde, le temps que l’appel à pandoc soit terminé.

Dans ce cas de documents courts ou sur une machine puissante, c’est à peine visible. Mais qu’en est-il des situations plus raisonnables en termes de machine hôte ou de contenu à rallonge?

La solution

Surprise surprise… libuv à la rescouse! Qui aurait pu le prévoir?

En deux parties: un code générique et du code spécifique appelant le premier. Après tout, rares sont les développeurs qui aiment se répéter.

Watch.lua

Comme d’habitude, je commence par le code à placer dans ~/.config/nvim/lua/watch.lua:

1local M = {}
2
3local event = nil
4
5function M.stop()
6 if event and event:is_active() then
7 event:stop()
8 event = nil
9 end
10end
11
12function M.watch(callback, path)
13 path = path or vim.fn.expand("%")
14
15 M.stop()
16
17 event = vim.loop.new_fs_event()
18 event:start(path, {}, function(err, filename, events)
19 assert(not err, err)
20
21 if events.change then
22 callback(filename)
23 end
24 end)
25end
26
27return M

Il s’agit d’un petit module lua définissant un event local et deux fonctions publiques:

  • La première, stop s’occupe d’arrêter la boucle de surveillance (l’event) si elle existe.
  • La seconde, watch est en charge de débuter la surveillance d’un fichier sur le disque (par défaut le fichier relatif au buffer courant) et appeler une fonction callback à chaque modification détectée.

La libuv est accessible via vim.loop.

Pour plus d’infos :help uv<CR>.

Ce bout de code ne contient aucune information précise quant au traitement à faire subir au fichier. Ce n’est qu’une façon de partager la logique de surveillance des fichiers, celle du traitement doit être fournie par un code appelant.

Pandoc.lua

Je reprends le fichier ~/.config/nvim/after/ftplugin/pandoc.lua car cet article est la suite logique de celui cité en introduction. Le raisonnement présenté ici est bien sûr portable à mon revealjs.lua ou autres plantuml.lua pour la génération de schémas.

Et, pour continuer comme avant, du code:

1local watch = require("watch")
2
3local function make(path)
4 local handle = vim.loop.spawn("pandoc", {
5 args = {
6 path,
7 "--to=html5",
8 "--standalone",
9 "--output", vim.fn.fnamemodify(path, ":r") .. ".html"
10 }
11 }, function(code, signal)
12 assert(code, code)
13 assert(signal, signal)
14 end)
15
16 handle:close()
17end
18
19local pandoc = vim.api.nvim_create_augroup("pandoc", { clear = true })
20
21vim.api.nvim_create_autocmd({ "BufEnter" }, {
22 group = pandoc,
23 callback = function() watch.watch(make) end
24})
25
26vim.api.nvim_create_autocmd({ "BufLeave" }, {
27 group = pandoc,
28 callback = function() watch.stop() end
29})

Je détaille les quatre parties surlignées ci-dessous:

  • J’importe watch.lua, le module défini dans la section précédente. J’ai donc accès aux fonctions watch et stop.
  • Je définis dans la fonction make ce qu’il faut faire du fichier lorsqu’un changement est détecté. Ici, je me contente de générer une version html du contenu dans le même dossier que la source en reprenant le reste du nom di fichier.
  • Je commence à surveiller le fichier quand je rentre dans un buffer dont le filetype est pandoc.
  • J’arrête la surveillance dans je sors dudit buffer.

Conclusion

L’intégration de la libuv dans neovim permet de lancer des calculs lourds en parallèle de l’édition du code. Ici il ne s’agit que de convertir du markdown en html mais des cas d’usages plus avancés sont triviaux à mettre en place. La documentation, truffée d’exemple, est une excellente source d’inspiration.

En termes d’expérience utilisateur, je suis content de ne plus avoir ce petit lag à chaque sauvegarde. Il ne me manque plus qu’un moyen de signaler à mon navigateur que la page web a été renouvelée et qu’il faut recharger l’onglet. Peut-être pour un prochain article?