Automatisation

Palier4 Cette page répond à une seule question : j’ai deux cents lignes à transformer de la même façon, et je refuse de le faire à la main. Il y a cinq outils pour ça, et le plus important est de savoir lequel sortir.

Le point répétable

. rejoue la dernière modification. C’est la touche la plus rentable de l’éditeur, et pourtant elle déçoit souvent — parce que ce qu’elle rejoue dépend entièrement de la façon dont on a formulé la commande.

Comparez deux manières de changer un mot :

vec        passe en visuel, étend jusqu'à la fin du mot, change
ciw        change le mot

Le résultat à l’écran est le même. Mais . après la première ne fera rien d’utile, alors que . après la seconde rejoue « change le mot » n’importe où. La règle qui en découle : une modification, un seul geste, exprimé avec un opérateur et un objet. Le mode visuel est confortable pour un cas isolé, et il casse la répétabilité.

L’idiome qui en tire le plus est la paire recherche + point :

*          cherche le mot sous le curseur
ciwbar     le remplace par bar
n.         occurrence suivante, on rejoue
n.         et ainsi de suite

C’est plus lent qu’un :%s sur deux cents occurrences, et plus rapide sur cinq — avec l’avantage de voir chaque cas avant de le valider. Même logique avec les déplacements de caractère : dt, puis ; puis . pour le suivant.

Le compteur ne se répète pas comme on croit

3dw supprime trois mots, et . en supprime trois de plus. Mais 2. ne rejoue pas deux fois la commande : il la rejoue une fois en remplaçant son compteur par 2. C’est utile pour corriger le tir — d2w puis 5. supprime cinq mots — et déroutant si on ne l’attend pas.

Les registres

Un registre est un emplacement de stockage nommé. On préfixe la commande par "{registre} :

"ayy    copie la ligne dans le registre a
"Ayy    l'ajoute à la fin du registre a
"ap     colle le registre a
RegistreContenu
"registre sans nom — dernière copie ou suppression
0dernière copie explicite, jamais écrasée par une suppression
1 à 9historique des suppressions de plus d’une ligne
-dernière suppression de moins d’une ligne
a à zregistres nommés, à disposition
_trou noir : y écrire ne conserve rien
+presse-papiers du système
%nom du fichier courant
.dernier texte inséré
:dernière commande Ex
/dernière recherche

:registers affiche tout le contenu.

Trois usages qui reviennent constamment :

  • "0p pour coller après un remplacement visuel. Le collage en mode visuel écrase le registre sans nom avec le texte remplacé ; "0 a gardé la copie.
  • "_d pour supprimer sans polluer. Utile juste avant un collage : "_dP remplace la sélection en conservant ce qu’on voulait coller.
  • Ctrl + r en mode insertion ou en ligne de commande insère un registre. Ctrl + r puis % insère le nom du fichier courant, Ctrl + r puis / la dernière recherche — pratique pour construire un :%s à partir d’un motif qu’on vient de tester avec /.

Les macros

Une macro est une suite de frappes stockée dans un registre. C’est le même mécanisme, ce qui explique qu’on puisse la coller pour la relire.

  • q{a-z} : démarre l’enregistrement
  • q : arrête
  • @{a-z} : rejoue
  • @@ : rejoue la dernière macro utilisée
  • {n}@a : rejoue n fois
  • :'<,'>normal @a : applique la macro à chaque ligne d’une sélection

Trois principes rendent les macros fiables plutôt qu’aléatoires :

Ancrer le départ. Commencer par 0 ou ^ pour ne pas dépendre de la position du curseur au moment du rejeu.

Se déplacer d’une ligne à la fin. Terminer par j permet d’écrire 100@a : la macro avance elle-même. Sans ça, il faut repositionner le curseur entre chaque rejeu.

Préférer un déplacement structurel à un compteur. f( est robuste si les lignes varient, 3w ne l’est pas.

Une macro qui échoue s’arrête d’elle-même : si f( ne trouve pas de parenthèse, le rejeu s’interrompt sur cette ligne plutôt que de faire n’importe quoi. C’est une propriété à exploiter — lancer 1000@a sur un fichier de quatre-vingts lignes fonctionne, la macro s’arrête au bout.

Corriger une macro sans la réenregistrer

"ap colle le contenu du registre a dans le buffer, sous forme de texte. On corrige la ligne, puis "ay$ la recharge dans le registre. C’est nettement plus agréable que de recommencer un enregistrement de quinze frappes pour une faute à la douzième.

La commande globale

:g exécute une commande Ex sur toutes les lignes qui correspondent à un motif ; :v sur toutes celles qui n’y correspondent pas. C’est l’outil le plus sous-estimé de Vim.

:g/console\.log/d          supprime toutes les lignes de log
:v/TODO/d                  ne garde que les lignes contenant TODO
:g/^$/d                    supprime les lignes vides
:g/^/m0                    inverse l'ordre du fichier

Sa vraie puissance vient de la combinaison avec :normal, qui exécute des touches du mode normal comme si on les tapait :

:g/import/normal A;        ajoute un point-virgule à chaque import
:g/function/normal @a      applique la macro a à chaque fonction
:g/^\s*def /normal >>      indente la ligne de chaque définition

C’est le pont entre les deux mondes : :g sélectionne les lignes, :normal applique la grammaire d’édition dessus. Une macro appliquée par :g cible exactement les lignes concernées, là où un 100@a avancerait aveuglément.

Agir sur tous les fichiers d’un projet

La quickfix — remplie par :grep, :vimgrep ou les diagnostics LSP, voir Recherche — se traverse aussi en écriture.

:grep -F "oldName" .
:copen                          on vérifie la liste
:cdo s/oldName/newName/g | update
  • :cdo {cmd} : exécute la commande sur chaque entrée de la quickfix
  • :cfdo {cmd} : une fois par fichier — préférable pour un :%s, qui traite déjà tout le fichier
  • | update : enregistre, mais seulement si le fichier a changé

C’est le renommage à travers un dépôt entier, sans quitter l’éditeur et sans plugin. Le contrôle est meilleur qu’un sed -i parce qu’on lit la liste avant d’agir, et que u fonctionne — même si l’annulation reste par buffer.

Vérifier avant, pas après

:cdo sur trois cents entrées modifie trois cents endroits sans confirmation. Ouvrir :copen et parcourir la liste prend dix secondes ; et un dépôt propre avant l’opération vaut mieux que n’importe quelle relecture, puisque git diff devient la vraie vérification.

Choisir l’outil

C’est la seule partie de cette page qui mérite d’être retenue par cœur.

SituationOutil
La même modification, quelques fois, en vérifiant chaque casciw puis n.
Un remplacement textuel simple, dans un fichier:%s/…/…/g
Le même remplacement, mais chaque cas mérite un œil:%s/…/…/gc
Une transformation en plusieurs étapes, ligne par ligneune macro
Une transformation qui ne concerne que certaines lignes:g/motif/normal @a
Supprimer ou garder des lignes selon un motif:g/…/d ou :v/…/d
Une modification à travers plusieurs fichiers:grep puis :cfdo
Une transformation vraiment complexe, sur beaucoup de fichiersun script, hors de l’éditeur

La dernière ligne compte autant que les autres. Vim n’est pas obligé d’être la réponse : ! filtre une sélection par une commande externe, et il y a des jours où jq ou un script Python de dix lignes est la bonne réponse.