dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

pdf-sync.el (28790B)


      1 ;;; pdf-sync.el --- Use synctex to correlate LaTeX-Sources with PDF positions. -*- lexical-binding:t -*-
      2 ;; Copyright (C) 2013, 2014  Andreas Politz
      3 
      4 ;; Author: Andreas Politz <politza@fh-trier.de>
      5 ;; Keywords: files, doc-view, pdf
      6 
      7 ;; This program is free software; you can redistribute it and/or modify
      8 ;; it under the terms of the GNU General Public License as published by
      9 ;; the Free Software Foundation, either version 3 of the License, or
     10 ;; (at your option) any later version.
     11 
     12 ;; This program is distributed in the hope that it will be useful,
     13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 ;; GNU General Public License for more details.
     16 
     17 ;; You should have received a copy of the GNU General Public License
     18 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     19 
     20 ;;; Commentary:
     21 ;;
     22 ;; The backward search uses a heuristic, which is pretty simple, but
     23 ;; effective: It extracts the text around the click-position in the
     24 ;; PDF, normalizes it's whitespace, deletes certain notorious
     25 ;; character and translates certain other character into their latex
     26 ;; equivalents.  This transformed text is split into a series of
     27 ;; token.  A similar operation is performed on the source code around
     28 ;; the position synctex points at.  These two sequences of token are
     29 ;; aligned with a standard sequence alignment algorithm, resulting in
     30 ;; an alist of matched and unmatched tokens.  This is then used to
     31 ;; find the corresponding word from the PDF file in the LaTeX buffer.
     32 
     33 
     34 (require 'pdf-view)
     35 (require 'pdf-info)
     36 (require 'pdf-util)
     37 (require 'let-alist)
     38 
     39 ;;; Code:
     40 
     41 (defgroup pdf-sync nil
     42   "Jump from TeX sources to PDF pages and back."
     43   :group 'pdf-tools)
     44 
     45 (defcustom pdf-sync-forward-display-pdf-key "C-c C-g"
     46   "Key to jump from a TeX buffer to it's PDF file.
     47 
     48 This key is added to `TeX-source-correlate-method', when
     49 command `pdf-sync-minor-mode' is activated and this map is defined."
     50   :group 'pdf-sync
     51   :type 'key-sequence)
     52 
     53 (make-obsolete-variable
     54  'pdf-sync-forward-display-pdf-key
     55  "Bound in Auctex's to C-c C-v, if TeX-source-correlate-mode is activate." "1.0")
     56 
     57 (defcustom pdf-sync-backward-hook nil
     58   "Hook ran after going to a source location.
     59 
     60 The hook is run in the TeX buffer."
     61   :group 'pdf-sync
     62   :type 'hook
     63   :options '(pdf-sync-backward-beginning-of-word))
     64 
     65 (defcustom pdf-sync-forward-hook nil
     66   "Hook ran after displaying the PDF buffer.
     67 
     68 The hook is run in the PDF's buffer."
     69   :group 'pdf-sync
     70   :type 'hook)
     71 
     72 (defcustom pdf-sync-forward-display-action nil
     73   "Display action used when displaying PDF buffers."
     74   :group 'pdf-sync
     75   :type 'display-buffer--action-custom-type)
     76 
     77 (defcustom pdf-sync-backward-display-action nil
     78   "Display action used when displaying TeX buffers."
     79   :group 'pdf-sync
     80   :type 'display-buffer--action-custom-type)
     81 
     82 (defcustom pdf-sync-locate-synctex-file-functions nil
     83   "A list of functions for locating the synctex database.
     84 
     85 Each function on this hook should accept a single argument: The
     86 absolute path of a PDF file.  It should return the absolute path
     87 of the corresponding synctex database or nil, if it was unable to
     88 locate it."
     89   :group 'pdf-sync
     90   :type 'hook)
     91 
     92 (defvar pdf-sync-minor-mode-map
     93   (let ((kmap (make-sparse-keymap)))
     94     (define-key kmap [double-mouse-1] 'pdf-sync-backward-search-mouse)
     95     (define-key kmap [C-mouse-1] 'pdf-sync-backward-search-mouse)
     96     kmap))
     97 
     98 (defcustom pdf-sync-backward-redirect-functions nil
     99   "List of functions which may redirect a backward search.
    100 
    101 Functions on this hook should accept three arguments, namely
    102 SOURCE, LINE and COLUMN, where SOURCE is the absolute filename of
    103 the source file and LINE and COLUMN denote the position in the
    104 file.  COLUMN may be negative, meaning unspecified.
    105 
    106 These functions should either return nil, if no redirection is
    107 necessary.  Or a list of the same structure, with some or all (or
    108 none) values modified.
    109 
    110 AUCTeX installs a function here which changes the backward search
    111 location for synthetic `TeX-region' files back to the equivalent
    112 position in the original tex file."
    113   :group 'pdf-sync
    114   :type '(repeat function))
    115 
    116 
    117 ;;;###autoload
    118 (define-minor-mode pdf-sync-minor-mode
    119   "Correlate a PDF position with the TeX file.
    120 \\<pdf-sync-minor-mode-map>
    121 This works via SyncTeX, which means the TeX sources need to have
    122 been compiled with `--synctex=1'.  In AUCTeX this can be done by
    123 setting `TeX-source-correlate-method' to 'synctex \(before AUCTeX
    124 is loaded\) and enabling `TeX-source-correlate-mode'.
    125 
    126 Then \\[pdf-sync-backward-search-mouse] in the PDF buffer will open the
    127 corresponding TeX location.
    128 
    129 If AUCTeX is your preferred tex-mode, this library arranges to
    130 bind `pdf-sync-forward-display-pdf-key' \(the default is `C-c C-g'\)
    131 to `pdf-sync-forward-search' in `TeX-source-correlate-map'.  This
    132 function displays the PDF page corresponding to the current
    133 position in the TeX buffer.  This function only works together
    134 with AUCTeX."
    135   :group 'pdf-sync
    136   (pdf-util-assert-pdf-buffer))
    137 
    138 
    139 ;; * ================================================================== *
    140 ;; * Backward search (PDF -> TeX)
    141 ;; * ================================================================== *
    142 
    143 (defcustom pdf-sync-backward-use-heuristic t
    144   "Whether to apply a heuristic when backward searching.
    145 
    146 If nil, just go where Synctex tells us.  Otherwise try to find
    147 the exact location of the clicked-upon text in the PDF."
    148   :group 'pdf-sync
    149   :type 'boolean)
    150 
    151 (defcustom pdf-sync-backward-text-translations
    152   '((88 "X" "sum")
    153     (94 "textasciicircum")
    154     (126 "textasciitilde")
    155     (169 "copyright" "textcopyright")
    156     (172 "neg" "textlnot")
    157     (174 "textregistered" "textregistered")
    158     (176 "textdegree")
    159     (177 "pm" "textpm")
    160     (181 "upmu" "mu")
    161     (182 "mathparagraph" "textparagraph" "P" "textparagraph")
    162     (215 "times")
    163     (240 "eth" "dh")
    164     (915 "Upgamma" "Gamma")
    165     (920 "Uptheta" "Theta")
    166     (923 "Uplambda" "Lambda")
    167     (926 "Upxi" "Xi")
    168     (928 "Uppi" "Pi")
    169     (931 "Upsigma" "Sigma")
    170     (933 "Upupsilon" "Upsilon")
    171     (934 "Upphi" "Phi")
    172     (936 "Uppsi" "Psi")
    173     (945 "upalpha" "alpha")
    174     (946 "upbeta" "beta")
    175     (947 "upgamma" "gamma")
    176     (948 "updelta" "delta")
    177     (949 "upvarepsilon" "varepsilon")
    178     (950 "upzeta" "zeta")
    179     (951 "upeta" "eta")
    180     (952 "uptheta" "theta")
    181     (953 "upiota" "iota")
    182     (954 "upkappa" "varkappa" "kappa")
    183     (955 "uplambda" "lambda")
    184     (957 "upnu" "nu")
    185     (958 "upxi" "xi")
    186     (960 "uppi" "pi")
    187     (961 "upvarrho" "uprho" "rho")
    188     (962 "varsigma")
    189     (963 "upvarsigma" "upsigma" "sigma")
    190     (964 "uptau" "tau")
    191     (965 "upupsilon" "upsilon")
    192     (966 "upphi" "phi")
    193     (967 "upchi" "chi")
    194     (968 "uppsi" "psi")
    195     (969 "upomega" "omega")
    196     (977 "upvartheta" "vartheta")
    197     (981 "upvarphi" "varphi")
    198     (8224 "dagger")
    199     (8225 "ddagger")
    200     (8226 "bullet")
    201     (8486 "Upomega" "Omega")
    202     (8501 "aleph")
    203     (8592 "mapsfrom" "leftarrow")
    204     (8593 "uparrow")
    205     (8594 "to" "mapsto" "rightarrow")
    206     (8595 "downarrow")
    207     (8596 "leftrightarrow")
    208     (8656 "shortleftarrow" "Leftarrow")
    209     (8657 "Uparrow")
    210     (8658 "Mapsto" "rightrightarrows" "Rightarrow")
    211     (8659 "Downarrow")
    212     (8660 "Leftrightarrow")
    213     (8704 "forall")
    214     (8706 "partial")
    215     (8707 "exists")
    216     (8709 "varnothing" "emptyset")
    217     (8710 "Updelta" "Delta")
    218     (8711 "nabla")
    219     (8712 "in")
    220     (8722 "-")
    221     (8725 "setminus")
    222     (8727 "*")
    223     (8734 "infty")
    224     (8743 "wedge")
    225     (8744 "vee")
    226     (8745 "cap")
    227     (8746 "cup")
    228     (8756 "therefore")
    229     (8757 "because")
    230     (8764 "thicksim" "sim")
    231     (8776 "thickapprox" "approx")
    232     (8801 "equiv")
    233     (8804 "leq")
    234     (8805 "geq")
    235     (8810 "lll")
    236     (8811 "ggg")
    237     (8814 "nless")
    238     (8815 "ngtr")
    239     (8822 "lessgtr")
    240     (8823 "gtrless")
    241     (8826 "prec")
    242     (8832 "nprec")
    243     (8834 "subset")
    244     (8835 "supset")
    245     (8838 "subseteq")
    246     (8839 "supseteq")
    247     (8853 "oplus")
    248     (8855 "otimes")
    249     (8869 "bot" "perp")
    250     (9702 "circ")
    251     (9792 "female" "venus")
    252     (9793 "earth")
    253     (9794 "male" "mars")
    254     (9824 "spadesuit")
    255     (9827 "clubsuit")
    256     (9829 "heartsuit")
    257     (9830 "diamondsuit"))
    258   "Alist mapping PDF character to a list of LaTeX macro names.
    259 
    260 Adding a character here with it's LaTeX equivalent names allows
    261 the heuristic backward search to find it's location in the source
    262 file.  These strings should not match
    263 `pdf-sync-backward-source-flush-regexp'.
    264 
    265 Has no effect if `pdf-sync-backward-use-heuristic' is nil."
    266   :group 'pdf-sync
    267   :type '(alist :key-type character
    268                 :value-type (repeat string)))
    269 
    270 (defconst pdf-sync-backward-text-flush-regexp
    271   "[][.ยท{}|\\]\\|\\C.\\|-\n+"
    272   "Regexp of ignored text when backward searching.")
    273 
    274 (defconst pdf-sync-backward-source-flush-regexp
    275   "\\(?:\\\\\\(?:begin\\|end\\|\\(?:eq\\)?ref\\|label\\|cite\\){[^}]*}\\)\\|[][\\&{}$_]"
    276   "Regexp of ignored source when backward searching.")
    277 
    278 (defconst pdf-sync-backward-context-limit 64
    279   "Number of character to include in the backward search.")
    280 
    281 (defun pdf-sync-backward-search-mouse (ev)
    282   "Go to the source corresponding to position at event EV."
    283   (interactive "@e")
    284   (let* ((posn (event-start ev))
    285          (image (posn-image posn))
    286          (xy (posn-object-x-y posn)))
    287     (unless image
    288       (error "Outside of image area"))
    289     (pdf-sync-backward-search (car xy) (cdr xy))))
    290 
    291 (defun pdf-sync-backward-search (x y)
    292   "Go to the source corresponding to image coordinates X, Y.
    293 
    294 Try to find the exact position, if
    295 `pdf-sync-backward-use-heuristic' is non-nil."
    296   (cl-destructuring-bind (source finder)
    297       (pdf-sync-backward-correlate x y)
    298     (pop-to-buffer (or (find-buffer-visiting source)
    299                        (find-file-noselect source))
    300                    pdf-sync-backward-display-action)
    301     (push-mark)
    302     (funcall finder)
    303     (run-hooks 'pdf-sync-backward-hook)))
    304 
    305 (defun pdf-sync-backward-correlate (x y)
    306   "Find the source corresponding to image coordinates X, Y.
    307 
    308 Returns a list \(SOURCE FINDER\), where SOURCE is the name of the
    309 TeX file and FINDER a function of zero arguments which, when
    310 called in the buffer of the aforementioned file, will try to move
    311 point to the correct position."
    312 
    313   (pdf-util-assert-pdf-window)
    314   (let ((size (pdf-view-image-size))
    315         (page (pdf-view-current-page)))
    316     (setq x (/ x (float (car size)))
    317           y (/ y (float (cdr size))))
    318     (let-alist (pdf-info-synctex-backward-search page x y)
    319       (let ((data (list (expand-file-name .filename)
    320                         .line .column)))
    321         (cl-destructuring-bind (source line column)
    322             (or (save-selected-window
    323                   (apply 'run-hook-with-args-until-success
    324                     'pdf-sync-backward-redirect-functions data))
    325                 data)
    326           (list source
    327                 (if (not pdf-sync-backward-use-heuristic)
    328                     (lambda nil
    329                       (pdf-util-goto-position line column))
    330                   (let ((context (pdf-sync-backward--get-text-context page x y)))
    331                     (lambda nil
    332                       (pdf-sync-backward--find-position line column context))))))))))
    333 
    334 (defun pdf-sync-backward--find-position (line column context)
    335   (pdf-util-goto-position line column)
    336   (cl-destructuring-bind (windex chindex words)
    337       context
    338     (let* ((swords (pdf-sync-backward--get-source-context
    339                     nil (* 6 pdf-sync-backward-context-limit)))
    340            (similarity-fn (lambda (text source)
    341                             (if (if (consp text)
    342                                     (member source text)
    343                                   (equal text source))
    344                                 1024 -1024)))
    345            (alignment
    346             (pdf-util-seq-alignment
    347              words swords similarity-fn 'infix)))
    348       (setq alignment (cl-remove-if-not 'car (cdr alignment)))
    349       (cl-assert (< windex (length alignment)))
    350 
    351       (let ((word (cdr (nth windex alignment))))
    352         (unless word
    353           (setq chindex 0
    354                 word (cdr (nth (1+ windex) alignment))))
    355         (unless word
    356           (setq word (cdr (nth (1- windex) alignment))
    357                 chindex (length word)))
    358         (when word
    359           (cl-assert (get-text-property 0 'position word) t)
    360           (goto-char (get-text-property 0 'position word))
    361           (forward-char chindex))))))
    362 
    363 (defun pdf-sync-backward--get-source-context (&optional position limit)
    364   (save-excursion
    365     (when position (goto-char position))
    366     (goto-char (line-beginning-position))
    367     (let* ((region
    368             (cond
    369              ((eq limit 'line)
    370               (cons (line-beginning-position)
    371                     (line-end-position)))
    372 
    373              ;; Synctex usually jumps to the end macro, in case it
    374              ;; does not understand the environment.
    375              ((and (fboundp 'LaTeX-find-matching-begin)
    376                    (looking-at " *\\\\\\(end\\){"))
    377               (cons (or (ignore-errors
    378                           (save-excursion
    379                             (LaTeX-find-matching-begin)
    380                             (forward-line 1)
    381                             (point)))
    382                         (point))
    383                     (point)))
    384              ((and (fboundp 'LaTeX-find-matching-end)
    385                    (looking-at " *\\\\\\(begin\\){"))
    386               (goto-char (line-end-position))
    387               (cons (point)
    388                     (or (ignore-errors
    389                           (save-excursion
    390                             (LaTeX-find-matching-end)
    391                             (forward-line 0)
    392                             (point)))
    393                         (point))))
    394              (t (cons (point) (point)))))
    395            (begin (car region))
    396            (end (cdr region)))
    397       (when (numberp limit)
    398         (let ((delta (- limit (- end begin))))
    399           (when (> delta 0)
    400             (setq begin (max (point-min)
    401                              (- begin (/ delta 2)))
    402                   end (min (point-max)
    403                            (+ end (/ delta 2)))))))
    404       (let ((string (buffer-substring-no-properties begin end)))
    405         (dotimes (i (length string))
    406           (put-text-property i (1+ i) 'position (+ begin i) string))
    407         (nth 2 (pdf-sync-backward--tokenize
    408                 (pdf-sync-backward--source-strip-comments string)
    409                 nil
    410                 pdf-sync-backward-source-flush-regexp))))))
    411 
    412 (defun pdf-sync-backward--source-strip-comments (string)
    413   "Strip all standard LaTeX comments from string."
    414   (with-temp-buffer
    415     (save-excursion (insert string))
    416     (while (re-search-forward
    417             "^\\(?:[^\\\n]\\|\\(?:\\\\\\\\\\)\\)*\\(%.*\\)" nil t)
    418       (delete-region (match-beginning 1) (match-end 1)))
    419     (buffer-string)))
    420 
    421 (defun pdf-sync-backward--get-text-context (page x y)
    422   (cl-destructuring-bind (&optional char edges)
    423       (car (pdf-info-charlayout page (cons x y)))
    424     (when edges
    425       (setq x (nth 0 edges)
    426             y (nth 1 edges)))
    427     (let* ((prefix (pdf-info-gettext page (list 0 0 x y)))
    428            (suffix (pdf-info-gettext page (list x y 1 1)))
    429            (need-suffix-space-p (memq char '(?\s ?\n)))
    430            ;; Figure out whether we missed a space by matching the
    431            ;; prefix's suffix with the line's prefix.  Due to the text
    432            ;; extraction in poppler, spaces are only inserted in
    433            ;; between words.  This test may fail, if prefix and line
    434            ;; do not overlap, which may happen in various cases, but
    435            ;; we don't care.
    436            (need-prefix-space-p
    437             (and (not need-suffix-space-p)
    438                  (memq
    439                   (ignore-errors
    440                     (aref (pdf-info-gettext page (list x y x y) 'line)
    441                           (- (length prefix)
    442                              (or (cl-position ?\n prefix :from-end t)
    443                                  -1)
    444                              1)))
    445                   '(?\s ?\n)))))
    446       (setq prefix
    447             (concat
    448              (substring
    449               prefix (max 0 (min (1- (length prefix))
    450                                  (- (length prefix)
    451                                     pdf-sync-backward-context-limit))))
    452              (if need-prefix-space-p " "))
    453             suffix
    454             (concat
    455              (if need-suffix-space-p " ")
    456              (substring
    457               suffix 0 (max 0 (min (1- (length suffix))
    458                                    pdf-sync-backward-context-limit)))))
    459       (pdf-sync-backward--tokenize
    460        prefix suffix
    461        pdf-sync-backward-text-flush-regexp
    462        pdf-sync-backward-text-translations))))
    463 
    464 (defun pdf-sync-backward--tokenize (prefix &optional suffix flush-re translation)
    465   (with-temp-buffer
    466     (when prefix (insert prefix))
    467     (let* ((center (copy-marker (point)))
    468            (case-fold-search nil))
    469       (when suffix (insert suffix))
    470       (goto-char 1)
    471       ;; Delete ignored text.
    472       (when flush-re
    473         (save-excursion
    474           (while (re-search-forward flush-re nil t)
    475             (replace-match " " t t))))
    476       ;; Normalize whitespace.
    477       (save-excursion
    478         (while (re-search-forward "[ \t\f\n]+" nil t)
    479           (replace-match " " t t)))
    480       ;; Split words and non-words
    481       (save-excursion
    482         (while (re-search-forward "[^ ]\\b\\|[^ [:alnum:]]" nil t)
    483           (insert-before-markers " ")))
    484       ;; Replace character
    485       (let ((translate
    486              (lambda (string)
    487                (or (and (= (length string) 1)
    488                         (cdr (assq (aref string 0)
    489                                    translation)))
    490                    string)))
    491             words
    492             (windex -1)
    493             (chindex 0))
    494         (skip-chars-forward " ")
    495         (while (and (not (eobp))
    496                     (<= (point) center))
    497           (cl-incf windex)
    498           (skip-chars-forward "^ ")
    499           (skip-chars-forward " "))
    500         (goto-char center)
    501         (when (eq ?\s (char-after))
    502           (skip-chars-backward " "))
    503         (setq chindex (- (skip-chars-backward "^ ")))
    504         (setq words (split-string (buffer-string)))
    505         (when translation
    506           (setq words (mapcar translate words)))
    507         (list windex chindex words)))))
    508 
    509 (defun pdf-sync-backward-beginning-of-word ()
    510   "Maybe move to the beginning of the word.
    511 
    512 Don't move if already at the beginning, or if not at a word
    513 character.
    514 
    515 This function is meant to be put on `pdf-sync-backward-hook', when
    516 word-level searching is desired."
    517   (interactive)
    518   (unless (or (looking-at "\\b\\w")
    519               (not (looking-back "\\w" (1- (point)))))
    520     (backward-word)))
    521 
    522 ;; * ------------------------------------------------------------------ *
    523 ;; * Debugging backward search
    524 ;; * ------------------------------------------------------------------ *
    525 
    526 (defvar pdf-sync-backward-debug-trace nil)
    527 
    528 (defun pdf-sync-backward-debug-wrapper (fn-symbol fn &rest args)
    529   (cond
    530    ((eq fn-symbol 'pdf-sync-backward-search)
    531     (setq pdf-sync-backward-debug-trace nil)
    532     (apply fn args))
    533    (t
    534     (let ((retval (apply fn args)))
    535       (push `(,args . ,retval)
    536             pdf-sync-backward-debug-trace)
    537       retval))))
    538 
    539 (define-minor-mode pdf-sync-backward-debug-minor-mode
    540   "Aid in debugging the backward search."
    541   :group 'pdf-sync
    542   (if (and (fboundp 'advice-add)
    543            (fboundp 'advice-remove))
    544       (let ((functions
    545              '(pdf-sync-backward-search
    546                pdf-sync-backward--tokenize
    547                pdf-util-seq-alignment)))
    548         (cond
    549          (pdf-sync-backward-debug-minor-mode
    550           (dolist (fn functions)
    551             (advice-add fn :around (apply-partially 'pdf-sync-backward-debug-wrapper
    552                                                     fn)
    553                         `((name . ,(format "%s-debug" fn))))))
    554          (t
    555           (dolist (fn functions)
    556             (advice-remove fn (format "%s-debug" fn))))))
    557     (error "Need Emacs version >= 24.4")))
    558 
    559 (defun pdf-sync-backward-debug-explain ()
    560   "Explain the last backward search.
    561 
    562 Needs to have `pdf-sync-backward-debug-minor-mode' enabled."
    563 
    564   (interactive)
    565   (unless pdf-sync-backward-debug-trace
    566     (error "No last search or `pdf-sync-backward-debug-minor-mode' not enabled."))
    567 
    568   (with-current-buffer (get-buffer-create "*pdf-sync-backward trace*")
    569     (cl-destructuring-bind (text source alignment &rest ignored)
    570         (reverse pdf-sync-backward-debug-trace)
    571       (let* ((fill-column 68)
    572              (sep (format "\n%s\n" (make-string fill-column ?-)))
    573              (highlight '(:background "chartreuse" :foreground "black"))
    574              (or-sep "|")
    575              (inhibit-read-only t)
    576              (windex (nth 0 (cdr text)))
    577              (chindex (nth 1 (cdr text))))
    578         (erase-buffer)
    579         (font-lock-mode -1)
    580         (view-mode 1)
    581         (insert (propertize "Text Raw:" 'face 'font-lock-keyword-face))
    582         (insert sep)
    583         (insert (nth 0 (car text)))
    584         (insert (propertize "<|>" 'face highlight))
    585         (insert (nth 1 (car text)))
    586         (insert sep)
    587         (insert (propertize "Text Token:" 'face 'font-lock-keyword-face))
    588         (insert sep)
    589         (fill-region (point)
    590                      (progn
    591                        (insert
    592                         (mapconcat (lambda (elt)
    593                                      (if (consp elt)
    594                                          (mapconcat 'identity elt or-sep)
    595                                        elt))
    596                                    (nth 2 (cdr text)) " "))
    597                        (point)))
    598         (insert sep)
    599 
    600         (insert (propertize "Source Raw:" 'face 'font-lock-keyword-face))
    601         (insert sep)
    602         (insert (nth 0 (car source)))
    603         (insert sep)
    604         (insert (propertize "Source Token:" 'face 'font-lock-keyword-face))
    605         (insert sep)
    606         (fill-region (point)
    607                      (progn (insert (mapconcat 'identity (nth 2 (cdr source)) " "))
    608                             (point)))
    609         (insert sep)
    610 
    611         (insert (propertize "Alignment:" 'face 'font-lock-keyword-face))
    612         (insert (format " (windex=%d, chindex=%d" windex chindex))
    613         (insert sep)
    614         (save-excursion (newline 2))
    615         (let ((column 0)
    616               (index 0))
    617           (dolist (a (cdr (cdr alignment)))
    618             (let* ((source (cdr a))
    619                    (text (if (consp (car a))
    620                              (mapconcat 'identity (car a) or-sep)
    621                            (car a)))
    622                    (extend (max (length text)
    623                                 (length source))))
    624               (when (and (not (bolp))
    625                          (> (+ column extend)
    626                             fill-column))
    627                 (forward-line 2)
    628                 (newline 3)
    629                 (forward-line -2)
    630                 (setq column 0))
    631               (when text
    632                 (insert (propertize text 'face
    633                                     (if (= index windex)
    634                                         highlight
    635                                       (if source 'match
    636                                         'lazy-highlight)))))
    637               (move-to-column (+ column extend) t)
    638               (insert " ")
    639               (save-excursion
    640                 (forward-line)
    641                 (move-to-column column t)
    642                 (when source
    643                   (insert (propertize source 'face (if text
    644                                                        'match
    645                                                      'lazy-highlight))))
    646                 (move-to-column (+ column extend) t)
    647                 (insert " "))
    648               (cl-incf column (+ 1 extend))
    649               (when text (cl-incf index)))))
    650         (goto-char (point-max))
    651         (insert sep)
    652         (goto-char 1)
    653         (pop-to-buffer (current-buffer))))))
    654 
    655 
    656 ;; * ================================================================== *
    657 ;; * Forward search (TeX -> PDF)
    658 ;; * ================================================================== *
    659 
    660 (defun pdf-sync-forward-search (&optional line column)
    661   "Display the PDF location corresponding to LINE, COLUMN."
    662   (interactive)
    663   (cl-destructuring-bind (pdf page _x1 y1 _x2 _y2)
    664       (pdf-sync-forward-correlate line column)
    665     (let ((buffer (or (find-buffer-visiting pdf)
    666                       (find-file-noselect pdf))))
    667       (with-selected-window (display-buffer
    668                              buffer pdf-sync-forward-display-action)
    669         (pdf-util-assert-pdf-window)
    670         (when page
    671 	  (pdf-view-goto-page page)
    672 	  (when y1
    673 	    (let ((top (* y1 (cdr (pdf-view-image-size)))))
    674 	      (pdf-util-tooltip-arrow (round top))))))
    675       (with-current-buffer buffer
    676         (run-hooks 'pdf-sync-forward-hook)))))
    677 
    678 (defun pdf-sync-forward-correlate (&optional line column)
    679   "Find the PDF location corresponding to LINE, COLUMN.
    680 
    681 Returns a list \(PDF PAGE X1 Y1 X2 Y2\), where PAGE, X1, Y1, X2
    682 and Y2 may be nil, if the destination could not be found."
    683   (unless (fboundp 'TeX-master-file)
    684     (error "This function works only with AUCTeX"))
    685   (unless line (setq line (line-number-at-pos)))
    686   (unless column (setq column (current-column)))
    687 
    688   (let* ((pdf (expand-file-name
    689                (with-no-warnings (TeX-master-file "pdf"))))
    690          (sfilename (pdf-sync-synctex-file-name
    691                      (buffer-file-name) pdf)))
    692     (cons pdf
    693 	  (condition-case error
    694 	      (let-alist (pdf-info-synctex-forward-search
    695 			  (or sfilename
    696 			      (buffer-file-name))
    697 			  line column pdf)
    698 		(cons .page .edges))
    699 	    (error
    700 	     (message "%s" (error-message-string error))
    701 	     (list nil nil nil nil nil))))))
    702 
    703 
    704 
    705 ;; * ================================================================== *
    706 ;; * Dealing with synctex files.
    707 ;; * ================================================================== *
    708 
    709 (defun pdf-sync-locate-synctex-file (pdffile)
    710   "Locate the synctex database corresponding to PDFFILE.
    711 
    712 Returns either the absolute path of the database or nil.
    713 
    714 See also `pdf-sync-locate-synctex-file-functions'."
    715   (cl-check-type pdffile string)
    716   (setq pdffile (expand-file-name pdffile))
    717   (or (run-hook-with-args-until-success
    718        'pdf-sync-locate-synctex-file-functions pdffile)
    719       (pdf-sync-locate-synctex-file-default pdffile)))
    720 
    721 (defun pdf-sync-locate-synctex-file-default (pdffile)
    722   "The default function for locating a synctex database for PDFFILE.
    723 
    724 See also `pdf-sync-locate-synctex-file'."
    725   (let ((default-directory
    726           (file-name-directory pdffile))
    727         (basename (file-name-sans-extension
    728                    (file-name-nondirectory pdffile))))
    729     (cl-labels ((file-if-exists-p (file)
    730                   (and (file-exists-p file)
    731                        file)))
    732       (or (file-if-exists-p
    733            (expand-file-name (concat basename ".synctex.gz")))
    734           (file-if-exists-p
    735            (expand-file-name (concat basename ".synctex")))
    736           ;; Some pdftex quote the basename.
    737           (file-if-exists-p
    738            (expand-file-name (concat "\"" basename "\"" ".synctex.gz")))
    739           (file-if-exists-p
    740            (expand-file-name (concat "\"" basename "\"" ".synctex")))))))
    741 
    742 (defun pdf-sync-synctex-file-name (filename pdffile)
    743   "Find SyncTeX filename corresponding to FILENAME in the context of PDFFILE.
    744 
    745 This function consults the synctex.gz database of PDFFILE and
    746 searches for a filename, which is `file-equal-p' to FILENAME.
    747 The first such filename is returned, or nil if none was found."
    748 
    749   (when (file-exists-p filename)
    750     (setq filename (expand-file-name filename))
    751     (let* ((synctex (pdf-sync-locate-synctex-file pdffile))
    752            (basename (file-name-nondirectory filename))
    753            (regexp (format "^ *Input *: *[^:\n]+ *:\\(.*%s\\)$"
    754                            (regexp-quote basename)))
    755            (jka-compr-verbose nil))
    756       (when (and synctex
    757                  (file-readable-p synctex))
    758         (with-current-buffer (find-file-noselect synctex :nowarn)
    759           (unless (or (verify-visited-file-modtime)
    760                       (buffer-modified-p))
    761             (revert-buffer :ignore-auto :noconfirm)
    762             (goto-char (point-min)))
    763           ;; Keep point in front of the found filename. It will
    764           ;; probably be queried for again next time.
    765           (let ((beg (point))
    766                 (end (point-max)))
    767             (catch 'found
    768               (dotimes (_x 2)
    769                 (while (re-search-forward regexp end t)
    770                   (let ((syncname (match-string-no-properties 1)))
    771                     (when (and (file-exists-p syncname)
    772                                (file-equal-p filename syncname))
    773                       (goto-char (point-at-bol))
    774                       (throw 'found syncname))))
    775                 (setq end beg
    776                       beg (point-min))
    777                 (goto-char beg)))))))))
    778 
    779 (provide 'pdf-sync)
    780 ;;; pdf-sync.el ends here