Synctex с обратным поиском для MacVim & zathura

29 июля 2022. Комментарии .

Запись обновлена в июле 2026.

Оглавление #

Цель #

Связать редактор и просмотрщик PDF в обе стороны:

Проблемы на macOS #

Forward-поиск завязан на D-Bus, которого в macOS нет. Чтобы zathura --synctex-forward нашёл уже открытое окно с нужным PDF, zathura общается с ним через freedesktop D-Bus session bus. В macOS такой шины нет, и попытка форварда падает:

1
2
error: Could not connect to session bus: Could not connect: Connection refused
error: Got no usable data from SyncTeX or D-Bus failed in some way.

Решение — session-шина как launchd-агент (идёт в комплекте homebrew-пакета dbus). Шина одна на весь логин-сеанс и общая для всех приложений: zathura, запущенная хоть из vim, хоть из терминала, видна форварду. Поднимать свой dbus-daemon из vim хуже: zathura, открытая не из vim, оказывается на другой шине, forward её не находит и будет дублировать окна.

Backward-поиск на MacVim. mvim --remote-silent шлёт файл в «дефолтный» сервер — то есть в первую попавшуюся инстанцию MacVim, где :drop молча заменяет активную вкладку чем-то посторонним. Значит, редактор-команда должна сама найти инстанцию, в которой файл открыт, а если такой нет — открыть новую. Это делает скрипт-обёртка (см. ниже).

Установка #

Установите zathura с поддержкой synctex на Mac OS.

1
2
3
4
5
6
7
brew tap zegervdv/zathura
brew install zathura --with-synctex
brew install zathura-pdf-poppler zathura-djvu zathura-ps
mkdir -p $(brew --prefix zathura)/lib/zathura
ln -s $(brew --prefix zathura-pdf-poppler)/libpdf-poppler.dylib $(brew --prefix zathura)/lib/zathura/libpdf-poppler.dylib
ln -s $(brew --prefix zathura-djvu)/libdjvu.dylib $(brew --prefix zathura)/lib/zathura/libdjvu.dylib
ln -s $(brew --prefix zathura-ps)/libps.dylib $(brew --prefix zathura)/lib/zathura/libps.dylib

Запустите D-Bus session-шину как launchd-агент (сам dbus уже стоит как зависимость zathura):

1
brew services start dbus

Это регистрирует агент org.freedesktop.dbus-session с socket-активацией: launchd сам создаёт сокет и публикует его путь в переменной DBUS_LAUNCHD_SESSION_BUS_SOCKET — откуда мы его и заберём в vimrc.

Также, разумеется, установите LaTeX и MacVim.

Реализация #

zathurarc #

~/.config/zathura/zathurarc:

~/.config/zathura/zathurarc
1
2
3
set dbus-service true
set synctex true
set synctex-editor-command "/Users/slisakov/.local/bin/mvim-synctex %{line} %{input}"

dbus-service true включает D-Bus-интерфейс (нужен для forward). Для backward вместо голого mvim вызывается скрипт-обёртка.

.vimrc #

~/.vimrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
" forward-search zathura ищет открытое окно через D-Bus session bus. В macOS
" эту шину поднимает launchd-агент org.freedesktop.dbus-session (общий для ВСЕХ
" приложений: Finder, терминал, vim). Здесь только указываем vim'у на её сокет,
" чтобы запущенная из vim zathura была на той же шине, что и открытая вручную,
" — иначе forward не найдёт окно и откроет новое.
function! s:EnsureSyncBus() abort
let l:sock = trim(system('launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET'))
if v:shell_error == 0 && !empty(l:sock)
let $DBUS_SESSION_BUS_ADDRESS = 'unix:path=' . l:sock
endif
endfunction

" Канонический путь к текущему .tex (realpath: единый регистр/симлинки).
" Важно: open и forward должны давать zathura ОДНУ И ТУ ЖЕ строку пути к PDF,
" иначе zathura (matching чувствителен к строке) не узнаёт уже открытый файл
" и открывает новое окно.
function! s:CanonTex() abort
let l:t = trim(system('realpath ' . shellescape(expand('%:p'))))
return (v:shell_error == 0 && !empty(l:t)) ? l:t : expand('%:p')
endfunction

" Открыть превью PDF (под нужной шиной, без дублей окон)
function! ZathuraOpen() abort
call s:EnsureSyncBus()
let l:pdf = fnamemodify(s:CanonTex(), ':r') . '.pdf'
if filereadable(l:pdf)
\ && system('pgrep -f ' . shellescape('zathura.*' . l:pdf)) !~# '\d'
call job_start(['/opt/homebrew/bin/zathura', l:pdf])
endif
endfunction

" Forward search: позиция курсора в vim -> подсветка в zathura
function! SyncTexForward() abort
call s:EnsureSyncBus()
let l:tex = s:CanonTex()
let l:pos = line('.') . ':' . col('.') . ':' . l:tex
call job_start(['/opt/homebrew/bin/zathura', '--synctex-forward',
\ l:pos, fnamemodify(l:tex, ':r') . '.pdf'])
endfunction

" Компиляция, открытие превью, forward
nnoremap <silent> <F11> :w<CR>:!pdflatex -synctex=1 '%'<CR><CR>
set macmeta " чтобы сработало Option+8
nnoremap <silent> <M-8> :call ZathuraOpen()<CR>
inoremap <silent> <M-8> <C-o>:call ZathuraOpen()<CR>
nnoremap <silent> <C-CR> :call SyncTexForward()<CR>
inoremap <silent> <C-CR> <C-o>:call SyncTexForward()<CR>

Несколько пояснений, почему именно так:

Скрипт обратного поиска #

~/.local/bin/mvim-synctex (не забудьте chmod +x):

~/.local/bin/mvim-synctex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/bin/zsh
# Обратный synctex zathura -> MacVim.
# Ищет среди запущенных инстанций MacVim ту, где уже открыт файл, и переключается
# на неё (с переходом на строку). Если файл не открыт нигде — открывает НОВУЮ
# инстанцию (окно) MacVim, не трогая буферы существующих.
#
# Вызов из zathurarc:
# set synctex-editor-command "/Users/slisakov/.local/bin/mvim-synctex %{line} %{input}"

emulate -L zsh
line="$1"
file="$2"
MVIM=/Applications/MacVim.app/Contents/bin/mvim

abs="$(realpath "$file" 2>/dev/null)" # канонический путь (регистр/симлинки)
[[ -z "$abs" ]] && abs="${file:a}"

# vim-выражение: открыт ли файл в этом сервере?
# Только канонический полный путь (resolve убирает симлинки). Сравнение по
# имени файла давало бы ложные срабатывания на одноимённых файлах в других
# папках — и тогда :drop заменял бы вкладку в чужой инстанции.
expr='!empty(filter(getbufinfo(), {i,b -> resolve(fnamemodify(b.name,":p"))==#"'"$abs"'"}))'

# Глюк MacVim: --remote-expr печатает результат в stderr, приклеивая
# "Send expression failed." (stdout пустой). Поэтому берём 2>&1 и смотрим,
# начинается ли ответ с "1".
target=""
for s in ${(f)"$("$MVIM" --serverlist 2>/dev/null)"}; do
[[ -z "$s" ]] && continue
res="$("$MVIM" --servername "$s" --remote-expr "$expr" 2>&1)"
if [[ "$res" == 1* ]]; then
target="$s"
break
fi
done

if [[ -n "$target" ]]; then
# файл уже открыт в этой инстанции: :drop перейдёт на его вкладку/окно
exec "$MVIM" --servername "$target" --remote-silent "+$line" "$abs"
else
# нигде не открыт: новая инстанция MacVim (без --remote ничего не шлётся
# в существующие серверы, значит их вкладки не затрагиваются)
exec "$MVIM" "+$line" "$abs"
fi

На какие грабли можно наступить без скрипта:

Порядок работы #

  1. Открыть .tex в MacVim (mvim file.tex).
  2. F11 — скомпилировать с -synctex=1 (для надёжных ссылок прогнать дважды).
  3. Alt+8 — открыть превью в zathura (под общей D-Bus-шиной).
  4. Ctrl+Enter на нужной строке — zathura подсвечивает соответствующее место (forward). Если pdf не был открыт, то он откроется и по Ctrl+Enter.
  5. Ctrl+левый клик в zathura — переход на нужную строку в той инстанции MacVim, где открыт этот .tex; если не открыт нигде — новая инстанция (backward).

Если zathura была запущена до старта launchd-агента (или до внесения правок), перезапустите её: окна, открытые вне общей шины, на forward-поиск не отзовутся.