Skip to main content

Emacs, Evince and SyncTeX Magic

The Nokia 7.2 phone I recently bought for my wife required downloading more than a gigabyte of updates after switching it on for the first time. It may seem strange in such quick paced times to use software older than a few years and so I was astonished to find that two important pieces of my digital tool set are more than 30 years old. Both the GNU Emacs editor and the typesetting system LaTeX started life already 36 years ago.

Emacs logo

Emacs is usually the first application I launch after a fresh boot and it is rare for me to "M-x kill-emacs" before shutting down the system again some weeks later for a kernel update. Of course the system is suspended to save power when it is not in use.

Having mastered the LaTeX learning curve already back in University it is still my go to system for presentations using the beamer class. Using plain text for presentations allows using git, the version control system of my choice, to track changes in a natural way.

Even though there is this nifty preview latex package for Emacs to embed LaTeX previews right into the editing environment, I haven’t used it for ages. I rather have the GNOME PDF viewer Evince display the generated PDF file on another monitor. Evince watches the PDF for changes, and automatically refreshes the output after another LaTeX run. So usually I do not switch a lot between Emacs and Evince and so this "waterfall" workflow with Evince at the bottom served me well for a long time.

Only by chance I stumbled upon SyncTeX and became interested if my setup was extensible in a way to close the loop more tightly by enabling jumps from the PDF to the source.

Enabling SyncTeX requires adding a single line to the LaTeX source file. Alternatively one can also use the command line flag "-synctex=1" for pdflatex to achieve the same result. As the previous link points to the online documentation of Evince, it should be no surprise that evince also supports it. Upon closer examination, it turns out that Evince uses D-Bus IPC for the SyncTeX functionality. On the one hand it listens to incoming "SyncView" messages and emits "SyncSource" events on Ctrl-Clicks.

For my setup, the remaining question is how to hook Emacs into DBus and sure enough D-Bus integration in Emacs appeared a while ago in version 24.3.

Some friendly people on StackExchange provide enough info to get going.

To get started, all I needed to do is to put the following code taken from StackExchange into sync-tex.el and simply evaluate it. Having done this, I can now Ctrl-Click in Evince and Emacs will jump to the LaTeX source. The other direction also works nicely - pressing "C-c C-a" in Emacs will compile the file if necessary and sync the Evince window to the buffer position in Emacs.

So very cool indeed!

; SyncTeX basics

; un-urlify and urlify-escape-only should be improved to handle all special characters, not only spaces.
; The fix for spaces is based on the first comment on

(defun un-urlify (fname-or-url)
  "Transform file:///absolute/path from Gnome into /absolute/path with very limited support for special characters"
  (if (string= (substring fname-or-url 0 8) "file:///")
      (url-unhex-string (substring fname-or-url 7))

(defun urlify-escape-only (path)
  "Handle special characters for urlify"
  (replace-regexp-in-string " " "%20" path))

(defun urlify (absolute-path)
  "Transform /absolute/path to file:///absolute/path for Gnome with very limited support for special characters"
  (if (string= (substring absolute-path 0 1) "/")
      (concat "file://" (urlify-escape-only absolute-path))

; SyncTeX backward search - based on, reproduced on

(defun th-evince-sync (file linecol &rest ignored)
  (let* ((fname (un-urlify file))
         (buf (find-file fname))
         (line (car linecol))
         (col (cadr linecol)))
    (if (null buf)
        (message "[Synctex]: Could not open %s" 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)
  ; cl is required for setf, taken from:
  (require 'cl)
  (when (and
         (eq window-system 'x)
         (fboundp 'dbus-register-signal))
    (unless *dbus-evince-signal*
      (setf *dbus-evince-signal*
             :session nil "/org/gnome/evince/Window/0"
             "org.gnome.evince.Window" "SyncSource"

(add-hook 'LaTeX-mode-hook 'enable-evince-sync)

; SyncTeX forward search - based on

;; universal time, need by evince
(defun utime ()
  (let ((high (nth 0 (current-time)))
        (low (nth 1 (current-time))))
   (+ (* high (lsh 1 16) ) low)))

;; Forward search.
;; Adapted from
(defun auctex-evince-forward-sync (pdffile texfile line)
  (let ((dbus-name
     (dbus-call-method :session
               "org.gnome.evince.Daemon"  ; service
               "/org/gnome/evince/Daemon" ; path
               "org.gnome.evince.Daemon"  ; interface
               (urlify pdffile)
               t     ; Open a new window if the file is not opened.
    (dbus-call-method :session
          (urlify-escape-only texfile)
          (list :struct :int32 line :int32 1)

(defun auctex-evince-view ()
  (let ((pdf (file-truename (concat default-directory
                    (TeX-master-file (TeX-output-extension)))))
    (tex (buffer-file-name))
    (line (line-number-at-pos)))
    (auctex-evince-forward-sync pdf tex line)))

;; New view entry: Evince via D-bus.
(setq TeX-view-program-list '())
(add-to-list 'TeX-view-program-list
         '("EvinceDbus" auctex-evince-view))

;; Prepend Evince via D-bus to program selection list
;; overriding other settings for PDF viewing.
(setq TeX-view-program-selection '())
(add-to-list 'TeX-view-program-selection
         '(output-pdf "EvinceDbus"))

Ok, things could be wired up more elegantly, but some things should be left as an exercise for the reader wink wink.

2021-01-30 Update

There is no code required for this to work at all. More details can be found in this newer post.


Comments powered by Disqus