Améliorations de mes prises de notes avec Vim

Un peu de contexte

Ma manière de prendre des notes avec Vim est minimaliste, mais fonctionnelle.

Cependant j’ai rapidement eu un souci avec vimgrep : sa syntaxe. En effet, pour chercher un simple motif dans les fichiers autour ou dans des sous-dossiers, il faut faire :

:vimgrep MOTIF **

Attendre que les résultats soient trouvés, et ouvrir la fenêtre de quickfi … ah flûte ! Vim vient de m’ouvrir un buffer avec un résultat (le 1er selon la doc) … Grumph.

Bon, je reviens sur mon premier buffer, je ferme la fenêtre quickfix (:cclose) et, pour ne pas ouvrir automatiquement le premier résultat, je relance une recherche ainsi :

:vimgrep /MOTIF/j **

ATTENDRE ENCORE, puis ouvrir la fenêtre de quickfix (:copen), naviguer sur un résultat intéressant, et se faire ouvrir un nouveau buffer pour commencer à utiliser le fichier difficilement trouvé.

Autant dire que pour simplement rechercher un terme dans mes notes, c’était fastidieux. Je notais donc mal, voir me servait pas de cet outil, préférant un bon vieux Ctrl + Z , suivi d’un grep -nir PATTERN **, puis fg.

Fonctionnel, mais peu pratique. Il était temps que je me penche sur la question.

Amélioration de la recherche interne

Toujours dans mon optique de ne pas installer plus de plugins Vim, voici la petite solution que j’ai trouvée. Elle consiste à faire une commande spécifique pour la recherche qui va faire office de raccourci pour être lancée sans saut automatique au premier résultat, tout en ouvrant la quickfix.

Tout ceci est à faire dans le .vimrc

Tout d’abord, des commandes pour améliorer le maniement de la quickfix window :

" Show the quickfix window
nnoremap <Leader>co :copen<CR>
" Hide the quickfix window
nnoremap <Leader>cc :cclose<CR>

Ensuite, une commande pour lancer la recherche (sans saut immédiat au premier résultat) et afficher la quickfix :

command! -nargs=+ Find execute 'silent vimgrep! /<args>/j **' | copen

Et enfin, changer le comportement de la touche Entrée dans la quickfix pour ouvrir un onglet plutôt qu’un nouveau buffer :

function! QuickfixMapping()
  " Ouvrir le résultat sélectionné dans un nouvel onglet,
  " en fait d'abord un split horizontal, puis le changer en nouvel onglet
  nnoremap <buffer> <Enter> <C-W><Enter><C-W>T
endfunction

augroup quickfix_group
    autocmd!
    autocmd filetype qf call QuickfixMapping()
augroup END

Satisfaction intermédiaire

Et hop ! Un comportement qui me plait bien plus avec une recherche bien plus facile à manipuler, car je n’ai plus qu’à taper :Find MOTIF, naviguer dans la quickfix qui s’est ouverte, et appuyer sur entrée.

Hey ! Mais ça fonctionne également ailleurs : quand je suis dans mon dossier contenant mes articles de blog, quand je suis dans un dossier de scripts … Bref, partout en fait. C’est super pratique !

Mais c’est LEEEEEEEEEEENT !

Comment est-ce que je pourrais améliorer ça ?

Ripgrep à la rescousse !

Là, il fa falloir passer par un outil externe. Il y a bien le bon vieux grep qui peut faire l’affaire, mais puisque j’en suis là, autant tester ripgrep qui à l’air extrêmement rapide.

On commence par spécifier que le grep externe à appeler est rg s’il est présent, tout en lui passant des arguments utiles :

if executable("rg")
    set grepprg=rg\ --vimgrep\ --no-heading\ --smart-case
    set grepformat=%f:%l:%c:%m
endif

Il faut ensuite changer la commande personnalisée Find pour lui dire d’appeler grep. On en profite pour virer les spécificités de vimgrep vues précédemment :

command! -nargs=+ Find execute 'silent grep! <args>' | copen

Wow ! Ça va déjà vachement plus vite. C’est presque instantané ! Par contre, Vim semble tout perdu. Mon affichage en dehors de la quickfix est cassé.

Je vais donc forcer Vim à refaire son rendu à la fin de la recherche :

command! -nargs=+ Find execute 'silent grep! <args>' | copen | redraw!

OK. C’est bien mieux.

Maintenant, il reste un autre souci lié au fait d’appeler un programme externe ainsi : sur la sortie standard de mon shell, le résultat de la recherche s’y trouve. Que ce soit en envoyant Vim en arrière plan, ou en le quittant tout simplement. Et il reste une espèce de clignotement. C’est dû au fait que Vim exécute les appels aux commandes externes dans son shell parent.

Rusons pour faire l’inverse en créant une fonction qui va faire l’appel à ripgrep dans un sous-shell :

function! GrepNotes(...)
    return system(join([&grepprg] + [expandcmd(join(a:000, ' '))], ' '))
endfunction

Pour plus de détails et explications techniques sur l’intérêt de procéder ainsi, je vous renvoie vers le lien « Instant grep + quickfix (en anglais) » en bas de l’article.

Maintenant, modifions encore une fois la commande Find pour appeler cette nouvelle fonction :

command! -nargs=+ -complete=file_in_path -bar Find cgetexpr GrepNotes(<f-args>) | copen

Vu que l’on part dans un sous-shell, plus besoin de forcer le rafraichissement de l’affichage de Vim. Un très bon point.

Derniers ajustements

Maintenant, un peu de cosmétique. Je trouve que la quickfix est trop petite. Comme on peut passer un argument (nombre en hauteur de lignes) à :copen, voici ce que j’ai mit. Ah, et puis au passage, je me rajoute un raccourci pour l’ouvrir verticalement.

nnoremap <Leader>co :copen 22<CR>
nnoremap <Leader>cO :vertical botright copen 90<CR>
nnoremap <Leader>cc :cclose<CR>

Et je modifie une ultime fois ma commande Find pour l’ouverture de la quickfix :

command! -nargs=+ -complete=file_in_path -bar Find cgetexpr GrepNotes(<f-args>) | copen 22

Tout en un

Voici le contenu complet de ce que j’ai ajouté à mon .vimrc pour en arriver là :

" QuickFix windows :
" Show the quickfix window (22 lines height)
nnoremap <Leader>co :copen 22<CR>
" Show vertical quickfix (90chr wide)
nnoremap <Leader>cO :vertical botright copen 90<CR>
" Hide the quickfix window
nnoremap <Leader>cc :cclose<CR>

" search with ripgrep
if executable("rg")
    " linux :
    set grepprg=rg\ --vimgrep\ --no-heading\ --smart-case
    " if windows w/ git-bash (see: https://github.com/BurntSushi/ripgrep/issues/275) :
    "set grepprg=rg\ --vimgrep\ --no-heading\ --smart-case\ --path-separator\ //
    set grepformat=%f:%l:%c:%m
endif
" call ripgrep in a sub-shell and capture the output,
" rather than the original parent-shell call
function! Grep(...)
    return system(join([&grepprg] + [expandcmd(join(a:000, ' '))], ' '))
endfunction
" search and opens quickfix windows
command! -nargs=+ -complete=file_in_path -bar Find cgetexpr Grep(<f-args>) | copen 22

function! QuickfixMapping()
  " Go to the previous location and stay in the quickfix window
  nnoremap <buffer> K :cprev<CR>zz<C-w>w
  " Go to the next location and stay in the quickfix window
  nnoremap <buffer> J :cnext<CR>zz<C-w>w
  " Open selected result in a new tab (in fact, first a split, then change it
  " to a new tab)
  nnoremap <buffer> <Enter> <C-W><Enter><C-W>T
endfunction

augroup quickfix_group
    autocmd!
    autocmd filetype qf call QuickfixMapping()
augroup END

Sans oublier : pacman -Sy ripgrep

Et voilà !

Pfiou ! C’était long et fastidieux d’y arriver. Il a fallu chercher plein de choses, comprendre le comportement de Vim sur certains points (la découverte du mécanisme et du fonctionnement de la quickfix window, les appels au grep externe, etc.), mais honnêtement, ça valait le coup et c’était très intéressant.

Je suis certain qu’on peut aller plus loin, pour éviter d’avoir à taper :Find PATTERN en mode commande. J’ai même vu de quoi lancer la recherche en arrière plan et avoir un affichage dynamique en fonction de ce qui est tapé via fzf. Mais c’est plus sage d’arrêter là pour l’instant.

Ça fonctionne très bien partout (pas seulement pour mes notes), c’est bien plus efficace et rapide qu’avant, et j’ai atteint l’objectif d’améliorer mon outillage sans utiliser de plugin. Mon système accueille un nouveau binaire, certes, mais mon éditeur et sa conf n’ont pas été trop alourdis.

C’est une bonne chose selon moi, car je peux continuer de déposer mon fichier de conf un peu partout pour garder les mêmes réflexes. De plus, ripgrep est disponible sur plein de systèmes.

Liens