2012年5月28日月曜日

SyncTeXの設定。 Emacs(YaTeX) + Evince

Ubuntu 12.04 64bitにTeXLive 2011+tlptexliveをインストールした環境を使っています。この環境はSyncTeXに対応していて、platexに-synctex=1 オプションを付けてタイプセットしたのち、dviファイルをdvipdfmxで処理することで、pdfファイルとtexファイル間の該当部分を互いに行き来することが出来ます。


EvinceやSyncTeXのバージョンで設定方法が変わってくるようなので要注意です。
ここではSyncTeXはTeXLive2011のものを、Evinceのバージョンは3.4.0, Emacsのバージョンは23.3です。EvinceとEmacsはUbuntu 12.04のリポジトリにあるのもです。



forward search(エディタからpdfの該当場所へのジャンプ)について



TeXWikiのEvinceの項目から下記の内容をfwdevinceという名前で保存します。
TeXWikiにはTeXLive2012とEvince 3.4.0, python 2.7.3での動作が確認されていますが、TeXLive2011でも大丈夫でした。pythonはUbuntu 12.04のリポジトリにあるもののバージョンが2.7.3でした。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dbus
import argparse
import os.path
import time

class FwdEvince:

def parse_args(self):
parser = argparse.ArgumentParser(description='Forward search with Evince')
parser.add_argument('pdf', nargs=1, help='PDF file')
parser.add_argument('line', nargs=1, type=int, help='Line')
parser.add_argument('tex', nargs=1, help='TeX file')
return parser.parse_args()

def run(self):
args = self.parse_args()
pdf = os.path.abspath(args.pdf[0])
line = int(args.line[0])
tex = os.path.join(os.path.dirname(os.path.abspath(args.tex[0])), './', os.path.basename(os.path.abspath(args.tex[0])))

try:
bus = dbus.SessionBus()
daemon = bus.get_object('org.gnome.evince.Daemon', '/org/gnome/evince/Daemon')
dbus_name = daemon.FindDocument('file://' + pdf, True, dbus_interface='org.gnome.evince.Daemon')
window = bus.get_object(dbus_name, '/org/gnome/evince/Window/0')
time.sleep(0.2)
window.SyncView(tex, (line, 1), 0, dbus_interface='org.gnome.evince.Window')
except dbus.DBusException:
print_exc()


if __name__ == '__main__':
evince = FwdEvince()
evince.run()
これを
$ chmod +x fwdevince
として実行権を与え、パスの通った場所に移動させます。

次にEmacsの設定ファイル(自分の場合は~/.emacs.d/init.el)に以下の内容を書き込みます。

(defun evince-forward-search ()
  (interactive)
  (let* ((ctf (buffer-name))
         (mtf)
         (pf)
         (ln (format "%d" (line-number-at-pos)))
         (cmd "fwdevince")
         (args))
    (if (YaTeX-main-file-p)
        (setq mtf (buffer-name))
      (progn
        (if (equal YaTeX-parent-file nil)
            (save-excursion
              (YaTeX-visit-main t)))
        (setq mtf YaTeX-parent-file)))
    (setq pf (concat (car (split-string mtf "\\.")) ".pdf"))
    (setq args (concat pf " " ln " " ctf))
    (message (concat cmd " " args))
    (process-kill-without-query
     (start-process-shell-command "fwdevince" nil cmd args))))

(add-hook 'yatex-mode-hook
          '(lambda ()
             (define-key YaTeX-mode-map (kbd "C-c e") 'evince-forward-search)))
これでforward searchがYaTeX modeのEmacs上からCtrl-c eまたは M-x evince-forward-search で行えるようになりました。pxdviでのforward searchは該当部分を含む段落へジャンプするくらいの割合ザックリとした機能でしたが、SyncTeXでのforward-searchはpdfファイルの該当行へ飛ぶので、ちょっと感動しました。但し、\include や\input を使ってTeXソースファイルを分割している場合は、Ctrl-c e などを実行する前に各ソースファイル毎に一度[prefix] t j でタイプセットを行うなどしてソースのメインファイル名をYaTeX側に伝えておく必要があります。


inverse search (pdfファイルからテキストファイルの該当場所へのジャンプ)について


TeXWikiのEmacsの項目にある「Evinceとの連携」「inverse search」に設定内容が記述してあるのですが、この内容は古いようです。
(追記2012/05/29: TeXWikiの内容は更新されたようです。仕事がはええ。)

最近のEvince(2.91.0以降?)を利用する場合には、EvinceからEmacsへメッセージを送るD-Busの使用が変更されているらしく、それが原因でTeXWikiにある設定では、\include を使ってソースファイルを複数に分けているようなTeXファイルではinverse searchがうまくいきませんでした。
具体的には、pdfファイルからソースファイルへジャンプさせようとする際に、pdfファイルの該当する部分がEmacsで開かれている場合にはうまくジャンプ出来るのですが、ソースファイルの該当部分がEmacsで開かれていない場合には「Sorry, file:///filename is not opened...」というエラーが出て、ファイル名の前に「file:///」なる余分な文字列が現れソースファイルを開けませんでした。
この「file:///」という文字列を消すようにする設定がEmacs WikiのAUCTeX SyncTeX and Evinceの項目にありました。ここではこれを参考に少しだけ変更をすることで望みの結果を得ることができました。以下をEmacsの設定ファイルに書き込みます。
(require 'dbus)

(defun un-urlify (fname-or-url)
  "A trivial function that replaces a prefix of file:/// with just /."
  (if (string= (substring fname-or-url 0 8) "file:///")
      (substring fname-or-url 7)
    fname-or-url))

(defun th-evince-sync (file linecol &rest ignored)
  (let* ((fname (un-urlify file))
 ;        (buf (find-buffer-visiting fname))
         (buf (find-file fname))
         (line (car linecol))
         (col (cadr linecol)))
    (if (null buf)
        (message "[Synctex]: %s is not opened..." fname)
      (switch-to-buffer buf)
      (goto-line (car linecol))
      (unless (= col -1)
        (move-to-column col)))))

(defvar *dbus-evince-signal* nil)

(defun enable-evince-sync ()
  (require 'dbus)
  (when (and
         (eq window-system 'x)
         (fboundp 'dbus-register-signal))
    (unless *dbus-evince-signal*
      (setf *dbus-evince-signal*
            (dbus-register-signal
             :session nil "/org/gnome/evince/Window/0"
             "org.gnome.evince.Window" "SyncSource"
             'th-evince-sync)))))

;(add-hook 'LaTeX-mode-hook 'enable-evince-sync)
(add-hook 'yatex-mode-hook 'enable-evince-sync)
ここではEmacs Wikiに記述があったものを少し書き換えてあります。コメントアウトしている部分とその下の行がそれです。find-buffer-visiting fname はEmacsが現在開いているバッファの中でfname という名前に相当するものがないとnil を返すようで、この記述があると分割されたソースファイルとの行き来が制限されていました。find-file fname と記述することで、fname という名前のファイルを開くことが出来るので問題が解消しました。最後のadd-hook の部分はAUCTeX用の記述をYaTeX用に書き換えただけです。elispが全く読めないので、変なことをしているかもしれませんが、今のところ順調に動いています。参考までに。

しかし数百ページある文章だとdvipdfmxも実行する必要があるのでpdfファイルでのプレビューは面倒ですね。またEvinceはpxdvi と違ってキーボード操作だけで指定ページヘ移動することが出来ないのが結構なストレスになります。まだまだ普段はpxdviのお世話になることが続きそうです。最近はTikzの練習を始めたので、pdfでプレビューする環境が整ってよかったです。