Latex unter Windows mit Vorschau und deinem Lieblingseditor

Mein Eindruck ist, dass viele vernünftige Leute das Problem haben, unter Windows texen zu wollen und bei schrecklichen Programmen wie LaTeX Editor oder TeXnicCenter hängen bleiben. Prinzipiell funktionieren die ja auch. Vielleicht nicht perfekt und manchmal sind sie hier oder da etwas unpraktisch, oder stürzen ab (beim Editiern von Text) - aber sie tun ihre Arbeit und, hey, es gibt ja auch nichts besseres.

Da ich völlig naiv mal einen ganz anderen Ansatz verfolgt habe, damit sehr zufrieden bin und gelegentlich darauf angesprochen werde, verfasse ich also jetzt diesen Blugpost. Als Zutaten brauchen wir

  • Deinen Lieblingseditor - z.B. Notepad++
  • Eine funktionierende MikTex-Installation inklusive dem großartigen Programm latexmk
  • cygwin inklusive der zweckmäßigen Extraktions- und Berichtssprache Perl (für latexmk) und dem etwas verschrobenen Programm make
  • SumatraPDF - ein PDF-Viewer, der keinen Handle auf die geöffnete Datei behält, sondern diese zum Überschreiben freigibt und sogar noch überwacht, ob sie sich ändert und ggf. neu einließt
  • AutoIt
  • Optional: AutoHotkey
  • Optional: Skripte für AutoHotkey / AutoIt zum Positionieren von Fenstern und für Latex-Shortcuts

Stelle sicher, dass beim Eintippen von pdflatex auch wirklich PDF-Latex aufgerufen wird (Strg-C liefert die traurigste Fehlermeldung, die ein LR(k)-Parser je ausgegeben hat). Weiterhin sollte latexmk ebenfalls funktionieren. Es ist also möglich, mit einem Aufruf von

latexmk -pdf MeineDatei.tex

Latex-Dateien nach PDF zu kompilieren. Das gleiche sollte mit

latexmk -pdf -pdflatex="pdflatex -interaction=nonstopmode -synctex=1" MeineDatei.tex

möglich sein, nur, dass dann noch synctex-Informationen mitgeschrieben werden, die es erst möglich machen, zwischen einem Text-Editor und einer PDF-Datei hin und herzuspringen. Dazu muss sowohl der PDF-Reader die Fähigkeit haben, einen Befehl zu einem bestimmten Ort in der Datei auszuführen (SumatraPDF kann dies) und der Texteditor das gleiche für einen bestimmten Ort in der LaTex-Datei (Notepad++ ist ebenfalls in der Lage dazu). Da ich in dieser Umgebung arbeite, werde ich das Verfahren anhand dieser Programme beschreiben, prinzipiell ist die Anleitung aber natürlich auch auf andere Umgebungen übertragbar.

Um zu Anfang einige Vokabeln zu Klären: Das Springen von der Latex-Datei zur entsprechenden Stelle in der PDF-Datei bezeichnet man als "Forward Search". Den dualen Vorgang, also das Springen von einer bestimmten Stelle in der PDF-Datei zu der entsprechenden Zeile im Latex-Quellcode bezeichnet man als "Inverse Search".

LaTeX kompilieren

Es wäre natürlich möglich, über das Menü Run einfach pdflatex aufzurufen. Notepad++ erlaubt natürlich, an Programme, die über Run ausgeführt werden, Informationen wie die gerade bearbeitete Datei zu übergeben. Mit einem Befehl der Form

latexmk -pdf -pdflatex="pdflatex -interaction=nonstopmode -synctex=1" "$(CURRENT_DIRECTORY)\$(FILE_NAME)"

kann man etwa die gerade bearbeitete Datei kompilieren. Dies hat sich für mich in der Vergangenheit aber als nicht flexibel genug herausgestellt: Es ist so, dass ich gerne meine Inhalte auf mehrere Dateien aufteile und dann z.B. \input verwende, um sie in einer "Haupt-Latex-Datei" zusammenzuführen. Gängige Latex-Editoren lösen dieses Problem über sogenannte Projekte - in diesen kann man dann einfach einstellen, welche .tex-Datei die primäre ist. Diese wird dann kompiliert, wenn man auf das Play-Button drückt.

Glücklicherweise haben viele Leute dieses oder ähnliche Probleme mit LaTeX und vor allem auch anderen Sprachen. Darum wurde das Programm make erfunden. Wir werden es dazu einsetzen, Verzeichnisse quasi zu Projekt-Verzeichnissen zu machen. Wenn man make aufruft, sucht es im aktuellen Arbeitsverzeichnis nach einer Datei mit dem Namen Makefile. Mit dieser kann man steuern, was dann passieren soll. Ich nutze häufig eine Modifikation des folgenden Makefiles:

.PHONY: all clean cleanall MeineDatei.pdf

all: MeineDatei.pdf

MeineDatei.pdf: MeineDatei.tex header.tex commands.tex biblio.bib chapter/*.tex content/*.tex
        latexmk -silent -pdf -pdflatex="pdflatex -interaction=nonstopmode -synctex=1" -use-make MeineDatei.tex

test: MeineDatei.pdf
        pdflatex MeineDatei

index: MeineDatei.pdf
        makeindex MeineDatei
        makeindex MeineDatei.nlo -s nomencl.ist -o MeineDatei.nls
        latexmk -pdf -pdflatex="pdflatex -interaction=nonstopmode -synctex=1" -use-make MeineDatei.tex

clean:
        latexmk -c

cleanall:
        latexmk -C
        rm -f MeineDatei.aux MeineDatei.tdo MeineDatei.bbl MeineDatei.loa MeineDatei.glo MeineDatei.nlo MeineDatei.nls MeineDatei.thm MeineDatei.synctex.gz

Ich habe weiter oben make als ein etwas verschrobenes Programm bezeichnet. Dies liegt daran, dass die Einrückungen in obiger Datei Tabulatoren sein müssen. Ein Aufruf von make im Verzeichnis der Makefile ruft dann den gewünschten Befehl auf. Ausserdem sind folgende Argumente möglich

  • make test dies rufe ich auf, wenn mein Code einen Fehler enthält und ich möchte, dass pdflatex an der entsprechenden Stelle stehenbleibt, damit ich herausfinden kann, was der Fehler ist
  • make clean entfernt den LaTeX-Müll
  • make cleanall entfernt wirklich den ganzen LaTeX-Mull
  • make indexErstellt die nötigen Dateien für einen Index

Um nun den Komfort noch etwas zu erhöhen, habe ich folgendes AutoIt-Skript geschrieben:

WinActivate('C:\Windows')
Send('make{ENTER}!{TAB}')

Dies ist auf jeden Fall eine schlechte Dreckslösung, funktioniert aber für mich: Ich habe über Run in Notepad++ einen Shortcut darauf erstellt. Beim Aufruft sucht es ein Command-Fenster, tippt make ein, drückt Enter und "Alt-Tab"t dann wieder zurück zu Notepad++. Verbesserungen erwünscht und werden gerne per e-Mail entgegen genommen, es funktioniert im Moment halt nur so wunderbar. Das ganze klappt natürlich nur, wenn auch ein Command-Fenster auf ist, dass im korrekten Arbeitsverzeichnis ist.

Forward Search

Soweit so gut: Nachdem das Kompilieren also funktioniert hat, wollen wir das Resultat auch anzeigen. Um die Vorwärtssuche zum Laufen zu bringen, müssen wir aus Notepad++ heraus SumatraPDF so aufrufen, sodass

  • das aktuelle Verzeichnis übergeben wird
  • die aktuelle Latex-Datei übergeben wird
  • die Zeilennummer in der Latex-Datei übergeben wird
  • SumatraPDF geöffnet wird, wenn es noch nicht geöffnet ist und die bereits existierende Instanz verwendet wird, wenn es bereits läuft
  • auf irgendeine geschickte Art und Weise die korrekte PDF-Datei geöffnet wird: Dies ist einfach, wenn man nur eine einzige Latex-Datei bearbeitet, die zugehörige PDF-Datei hat dann nämlich den gleichen Namen mit einer anderen Dateinamenerweiterung. Schwierig wird es vor allem, wenn man größere Projekte mit Latex realisiert (oder einfach nur sehr ordentlich ist) und z.B. per \input andere Latex-Dateien einbindet und in diesen dann arbeitet.

Der letzte Punkt ist am schwierigsten. Ich habe es so gelöst, dass ich mit dem Befehl

"C:\Program Files (x86)\AutoIt3\AutoIt3.exe" "C:\System\Scripts\npp_forward.au3" "$(CURRENT_DIRECTORY)" "$(FILE_NAME)" $(CURRENT_LINE)

Aus Notepad++ heraus das folgende AutoIt-Skript aufrufe (Beschreibung über Funktionsweise als Kommentar):

; this script traverses from the CURRENT_DIRECTORY up the folder hierarchy, looking
; for pdf files. If it finds one or more, it tries the following (in this order):
; 1) If a Makefile exists, look for a line starting with all and execute the
;    corresponding pdf-file
; 2) open FILE_NAME with the extension replaced by .pdf
; 3) try all file names from $ValidPdfNames (concated with .pdf)
; 4) open any of the pdf files
; and opens the correnspondig pdf-file at the given CURRENT_LINE

; call from Notepad++ like
; "C:\Program Files (x86)\AutoIt3\AutoIt3.exe" "C:\path\to\this\npp_forward.au3" "$(CURRENT_DIRECTORY)" "$(FILE_NAME)" $(CURRENT_LINE)

; configure location of sumatra
Global $Sumatra = 'C:\Program Files (x86)\SumatraPDF\SumatraPDF.exe'
Global $ValidPdfNames[2] = [ 'main.pdf', 'skript.pdf' ]

; --------- end of configuration section ---------

; only go on, if 3 arguments are given
If $cmdLine[0] <> 3 Then
    Exit
EndIf

Func RemoveExtension($Filename)
    Return StringLeft($Filename, StringInStr($Filename, ".", Default, -1)-1)
EndFunc

Func RemoveLastPathPart($Folder)
    Return StringLeft($Folder, StringInStr($Folder, '\', 0, -1)-1)
EndFunc

Func GetLastPathPart($Folder)
    Return StringRight($Folder, StringLen($Folder)-StringInStr($Folder, '\', 0, -1))
EndFunc

Func PdfExist($Folder, $Filename)
    Return FileExists($Folder & '\' & $Filename & '.pdf')
EndFunc

; name commandline arguments
$Folder = $cmdLine[1]
$Filename = $cmdLine[2]
$CurrentLine = $cmdLine[3]
$Basename =  RemoveExtension($Filename)
; MsgBox(0, '', $Folder & @CRLF & $Filename & @CRLF & $CurrentLine)

Func CheckDir($Folder)
    Local $Filename = False
    Local $Search = FileFindFirstFile($Folder & '\*.pdf')
    If $Search <> -1 Then
        If FileExists($Folder & '\Makefile') Then
            Local $fp = FileOpen($Folder & '\Makefile')
            While 1
                Local $Line = FileReadLine($fp)
                If @error = -1 Then ExitLoop
                If StringLeft($Line, 5) == 'all: ' Then
                    Local $temp = StringRight($Line, StringLen($Line)-5)
                    If FileExists($Folder & '\' & $temp) Then
                        $Filename = $Folder & '\' & $temp
                    EndIf
                    ExitLoop
                EndIf
            WEnd
        EndIf
        If Not $Filename Then
            if PdfExist($Folder, $Basename) Then
                $Filename = $Folder & '\' & $Basename & '.pdf'
            Else
                ; try ValidPdfNames array
                For $i=0 To UBound($ValidPdfNames) -1
                    if PdfExist($Folder, $ValidPdfNames[$i]) Then
                        $Filename = $Folder & '\' & $ValidPdfNames[$i] & '.pdf'
                        ExitLoop
                    EndIf
                Next
               
                ; if not found, just take the first
                $Filename = $Folder & '\' & FileFindNextFile($Search)
            EndIf
        EndIf
    EndIf
    FileClose($Search)
    Return $Filename
EndFunc

; Change to Latex-Directory such that SumatraPDF will find the tex-file
FileChangeDir($Folder)

; Look for PDF files
$ThisFolder = $Folder
$PdfFile = CheckDir($ThisFolder)
While Not $PdfFile and $ThisFolder
    $ThisFolder = RemoveLastPathPart($ThisFolder)
    $PdfFile = CheckDir($ThisFolder)
WEnd
$TitleSearchString = GetLastPathPart($PdfFile)

; if no PDF file found, exit
If Not $PdfFile Then
    Exit
EndIf

; execute sumatra PDF command
$run = '"' & $Sumatra & '" -reuse-instance "' & $PdfFile & '" -inverse-search "\"$(#0)\" \"%f\" -n%l" -forward-search "'& $Filename & '" ' & $CurrentLine
Run($run)

; wait for sumatra window
WinWaitActive($TitleSearchString)

; active notepad++ window
WinActivate($Folder & '\' & $Filename)

Inverse Search

Die Rückwärtssuche ist ungleich einfacher zu verwenden: Wenn man mit dem oben beschriebenen Parameter -synctex=1 kompilliert hat, wird eine zusätzliche Datei .synctex.gz erstellt, die SumatraPDF ermöglicht, bei Doppelklick Wunder zu wirken: Trägt man unter Settings -> Options im Feld bei "Set inverse search command-line" folgende Zeichenkette ein

"$(#0)" "%f" -n%l

so springt ein Doppelklick in die PDF-Datei an die richtige Stelle in Notepad++.

Optional: Fenster anordnen

Besitzt man nur einen Monitor, so ist das weitere Vorgehen klar: Man zieht Notepad++ an die eine Seite des Bildschirms und SumatraPDF an die andere und hat das Look & Feel einer Live-Vorschau. Hat man allerdings zwei Monitor ist es aus unerfindlichen Gründen nicht möglich, ein Fenster an den "mittleren" Rand zu ziehen. Darum verwende ich das folgende AutoIt-Skript zum Anordnen von Fenstern.

Optional: LaTeX-Shortcuts

Und natürlich verwende ich noch ein AutoHotkey-Skript um schneller zu texen. Dazu aber später in einem eigenen Blagpost mehr.WindowPadWindowPad

One Response to “Latex unter Windows mit Vorschau und deinem Lieblingseditor”

  1. Mee says:

    Danke.

    "Hat man allerdings zwei Monitor ist es aus unerfindlichen Gründen nicht möglich, ein Fenster an den "mittleren" Rand zu ziehen."
    Das sollte mit der Tastatur möglich sein: Windows + Pfeiltaste

Leave a Reply