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