Emacs: Yasnippet

Hello,

Au menu aujourd'hui: Yasnippet, un système de gabarit pour Emacs.

Son principe est simple: Sauvegarder des extraits de texte, aussi appelés gabarits, pour les ré-utiliser plus tard. Chaque gabarit est associé à un mot-clé. Et à chaque fois où tu as besoin d'un gabarit, tu écris juste son mot-clé et il sera remplacé par le texte qui lui associé.

Avec Yasnippet:

Ces champs:

Prérequis

Connaissances

Pour comprendre cette article, il faut avoir quelques notions de base avec Emacs. Savoir comment appeler une fonction interactive et ce qu'est un mode sera suffisant.

Il faut aussi connaitre le format suivant pour les raccourcis clavier:

  • C-i: Correspond à [CTRL] + [i]
  • M-x: Correspond à [Alt] + [x]
  • S-i: Correspond à [Shift] + [i]
  • C-c c: Correspond à [Ctrl] + [c], relâcher, puis [c]
  • C-c C-e: Correspond à [Ctrl] + [c], relâcher, puis [Ctrl] + [e]
  • Tab: Correspond à [Tabulation]

Logiciels

Pour utiliser Yasnippet, il faut :

Pour Emacs, si tu utilise une distribution GNU/Linux, il est certainement disponible sur les dépôts de ta distribution. Si tu utilise Mac OS ou Windows, je te renvoie au site web officiel d'Emacs.

Pour Yasnippet, j'explique dans la section Installation de Yasnippet comment l'installer.

Préparations

TL;DR

Si les détails ne t'intéressent pas, voici comment préparer Yasnippet en 4 étapes:

  • Depuis Emacs, appeler le raccourcis clavier M-x package-refresh-content
  • Appeler ensuite M-x package-install et indiquer yasnippet
  • Ajouter ce morceau de code Elisp dans le fichier ~/.emacs.d/init.el:
(require 'yasnippet)
(yas-global-mode 1)
  • Évaluer le morceau de code Elisp

Alternativement, si tu as use-package, 2 étapes suffisent:

  • Ajouter ce morceau de code Elisp dans le fichier ~/.emacs.d/init.el:
(use-package yasnippet
  :config
  (require 'yasnippet)
  (yas-global-mode 1))
  • Évaluer le morceau de code Elisp

Installation de Yasnippet

Yasnippet est disponible sur le dépôt ELPA, que Emacs connait par défaut. Il faut juste dire à Emacs d'installer Yasnippet.

Pour ça, 2 possibilités. Au choix: Manuellement ou automatiquement.

Manuellement

Depuis Emacs, lance la fonction package-refresh-content, avec le raccourcis clavier M-x suivit du nom de la fonction. Emacs vas récupérer auprès des dépôts les paquets disponibles.

Ensuite, lance la fonction package-install, toujours avec le raccourcis M-x. Emacs demande quel paquet installer. Réponds simplement yasnippet.

Et voilà, Yasnippet est installé.

Automatiquement

Si tu as l'habitude de configurer Emacs avec le fichier ~/.emacs.d/init.el, et que le paquet use-package est installé, tu peux ajouter ceci dans ~/.emacs.d/init.el:

(use-package yasnippet)

Une fois cet instruction Elisp évaluée, Yasnippet est installé.

Note: use-package est installé par défaut à partir d'Emacs 29.1.

Activer un des 2 modes Yasnippet

Yasnippet est utilisable en activant l'un de ces 2 modes mineur:

  • yas-global-mode
  • yas-minor-mode

Un seul des deux modes est nécessaire.

Pour cet article, on va utiliser le mode globale car il est accessible depuis chaque buffer Emacs. Pour l'utiliser, il suffit d'ajouter ces instructions Elisp dans le fichier ~/.emacs.d/init.el:

(require 'yasnippet)
(yas-global-mode 1)

Une fois ces instructions évaluées, Yasnippet peut être utilisé.

Pour information, la première instruction charge Yasnippet et la seconde active le mode globale de Yasnippet.

Alternativement, si tu as appelé use-package pour l'installation, tu peux lier l'activation du mode à la présence de Yasnippet. Pour ça, modifie l'instruction d'installation comme ceci:

(use-package yasnippet
  :config
  (require 'yasnippet)
  (yas-global-mode 1))

Le deuxième mode, yas-minor-mode ne sera actif que dans le buffer où tu l'appelle. Il s'utilise comme n'importe quel mode mineur. Mais je ne vais pas rentrer plus en détails sur son activation. Pour commencer, le mode global est suffisant.

Un premier gabarit

On va prendre un premier exemple simple: Une signature.

Imagine que tu souhaite ajouter la signature suivante sur un texte:

Avec mes plus sincères salutations,
-----

Fox

On va créer un gabarit à partir de cette signature.

Création du gabarit

Pour créer ce gabarit, il faut:

  • Ouvrir, avec Emacs, un texte où tu souhaite utiliser ta signature pour la première fois
  • Écrire ta signature là où tu le souhaite
  • Sélectionner cette signature et appeler la fonction yas-new-snippet (avec le raccourcis M-x)

Là, une nouvelle fenêtre s'ouvre et tu peux y lire ceci:

# -*- mode: snippet -*-
# name: 
# key: 
# --
Avec mes plus sincères salutations,
-----

Fox

Il s'agit de ton nouveau gabarit, pas encore sauvegardé.

On peut y voir un en-tête, avec 2 champs, name et key, ainsi que la signature que tu avais sélectionné. Le champ name sera le nom du gabarit et key le mot-clé qui lui sera associé.

Ton curseur d'écriture se trouve juste après le champ name, il te suffit d'écrire le nom du gabarit. Au fur et à mesure que tu l'écris, le champ key se complétera tout seul.

Après avoir écrit le nom du gabarit, appuie sur la touche Tab de ton clavier. Ton curseur d'écriture va sauter au niveau du champ key. Tu pourra ainsi corriger le mot-clé, si tu en veux une autre que celui proposé.

Une fois le mot-clé défini, appuie à nouveau sur la touche Tab et ton curseur va sauter au début du texte de ton gabarit. Tu peux maintenant l'ajuster, si nécessaire.

Activer/sauvegarder le gabarit

Pour Activer/sauvegarder ton gabarit, il faut appeler le raccourcis clavier C-c C-c.

Emacs te demandera:

  • Pour quel mode utiliser ton gabarit
  • Si tu souhaite le sauvegarder
  • Si oui pour la sauvegarde, où tu souhaite le sauvegarder

Pour le chemin de sauvegarde, il est important de garder le dossier ~/.emacs.d/snippet/ton-mode/ pour le moment. ton-mode correspondant au mode pour lequel tu veux utiliser ce gabarit. Voir la section Définir les dossiers de gabarit pour plus de détails.

Quand un gabarit est actif, voir sauvegardé, il le sera pour un seul mode. Ainsi, par exemple, les gabarits définit pour Org-mode ne seront accessible que depuis un buffer avec le mode Org-mode actif. Ceci afin que pour chaque mode Emacs, tu n'ai accès qu'aux gabarits qui sont pertinent. Dans la section Utiliser des gabarits d'un mode à l'autre, j'explique comment ré-utiliser pour un mode les gabarits d'un autre mode.

Utilisation du gabarit

Maintenant, depuis un texte où tu souhaite utiliser ta signature, il suffit d':

  • Écrire le mot-clé associé à la signature
  • Appuyer sur la touche du clavier Tab

Et voilà, le mot-clé est remplacé par ta signature.

Une autre façon d'insérer un gabarit est d'appeler la fonction yas-insert-snippet, avec la raccourcis M-x, puis d'indiquer le gabarit à insérer.

Définir et utiliser des champs

Pour le moment, on a définit un gabarit avec un texte statique. Mais peut-être qu'on veut définir dynamiquement certaines parties du texte.

Prenons l'exemple de texte suivant: Des notes de lecture pour un livre, géré avec Org-mode.

Voici à quoi le texte ressemblerait:

* Titre du livre                                            :book:read_notes:
:PROPERTIES:
:Author:      Nom de l'auteur ou autrice
:Editor:     Nom de la société d'édition
:Collection:  Nom de la collection
:END:

Ici, on a:

  • Une section Org-mode, dont le titre est le titre du livre
  • Les noms de l'auteur/autrice, de société d'édition et de collection, sous la forme de propriétés
  • 2 étiquettes: book et read_notes

Les notes de lectures pourront ensuite être ajoutées sous la forme de sous-sections.

Maintenant, si je transforme ce texte en gabarit, j'aimerais bien qu'au moment où je l'utilise Emacs me demande les valeurs pour le titre, l'auteur/autrice, la collection, etc.

Voici comment faire.

Définir des champs

Au moment de créer un nouveau gabarit, on peut déclarer des champs dans son texte en utilisant la syntaxe suivante:

$n, où n est le numéro du champ.

Si je reprends l'exemple des notes de lectures, voici son gabarit:

# -*- mode: snippet -*-
# name: Read notes
# key: read_notes
# --
* $1                                            :$5:read_notes:
:PROPERTIES:
:Author:      $2
:Editor:     $3
:Collection:  $4
:END:

$0

Comme tu peux le voir, j'ai définit 5 champs:

  • Titre du livre, champs numéro 1 ($1)
  • Nom de l'auteur/autrice, champs numéro 2 ($2)
  • Nom de la société d'édition, champs numéro 3 ($3)
  • Nom de la collection, champs numéro 4 ($4)
  • Étiquette du type d'ouvrage, champs numéro 5 ($5)

J'ai également définit un 6ème champ, $0. C'est là où ton curseur d'écriture sera positionné après que le texte du gabarit soit inséré et complété.

Utiliser des champs

Maintenant, si tu sauvegarde le gabarit et l'utilise:

  • Le texte du gabarit sera inséré sans les $n
  • Ton curseur d'écriture sera placé au niveau du $1: Tu peux écrire le titre du livre
  • Si tu appuie sur la touche Tab, ton curseur sautera à l'emplacement du $2
  • Chaque saut du curseur va suivre l'ordre des champs, du plus petit au plus grand
  • Pour revenir à un champ précédent, appui sur le raccourcis S-Tab ([Shift] + [Tabulation])
  • Après être arrivé au dernier champ ($5), un nouvel appui sur Tab stoppera le remplissage des champs et déplacera le curseur sur l'emplacement de $0

Valeurs par défaut

Définir des champs, c'est pratique. Mais ça l'est encore plus si on peut définir des valeurs par défaut.

Pour ça, quand tu déclare un champs dans un gabarit, utilise la syntaxe suivante:

${n:Valeur par défaut}

Remplace n par le numéro du champ et Valeur par défaut par ce que tu veux.

Maintenant, quand tu utilisera ton gabarit, tes champs auront une valeur par défaut. Au moment de compléter un champ, si tu écrit quelque chose, la valeur par défaut est effacée et remplacée par ce que tu écris. Si tu n'écris rien et saute au champs suivant, la valeur par défaut est préservée.

Reprendre une valeur d'un champ à l'autre

Si tu définis 2 champs avec le même numéro, ils seront gérés comme un seul champ.

Quand tu utilise ton gabarit et complète le premier champ, le texte que tu y écris sera répété dans le second. Par contre, en sautant d'un champ à l'autre avec le raccourcis Tab, tu n'arrivera jamais sur le second champ.

Si tu as 2 champs avec un numéro différent, il est posisble reprendre la valeur du premier en tant que valeur par défaut du second. Dans ce cas, en sautant d'un champ à l'autre avec le raccourcis Tab, tu arrivera sur le second champ que tu peux éditer si tu veux.

Pour ça, utilise la syntaxe suivante:

  • Pour le champ N: $N
  • Pour le champ M: ${M:$N}

Il faut bien-sur remplacer N et M par les numéros que tu veux utiliser. Et tu peux aussi définir une valeur par défaut pour le champ N.

Utiliser du code Elisp pour modifier/définir une valeur

Quand tu définit le texte d'un gabarit, tu peux indiquer à Yasnippet d'y insérer le résultat d'une instruction Elisp. Au moment où le texte du gabarit sera inséré, l'instruction Elisp sera exécutée et son résultat inséré au bon endroit.

Pour ça, tu peux écrire du code Elisp directement dans le texte du gabarit, à l'endroit souhaité, et l'entourer avec des contre-guillemets. Voici un exemple qui insère son nom et son adresse e-mail:

`(format "%s <%s>" user-full-name user-mail-address)`

Ici, le code Elisp appelle à la fonction (format), avec 3 paramètres:

  • La chaine de formatage: "%s <%s>"
  • Le contenu de la variable user-full-name, qui est le nom complet de l'utilisateur/utilisatrice
  • Le contenu de la variable user-mail-adress, qui est l'adresse e-mail de l'utilisateur/utilisatrice

Au moment où le texte du gabarit sera inséré, le code Elisp de notre exemple sera remplacé par ceci:

Le code Elisp peut être utilisé n'importe où dans le texte du gabarit, y compris comme valeur par défaut d'un champ.

Exécuter du code Elisp sur une valeur de champ

Si tu définit 2 champs avec le même numéro, tu peux exécuter du code Elisp sur la valeur du premier champ et utiliser le résultat comme valeur du second.

Par exemple:

Nom:             ${1:Tapez un nom}
Nom minuscule:   ${1:$(downcase yas-text)}

Ici, on a 2 champs numéro 1, chacun définit une valeur par défaut et le second appelle du code Elisp. Cette fois, la syntaxe est un peut différente: $(func yas-text), où func est le nom de la fonction que tu veux appeler et yas-text est une variable représentant la valeur rentrée pour le premier champ.

Avec cet exemple, au moment où le text du gabarit est inséré, on est invité à écrire la valeur du premier champ. Ce qu'on écrit est recopié comme valeur du second champ, mais en minuscule. Car la fonction appelée est downcase.

Où et comment sont sauvegardés les gabarits ?

Structure et format des gabarits sauvegardés

Chaque gabarit est sauvegardé sous la forme d'un fichier texte. Tous les gabarits sont regroupés dans un "dossier de gabarits".

Ce "dossier de gabarits" est structuré comme ceci:

  • À la racine, un sous-dossier par mode
  • Dans chaque sous-dossier, un fichier texte par gabarit
  • Un dossier de second niveau peux être créé dans un dossier de mode, pour regrouper des gabarits

Définir les dossiers de gabarit

Il est possible d'avoir plusieurs "dossiers de gabarits". La liste des dossiers où Yasnippet cherche des gabarits est stockée dans la variable Emacs yas-snippet-dirs.

Sa valeur par défaut est '("/home/your_home/.emacs.d/snippets"), mais tu peux la personnaliser en écrivant ceci dans le fichier ~/.emacs.d/init.el:

(add-to-list 'yas-snippet-dirs "/path/to/new/directory")

Remplace "/path/to/new/directory" par le chemin vers le dossier à utiliser.

Cette variable peux être personnalisée par des paquets Emacs si ils fournissent des gabarits.

Grouper les gabarits

Pour regrouper plusieurs gabarits, il faut ajouter le champs group dans l'en-tête du gabarit.

Comme ceci:

# -*- mode: snippet -*-
# name: Dot dot dot
# key: dots
# group: specials
# --
…

Au niveau du dossier des gabarits, tu es libre d'organiser les sous-dossiers de mode comme tu veux. Tu peux créer un dossier par groupe, ou tu peux tout garder au même niveau.

Le dossiers de second niveau ne créent pas automatiquement de groupes.

Utiliser des gabarits d'un mode à l'autre

Il est possible, pour un mode, d'utiliser les gabarits d'autres modes.

Pour ça, dans le dossier de gabarits, sous-dossier du mode, crée un fichier texte nommé .yas-parent. Dans ce fichier, écrit la liste des autres modes dont tu souhaite utiliser les gabarits.

Par exemple: Pour le c-mode, tu souhaite charger les gabarits des modes fundamental-mode et text-mode.

Alors: Dans le dossier ~/.emacs.d/snippets/c-mode/, crée un fichier nommé .yas-parent et écris y "fundamental-mode text-mode" (sans les guillemets).

Maintenant, quand tu utilisera c-mode, en plus de ses gabarits tu aura accès à ceux de fundamental-mode et text-mode

Astuces

Insérer un gabarit autour d'un texte

Si tu sélectionne du texte avant d'insérer un gabarit avec la fonction Emacs yas-insert-snippet, le texte sélectionné est supprimé puis le texte du gabarit est inséré.

Mais il est possible de configurer Yasnippet afin de garder le texte sélectionné et d'insérer le celui du gabarit autour de cette sélection.

Pour ça, ajoute cette ligne dans le fichier ~/.emacs.d/init.el:

(setq yas-wrap-around-region t)

Cette instruction Elisp va associer la valeur t (True) à la variable yas-wrap-around-region. Cette variable indique à Yasnippet si il doit, ou pas, garder le texte sélectionné et l'entourer avec celui du gabarit inséré. Il faut évaluer cette instruction pour qu'elle soit prise en compte.

Maintenant, si tu sélectionne du texte avant d'insérer un gabarit, le texte sélectionné sera copié à la place du champ $0 du gabarit.

Par exemple:

Si on a ce gabarit pour du texte au format HTML:

# -*- mode: snippet -*-
# name: Paragraphe
# key: p
# --
<p>
  $0
</p>

Qu'on sélectionne ce texte:

Ceci est un exemple de texte.

Et qu'on insère le gabarit p, on obtient ceci:

<p>
  Ceci est un exemple de texte.
</p>

Gabarit utilisable sous condition

Tu peux limiter l'utilisation d'un gabarit à une condition. Pour ça, quand tu définit un gabarit, utilise le champ condition et donne lui comme valeur du code Elisp. Le gabarit ne sera utilisable que si le code Elisp retourne une valeur autre que nil.

Associer un raccourcis clavier à un gabarit

À la définition d'un gabarit, le champ binding peut être utilisé pour associer un raccourcis clavier à ce gabarit.

Par exemple, la valeur C-c r correspondra à [Ctrl] + [C], puis [R]. Si tu utilise ce raccourcis, ton gabarit sera inséré.

Recharger la liste de gabarits

Si tu as ajouté un gabarit en copiant son fichier dans un dossier de gabarits, Yasnippet ne le verra pas tout seul.

Pour que Yasnippet scanne tous ses dossiers de gabarits, il faut appeler la fonction Emacs yas-reload-all (avec M-x).

Ajouter des gabarits créés par la communauté

La communauté propose des gabarits, distribués sous la forme de paquets Emacs. Il suffit d'en installer un et d'appeler la fonction Emacs yas-reload-all (avec M-x) pour utiliser ses gabarits.

Voici une petite liste de paquets:

Nom du paquet Description Disponnible sur dépôt
yasnippet-classic-snippets Gabarits autrefois fournit avec Yasnippet ELPA
yasnippet-snippets Une grande collection de gabarits NonGNU ELPA
angular-snippets Gabarits pour Angular MELPA
awk-yasnippets Gabarits pour AWK MELPA
django-snippets Gabarits pour Django MELPA
haskell-snippets Gabarits pour Haskell MELPA
license-snippets Gabarits de licences logiciel MELPA
vala-snippets Gabarits pour Vala MELPA

Les dépôts ELPA et NonGNU ELPA sont déjà configuré dans Emacs. MELPA est un dépôt communautaire, qu'il faut ajouter.

Pour ajouter MELPA, il suffit d'ajouter ceci dans ton fichier ~/.emacs.d/init.el:

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)

Après avoir évalué ces 2 lignes puis appelé la fonction Emacs package-refresh-contents, tu pourra installer des paquets en provenance de MELPA.

Pour information, les 2 lignes de code Elisp ci-dessus vont: Charger la fonctionnalité package et ajouter une cellule dans la variable package-archives. La variable package-archives est de type associated list. La cellule à pour clé le nom du dépôt, melpa, et pour valeur l'URL vers le dépôt.

Aller plus loin

Pour en apprendre plus sur l'utilisation de Yasnippet, je t'invite à aller lire ça documentation officielle.

Je t'invite également à voir l'épisode 06 de Emacs Rocks, dédié à Yasnippet. Je recommande même toutes les vidéos d'Emacs Rocks.

Conclusion

Yasnippet est un outils très pratique pour éviter de devoir ré-écrire plusieurs fois le même texte. Son utilisation est simple et on peut créer rapidement un gabarit à partir d'un morceau de texte déjà écrit.

Le système de champ, avec des valeurs par défaut, est particulièrement utile pour avoir un gabarit qu'on peut adapter en fonction de la situation.

Et enfin, l'utilisation de code Elisp permet d'automatiser la génération de certaines parties du gabarit. Parfois en fonction de la valeur d'un ou plusieurs champs.

Un grand merci aux personnes qui ont travaillé sur ce super paquet Emacs.