Klimi's new dotfiles with stow.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

781 行
28 KiB

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