Créer un plugin URxvt pour sauvegarder VIM automatiquement
Un problème récurrent avec les applications en mode texte est qu'elle n'ont pas conscience du fait que la fenêtre qui les héberge gagne, ou perd le focus. Cette information est pourtant bien utile car elle peut par exemple permettre sous VIM de sauvegarder les buffers modifiés lorsque l'on laisse l'éditeur de côté (perte de focus). Mais aussi de mettre les buffers à jour s'ils ont été modifiés à l'extérieur de VIM lorsque l'on y retourne (gain de focus). Nous allons donc voir comment rendre une application texte consciente de ces deux évènements.
Perte et gain de focus
Lorsqu'une fenêtre a le focus, tout ce qui est tapé au clavier lui est envoyé. La sélection de la fenêtre qui a ce focus est réalisée par le gestionnaire de fenêtre. Certains considèrent que le focus est là où se trouve la souris. C'est ainsi que l'on fonctionne sous Unix depuis des lustres. Du moins jusqu'à ce que la mode windowsienne ne prenne le pas et obligent en plus à cliquer dans la fenêtre à rendre prioritaire.
Le fait de perdre le focus est donc un bon moyen de dire à une application que l'on passe à une autre tâche. On a donc logiquement envie, s'agissant d'un éditeur de texte, d'en profiter pour faire une petite sauvegarde automatique.
Lorsque l'éditeur est une chose graphique (gedit, gVim), il est capable de recevoir directement les messages perte et gain de focus, envoyés par X11. En revanche lorsqu'il s'agit d'une application texte (Vim, Mutt, etc.) c'est plus compliqué car c'est l'émulateur de terminal qui reçoit ces messages. Il nous faut donc faire un pont entre le gestionnaire de fenêtre et l'application en mode texte en utilisant l'émulateur de terminal comme intermédiaire.
Construction d'un micro-protocole ANSI
Comme vous le savez déjà, les applications en mode texte conversent avec le terminal sous la forme de séquence ANSI commençant par le code 27 (aka ESC, aka \\033).
Par exemple pour prendre en charge la souris, l'application va écrire dans le terminal une séquence particulière que ce dernier va détecter pour activer le mode "Mouse Tracking". Une fois ce mode activé, lorsque la souris survole la console, le terminal va envoyer à l'application des séquences indiquant la sa position, l'état des boutons, etc. Lorsque l'application n'a plus besoin de la souris (ex. lorsqu'elle vous la quittez), elle écrit une séquence qui va désactiver "Mouse Tracking". Sinon ça serait l'horreur sous bash ;-)
Pour notre focus nous allons emprunter ce fonctionnement avec un protocole maison :
- Activation du mode mode "focus" par la séquence \\033]777;focus;on\\007
- Désactivation du mode "focus" par la séquence \\033]777;focus;off\\007
- Focus gagné par la séquence \\033UlFocusIn.
- Focus perdu par la séquence \\033UlFocusOut.
Le choix de la forme \\033]777;...\\007 n'est pas anodin. C'est la forme appelée OSC pour Operating System Control. C'est ainsi que sont décrites toutes les opérations qui modifient le comportement du terminal (voir pour cela l'article ce le changement de palettes).
En revanche les séquences \\033UlXXXX sont totalement arbitraires.
Construction du plugin URxvt
URxvt dispose d'une API en perl très bien documentée (man urxvtperl) qui fournit une série d'évènements dont le gain et la perte du focus, mais aussi la gestion des séquences OSC custom.
Construire un plugin pour URxvt est relativement simple. Il suffit pour cela de créer un script perle focus que l'on place par exemple dans un dossier ~/.urxvt/perl :
#!/usr/bin/perl
my %focus_activated = ();
sub on_start {
my($term) = @_;
$focus_activated{$term->vt} = 0;
}
sub on_focus_in {
my($term) = @_;
if ($focus_activated{$term->vt}) {
$term->tt_write("\\033[UlFocusIn");
}
}
sub on_focus_out {
my($term) = @_;
if ($focus_activated{$term->vt}) {
$term->tt_write("\\033[UlFocusOut");
}
}
sub on_osc_seq_perl {
my ($term, $osc, $resp) = @_;
return unless $osc =~ s/^focus;//;
$focus_activated{$term->vt} = $osc eq 'on'?1:0;
}Plugin URxvt - ~/.urxvt/perl/focus
Comme vous le voyez le plugin est assez simple. Les subs définies sont des hooks, c'est à dire des fonctions qui sont appelées par URxvt en réaction à des évènements :
- on_focus_in est déclenché lorsqu'URxvt est notifié de la prise de focus. On vérifie alors que le mode focus est activé et si c'est le cas, on écrit la séquence qui va bien dans le terminal.
- on_focus_out est déclenché lorsqu'URxvt est notifié de la perte de focus.
- on_osc_seq_perl est déclenché dés qu'URXVT reçoit une séquence OSC. On vérifie juste qu'il s'agit bien de la notre et on active/désactive le mode focus.
Une fois que ce plugin écrit, il nous reste à l'installer dans URxvt. Pour cela rajouter les lignes suivantes à ~.Xdefaults :
# On dit ou se trouvent nos extensions
URxvt.perl-lib : /home/gaston/.urxvt/perl
# Et on active l'extension "focus"
URxvt.perl-ext-common: focus
Nous pouvons maintenant relancer URxvt (éventuellement faire un coup de xrdb -load ~/.Xdefaults pour s'assurer que les ressources sont bien à jour) et tester l'activation du mode "focus" :
gaston$echo -ne "\\033]777;focus;on\\007"
Si tout c'est bien passé, en survolant la fenêtre (ou en cliquant dessus selon votre gestionnaire de fenêtres) vous devriez voir des choses apparaître. Idem en perte de focus. Les choses en question sont une tentative échouée d'interprétation de nos séquences par Bash. D'où l'intérêt de pouvoir activer/désactiver ce mode à convenance.
Implémentation du mode focus dans VIM
Maintenant que notre plugin est en place, il ne reste plus qu'à l'intégrer dans VIM :
" Initialisation du mode "focus"
exe 'silent !echo -ne "\\033]777;focus;on\\007"'
" Gestion de la séquence focusin/focusout
map ^[[UlFocusIn :bufdo checktime<CR>
map ^[[UlFocusOut :wa!<CR>
map! ^[[UlFocusIn <C-O>:bufdo checktime<CR>
map! ^[[UlFocusOut <C-O>:wa!<CR>
" Désactivation du mode focus en partant
autocmd VimLeavePre * exe 'silent !echo -ne "\\033]777;focus;off\\007"'
En relançant votre VIM, la magie devrait fonctionner. En perte du focus on force la sauvegarde de tous les buffers (wa!). Et lorsque l'on regagne le focus, on demande à VIM de passer en revue tous les buffers ouverts (bufdo) pour les recharger s'ils ont été modifiés (checktime).
Conclusion
Voilà en tout cas une manière de replacer avantageusement l'auto-sauvegarde pour VIM que j'avais réalisée précédemment. Car contrairement à la méthode que je proposais, celle-ci fonctionne en toute circonstances et sans aucun délai.
Après cette technique peut être exploitée pour bien d'autres usages. Par exemple un problème classique lorsque l'on bosse en console est de pouvoir copier du texte sur une console distante (un vim en ssh) et de récupérer ce contenu en local. Par cette approche c'est facilement réalisable en définissant une séquence OSC "presse-papier" qu'URxvt récupérerait pour transférer le contenu dans la sélection CLIPBOARD du serveur X11 local. Quelque chose comme :
gaston$echo -en "\\033]777;CLIPBOARD;ON\\077Ceci est mon texte à copier\\033]777;CLIPBOARD;OFF\\077"
D'ailleurs l'idée est déjà une petit peu mis en place dans un plugin peu connu d'URXVT, clipboard-osc. Sur le même principe peut être utilisé pour implémenter un protocole de transfert de fichier type ZModem/Kermit.
Comme vous le voyez, les usages sont nombreux. Et c'est une fois de plus la preuve qu'URxvt est un fantastiquement outil rentrant pleinement dans la catégories des "ce que je veux, je peux".