Vim, LaTeX, Evince, and Forward Search
By John Lenz. May 11, 2013.
After writing the previous post (see also part 1), I started looking into a better forward search for LaTeX and Vim, specifically controlling evince via the dbus API. These instructions work with GNOME 3.8 (specifically evince 3.8.0 and gedit-plugins-3.8.1) and should work for future versions. I am unsure if they will work for GNOME 3.6 and earlier.
The way SyncTex works is that when compiling with pdflatex, you specify "-synctex=1" on the command line and a <filename>.synctex.gz file is then generated. This synctex.gz file contains information about the input files and how the lines of the input file map to items in the pdf. Evince has code to parse these .synctex.gz files, so we just need to tell evince the pdf file, the input file, and the line number via a dbus method.
I use rubber (see the first post) to compile. There is a bug report for adding synctex support to rubber. For now, I took the approach suggested by Smeuuh in the bug report. In ~/bin, I added a file called pdflatex with the contents:
#!/bin/sh exec /usr/bin/pdflatex -synctex=1 "$@"
I also added *.synctex.gz to "make clean" in my generic makefile.
Evince DBUS Call
Evince has a dbus method called SyncTex which takes the pdf filename, the input filename, a line number, and a timestamp. We could make this dbus call directly from Vim using python, execute dbus-send from the command line, or run some external python script. The main issue is in the future evince might change the dbus API. Thankfully, the gedit synctex plugin contains a python script called evince_dbus.py which makes the dbus call and can be called on the command line. Presumably this script will be kept up to date since it is part of gedit, and so we can just directly call the latest version of the script from Vim. I did make one minor change to the script. The current version (as of May 11) hangs after sending the message when run from the command line. The following minor change fixes this.
--- /usr/lib/gedit/plugins/synctex/evince_dbus.py 2013-04-15 13:42:37.000000000 -0500 +++ evince_dbus.py 2013-05-11 15:35:31.991411344 -0500 @@ -178,10 +178,12 @@ logger.addHandler(ch) a = EvinceWindowProxy('file://' + path_output, True,logger=logger) + loop = GObject.MainLoop() + def sync_view(ev_window, path_input, line_number): ev_window.SyncView (path_input, (line_number, 1),0) + loop.quit() GObject.timeout_add(400, sync_view, a, path_input, line_number) - loop = GObject.MainLoop() loop.run() # ex:ts=4:et:
I just installed the gedit-plugins Arch Linux package and made the above small change, but you could also download the evince_dbus.py file directly from git, it has no other dependencies on other files within gedit-plugins.
To wire up Vim, I added the following to tex ftplugin script (located for me at ~/academic/tools/ftplugin/tex.vim).
function! SyncTex() let filename = bufname("%") let lineno = line(".") for syncfile in split(system('zgrep -l "' . filename . '" *.synctex.gz'), "\n") let pdffile = substitute(syncfile, ".synctex.gz$", ".pdf", "") exec 'silent ! python /usr/lib/gedit/plugins/synctex/evince_dbus.py ' . \ '"' . pdffile . '" ' . lineno . ' "' . filename . '"' endfor endfunction map <buffer> <LocalLeader>e :call SyncTex()<CR>
Almost all my papers are made up of multiple tex files which I \input into one main file which is then compiled using rubber and make, so I needed a way to find the pdf given the currently open file. The <filename>.synctex.gz contains all the filenames that went into building the corresponding pdf, so I use zgrep to find the synctex.gz file containing the current file. I even have one project where the same source file is imported into two different compilations (the full paper and an executive summary), so the vimscript above will open both pdfs because it loops through all the lines returned by zgrep.
I do not use backward search (clicking in evince to change vim to the cooresponding line) because CtrlP on tags (which the makefile generates from LaTeX labels) plus normal Vim movement commands like forward/back paragraph is very fast to navigate anywhere in my input, even for my 50+ page papers where the input is spread over 12-15 files. Therefore I keep my fingers on the keyboard and use Vim forward search to scroll evince. If I want to scroll evince down or move evince to a different section, I use vim movement commands like page down, paragraph down, and/or CtrlP, and then \e to update the evince position. This way I keep my fingers on the keyboard and moving around large pdfs is very fast, faster even than scrolling with the mouse. Also focus never leaves the vim window so at any time I can just type a vim command. It feels a little strange at first to scroll evince by moving in Vim, but I quickly got the hang of it. Having said that, evince does support backward search and the evince_dbus.py script has some code for it which could be the basis for a vim plugin, perhaps importing evince_dbus.py from python vimscript.