Neovim + LibUV = <3
Table des matières
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
:
1 local M = {}
2
3 local event = nil
4
5 function M.stop()
6 if event and event:is_active() then
7 event:stop()
8 event = nil
9 end
10 end
11
12 function 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)
25 end
26
27 return 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:
1 local watch = require("watch")
2
3 local 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()
17 end
18
19 local pandoc = vim.api.nvim_create_augroup("pandoc", { clear = true })
20
21 vim.api.nvim_create_autocmd({ "BufEnter" }, {
22 group = pandoc,
23 callback = function() watch.watch(make) end
24 })
25
26 vim.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 fonctionswatch
etstop
. - 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 versionhtml
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
estpandoc
. - 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?