Vim et Python

Toute polémique mise à part, Vim est l'un des éditeurs de texte les plus appréciés, et Python l'un des langages interprétés les plus populaires. Leur rencontre était inévitable : Vim peut invoquer un interpréteur Python, et en retour un programme Python peut agir sur Vim.

Pour que cette alliance prenne vie, il est nécessaire de disposer d'une version de Vim avec le support pour le langage Python. Sur une distribution Debian, une commande comme

# apt-get install vim-python

devrait faire l'affaire. Si vous ne trouvez pas une telle version dans votre distribution, vous pouvez toujours recompiler Vim à partir de ses sources (dûment téléchargées à partir du site de Vim[1]), en le configurant avec par exemple une séquence de commandes comme :

$ tar jxf ../Downloads/vim-6.3.tar.bz2
$ cd vim63
$ ./configure --enable-pythoninterp --with-python-config-dir=/usr/lib/python2.3/config

Le deuxième argument, --with-python-config-dir, permet de préciser le répertoire contenant les fichiers de Python[2] - dans le cas d'une installation standard, cet argument est superflu. Notez que la compilation de Vim nécessite une installation complète de Python, notamment les fichiers permettant d'embarquer Python dans du code C.

Invocation de l'interpréteur et sorties standards

La manière la plus immédiate d'invoquer l'interpréteur est simplement avec la commande :python, qui peut être abrégée en :py. Il faut naturellement être en mode commande dans Vim. Voyons deux exemples, le premier qui utilise la sortie standard de Python (sys.stdout), et le deuxième qui écrit sur la sortie d'erreur standard. Les captures d'écran montrent l'aspect de la fenêtre Vim après l'exécution des commandes.

Exemples simples d'utilisation de la commande :python
:python print "Coucou"
:py import sys
:py sys.stderr.write("première ligne\ndeuxième ligne")
:python :py stderr

Le premier exemple, à gauche, affiche un simple message avec la commande (Python) print. La chaîne de caractères apparaît dans la zone de messages de Vim, la dernière ligne en bas. Le deuxième exemple, à droite, montre plusieurs aspects.

Tout d'abord, nous avons invoqué deux commandes : d'abord l'importation d'un module, puis l'utilisation d'un élément de ce module. Le comportement est à ce titre exactement le même que dans l'interpréteur Python « normal » : tout se passe comme si les commandes successives faisaient parties d'un programme. Il est ainsi possible de créer une variable, puis de la réutiliser par la suite dans une autre commande. Remarquez par ailleurs que vous pouvez importer n'importe quel module Python : un programme pourrait par exemple utiliser Tkinter pour fournir une interface graphique. Toutefois, Vim lui-même reste insensible aux actions de l'utilisateur tant que le programme Python s'exécute.

Ensuite, la commande a pour effet d'écrire sur la sortie d'erreur standard de l'interpréteur Python : la chaîne de caractères ainsi émises est récupérée par Vim, et affichée dans la zone de message avec la mise en forme propre aux erreurs (généralement, caractères blancs sur fond rouge).

Enfin, nous demandons à afficher deux lignes de texte. La zone de message est alors étendue, et Vim affiche le message d'attente usuel (en vert) dans cette situation.

Petite limitation actuelle de l'intégration de Python dans Vim, il n'est pas possible de faire appel à l'entrée standard depuis une commande Python (comme raw_input). En fait, la documentation de Vim stipule qu'une telle tentative pourrait aisément résulter en un plantage. Mais il est également stipulé que cela devrait être corrigé un de ces jours... Nous verrons bientôt comment contourner cela, en faisant appel aux fonctions de Vim lui-même.

La méthode présentée ici convient pour des commandes simples, mais elle devient rapidement fastidieuse pour du code un peu plus compliqué - par exemple, la définition d'une fonction, ou l'intégration de code Python dans un script Vim. Il est possible d'entrer plusieurs lignes « d'un coup », selon un principe similaire aux commande :append ou :insert de Vim. Par exemple, saisissez la séquence suivante en mode commande dans Vim :

:py << EOF
def pr(s, nb) :
print nb*s
pr("--8<---", 3)
EOF

On définit une simple fonction, qui permet de répéter une chaîne de caractères à l'affichage (l'utilité d'une telle fonction, proche de zéro, ne nous concerne pas ici). À l'issu de la saisie de la dernière ligne, la fenêtre Vim ressemble à ceci :

:py eof

On retrouve le mini-programme que nous venons de taper, suivi de son résultat (la ligne juste au-dessus du message d'attente). Attention si vous utilisez cette méthode : rappelons que Python est très sensible à l'indentation. En particuliers, la première ligne (définition de la fonction) ne doit pas être indentée, non plus que la dernière (le EOF).

Fait intéressant, nous pouvons réutiliser ultérieurement la fonction que nous venons de définir, par exemple :

:py pr(".oOo.", 4)

Tout objet défini dans l'interpréteur Python, variable, fonction ou classe, perdure ainsi jusqu'à la fin de la session.

Pour un programme sensiblement plus sophistiqué, il est heureusement possible de le stocker dans un fichier à part, et de demander l'exécution de ce fichier. Par exemple, pour exécuter un script contenu dans un fichier mon_script.py, il suffit d'utiliser la commande :pyfile :

:pyfile mon_script.py

Cette commande peut être abrégée en :pyf. À noter que tout se passe comme si les instructions contenues dans le fichier étaient exécutées l'une après l'autre directement dans la ligne de commande de Vim, ce qui signifie que le script peut bénéficier de tout ce qui a été préalablement défini. Cela peut être utile pour passer des arguments au script, comme s'il les recevait de la ligne de commande, en définissant explicitement le tableau sys.argv. Exemple de séquence pour cela :

:py import sys
:py sys.argv = ["arg1", "arg2"]
:pyf mon_script.py

Le script pourrait même utiliser la fonction pr() que nous avons définie plus haut !

Le module vim

Tout cela est bien beau, mais pour l'instant le coté « intégration » entre Vim et Python ne vous paraît peut-être pas évident. Le lien est établit au moyen d'un module Python nommé vim, qui permet au script d'accéder au contenu de l'éditeur. Pour être utilisé, il est naturellement nécessaire d'importer ce module. Nous pourrons dès lors accéder aux fonctionnalités de Vim depuis le script Python, ainsi qu'aux fichiers et aux fenêtres.

:py import vim

Fonctions du module

Le module vim ne fournit que deux fonctions : eval() et command().

La fonction vim.eval() permet de faire appel à l'évaluateur d'expressions de Vim, le résultat de l'évaluation étant retourné sous la forme d'une chaîne de caractères. Par exemple :

:py somme = vim.eval("1+2")

La variable (Python) somme contient alors la chaîne de caractères "3". Pour l'utiliser en tant que valeur numérique, il sera nécessaire de la convertir avec la fonction atoi() du module string. Mais voyons un exemple plus intéressant :

:py chaine = vim.eval("input('Donnez une chaine : ')")

Ceci va exécuter la fonction input() de Vim, laquelle a pour effet de demander une saisie à l'utilisateur. À l'issu de cette instruction, la variable chaine (qui appartient à l'interpréteur Python, pas à Vim) contiendra la chaîne de caractères saisie par l'utilisateur. En fait, nous avons là un comportement équivalent au raw_input de Python, ce qui est un moyen de contourner la limitation concernant l'entrée standard que nous avions évoquée plus haut.

La fonction vim.command() permet simplement d'exécuter une commande Vim, tout comme elle serait saisie dans Vim, par exemple :

:py vim.command("wq")

Ceci est l'exact équivalent de :wq, c'est-à-dire écrire le fichier en cours d'édition et quitter Vim (ou la fenêtre courante). La fonction ne retourne rien. L'exemple peut paraître trivial, mais l'intérêt de cette fonction apparaît dans les situations où l'on souhaite exécuter une commande complexe construite par programme.

Accès aux buffers

Voyons maintenant comment nous pouvons agir sur le ou les fichiers en cour d'édition. Chacun est représenté par un objet de type Buffer. L'ensemble des buffers disponibles est contenu dans le tuple vim.buffers : il s'agit bien d'un tuple au sens Python du terme, et supporte donc les opérations usuelles sur les tuples comme l'indexation ou le parcours. Par contre, il n'est pas directement possible d'ajouter ou retirer des éléments, non plus que modifier ceux contenus : pour cela, il faut avoir recours aux commandes Vim ad hoc.

Le petit morceau de code suivant affiche simplement la liste des noms des fichiers en cours d'édition :

:py << EOF
for buf in vim.buffers:
print buf.name
EOF

Le membre name contient le nom du fichier (chemin complet et absolu) en cours d'édition dans le buffer (ou None s'il s'agit d'un nouveau fichier). Vous ne pouvez pas modifier la chaîne contenue.

Le buffer courant, en cours d'édition, est donné par vim.current.buffer. Nous retrouverons la variable vim.current un peu plus loin.

Chaque buffer peut être vu comme une liste de chaînes de caractères, une chaîne par ligne. Il s'agit bien d'une liste : vous pouvez utiliser l'indexation, le parcours, et même la sélection d'une partie. Il est également possible de modifier la liste, ou chacun de ses éléments, ce qui a pour effet de modifier le contenu du buffer, et incidemment le fichier à la prochaine sauvegarde. Par exemple :

lst = ["une ligne", "une autre ligne"]
buf = vim.current.buffer
buf.append(lst)

La dernière instruction a pour effet d'ajouter la liste de chaînes de caractères donnée en paramètre à la fin du buffer, c'est-à-dire, dans l'exemple, d'ajouter deux lignes à la fin du fichier. Chaque élément de la liste est en effet considéré comme étant une ligne. Attention, ces chaînes de caractères ne doivent pas contenir le caractère de saut de ligne ('\n'). Si vous tentez d'ajouter une ligne contenant ce caractère, Vim renverra une erreur :

Erreur lors d'ajout de lignes
:py buf.append("une\nligne")
erreur newline

Mais comment ajouter des lignes au début du fichier ? Comme ceci :

:py buf[0:0] = lst

Plus généralement, l'insertion de lignes au milieu du fichier se fait par

:py buf[n:0] = lst

n est le numéro de la ligne avant laquelle on souhait réaliser l'insertion, les lignes étant numérotées à partir de zéro. Le caractère important, ici, est le zéro après les deux-points. Sa valeur va en fait déterminer le nombre de lignes qui seront effectivement insérées, et le nombre de lignes existentes qui seront remplacées. Prenons par exemple un buffer contenant cinq lignes, avec l'affichage des numéros de lignes (commande Vim :se nu) :

insert start

Exercice : trouvez la ligne de code Python qui permettrait de remplir un fichier vide ainsi !

Réponse à l'exercice (en supposant que buf désigne le buffer à remplir) :p

:py buf[0:0] = ["ligne %d"%i for i in range(1,6)]

Définissons ensuite une liste de deux chaînes, et récupérons le buffer courant (en supposant que le module vim a été importé préalablement) :

:py lst = ["---", "+++"]
:py buf = vim.current.buffer

Voyons maintenant l'effet de diverses modifications :

Insertions et remplacements de lignes
:py buf[2:2]=lst
:py buf[2:3]=lst
:py buf[2:4]=lst
insert_1 insert_2 insert_3

La première commande se comporte comme l'insertion : le deuxième '2' pourrait tout aussi bien être un 0 ou un 1. Dans ce cas, le nombre de lignes « écrasées » est nulle, ou négatif : on réalise donc une insertion pure.

Dans la deuxième commande, par contre, on demande le remplacement de 3-2 = 1 ligne. En pratique, les lignes de la liste sont insérées avant la ligne 2 (celle qui contient le texte "ligne 3"), puis une ligne est supprimée. Comme notre liste ne contenait que deux lignes, tout se passe comme si nous n'avions inséré qu'une ligne (la première de la liste) et remplacé une autre. Si notre liste avait contenu 23 lignes, 22 auraient été insérée, la dernière remplaçant l'ancienne ligne 2.

La dernière expérience confirme ce principe : on insère deux lignes, et on a demandé le remplacement de 4-2 = 2 lignes : cela revient à remplacer les lignes 2 et 3 (qui sont, rappellons-le, respectivement les troisième et quatrième lignes du buffer).

Si on continu à augmenter la deuxième valeur, on en arrivera à remplacer la totalité du fichier par les lignes contenues dans la liste. Par exemple, :py buf[2:100]=lst est parfaitement valide, même si notre buffer ne contient que cinq lignes au départ. Simplement, les trois dernières seront remplacées par les deux lignes de la liste. Par contre, cela ne va pas créer une centaine de lignes vides à la fin du buffer.

Dans toutes ces manipulations, il est important de distinguer la variable permettant d'accéder au buffer (ici, buf) du contenu du buffer lui-même (que l'on peut désigner par buf[:]). Ainsi,

:py buf[1]="une chaine"

remplace la deuxième ligne du buffer, mais

:py buf = "une chaine"

ne modifie en rien le buffer, et ne fait que changer l'affectation de la variable buf. Cela est particulièrement notable quand on souhaite supprimer des lignes du buffer :

:py del buf[1]
:py del buf[2:4]
:py del buf[:]
:py del buf
  1. la première instruction supprime une ligne unique du buffer, ici la deuxième ;
  2. la deuxième instriction supprime 4-2 = 2 lignes du buffer, à partir de la troisième ;
  3. la troisième instruction supprime toutes les lignes du buffer ;
  4. enfin, la dernière ne modifie pas le buffer, mais détruit simplement la variable buf.

Si vous avez essayé la solution à l'exercice proposé plus haut, vous aurez sans doute remarqué qu'il reste une ligne vide après l'insertion des cinq lignes au début du buffer. En effet, un buffer vide contient toujours au moins une ligne, et nous avons bien ajouter des lignes à ce buffer, conservant ainsi la ligne vide. Pour éviter cette ligne « en trop », utilisez la notation buf[:] :

:py buf[:] = ["ligne %d"%i for i in range(1,6)]

Ceci a pour effet de remplacer le contenu actuel du buffer.

Intervalles, ou sous-buffers

Il est parfois plus intéressant de travailler sur une partie d'un buffer, plutôt que sur la totalité. Vim permet ainsi de définir un intervalle de lignes auxquelles doit s'appliquer une commande donnée. Les scripts Python peuvent tirer profit de cela :

Si on reprend notre exemple précédent, l'instruction :

:py rg = buf.range(2,4)

va créer un intervalle contenant les lignes entre la deuxième et la quatrième (incluses). Attention, cette fois on utilise la numérotation « usuelle » pour les lignes : du point de vue de la liste des lignes du buffer, l'intervalle précédent va de la ligne d'indice 1 à la ligne d'indice 3, incluses. Cette information peut être obtenue avec les attributs start et end de l'objet intervalle. Ici, rg.start vaut 1, et rg.end vaut 3.

On peut dès lors appliquer toutes les opérations que nous venons de voir sur l'intervalle, comme s'il s'agissait d'un buffer. Le fait d'insérer, remplacer ou supprimer des lignes dans l'intervalle se traduira dans le buffer, et si nécessaire la valeur de rg.end sera mise à jour. Et comme précédemment, il faut distinguer la variable permettant d'accéder à l'intervalle, de l'intervalle lui-même : del rg ne modifiera ni l'intervalle ni le buffer, tandis que del rg[:] aura pour effet de supprimer toutes les lignes contenues dans l'intervalle - et donc dans le buffer.

Accès aux fenêtres

Vim étant capable d'afficher un ou plusieurs buffers dans une ou plusieurs fenêtres, il paraissait normal de permettre aux scripts Python d'accéder aux informations relatives aux fenêtres - même si, en vérité, on ne peut pas faire grand-chose avec, seuls quelques attributs étant disponibles.

La fenêtre courante, celle où se trouve le curseur, est donnée par vim.current.window. La liste des fenêtres existentes est contenue dans le tuple vim.windows, qui présente les mêmes fonctionnalités et limitations que la liste des buffers vim.buffers.

À propos de buffers, justement, si w est une variable permettant d'accéder à une fenêtre (obtenue par exemple avec :py w=vim.current.window), alors w.buffer donne le buffer en cours d'édition dans cette fenêtre. Cet attribut ne peut être modifié, mais rien ne vous empêche de modifier son contenu, comme nous l'avons vu précédemment.

La position du curseur est contenue dans le tuple w.cursor. Ce tuple contient deux éléments, d'abord la ligne (numérotée à partir de 1, contrairement à la numérotation utilisée dans un buffer) puis la colonne (numérotée à partir de zéro). Le tuple (1,0) indique donc que le curseur se trouve dans la première ligne du fichier, et sur le premier caractère de cette ligne. Vous pouvez modifier la position du curseur simplement en affectant cette variable, par exemple :

:py w.cursor = (3, 4)

tente de positionner le curseur dans la troisième ligne, sur le cinquième caractère. Si vous donnez un numéro de ligne plus grand que le nombre de lignes contenues dans le fichier, Vim vous gratifiera d'un message d'erreur et le script sera interrompu. Donner un numéro de colonne plus grand que le nombre de caractères dans la ligne peut en revanche être catastrophique. En apparence, le curseur se trouve simplement placé après le dernier caractère de la ligne, mais si vous passez en mode inertion ou ajout de caractère, Vim se terminera avec une erreur se segmentation dès le premier caractère saisi. Donc prenez garde à cela !

Derniers attributs éventuellement modifiables, w.height est la hauteur de la fenêtre (en nombre de lignes visibles), et w.width est sa largeur (en nombre de caractères visibles). La hauteur ne peut être modifiée que s'il y a eu partage horizontal de l'écran, et la largeur s'il y a eu partage vertical. Donner des valeurs aberrantes est sans conséquence : vous ne ferez pas disparaître une fenêtre en lui donnant une dimension null (ou négative), ou en donnant une dimension plus grande que l'espace disponible à l'écran.

Conclusion

Voilà pour cette petite présentation des possibilités offertes par l'interpréteur Python dans l'éditeur Vim. Si la syntaxe des scripts Vim vous rebute, vous devriez trouver là un moyen d'écrire plus facilement des scripts sophistiqués pour des tâches d'édition complexes. À noter que Vim peut également faire appel aux langages Perl, Tcl ou Ruby, chacun offrant plus ou moins de fonctionnalités. Mais c'est une autre histoire...

Références

[1] Vim : http://www.vim.org

[2] Python : http://www.python.org


Yves Bailly

http://www.kafka-fr.net

1er janvier 2005