Nos premières macros Vim

Nous l'avons vu le mois dernier, les macros (ou scripts) de Vim offre un grand nombre de possibilités. Si vous avez suivi les conseils du précédent article, vous avez dû constater de par vous-même que le langage utilisé était un véritable langage de programmation utilisant boucles, fonctions, variables, etc.

Bon nombre de programmeurs, lorsqu'ils développement une macro Vim, essaient d'optimiser au maximum leur code. Le débutant est alors freiné dans la lecture de ces macros. Vous avez dû en faire l'expérience en consultant le contenu des quelques fichiers fournis dans /usr/local/share/vim/vimXX/macro/. Le langage n'est pas innocent à cet état de fait. En effet, la plupart des fonctions peuvent être utilisées avec leur nom long mais surtout leur nom court, qui paraît immédiatement obscur aux petits curieux que nous sommes.

Notre toute première fois

En dehors des fonctions spécifiques aux scripts Vim, la totalité des commandes habituelles sont utilisables dans les macros. Vous pouvez donc parfaitement faire tout ce que vous faites "normalement" avec Vim à l'aide d'une macro. Voici un exemple très simple (en fait tellement simple qu'il est un peu ridicule d'en faire une macro). Editez un nouveau fichier appelé, par exemple, mesmacro.vim :

function! Amoa(n1)
  execute("normal".a:n1."iA\e")
endfunction

Nous commençons par déclarer une fonction Amoa. Celle-ci prendra un argument stocké dans la variable n1. Attention, un nom de fonction doit TOUJOURS commencer par un caractère majuscule. Dans le cas contraire, Vim ne manquera pas de vous rappeler à l'ordre. La fin de la fonction est marquée par endfunction.

Le corps de la fonction est, bien sûr, la partie la plus intéressante. Nous utilisons ici la commande execute afin d'évaluer la chaîne passée en paramètre et l'exécuter en mode Ex. La chaîne de caractère que nous lui passons ici utilise l'argument passé à notre fonction. Pour récupérer cet argument, nous utilisons la notation a:n1. a: précise qu'il s'agit d'un argument de la fonction et non d'une variable locale. Nous demandons donc l'exécution de la commande si n1=5, 5iA <Echap>. Ceci provoquera l'insertion de 5 caractères A, tout comme si vous les aviez tapés vous-même.

Enregistrez le fichier macro puis créez un tampon vide. Tapez la commande ex permettant de charger le fichier :

:so mesmacro.vim

Vim vient de "sourcer" le fichier et connaît à présent la fonction Amoa. Vous pouvez d'ailleurs vous en assurer en faisant :

:function Amoa
function Amoa(n1)
1  execute("normal".a:n1."iA\e")
   endfunction

Enfin, vous pouvez essayer votre fonction en faisant :

:call Amoa(34)

Ceci devrait provoquer l'insertion du nombre de A spécifié en argument de votre fonction. Certes, ceci n'est pas très puissant, mais c'est un début. Nous avons utilisé les noms complets de chaque commande, mais nous aurions tout aussi bien pu écrire :

fu! Amoa(1)
  exec("normal".a:n1."iA\e")
endf

Première boucle

Comme tout langage, Vim est capable de gérer des itérations dans ses macros. La notion de boucle nécessite impérativement la présence de variables permettant de se positionner dans le nombre d'itérations de la boucle. Commençons donc par ce point :

function! Amoa2(n1)
  let i=a:n1
  execute("normal".i."iA\e")
endfunction

La manipulation est simple, nous déclarons une variable i et l'initialisons avec la valeur passée en argument. Enfin, nous l'utilisons à la place de l'argument dans la ligne execute ... Utilisons alors notre savoir pour construire une boucle while :

function! Amoa2(n1)
  let i=0
  while i<=a:n1
    execute("normal a".i."coucou\n\e")
    let i=i+1
  endwhile
endfunction

La portée de la boucle est comprise entre while condition et endwhile. Ici, tant que i est inférieur à la valeur passée en argument, nous écrivons la valeur de i suivi de la chaîne "coucou<CR>" dans le tampon. Si n1 est égal à 10, vous verrez apparaître 0 coucou, 1 coucou, ... 10 coucou.

Mapper les macros

Pour l'heure, l'utilisation de nos fonctions n'est pas très agréable, nous sommes en effet obligés de saisir une commande ex appelant la fonction désirée. Fort heureusement, nous pouvons associer une touche ou un groupe de touches à une séquence de commandes. Ceci est possible grâce aux commandes map, comme par exemple :

:map <F5> 0i<!-- <ESC> $a--> <ESC>

Ici, nous associons la touche de fonction F5 à une suite de commandes :

- Aller en début de ligne
- Insérer la chaîne <!-- avant le premier caractère
- Repasser en mode commande
- Aller en fin de ligne
- Insérer --> après la position courante
- Repasser en mode commande

En plaçant cette ligne dans votre ~/.vimrc (sans les :), cette association sera valable pour chaque exécution de Vim. De la même manière, il est possible de définir des associations (mapping) en fonction du mode :

imap : pour le mode insertion
vmap : pour le mode visual
nmap : pour le mode normal (commande)

Pour les utilisateurs de la version GUI de Vim, il est également possible d'ajouter ainsi des entrées dans les menus. Voici l'équivalence de l'association précédente pour l'ajout d'une entrée dans le menu File :

:menu File.Comment_HTML 0i<!-- <ESC> $a--> <ESC>

La commande menu peut être déclinée en fonction du mode de la même manière que map. Enfin, il est possible d'associer une touche à l'exécution d'une fonction :

:map <F5> :call Amoa2(23)

Mapper avec des arguments

Nous allons maintenant écrire notre première macro véritablement utile. L'objectif est de permettre à l'utilisateur de sélectionner une valeur numérique en mode visual et la remplacer par une énumération en 0 et la valeur sélectionnée.

Pour cela, nous avons besoin de récupérer la sélection du mode visual afin de pouvoir traiter la valeur. Lorsqu'une sélection est copiée avec y (yanked) ou supprimée avec d, le contenu est placé dans un registre particulier symbolisé par @. Vous pourrez en faire l'expérience très simplement en sélectionnant un bloc quelconque de texte, en le supprimant (avec d) puis en faisant :

:echo @

Le bloc fraîchement supprimé devrait alors s'afficher dans la ligne d'état. Conclusion, nous pouvons récupérer notre sélection de cette manière puisque la valeur sélectionnée devrait disparaître pour laisser sa place à une énumération de valeurs. Notre fonction macro pourra directement utiliser le contenu de @ pour la condition de la boucle while :

function! plusutil()
  let i=0
  while i<=@
    execute("normal a".i.", \e")
    let i=i+1
  endwhile
endfunction

La suppression sera effectuée lors du mappage. Nous pouvons d'ailleurs faire cela directement dans notre fichier de macro Vim en ajoutant une ligne :

vmap B d:call plusutil()<CR>

Enregistrons le fichier, sourçons-le et procédons au premier test :

- Ecrivez une phrase quelconque dans un tampon :

Voici une phrase où vont apparaître les valeurs 14

- Sélectionnez "14" en mode visual
- Appuyez sur MAJ+b :

Voici une phrase où vont apparaître les valeurs 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14

Notre macro fonctionne et remplace 14 par une série de valeurs allant de 0 à 14.

Pour finir

Il n'est pas forcément nécessaire d'utiliser des fonctions complexes pour créer de belles macros. En effet, dans bien des cas, il suffit de remapper le clavier en fonction du mode pour obtenir un résultat rapide et puissant. Si nous prenons un dernier exemple d'une macro transformant les caractères tapés en leur équivalent en morse, nous n'avons besoin que d'imap :

imap A .-
imap B -...
imap C -.-.
imap D -..
imap E .
imap F ..-.
imap G --.
imap H ....

etc

Enfin, si vous désirez vous lancer dans l'écriture de macros Vim, voici quelques conseils :

  • Utilisez du code existant. Bon nombre de programmeurs mettent à disposition des fichiers macro comprenant des fonctions de base parfaitement réutilisables dans vos propres fichiers.
  • Inscrivez-vous aux listes de diffusion Vim et baladez-vous sur les canaux IRC #vim sur Undernet ou Openprojects.
  • Analysez le maximum de code des macros existantes. C'est sans doute là que vous en apprendrez le plus.

    Linux Magazine France n°30 - Juillet 2001