Aus Linux-Magazin 06/2003

Vims Skriptsprache praktisch erklärt

Nicht nur Emacs ist mittels Skripting erweiterbar. Auch Vim, der zeitgemäß erweiterte Uralt-Editor »vi«, enthält eine eingebaute Skriptsprache. Sie ist nicht ganz so üppig wie Emacs-Lisp, braucht aber weniger Klammern. Wir erklären sie anhand eines Editor-Plugins zum Übersetzen von Wörtern.

Der Editor Vim [1] ist die moderne Variante des Klassikers »vi«. Mit seiner grafischen Oberfläche, Syntax-Highlighting für jede denkbare Programmiersprache und mehrfachem Undo bietet er deutlich mehr als das Original. Viele Anwender wissen, dass man mit Perl, Python, Ruby oder auch TCL einige Vim-Funktionen aufrufen kann. Der Editor hat aber auch eine eingebaute eigene Sprache: Mit While-Schleife, If-Else-Konstrukten, Variablen, vorgefertigten Funktionen und einer Handvoll üblicher Operatoren für Arithmetik ist Vim zwar nicht üppig, aber ausreichend bestückt.

Das Besondere an Vims Sprache: Das komplette Sortiment Tastatürkürzel und Ex-Befehle (die Doppelpunkt-Befehle) sind auch im Skript verfügbar, in der GUI-Version von Vim kommen noch die Menüfunktionen hinzu.

Aus der Skriptsprache kann man sich kleine Helfer basteln, die anspruchsvolle Ergänzungen zur Syntax-Hervorhebung oder das Verhalten je nach Dateiart steuern. Das Entwicklungstempo ist hoch, bisher sind in jeder größeren Release neue Funktionalitäten hinzugekommen. Das erweist sich aber gleichzeitig als Falle. Je nach Version von Vim haben ein paar Funktionen neue Namen erhalten, Rückwärtskompatibilität ist also nur bedingt gegeben.

Ein Tutorial und eine Funktionsliste stehen im Vim-Helpfile »usr_41.txt«, das man mit »:help usr_41« aufruft. Die Funktionen im Beispielskript beziehen sich alle auf Vim 6.1. Wer einen älteren Vim hat, muss vielleicht ein paar Änderungen nachschlagen.

Schneller Griff zur Übersetzung

Skriptsprachen lassen sich am besten am Beispiel erklären. Das hier vorgestellte kleine Skript enthält alle wichtigen Elemente der Vim-Sprache und tut gleichzeitig etwas Sinnvolles: Es erfasst ein englische Wort unter dem Cursor und schlägt es in einer Wörterbuchdatei für Deutsch und Englisch nach. Es zeigt die Übersetzung des Wortes an – dann hat der Nutzer die Wahl: Er kann das Wort einfügen oder das Suchwort mit Übersetzungswort in eine externe Datei schreiben.

Umgekehrt lässt sich in einem Dialog ein deutsches Suchwort eingeben und das englische Pendant anzeigen. Dazu sind erforderlich: Ein Dialog mit dem Anwender, der Zugriff auf externe Dateien und Kommandos, das Erkennen von Suchmustern und eine Verzweigung je nach Auswahl des Benutzers. Der Zugriff auf das Skript erfolgt über eine spezielles Tastaturmapping. Einige Funktionen für Dialoge mit dem Benutzer arbeiten übrigens sowohl mit dem GUI als auch auf der Konsole – Vim zeigt selbstständig die jeweils korrekte Variante an.

Skript als Plugin

Es gibt mehrere Möglichkeiten, um ein eigenes Skript in Vim zu aktivieren: Man schreibt kürzere Codestücke einfach in die ».vimrc« oder legt eine Datei mit der Endung » myscript.vim« ins Plugin-Verzeichnis von Vim. Vim lädt beim Start Skripte, die im Plugin-Pfad liegen, automatisch – genauso als wäre das Skript in die ».vimrc« eingetragen. Der globale Pfad für Vim-Plugins zeigt oft auf »/usr/ share/vim/vim Version/plugin« (oder auf »/usr/local/share«). Diese Plugins sind anschließend jedem User des Systems zugänglich.

Eigene Plugins kommen im Homeverzeichnis in »~/.vim/plugin«. Die Pfade kann man beim Kompilieren, über Umgebungsvariablen oder in der globalen Vim-Konfigurationsdatei ändern. Dank der Konfigurationsoption »FileType« lässt sich ein neues Plugin abhängig vom Dateityp laden: Die Beispiel-Vimrc nutzt die Möglichkeit für gzippte Dateien oder für C-Sourcen. Aber genauso ist je nach Endung »datei.gb« oder »datei.fr« einfach ein englisches oder französisches Wörterbuch zu laden – oder im Falle einer Unicode-Datei ein Chinesisch-sprachiges Plugin [2].

Abbildung 1: Das Vim-Menü für Übersetzungen in der schlichten Konsolen-Version.

Abbildung 1: Das Vim-Menü für Übersetzungen in der schlichten Konsolen-Version.

Von der Taste ins Skript

Um gezielt die gewünschte Funktion zum Übersetzen aus »dict.vim« mit einem Tastendruck aufrufen zu können, belegt man ein bequemes Tastaturkürzel mittels »map« mit dem Aufruf der jeweiligen Funktion. Vim lädt dieses Mapping automatisch beim Start. Allerdings sind die meisten Tasten schon von Vim belegt und viele User habe sich zusätzlich eigene Mappings angelegt. Woher also eine freie Tastenbelegung nehmen? Das Kommando »:map« zeigt bereits gemappte Belegungen an; ein paar Tipps liefert »:help map-which-key«. Alle Tastaturkürzel für Kommandos sind in »:help index« zu finden.

Um nicht versehentlich wichtige Tasten mit eigenen Mappings zu überschreiben, bietet Vim die Variable »mapleader« an. Das ist ein Schutz-Tastaturkürzel, das jeder Benutzer selbst definieren kann. Es schützt vor dem Überschreiben schon vorhandener Belegungen. Für Plugins, die zur Veröffentlichung vorgesehen sind, ist dies unerlässlich. Mehr Infos bringt »:help mapleader«. Für das Beispielskript »dict.vim« kommen die Tasten [F9] (Englisch nach Deutsch) und [F8] (Deutsch nach Englisch) zum Einsatz – sie sind meist unbelegt.

Um das Wort unter dem Cursor mit einem Tastendruck an eine Funktion weiterzugeben, mappen sich also die Funktionen »Eng2Ger()« und »Ger2Eng()« auf eine Taste. Die Funktion »Eng2Ger()« soll das Wort unter dem Cursor verwenden, die umgekehrte Übersetzung nimmt User-Eingaben entgegen (Anfang von Listing 1).

Der wichtigste Teil ist hier der Aufruf von »expand« mit einem speziellen Parameter: Beim Drücken der Taste [F9] wird mit »:call« die Funktion »MyDict« mit einem speziellen Vim-Parameter »<cword>« aufgerufen. »<cword>« ist das Wort unter dem Cursor. Solche Spezialvariablen – auch Umgebungsvariablen gehören dazu – löst Vim mit »expand()« auf. Die selbst definierte Funktion »Eng2Ger()« erhält »<cword>« als Parameter (Listing 1, Zeilen 7 bis 15). Funktionen und Schleifen werden nicht mit Klammern umschlossen, sondern mit »while« – »endwhile« oder »function« – »endfunction«.

Das Schlüsselwort »let« weist Variablen Werte zu, hier die Pfade für das Skript. Die Argument-Variablen einer Funktion sind über die Vim-Referenz »a:word« zugänglich. »a« steht für Argument, »s:« für Variablen lokal zum aufgerufenen Skript, »g:« für eine globale Variable, die innerhalb eines lokalen Blocks erreichbar sein soll, »b:« ist lokal zum Buffer, »w:« lokal zum Fenster und »v:« steht für eine Vim-eigene Variable. Zur Erinnerung: Fenster heißt nicht nur GUI-Fenster, sondern meint auch einen Teil eines aufgeteilten Konsolen-Vim.

Mit den Vim-Funktionen »input()« und »inputdialog()« nimmt Vim Eingaben vom Benutzer entgegen. Der Dialogtext wird als Parameter übergeben und die Funktion liefert die Eingabe des Benutzers zurück. Die gesuchten Wörter liegen jetzt für das Skript griffbereit in den Variablen lokal zur Funktion. Jetzt sucht die Funktion »Extract()« (siehe Listing 1, Zeilen 17 bis 29) das gewünschte Wort in der externen Datei (»dictpath«) und liefert ein Wortpaar aus der Datei zurück. Das Wortpaar übergibt man mit der gewünschten Zielsprache an die Funktion »Translate()« (siehe Listing 1, Zeilen 31 bis 42).

Listing 1: Das
Vim-Skript »dict.vim«

 

01 map <F9> :call Eng2Ger(expand("<cword>"))<CR>
02 map <F8> :call Ger2Eng()<CR>
03 
04 let dictpath = '~/Kram/eng2ger.vok'
05 let vocpath  = '~/Kram/vimvoc.txt'
06 
07 function Ger2Eng()
08     let word = inputdialog("Übersetze de -> eng: ")
09     call Extract(word, "want_eng")
10 endfunction
11 
12 function Eng2Ger(word)
13     let word = a:word
14     call Extract(word, "want_ger")
15 endfunction
16 
17 function Extract(word, lang)
18     let word = a:word
19     let lang = a:lang
20     let wordpair = system('grep "^' . word . 'b -|- b'. word . '$" ' . g:dictpath)
21     if(v:shell_error)
22         echohl ErrorMsg
23         echo "Fehler aufgetreten!"
24         echohl None
25     else
26         let match = matchstr(wordpair, "[^n]*")
27         call Translate(match, word, lang)
28     endif
29 endfunction
30 
31 function Translate(match, word, lang)
32     let match = a:match
33     let word = a:word
34     let lang = a:lang
35     if(lang == "want_eng")
36         let result = substitute(match, "\ (" . word . "\) -- ", submatch(1), "")
37         call ConfirmDialog(result, match)
38     elseif(lang == "want_ger")
39         let result = substitute(match,  " -- \(" .  word .  "\)", submatch(1), "")
40         call ConfirmDialog(result, match)
41     endif
42 endfunction
43 
44 function ConfirmDialog(result, match)
45     let result = a:result
46     let match = a:match
47     let choice = confirm(result,  "&Insertn&Save to Filen&Cancel",  3, "Question")
48     if(choice == 1)
49         execute "normal ea " . result
50     elseif(choice == 2)
51         execute "e " . g:vocpath
52         execute "normal o" . match
53         execute "w" . g:vocpath . "|bd"
54     elseif(choice == 3)
55         execute "normal e"
56     endif
57 endfunction

Nachschlag in der Datei

Um das Wort unter dem Cursor jetzt in einer Wörterbuchdatei nachzuschlagen, gibt es mehrere Möglichkeiten: Mit der Funktion »system()« sind beliebige Systemkommandos aufrufbar, etwa »grep«. Für Englisch gibt es bereits eine sehr einfache Wörterbuchdatei, die sich damit durchsuchen lässt (Listing 1, Zeile 20). Alternativ könnte man mit dem »system«-Aufruf natürlich auch Zugriffe auf »dict.leo.org« erledigen oder einen »dictd«-Server ansprechen. (Dictd ist ein Dämon, der unterschiedlichste Wörterbuchdateien übers Netz anbieten kann.) Vorteil der Grep-Version: Sie braucht keinen Netzwerkzugriff.

Im Grep-Aufruf ist ähnlich wie in Perl mit ».« eine Zeichenkette zu verbinden, um die Variablen aufzulösen. Der Systemaufruf führt auf der Shell zu »grep DasWort ~/Kram/eng2ger.vok«. Der Rückgabewert von Grep – nämlich alle gefundenen Zeilen, die dem regulären Ausdruck entsprechen – landen in der Variablen »wordpair«. Die Vokabeldatei liefert Wortpaar-Zeilen im Format » Wort – word«. Findet Grep nichts, wertet das Skript die Vim-Variable »v:shell_error« aus. Der Fehlermeldung fügt es noch das Standard-Highlighting für »ErrorMsg« hinzu und nimmt es nach Ausgabe des Fehlers wieder zurück (siehe Listing 1, Zeilen 20 bis 25).

Da Grep möglicherweise mehrere Zeilen inklusive Newlines zurückliefert und Vim die Eigenheit hat, die komplette Rückgabe als einen langen String in eine Variable zu stecken, zieht »matchstr()« den entscheidenden Teil heraus (Listing 1, Zeile 26). Wer ein anderes Dictionary-File verwendet, muss eventuell auf diese Bereinigung verzichten. Das Extrahieren der Vokabeln ist also je nach Format der Vokabeldatei neu zu definieren. Abhilfe wird hier in ein bis zwei Jahren hoffentlich ein XML-Format schaffen, das natürliche Sprachen auszeichnet und auch Flexionen erkennt, also die Tatsache, das “fiel” von “fallen” kommt[3].

Abbildung 2: Wenn etwas schief geht, taucht die Fehlermeldung im entsprechenden Farbschema auf.

Abbildung 2: Wenn etwas schief geht, taucht die Fehlermeldung im entsprechenden Farbschema auf.

Die Übersetzung

Das bereinigte Wortpaar und die gewünschte Sprache landen anschließend in der Funktion »Translate()«. Translate schneidet mittels »substitute()« den unerwünschten Teil des Wortpaars ab und behält das Gegenstück. Die Zielsprache wird gemeinsam mit dem Wortpaar an die Funktion »ConfirmDialog()« weitergereicht. Sie zeigt die Übersetzung an und erledigt das Speichern oder Einfügen der Übersetzung.

Grundsätzlich enthält Vim eine Art von Perl-Regex-Dialekt, jedenfalls wurden viele seiner Fähigkeiten übernommen: Lookahead und Lookbehind, verschiedene Quantifizierer und vieles mehr. Aber einige Elemente haben eine andere Syntax als von Perl gewohnt und werden außerdem in der Funktion »substitute()« auch anders aufgerufen als in der Kommandozeile. Der Parameter »submatch( Zahl)« entspricht dem »1« in der Regex auf der Kommandozeile von Vim. Der Teil des Wortpaars, der nicht erwünscht ist, wird einfach abgeschnitten (siehe Listing 1, Zeilen 31 bis 42).

Anzeigen, einfügen oder speichern?

Für Dialoge mit dem Anwender bietet Vim die oben verwendeten Funktionen wie »input()« oder »inputdialog()«. Die Funktion »Confirm()« zeigt einen Dialog an, der mit GUI verschiedene Buttons enthält und ohne GUI mit Tastaturkürzeln bedient wird. Hier zeigt die zweite Variable »vocpath« für das Einfügen des Wortpaars, wo zum Beispiel eine anzulegende Vokabeldatei liegt (Listing 1, Zeilen 44 bis 57).

Der Confirm-Dialog ist ganz einfach einzusetzen:

let choice = confirm(translation, 
"&Insertn&Save to Filen&Cancel", 
1, "Question").

Der erste Parameter ist der Text, der angezeigt werden soll – in »dict.vim« die Übersetzung des Wortes. Der nächste Parameter ist eine Liste von Optionen. Der Buchstabe direkt nach dem Ampersand »&« wird im Konsolenmenü mit »(C)ancel« als Shortcut angezeigt, im GUI als Button mit entsprechender Beschriftung »Cancel«. Das dritte Argument ist ein Defaultwert: Die Rückgabe von »confirm« ist schlicht eine Zahl, je nachdem, wie viele Optionen im zweiten Parameter stehen. Der Defaultwert ist jene Zahl, die ausgelöst werden soll, wenn der User [Enter] drückt – in »dict.vim« also Option »3«, »Cancel«.

Der letzte Parameter ist vor allem für das GUI wichtig: Je nach Parameter »Generic«, »Question«, »Error«, »Info« oder »Warning« zeigt Vim das passende Icon. Die Rückgabewerte »1«, »2« und »3« werden in der If-Elseif-Endif-Verzweigung ausgewertet (Listing 1, Zeilen 48 bis 56).

Abbildung 3: In der GUI-Version zum Übersetzen poppt automatisch ein kleines Fenster auf.

Abbildung 3: In der GUI-Version zum Übersetzen poppt automatisch ein kleines Fenster auf.

Einfach einfügen

Ein Wort in eine Vokabeldatei einfügen ist sehr einfach: In Vim ruft man mit »execute()« in einem Skript Befehle für den Normal-Modus auf, den Modus, der sonst durch Drücken von [Esc] erreichbar ist. Vim soll jetzt »e« für “End of Word” und dann »a« für “Append” ausführen und das übersetzte Wort hinter dem Suchwort einfügen. Fertig. Option »3« ermöglicht es, ohne jede Aktion das Übersetzungsmenü wieder zu verlassen. Das Skript platziert einfach den Cursor am Ende des Wortes. Wer will, kann ans Ende der Zeilen springen oder irgendeine andere Aktion ausführen.

Die zweite Option ist komplizierter: Sie soll das Suchwort mit dem Übersetzungswort in eine Liste schreiben, die später zum Beispiel beim Vokabellernen gute Dienste leisten soll. Der zuständige Dialog (siehe Listing 1, Zeile 53) erledigt auch gleich das Speichern und Einfügen der Übersetzung. »execute« in Kombination mit »normal« simuliert nur die Tastendrücke, mit denen der Nutzer sonst in Dateien schreiben oder navigieren würde. Alle Vim-Befehle lassen sich hier beliebig aneinander reihen und miteinander kombinieren – genau wie im [Esc]-aktivierten oder im »:«-Modus.

Vokabeln lernen

Um das Vokabelpaar in eine neue Datei auszulagern, öffnet »:e Datei« die gewünschte Datei. Im Vim-Skript aktiviert »execute« das »:«-Kommando. Im Gegensatz zu einem Normal-Kommando ist dabei kein Schlüsselwort wie »colon« oder etwas in der Art nötig. Es genügt, »execute« das Kommando direkt zu übergeben. Die neue Datei ist für kurze Zeit der aktuelle Buffer, in sie wird mit dem nächsten Kommando per [O] das neue Vokabelpaar jeweils über dem vorhandenen neu eingefügt.

Wer die Reihenfolge umdrehen möchte – die aktuellen jeweils nach unten in die Datei -, der benutzt einfach [Shift]+ [O]. Um später die Vokabeldatei mit den gleichen Mitteln wie die Dictionary-Datei bearbeiten zu können, schreibt »dict .vim« das Vokabelpaar mit der gleichen Syntax. Danach speichert man mit [W] den aktuellen Buffer – also die Vokabeldatei – und ruft nach erfolgreichem Speichern mit »|bd« das Schließen des Buffers (Buffer Delete) auf. Das Zeichen »|« bedeutet im »:«-Mode bei Vim: Führe den nächsten Befehl nur aus, wenn der vorherige geklappt hat (Listing 1, Zeile 53). Außerdem besteht auch die Möglichkeit, jede bisher unbekannte Vokabel formatiert in eine externe Datei schreiben zu lassen und so elegant etwa Vokabelkärtchen zu erstellen.

Mehr Featurezzzz!

Wem das einfache Übersetzen zwischen Deutsch und Englisch nicht genügt, der kann das Skript beliebig ausbauen – zusammen mit dem Vim-Interface zu Perl, Python, Ruby oder TCL geht noch viel mehr: Vokabeln lassen sich auch Unicode-encodiert nachschlagen und damit sogar sehr exotische Wörterbücher verwenden. Viele Verlage haben digitale Versionen ihrer Lexika im Angebot, wer das Format rausfindet, kann sich ein Interface zum “Micro Robert” (Französisch) oder zum fetten “Oxford Dictionary” (Englisch) schreiben.

Mit Hilfe von Ispell sind auch flexible Suchmuster möglich: Ispell versteht sich auch auf gebeugte Verben, wie sie im Deutschen oder Französischen vorkommen. Dann erkennt ein Plugin, dass es sich bei “lis”, “lu” oder “lirai” um verschiedene Formen des französischen Verbs “lire” (lesen) handelt. Von der Ispell-Fähigkeit beim Erkennen von Stamm und Endung eines Wortes sind es nur ein paar (Programmier-)Schritte bis zum eleganten Syntax-Highlighting von Rechtschreibfehlern, Grammatikproblemen oder sogar Stilfragen.

Die Regeln für gute Artikel zum Beispiel berücksichtigt Vim dann schon beim Tippen und zeigt Verstöße an. Und wenn endlich die Lese-Pads von der Enterprise verfügbar sind, tippt man bloß mit dem Finger auf das Wort und bekommt sofort die Übersetzung angezeigt. (uwo)

Infos

[1] Vim-Hauptseite: [http://vim.sf.net]

[2] Englisch-Deutsch-Wörterliste: [http://ftp.leo.org/download/pub/comp/doc/dict/]

[3] Natural Language Processing mit XML: [www.w3c.org/TR/2000/WD-nl-spec-20001120/]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben