Cela fait des années que j’utilise les Sessions, qu’elles soient de tmux ou de vim puis désormais neovim. Elles permettent de conserver l’état de mon activité d’un jour à l’autre, d’un redémarrage ou d’une déconnexion à l’autre.

Mon éditeur de texte favoris a une commande pour sauvegarder son état: :mksession<CR> et une pour le restaurer: nvim -S. Mais bon, devoir se souvenir de sauvegarder, c’est tout de même une bonne façon d’oublier. Donc, j’ai utilisé le plugin Obsession du merveilleux Tim POPE et ce pendant de nombreuses années.

Mais aujourd’hui… Tout a changé !

Coucou Evan 👋 Merci de m’avoir motivé à écrire cet article !

Au début, il n’y avait rien

Oui, tout a changé, quand je me baladais naïvement sur reddit, en quête de rien. Sans crier gare, je tombe sur l’annonce de lazy.nvim, un nouveau gestionnaire de plugins.

Même si je suis pleinement content de packer pour ce travail, je devais satisfaire ma curiosité: le contenu est bon mais ce qui m’interroge le plus, c’est l’auteur dudit plugin: u/folke. Il a l’air d’être connu comme le loup blanc, c’est le taulier, tout le monde salue son travail. Et j’avoue que je suis un peu vexé, car je traine mes guêtres dans vim/nvim depuis pas mal de temps sans connaître l’énergumène.

Un petit tour sur son profil github m’apprends que le bonhomme a effectivement tout d’une sommité et je continue sur la liste de ses plugins les plus côtés pour tomber sur persistence.

Puis Folke créa Persistence

Persistence is a simple lua plugin for automated session management.

Il le dit, et il le fait ! Mais encore plus simplement et sensiblement mieux que Obsession.

Simple

Un des côtés négatifs de Obsession est qu’il faut penser à le démarrer: :Obsession<CR>. Sans ça, la session n’est pas mémorisée.

Là où Persistence se contente de démarer silentieusement au dès lors qu’un buffer est écrit sur disque. Alors, oui, c’est fancy, il y a du lazy loading, toussa toussa… N’empêche que ça fait le café tout seul et c’est une occasion de moins de se planter.

Mieux

Obsession repose sur les :h :mksession natives de vim/nvim. Et, par défaut, elles écrivent l’état dans un fichier Session.vim, là où la commande a été invoquée.

Il est possible de spécifier un chemin pour les ranger “ailleurs”, loin du code. Mais ensuite, il faut retourner les chercher, se souvenir de “laquelle est laquelle” etc. C’est une activité à laquelle je me suis adonnée un bout de temps avant de finalement laisser les Session.vim sur place et simplement lancer (n)vim -S en me prenant un message d’erreur si aucune session n’est présente. (En réalité j’ai écrit une fonction bash qui ajoute ou non le -S si session il y a mais là n’est pas la question.)

Persistence quant-à-lui s’occupe déjà de tous ces problèmes: il y a un champ dir dans sa configuration pour indiquer où ranger les sessions et ne plus avoir à s’en occuper. Et la commande require("persistence").load() va chercher la session la plus récente utilisée dans ce dossier. Que de soucis en moins !

Bonus

Avec les copains geeks (👋 Yannick, 👋 Pierre-Luc 👋), on parlait des folds. Les folds permettent de cacher des parties du code qui ne nous importent que peu la plupart du temps. Un exemple typique est l’implémentation d’un trait en Rust 🦀 : un fold nous informe du nom du trait et du type pour lequel il est implémenté ; C’est amplement suffisant, pas besoin d’avoir les 30+ lignes de code ouvertes.

Hé bien, Persistence, en plus du champ dir a un champ options a destination de :h sessionoptions. Et ce petit champ bien pratique accepte l’entrée "folds" ce qui a pour effet de mémoriser l’état des folds dans la session.

Terminé les folds à réouvrir ou refermer à chaque chargement de session, c’est tout automagique et configuré dans un bête dictionnaire lua! Je suppose que j’aurais pu obtenir le même résultat avec Obsession et un peu de code, mais comme dit au-dessus, là, c’est plus simple.

Configuration

Comme dit au début, j’utilise Packer pour télécharger et configurer mes plugins:

1use({
2 "folke/persistence.nvim",
3 as = "persistence",
4 event = "BufReadPre",
5 module = "persistence",
6 config = function()
7 require("persistence").setup({
8 options = { "buffers", "curdir", "tabpages", "folds" },
9 dir = vim.fn.expand(vim.fn.stdpath("state") .. "/sessions/"),
10 })
11 end,
12})

Et pour ce qui est des folds, j’ai aussi ceci dans mon init.lua:

1vim.opt.foldmethod = "expr"
2vim.opt.foldexpr = "nvim_treesitter#foldexpr()"
3vim.opt.foldlevel = 99

Cela permet d’utiliser le merveilleux parsing de TreeSitter pour savoir où créer des folds et indiquer de ne pas les replier en première lecture.

Amélioration

Là où Obsession permettait un (n)vim -S pour charger la précédente session, il est désormais nécessaire d’appeler lua require("persistence").load()<CR> ou de l’automatiser.

Le premier car peut s’améliorer avec un mapping. Le second cas, s’il est lancé à chaque ouverture de nvim va restaurer la précédente session même si nvim a été ouvert avec un chemin vers un fichier en argument.

Ma solution est un peu naïve mais a le mérite de fonctionner:

1if next(vim.fn.argv()) == nil then
2 require("persistence").load()
3end

Si nvim a été invoqué sans argument, c’est-à-dire buffer à ouvrir au lancement, alors la session précédente est restaurée. Persistence n’émet pas d’erreur s’il n’y a pas de session à restaurer, ce qui évite un message d’erreur disgracieux.

Je sais que ce n’est pas 100% parfait. Par exemple, je pourrai charger la session si les arguments de nvim étaient des options et pas des buffers à ouvrir. Mais je pense que ces quelques lignes couvrent largement plus de 80% de mes usages et la loi de Pareto me dit qu’il est temps de m’arrêter là.