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.
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.
:python print "Coucou" |
:py import sys |
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 :
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 !
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
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.
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 :
:py buf.append("une\nligne") |
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
où 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
) :
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 :
:py buf[2:2]=lst |
:py buf[2:3]=lst |
:py buf[2:4]=lst |
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
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.
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 :
par vim.current.range
;range()
des objets buffer (à ne pas confondre avec
la fonction prédéfinie range()
de Python !).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.
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.
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...
[1] Vim : http://www.vim.org
[2] Python : http://www.python.org
Yves Bailly
1er janvier 2005