Le weekend dernier avait lieu les Journées du Logiciel Libre et à leurs programme pléthore de sujets passionants. Parmis les sujets qui me touchent le plus, on y a parlé de claviers ergonomiques sur un stand dédié et durant une conférence, mais aussi de neovim via conférences et #TupperVim.

L’avantage d’être entouré de centaines de personnes partageant notre enthousiasme pour ces sujets et qu’on y apprend forcément quelque chose. Petit billet donc pour deux découvertes me concernant au tour de nvim.

J’en ai déjà parlé à de multiple reprises sur ce site: j’utilise nvim. C’est pour moi un outil central pour toute rédaction, qu’il s’agisse de code, de documentation ou de simple prise de note.

L’un des plugins incontournable dans l’écosystème de nvim est telescope. Telescope propose une interface unifiée de fuzzy-finding : grep, git files, buffers et j’en passe.

Si j’ai tenté d’autres approches comme harpoon pendant un temps, je suis toujours revenu à telescope pour me balader entre mes buffers ouverts ou accéder à ceux qui ne le sont pas encore. C’est tellement devenu un automatisme que j’ai désormais les mappings suivants pour ouvrir telescope facilement:

local builtin = require("telescope.builtin")
vim.keymap.set("n", "<Leader><Leader>", builtin.buffers, { desc = "Telescope buffers" })
vim.keymap.set("n", "<Leader>|", builtin.git_files, { desc = "Telescope git files" })

vim.keymap.set("n", "<Leader>tg", builtin.live_grep, { desc = "Telescope live grep" })
vim.keymap.set("n", "<Leader>th", builtin.help_tags, { desc = "Telescope help tags" })
vim.keymap.set("n", "<Leader>tk", builtin.keymaps, { desc = "Telescope keymaps" })

Comme montré ci-dessus, j’utilise <Leader><Leader> pour faire apparaître le buffer picker. Il suffit ensuite de taper quelques lettres contenues dans le chemin d’un fichier pour raffiner la sélection et la valider avec <CR>.

Mais j’utilise aussi <Leader>| et <leader>tg pour naviguer entre les fichiers versionnés ou rechercher une chaîne de caractères dans l’ensemble des fichiers accessibles. Ce sont mes deux principales méthodes pour charger de nouveaux fichiers dans des buffers.

La liste des buffers peut vite s’agrandir, notamment lors d’une session exploratoire quand il s’agit de traquer un bout de code (généralement appelé bug par les clients et fonctionnalité par les commerciaux 🤷). De fait, le *buffer picker présente de plus en plus de lignes qui ne seront en fait plus utilisées et un peu de ménage devient nécessaire.

Avant (c’était pas mieux)

Auparavant, j’utilisais une fonctionnalité native de (n)vim: bdelete.

Son usage est simple:

  • :bdelete<CR> ferme le buffer courant,
  • :bdelete buffer<CR> ferme le buffer buffer,
  • :bdelete regex<C-a><Cr> ferme tous les buffers dont les noms matchent la regex.

Problème, les deux premières méthodes sont précises mais lentes alors que la dernière, rapide, peut fermer trop de buffers si la regex est trop générale.

Maintenant (c’est le futur donc c’est mieux)

L’un des participants au tupper vim des JdLL nous a présenté le plugin buffer manager. Ce dernier propose (entre autres) une interface interactive pour la sélection et la fermeture des buffers.

Après intense réflexion, je me suis dit qu’il devait forcément y avoir un moyen d’obtenir le même résultat avec telescope. Après tout, s’il peut ouvrir et lister des buffers, il doit bien pouvoir les fermer. Et la réponse est: oui.

1require("telescope").setup({
2 pickers = {
3 buffers = {
4 show_all_buffers = true,
5 sort_lastused = true,
6 mappings = {
7 i = {
8 ["<c-d>"] = "delete_buffer",
9 }
10 }
11 }
12 }
13})

Ce petit bout de configuration ajoute le mapping <C-d> dans l’interface de telescope pour fermer le buffer sous la sélection courante.

Donc le workflow devient trivial:

  1. <Leader><Leader> pour ouvrir le buffer picker de telescope,
  2. Fuzzy finding pour réduire la sélection possible interactivement,
  3. <C-d> jusqu’à ce que ce ne soit plus nécessaire.

Hop, pas besoin de plugin supplémentaire pour cette fonctionnalité bien pratique.

Rechercher/Remplacer global

Tous les utilisateurs ayant dépassé le vimtutor connaissent la commande :[range]s[ubstitute]/{pattern}/{string}/[flags] [count]. Utilisable en version courte :s/search/replace, c’est toute la puissance de sed à portée de l’éditeur.

Sauf que voilà, il est parfois nécessaire de modifier plus loin que le buffer courant pour garder une cohérence à l’échelle d’un projet entier. Alors, oui, nous sommes en 2023 et il y a les LSP1 qui font des miracles pour renommer variables et fonctions. Mais ils ne sont pas toujours présents ou n’ont pas toujours toutes les fonctionnalités requises et il faut de temps en temps en revenir à des méthodes datant de vim (ou est-ce de vi ?).

Avant (nan, vraiment, c’était pas mieux)

Ma méthode favorite avant les JdLL était efficace mais peu sure et obscure.

:args regex<CR>
:argdo %s/search/replace/ge | update

La première ligne avec args permet de définir une liste de buffers. Par exemple, pour choisir tous les fichiers Rust 🦀 d’un dépôt: :args src/**/*.rs<CR> est efficace.

La seconde ligne combine deux commandes: argdo va appeler la substitution sur les buffers chargés dans la liste par args avec l’option e pour ne pas s’arrêter si search est absente. Puis update afin d’écrire le contenu des buffers sur disque si et seulement s’ils ont été modifiés (une sorte de :w[rite] intelligent).

Maintenant (pas avant, donc mieux)

J’ai abordé <Leader>tg (pour telescope grep) tout à l’heure: un grep avec fuzzy finding intégré à l’éditeur. Je m’en étais toujours servi pour charger des fichiers dans des buffers. Mais il y a une autre utilité à ce picker dans le contexte présent 🎉

<Leader>tg fuzzy-finding<C-q>. Le <C-q> va venir charger les résultats de la recherche dans la quick-fix list de nvim. Cela permet une étude détaillée des résultats de la recherche contrairement à la méthode précédente qui peut être utilisée avec une regex trop large avec argdo. Il devient donc possible de répéter le processus jusqu’à l’obtention contrôlée de l’ensemble minimal recherché.

La suite, c’est comme argdo mais avec cfdo pour appliquer une commande aux buffers répertoriés dans la quick-fix list. La méthode est plus sûre, car il est aisé de raffiner itérativement sa recherche mais aussi moins obscure: si quelqu’un ne connaissant pas nvim me voyait utiliser les deux méthodes, il est certain qu’il comprendrait bien mieux la seconde approche qui est donc à privilégier en cas de pair-programming.

Conclusion

Comme disait l’autre: Sharpen the saw.

Ces deux tips ne font pas de moi un meilleur programmeur, mais ils me permettent un usage plus sain et évident de mon outil de travail principal: l’éditeur de texte.

J’espère apprendre au plus vite de nouvelles approches pour mieux répondre à mes besoins dans ce domaine!

1

Language Server Protocol