прямой поиск (forward): из позиции курсора в .tex подсветить
соответствующее место в zathura (Ctrl+Enter), не плодя лишних окон;
обратный поиск (backward): Ctrl+клик в zathura открывает нужную
строку именно в той инстанции MacVim, где файл уже открыт. Если файл не
открыт нигде — открывается новая инстанция, буферы остальных не трогаются.
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-агентом
org.freedesktop.dbus-session (plist поставляется с brew-пакетом dbus,
который ставится как зависимость zathura). Launchd создаёт сокет сам и
публикует его путь в переменной DBUS_LAUNCHD_SESSION_BUS_SOCKET для всего
GUI-сеанса; демон стартует при первом обращении. Vim перед запуском zathura
лишь узнаёт этот сокет через launchctl и прокидывает его в своё окружение —
так все процессы zathura (и запущенные из vim, и открытые вручную) оказываются
на одной шине.
В ранней версии этой записи vim поднимал собственную шину на фиксированном
сокете. От этого я отказался: окна zathura, открытые не из vim, жили на другой
шине (или вовсе без неё), forward их не видел и открывал дубли.
Ещё две мелочи, из-за которых плодятся лишние окна:
zathura узнаёт «этот PDF уже открыт» по точному совпадению строки пути,
поэтому все пути надо канонизировать через realpath;
обратный поиск при нескольких запущенных MacVim должен попасть в ту
инстанцию, где файл действительно открыт, — стандартный
mvim --remote-silent шлёт всё в один сервер VIM.
#!/bin/zsh # Обратный synctex zathura -> MacVim. # Ищет среди запущенных инстанций MacVim ту, где уже открыт файл, и переключается # на неё (с переходом на строку). Если файл не открыт нигде — открывает НОВУЮ # инстанцию (окно) MacVim, не трогая буферы существующих. # # Вызов из zathurarc: # set synctex-editor-command "/Users/slisakov/.local/bin/mvim-synctex %{line} %{input}"
# 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
" --- zathura synctex (macOS) --------------------------------------------- " forward-search zathura ищет открытое окно через D-Bus session bus. В macOS " эту шину поднимает launchd-агент org.freedesktop.dbus-session (общий для ВСЕХ " приложений: Finder, терминал, vim). Здесь только указываем vim'у на её сокет, " чтобы запущенная из vim zathura была на той же шине, что и открытая вручную, " — иначе forward не найдёт окно и откроет новое. function!s:EnsureSyncBus() abort letl:sock = trim(system('launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET')) ifempty(l:sock) letl:uid = trim(system('id -u')) letl:cmd = 'launchctl print gui/' . l:uid \ . " | awk '/DBUS_LAUNCHD_SESSION_BUS_SOCKET =>/ {print $3; exit}'" letl:sock = trim(system(l:cmd)) endif ifempty(l:sock) echoerr'SyncTeX: D-Bus session socket not found; refusing to open a duplicate PDF' return0 endif let $DBUS_LAUNCHD_SESSION_BUS_SOCKET = l:sock let $DBUS_SESSION_BUS_ADDRESS = 'unix:path=' . l:sock return1 endfunction
" Канонический путь к текущему .tex (realpath: единый регистр/симлинки). " Важно: open и forward должны давать zathura ОДНУ И ТУ ЖЕ строку пути к PDF, " иначе zathura (matching чувствителен к строке) не узнаёт уже открытый файл " и открывает новое окно. function!s:CanonTex() abort letl:t = trim(system('realpath ' . shellescape(expand('%:p')))) return (v:shell_error == 0 && !empty(l:t)) ? l:t : expand('%:p') endfunction
" Forward search: позиция курсора в vim -> подсветка в zathura function!SyncTexForward() abort if !s:EnsureSyncBus() return endif letl:tex = s:CanonTex() letl:pos = line('.') . ':' . col('.') . ':' . l:tex letl:pdf = fnamemodify(l:tex, ':r') . '.pdf' letl:service = s:ZathuraServiceFor(l:pdf) if !empty(l:service) " Обращаемся прямо к открытому окну: новый процесс Zathura не запускается. letl:args = ['/opt/homebrew/bin/gdbus', 'call', '--session', \ '--dest', l:service, \ '--object-path', '/org/pwmt/zathura', \ '--method', 'org.pwmt.zathura.SynctexView', \ l:tex, string(line('.')), string(col('.'))] " Не блокируем Vim, пока Zathura обрабатывает переход и подсветку. call job_start(l:args, {'stoponexit': ''}) return endif " Нужный PDF действительно не открыт — только тогда создаём окно. call job_start(['/opt/homebrew/bin/zathura', '--synctex-forward', \ l:pos, l:pdf], {'stoponexit': ''}) endfunction
" Компиляция, открытие превью, forward map<F11><ESC>: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>
Несколько пояснений, почему именно так:
job_start, а не :!...&. В MacVim :! синхронный (блокирует ввод до
закрытия программы), а фоновый & из GUI-MacVim часто гибнет от SIGHUP, когда
завершается породивший его временный шелл. job_start запускает zathura как
настоящий асинхронный процесс — окно появляется, управление возвращается
мгновенно. Опция {'stoponexit': ''} не даёт vim убить zathura при своём
закрытии.
Прямой вызов SynctexView через gdbus. Если окно с нужным PDF уже
есть, vim обращается к нему по D-Bus напрямую, без запуска промежуточного
процесса zathura --synctex-forward — быстрее и без риска дублей.
realpath везде. SyncTeX сопоставляет файлы по каноническому пути, а
zathura узнаёт уже открытый PDF по точному совпадению строки: одно и то же
имя, написанное по-разному (симлинк, другой регистр, относительный путь),
открыло бы второе окно.
Поиск инстанции в mvim-synctex — по getbufinfo(), а не по имени
файла. Сравнение только каноническим полным путём: одноимённый файл в
другой папке дал бы ложное срабатывание, и :drop заменил бы вкладку в
чужой инстанции. Попутно обходится глюк MacVim: --remote-expr печатает
результат в stderr вперемешку с «Send expression failed.», поэтому скрипт
читает 2>&1 и проверяет только первый символ ответа.
<C-CR> (а не <C-enter>) — так клавишу Ctrl+Enter надёжнее распознаёт
MacVim в GUI.
F11 — скомпилировать с -synctex=1 (для надёжных ссылок прогнать дважды);
под xelatex у меня такая же привязка на F12.
Alt+8 — открыть превью в zathura (под общей D-Bus-шиной).
Ctrl+Enter на нужной строке — zathura подсвечивает соответствующее место
(forward). Если pdf не был открыт, то он откроется и по Ctrl+Enter.
Ctrl+левый клик в zathura — переход на нужную строку в той инстанции
MacVim, где файл открыт; если он не открыт нигде — откроется новая
инстанция (backward).
Окна zathura, открытые до загрузки launchd-агента (или до внесения правок),
к шине не подключены — перезапустите их, иначе forward-поиск их не увидит.