L’autre jour (c’était il y a au moins longtemps!), j’étais à un TupperVim. Et notre maître de cérémonie, l’illustre Fabien CAZENAVE a rappelé les bases de notre activité favorite : Sharpen The Saw.

  1. Prendre une fonctionnalité peu ou mal connue susceptible de répondre à un besoin récurrent,
  2. La poncer pendant quelque temps jusqu’à savoir l’utiliser en dormant,
  3. Recommencer.

Je l’ai mis dans d’autres articles, j’utilise vim et maintenant nvim depuis une décennie. Mais loin de tout savoir sur l’outil qui me surprends fréquemment, j’ai noté qu’il y avait des fonctionnalités basiques que je n’avais jamais approfondies. Peut-être est-il temps d’y consacrer un peu de temps et d’énergie?

Dans cet article, je m’efforce de décortiquer les registres. On les utilise fréquemment sans s’en rendre compte: yyp, le copier-coller de l’éditeur modal utilise un registre. Une recherche avec /regex? Registres. Une commande avec :command? Registres.

Allez, c’est parti.

Des registres ? Combien de registres ?

La documentation, :h registers indique fièrement qu’il n’y a pas moins de 10 types de registres. Eh bien je vais bêtement décortiquer cette liste et tenter d’expliquer avec mes mots et mes exemples comment chacun fonctionne et quelle est son utilité par rapport aux autres. J’introduirai les méthodes de manipulation des registres au fur-et-à-mesure que l’article avance.

Le registre anonyme ""

Unnamed register en albionnais

À la moindre commande ou action qui implique de copier (y) ou supprimer du texte (d, c, x, s), le registre anonyme est rempli.

Mettons que j’ai un buffer d’ouvert avec le curseur sur la première ligne:

1Autruche
2Kiwi

Si je copie (yy) ou supprime (dd) ou remplace (cc) ladite ligne, je peux vérifier le contenu du registre anonyme avec la commande registers ":

:registers "
Type Name Content
  l  ""   Autruche^J

On constate que le registre anonyme "" est de type l (line wise) et contient Autruche^J, c’est-à-dire le contenu de la ligne ainsi que le saut de ligne final.

Si, au lieu d’une opération sur une ligne, je me contente d’opérer sur des mots. Par exemple, avec un daw avec le curseur quelque part sur Kiwi, le contenu du registre anonyme est bien mis-à-jour ainsi que son type qui passe à c pour characterwise.

:registers "
Type Name Content
  c  ""   Kiwi

Le dernier type est b pour blockwise. Sans surprise, il est utilisé lors des opérations sur les blocs. Par exemple, si je me place sur le premier u de Autruche et remplace deux lettres sur les deux lignes avec ^vjlc, le registre anonyme contient les deux bouts de mots et le saut de ligne entre.

:registers "
Type Name Content
  b  ""   ut^Jiw

Les registres nommés "[a-z]

Eh bien oui, l’existence d’un registre anonyme implique l’existence d’au moins un registre nommé. En l’occurrence, 26 registres nommés, un par lettre allant de a à z.

Depuis le début de ce billet, j’utilise "" pour parler du registre anonyme et "[a-z] pour les registres nommés. Ce ", c’est le caractère qui permet d’expliciter le registre à utiliser. Ainsi, yy va remplir le registre anonyme noté " (d’où "") et "ayy va remplir le registre a.

Si je reprends mon exemple animalier de tout à l’heure, toujours avec le curseur sur la première ligne et que je tape "addyy pour couper Autruche dans a et copier Kiwi dans le registre anonyme, j’obtiens exactement le résultat recherché :

:registers " a
Type Name Content
  l  ""   Kiwi^J
  l  "a   Autruche^J

Par contre, si je fais l’inverse, c’est-à-dire si je copie Autruche dans le registre anonyme puis coupe Kiwi dans "a", avec yyj"add surprise:

:registers " a
Type Name Content
  l  ""   Kiwi^J
  l  "a   Kiwi^J

Enfin, surprise, surprise… Pas tant que ça puisque après tout, comme dit au début, le registre anonyme est rempli par vim à chaque opération parmi y, d, c, x, s. Donc, oui, "add écrit dans "a. Mais aussi dans "".

L’exception, le registre trou noir "_

Le registre trou noir, c’est un peu le /dev/null de vim. C’est simple, il suffit de mettre un truc dedans et pouf, disparu. Et sa particularité c’est de ne pas non plus écrire dans le registre anonyme dans la foulée.

Je me place encore et toujours sur mon oiseau fétiche et yyj"_dd:

:registers " a _
Type Name Content
  l  ""   Autruche^J
  l  "a   Kiwi^J

Le registre anonyme est rempli par yy et l’écriture dans "_ laisse bien Autruche en place.

Les registres nommés "[A-Z]

Non non, ce n’est pas une erreur, je ne suis pas en train de boucler. Je parle bien de "[A-Z] et pas "[a-z]. On prend les mêmes et on change la casse. Et on gagne un truc marrant: au lieu d’écrire à la place du registre nommé comme avec les lettres minuscules, vim va venir à la fin dudit registre.

On prend les mêmes emplumés et on recommence. Avec "bdd"Bdd j’écris Autruche dans "b puis j’ajoute Kiwi à sa suite.

:registers b
Type Name Content
  l  "b   Autruche^JKiwi^J

Alors, ok, dit ainsi, ça ne sert pas à grand-chose. Mais voici un autre cas d’usage un peu plus marrant: qbq:g/Autruche/y B. Décortiquons:

  1. qbq: enregistre une macro dans b et arrête de suite. Euh, oui, fun fact, j’y reviens plus tard mais les macros sont stockées dans les registres. Donc qbq va simplement vider `“b”.
  2. :g/Autruche/y B: cherche toutes les lignes contenant Autruche et les ajoute à la suite du registre "b.
:registers b
Type Name Content
  l  "b   ^JAutruche^J  l [...]

Et bim, le registre "b de type b pour blockwise contient toutes les lignes avec Autruche. C’est typiquement le genre de chose qui m’a déjà été utile pour extraire les informations de fichiers de logs trop grands. Je savais à peu près ce que je cherchais et je voulais plus de contexte utile. Il suffit d’ouvrir un autre buffer et d’y coller "bp l’ensemble des données trouvées pour ne pas être parasité par le reste.

Les registres numérotés "[0-9]

Copier avec yy va écrire dans le registre anonyme certes, mais aussi dans "0. C’est la documentation qui le dit: :h quote0. Le registre "0 contient le dernier yank, sauf si un autre registre a été spécifié. Autrement dit, yy écrit dans "0 mais pas "cyy.

Mais elle ne s’arrête pas là cette documentation. Elle continue en expliquant que le registre "1 contient lui le texte cible de la suppression d ou du changement c le plus récent. Et un peu plus loin de raconter que chaque édition similaire décale au registre numéroté suivant. Donc, ce qui était dans "1 passe dans "2, "2 va dans "3 jusqu’à "9 qui est perdu à tout jamais, remplacé par "8.

Un exemple pour mieux comprendre? Il va falloir rajouter un emplumé et placer le curseur sur le plus gros:

Autruche
Kiwi
Dodo

Si je fais yyjdddd (et pas yyj2dd), que se passe-t-il?

  1. yy place Autruche dans "0,
  2. jdd place Kiwi dans "1,
  3. dd place "1 dans "2 et Dodo dans "1.
:registers 0 1 2
Type Name Content
  l  "0   Autruche^J
  l  "1   Dodo^J
  l  "2   Kiwi^J

Ok, c’est un peu farfelu. Mais c’est tellement plus puissant que le Ctrl-c ou Ctrl-x et Ctrl-v classique. Non seulement on a un registre différent pour le Ctrl-c copier et les Ctrl-x couper mais en prime, on a non plus un presse-papier de coller mais neuf!

Le registre de petite suppression "-

C’est pas la taille qui compte hein. Mais Il faut croire que les développeurs de vim avait un autre avis sur le sujet. Ils ont dont créé le registre de petite suppression "-. Le tiret du 6 pour les intimes.

Dans les grandes lignes, le registre de petite suppression voit son contenu changer si:

  • Une suppression (d, c, r, s) a lieu, pas une copie (y),
  • Que cette suppression fait moins d’une ligne (daw, ciw…),
  • Et qu’aucun registre spécifique n’est précisé.

En prenant le bloc de tout à l’heure et en tapant dddawj"ddiw on a donc:

  1. Une suppression de ligne,
  2. Une suppression de mot,
  3. Une suppression de mot dans un registre.
:registers -
Type Name Content
  c  "-   Kiwi

Seul la seconde action change le contenu du registre de petite suppression 👌

Les registres en lecture seule "%, ": et ".

Jusqu’à présent, j’ai parlé de comment remplir les registres avec des commandes ou des macros. Mais il y a une autre façon de faire: la commande let.

Si je fais :let @-="I like Vim" puis "-p, je fais coller I like Vim depuis le registre de petite suppression dans jamais avoir petit-supprimé I like Vim du contenu d’un buffer.

Le registre de buffer courant "%

Difficile de ne pas avoir l’intuition de ce qu’est le registre de buffer courant. Il contient à tout instant le chemin, relatif répertoire de travail de vim, du buffer courant. Il va de pair avec le registre de buffer alternatif "# sur lequel je reviendrai sous peu.

Évidement, :let @% n’aurait aucun sens et il n’est dont pas possible d’altérer manuellement le contenu de ce registre.

Le registre de dernière commande ":

Hum, décidément, voilà un nom de registre bien obscur, impossible de deviner ce qu’il peut bien contenir… À moins que… Regardons ce qu’il y a dedans en live avec Ctrl-r:: let @-="I like vim".

Oui, en mode insertion, Ctrl-r insert le contenu du registre demandé. Donc, Ctrl-r: sans quitter le mode insertion est équivalent à Esc ":p i. Yeah, plein de touches en moins, les doigts disent merci, les mains applaudissent, qu’est-ce que c’est bien vim!

Surprise, stupéfaction, le registre de dernière commande ": contient… la dernière commande! Et comme la dernière commande consistait à mettre I like vim dans le registre de petite suppression, c’est ce que l’on récupère.

Là encore, :let @: n’aurait strictement aucun sens, ce serait comme dire True=False Et comme on est pas en train de coder en Python2 🐍, on ne fait pas ce genre de chose entre gens raisonnables.

Il y a un truc cool avec ": c’est son usage avec les macros. Pour jouer une macro, on utilise @ suivi du registre. En général, on enregistre une macro dans un registre nommé "[a-z] puis on la joue, une ou plusieurs fois. Eh bien là, on gagne @: gratis pas cher afin de rejouer la dernière commande. Plus la peine d’aller la chercher avec les flèches en mode commande et de la valider, elle est déjà là, à portée de doigts avec beaucoup moins de touches à utiliser.

Le registre de dernière insertion ".

Nan, vraiment, les nomenclatures cryptiques, que je n’en peux plus. Ctrl-r. : Nan, vraiment, les nomenclatures cryptiques, que je n'en peux plus.

Allez hop, on a compris, le registre de dernière insertion :. contient le dernier texte entré en mode insertion. Il y a quelques subtilités détaillées dans :h quote.. Cependant, j’utilise trop peu ". pour pouvoir entrer dans les détails 🤷

Le registre de buffer alternatif "#

Il y a un truc que j’utilisais énormément sur Linux et qui ne fonctionne absolument pas sur macOS c’est Ctrl-^. Cette combinaison de touche amène au précédent buffer visité. Et en refaisant Ctrl-^, on revient au buffer initial. On alterne entre les deux buffers.

Eh bien, vim stocke cette information dans le registre de buffer alternatif dont je parlais un tantinet plus tôt. Et ces deux-là vont tellement bien ensemble que si on affiche le premier, vim donne le second avec:

:registers %
Type Name Content
  c  "%   [path to current buffer]
  c  "#   [path to alternate buffer]

Et je découvre en rédigeant ce billet que :registers # ne donne aucune information. Pour avoir son contenu, il faut obligatoirement demander :registers %.

Ici, on est à nouveau dans des registres accessibles en lecture et en écriture. Si j’étais sur un système digne de ce nom, je pourrais faire :let @#="/foo" puis Ctrl-ˆ et me retrouver à éditer /foo. Mais le monde est mal fait, la vie est triste et je rédige ce billet sur un OS médiocre.

Le registre d’expression "=

Le registre d’expression "=, 🎼 c’est un registre pas comme les autres 🎶 mais moi je l’aime c’est pas ma faute 🎶

Il ne contient pas vraiment du texte mais va calculer du texte. Par exemple, on peut lui demander de faire un peu de maths et d’afficher le résultat en mode insertion Ctrl-r=21+21: 42. Ou bien en mode commande :put =84/2: 42.

Ha oui tiens, la commande put permet d’aller chercher de contenu d’un registre et de l’insérer dans le buffer courant.

Ça ne vous rappelle rien? put -> p ! Eh bien le p que l’on utilise pour coller comme dans yyp ne veut pas dire paste mais bien put. L’usage du paste vient en fait d’autres systèmes et même si tout le monde le comprend, il reste néanmoins faux.

Au final, tant que l’expression passée peut être convertie en un texte, elle peut être passée au registre d’expression. Par exemple, Ctrl-r =expand("%") est totalement équivalent à Ctrl-r %. Mais peut devenir bien plus puissant si l’on souhaite calculer des informations plus complexes à la volée.

Les registres de sélection "* et "+

Eux, ce sont mes chouchous sur Linux. Mais bon, Apple, toussa toussa. Bref, trêve de digressions, ce billet est déjà bien assez long ainsi.

Sur Windows et macOS, "* et "+ se comportent de manières rigoureusement identiques. Mais sur un vrai système, ils permettent d’interagir avec les presse-papiers de l’hôte. Car oui, le presse-papier de vim ne vient pas lire ni plus écrire dans celui de l’OS. Même après toutes les manipulations ci-avant, un Ctrl-v n’aurait jamais collé Autruche ni Kiwi. Les registres de sélection pallient à ce manque.

Sélection primaire

Il existe plusieurs presse-papiers sur Linux. Celui de sélection primaire correspond à n’importe quel texte simplement surligné à la souris. Il correspond à "* sur vim.

Autrement dit, si je surligne un texte avec ma souris dans n’importe quelle application, ledit texte sera écrit dans le presse-papier de sélection primaire. Et :put * en mode commande, Ctrl-r * en mode insertion ou "*p en mode normal écriront son contenu dans le buffer.

Si jamais cette sélection primaire n’existe pas sur le système sur lequel vim est lancé, alors c’est le presse-papier habituel qui est utilisé.

Presse-papier

Tous les systèmes modernes disposent du classique Ctrl-C/Ctrl-v pour copier-coller.

Eh bien Ctrl-c quelque part puis :put + en mode commande, Ctrl-r + en mode insertion ou "+p en mode normal ira écrire dans vim.

À l’inverse, "+yy dans vim ira écrire dans le presse-papier du système. Il sera alors possible de coller le contenu en provenance de vim dans une autre application avec Ctrl-v.

Le registre de recherche "/

C’est quand même dingue, le registre de recherche "/ est noté /, comme le caractère utilisé pour faire une recherche. Vraiment, il y a des gens intelligents derrière ce petit bout de logiciel 👌

Le registre de recherche est le dernier registre présenté ici. Il n’est pas compliqué, il contient la dernière recherche effectuée. Si je cherche les oiseaux utilisés dans les exemples, /\(Autruche\|Kiwi\|Dodo\) et que je vérifie le contenu du *registre de recherche:

:registers /
Type Name Content
  c  "/   \(Autruche\|Kiwi\|Dodo\)

Époustouflant n’est-ce pas? Alors, ça ne paraît pas dingue ainsi, mais la puissance se révèle quand on rédige des scripts et greffons. Il peut être extrêmement pratique de remplir "/ de manière programmatique: let @/="vim" fera que le prochain appui sur n ou N ira chercher vim sans que l’utilisateur n’ait jamais tapé /vim.

Macros

Je l’ai dit auparavant, il y a un truc top avec les macros, c’est que ce ne sont rien de plus que des chaînes de caractères dans des registres.

Mettons que je veuille écrire une macro qui change la prochaine Autruche en Kiwi. J’utilise q pour débuter un enregistrement, j’indique un registre, et je fais ma popote: qx/Autrche<CR>cwKiwi<Esc>q.

:registers x
Type Name Content
  c  "x   /Autrche^McwKiwi^[

Horreur et faute de frappe! Une erreur s’est glissée dans Autruche, le second u a disparu.

Que faire alors? Raisonnablement, dans le cas présent, recommencer, car je ne suis pas encore totalement fou. Mais dans le contexte d’une macro plus grande, plus complexe? Eh bien il est possible d’écrire le contenu du registre dans le buffer, l’éditer puis écrire depuis le buffer dans le registre pour ensuite appeler la macro corrigée! Simple et sophistiqué à la fois, mais tout est dans le présent billet: "xp va écrire le contenu du registre nommé "x, éditer la ligne puis l’écrire à nouveau: 0"xy$ et hop, le tour est joué, @x ira changer l’Autruche en Kiwi.

Et si je veux en fait que ce soit KIWI et pas Kiwi? Pourquoi s’embêter? qXviwUq. Ben oui, j’ai déjà la macro qui change Autruche en Kiwi et mon curseur est sur le K. Je n’ai qu’à enregistrer à la suite, l’action de mise en majuscule.

Conclusion

Ainsi se termine un tour des dix types de registres de vim. Si je devais retenir quelques petites choses ce serait les points suivants:

  • Les registres nommés en majuscule ne remplacent pas le contenu mais ajoute à la fin du registre,
  • Les registres numérotés sont des presse-papiers boostés top-of-the-pop®,
  • Il existe plusieurs façons de lire depuis les registres:
    • En mode normal avec ",
    • En mode insertion avec Ctrl-r,
    • En mode commande avec :put
  • Il existe plusieurs façons d’écrire dans les registres:
    • En mode normal avec "
    • En mode commande avec :let
  • Les macros ne sont rien de plus que des commandes enregistrées dans des registres. Elles sont amendables, modifiables et combinables à merci.

Comme d’habitude avec vim, il faut pratiquer pour détecter les cas d’usages et y répondre efficacement. Je ne me sers pour ainsi dire jamais de "- ni même "_. Mais je sais qu’ils sont là si jamais je dois automatiser quelque chose d’un peu compliqué.

J’espère que ça pourra servir à quelqu’un de voir la documentation un peu reformulée. Si jamais j’ai écrit des bêtises, laissé une typo ou juste pour faire un coucou, ne surtout pas hésiter à me prévenir, je suis .

Ressources