blog · git · desktop · images · contact
2010-09-21
Vim kann ja grundsätzlich gut mit externen Programmen umgehen. So kann man zum Beispiel eine Datei sortieren lassen:
:%!sort
Dabei wird die ganze Datei ("%") einmal durch "sort" gepiped ("!"). Oder man markiere einen Bereich im visuellen Modus und gebe dann ein (den Bereich fügt er selbst ein):
:'<,'>!column -t
Damit würde aus
hi Statement ctermfg=130 cterm=bold
hi Identifier ctermfg=19 cterm=bold
hi Type ctermfg=70 cterm=bold
das hier
hi Statement ctermfg=130 cterm=bold
hi Identifier ctermfg=19 cterm=bold
hi Type ctermfg=70 cterm=bold
werden.
Äußerst praktisch wird es aber dadurch, dass man auch im "s"-Befehl (":substitute") Pipes nutzen kann. Es ist allerdings nicht gerade offensichtlich. Die Basics des Befehls dürften bekannt sein, das hier ersetzt in der Datei alle Vorkommen von "foo" durch "bar":
:%s/foo/bar/g
Beginnt das Ersetzungspattern jedoch mit "\=", dann kann eine Expression
folgen, deren Rückgabewert als Ersetzung verwendet wird -- hier wird
jedes "__date__
" durch das aktuelle Datum ersetzt:
:%s/__date__/\=strftime("%F")/g
Und nun kommt die Pipe ins Spiel. Die Funktion "system()" kann einen externen Befehl ausführen und seine Ausgabe auffangen. Als zweites Argument kann man einen String angeben, den das Programm von STDIN lesen kann. Innerhalb eines "s"-Befehls kann man hierzu "submatch()" nutzen, um den Treffer abzufragen. Fertig ist die Pipe. Als einfaches, akademisches Beispiel diene folgendes, was jedes Auftreten des Strings "Ümläute" einmal durch "base64" jagt:
:%s/Ümläute/\=system("base64", submatch(0))/g
"submatch(0)" ist der komplette Treffer des Musters. Verwendet man im Muster Gruppierungen, dann kann man hier auswählen, welche Gruppe man gerne hätte.
Zum Abschluss noch ein Real-Life-Beispiel. Hiermit kann man Befehle über
explain erklären, indem bestimmte Bereiche des Textes einmal durch das
Skript geschickt werden. Sie beginnen mit "<<<explain
" und enden mit
">>>
", jeweils in einer einzelnen Zeile, dazwischen die normale
explain-Notation:
:%s/\n\@<=<<<explain\n\(\_.\{-}\n\)>>>\n/\=system("explain.py", submatch(1))/g
| \__________________________________/ \______/ \__________/ \_________/ |
| | | | | \- Alles.
| | | | |
| | | | \- Wird zu STDIN
| | | | für das
| | | | Programm. Hier
| | | | der erste
| | | | Submatch des
| | | | Ausdrucks vorne.
| | | |
| | | \- Zu startendes Programm.
| | |
| | \- Nutze einerseits zur Ersetzung eine
| | Expression ("\=") und andererseits den
| | system()-Befehl, um den Text durch eine
| | Pipe zu jagen.
| |
| \- Das Muster für den explain-Block: Eingeleitet wird er durch ein
| "<<<explain" in einer einzelnen Zeile, dann der explain-Inhalt
| und abgeschlossen wird es durch ein ">>>" in einer einzelnen
| Zeile.
|
\- Ersetze in der gesamten Datei.
Ich persönlich habe mir das in eine Funktion gesteckt und zwei Mappings angelegt:
fun! DoExplainFile(...)
" Merke dir die alte Position des Cursors. Führe dann die Ersetzung
" durch.
let l:prev = getpos(".")
%s/\n\@<=<<<explain\n\(\_.\{-}\n\)>>>\n/\=system("explain.py", submatch(1))/g
" Dauerhaft? Oder nur yanken? Im Falle eines optionalen Arguments
" mit Wert "yank" kopierst du den Inhalt und machst die Ersetzung
" dann rückgängig.
if a:0 > 0 && a:1 == "yank"
silent %y
u
endif
" Cursor wieder an die alte Stelle zurück, Highlight des
" Suchtreffers abschalten.
call setpos(".", l:prev)
noh
endfun
nmap <Leader>e :call DoExplainFile("yank")<CR>
nmap <Leader>E :call DoExplainFile()<CR>
"<Leader>e
" führt die Ersetzung durch, kopiert danach die ganze Datei
in die Zwischenablage und macht dann die Ersetzung wieder rückgängig.
Ist zum Beispiel für Blogposts praktisch: Der explain-Quelltext bleibt
erhalten und man hat die verarbeitete Version im Clipboard, sodass man
sie im Browser einfügen kann. "<Leader>E
" dagegen macht die Ersetzung
im Dokument selbst, dauerhaft. So entstand auch dieses Posting. ;)