From: Aaron Ecay <aaronecay@gmail.com>
To: emacs-orgmode@gnu.org
Subject: [RFC] [export] synctex support for ox-latex
Date: Mon, 28 Oct 2013 14:17:34 -0400 [thread overview]
Message-ID: <87fvrl1cqp.fsf@gmail.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 920 bytes --]
Hello,
Some months ago, I proposed a patch to implement synctex support for the
latex exporter. Following feedback from Nicolas, I was working on
converting that code to use hooks and advice, without modifying core
org-mode functions. It was a frustrating but very educational task, and
it is now complete. I attach the patch (which consists only of a single
file addition to the contrib directory) to this message.
It is very simple to use: load the file, and then M-x
ox-synctex-activate. Now every time you export latex to a pdf, the code
will patch the resultant synctex file so you can jump from a pdf viewer
to the (approximate) org source line that generated it. The hooks and
advice can all be removed with M-x ox-synctex-deactivate.
Aaron
PS Note that you must do the latex compilation within org (C-c C-e l p);
you can’t export the .tex file and compile it from the command line.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-add-synctex-support.patch --]
[-- Type: text/x-diff, Size: 9953 bytes --]
From 43d4bc0ee82874d4389dd7ead7fccd3c81c68418 Mon Sep 17 00:00:00 2001
From: Aaron Ecay <aaronecay@gmail.com>
Date: Wed, 23 Oct 2013 15:29:56 -0400
Subject: [PATCH] add synctex support
* contrib/lisp/ox-synctex.el: new file
---
contrib/lisp/ox-synctex.el | 252 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 252 insertions(+)
create mode 100644 contrib/lisp/ox-synctex.el
diff --git a/contrib/lisp/ox-synctex.el b/contrib/lisp/ox-synctex.el
new file mode 100644
index 0000000..6f04c14
--- /dev/null
+++ b/contrib/lisp/ox-synctex.el
@@ -0,0 +1,252 @@
+;;; ox-synctex.el --- Synctex functionality for org LaTeX export
+
+;; Copyright (C) 2013 Aaron Ecay
+
+;; Author: Aaron Ecay <aaronecay@gmail.com>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This code provides synctex support for org mode export to latex.
+;; To activate, execute (ox-synctex-activate). To deactivate,
+;; (ox-synctex-deactivate)
+
+;; TODOs:
+;; - support multi-file documents through #+include and friends
+;; - do something so that clicks on a minted source code block go to
+;; the .org file and not the .pyg intermediate
+;; - ...
+
+;;; Code:
+
+;;;; Internal functions and variable
+
+(defvar ox-synctex--concordance nil
+ "The concordance resulting from the last export operation.")
+
+(defun ox-synctex--read-concordance (concordance src-line)
+ "Get the output line number from CONCORDANCE for input line SRC-LINE."
+ ;; TODO: not robust against malformed concordances
+ (while (and (caadr concordance)
+ (<= (caadr concordance) src-line))
+ (setq concordance (cdr concordance)))
+ (cdar concordance))
+
+(defun ox-synctex--propertize-buffer ()
+ "Put line-number text properties on a buffer.
+
+Each line gets a org-line-num-pre property, which is its line
+number in the buffer. When export operations change the buffer,
+the text property will still reflect the original state of affairs."
+ (save-restriction
+ (widen)
+ (while (= 0 (forward-line 1))
+ (put-text-property (point) (point-at-eol)
+ 'ox-synctex-line-num
+ (line-number-at-pos)))))
+
+(defun ox-synctex--line-number-at-pos (pos)
+ "Return the buffer line number at POS, widening if necessary.
+
+This function first looks for text properties set by
+`ox-synctex--propertize-buffer' which allow it to return an
+accurate line number in a buffer copy modified during export. It
+falls back to the usual method of calculating line numbers if no
+text properties are found."
+ (or (get-text-property pos 'ox-synctex-line-num)
+ (save-excursion
+ (widen)
+ (line-number-at-pos pos))))
+
+(defun ox-synctex--add-line-to-element (element)
+ "Add begin and end line numbers to an element as returned by `org-element'."
+ (let* ((plist (cadr element))
+ (beg (plist-get plist :begin))
+ (end (plist-get plist :end)))
+ (and beg (plist-put plist :begin-line (ox-synctex--line-number-at-pos beg)))
+ (and end (plist-put plist :end-line (ox-synctex--line-number-at-pos end)))
+ element))
+
+
+(defun ox-synctex--propertize-string (data string)
+ "Add line number text properties to STRING, based on DATA.
+
+The function works by copying the properties added by
+`ox-synctex--add-line-to-element' to the string. This will allow
+the construction of a concordance from the exported string."
+ (let ((len (length string)))
+ (when (> len 1)
+ (put-text-property 0 1 'org-line-num
+ (org-element-property :begin-line data)
+ string)
+ (put-text-property (1- len) len 'org-line-num
+ (org-element-property :end-line data)
+ string)))
+ string)
+
+(defun ox-synctex--build-concordance ()
+ "Build a concordance, based on text properties added by
+`ox-synctex--propertize-string' and accumulated in in an export
+result buffer.
+
+Has the form ((OUT-LINE . IN-LINE) ...)"
+ (save-excursion
+ (let ((res '())
+ next)
+ (goto-char (point-min))
+ (while (setq next (next-single-property-change (point) 'org-line-num))
+ (goto-char next)
+ (let ((ln (get-text-property (point) 'org-line-num)))
+ ;; TODO: `ln' should never be nil, but sometimes it is. For
+ ;; now, we hack around that with this `when'.
+ (when ln
+ (setq res (cons (cons (line-number-at-pos) ln)
+ res))))
+ (forward-char 1))
+ (setq res (nreverse res))
+ (setq next res)
+ (while (cdr next)
+ (if (equal (caar next) (caadr next))
+ (setcdr next (cddr next))
+ (setq next (cdr next))))
+ res)))
+
+(defun ox-synctex--patch-synctex (file)
+ "Patch the synctex file resulting from the last export
+operation, using the information stored in
+`ox-synctex--concordance'."
+ (let* ((file-base (file-name-nondirectory
+ (replace-regexp-in-string "\\.tex\\'" "." file)))
+ (synctex-file (concat file-base "synctex.gz")))
+ (cond
+ ((not ox-synctex--concordance)
+ (message "No concordance, not patching."))
+ ((not (file-exists-p synctex-file))
+ (message "No synctex file found, not patching."))
+ (t
+ (let* ((conc ox-synctex--concordance)
+ (buf (find-file-noselect synctex-file)))
+ (with-current-buffer buf
+ (let ((max-index 0)
+ the-index extra-path new-index)
+ (goto-char (point-min))
+ (while (re-search-forward "^Input:\\([0-9]+\\):" nil t)
+ (setq max-index (max max-index (string-to-number (match-string 1)))))
+ (setq new-index (number-to-string (1+ max-index)))
+ (goto-char (point-min))
+ (when (re-search-forward (concat "^Input:\\([0-9]+\\):\\(.*\\)"
+ (regexp-quote file-base) "tex$")
+ nil t)
+ (setq the-index (string-to-number (match-string 1)))
+ (setq extra-path (match-string 2))
+ (goto-char (line-end-position))
+ (insert (format "\nInput:%s:%s%sorg" new-index extra-path file-base)))
+ (goto-char (point-min))
+ (while (re-search-forward (format "^[vhxkgr$[()]\\(%s\\),\\([0-9]+\\):"
+ the-index)
+ nil t)
+ (let ((new-line (ox-synctex--read-concordance
+ ox-synctex--concordance
+ (string-to-number (match-string 2)))))
+ (when new-line
+ (replace-match new-index nil t nil 1)
+ (replace-match (int-to-string new-line)
+ nil t nil 2))))
+ (save-buffer)))
+ (kill-buffer buf))))))
+
+;;;; Hooks and advice
+
+(defun ox-synctex--before-processing-hook (&rest ignore)
+ (ox-synctex--propertize-buffer))
+
+(defconst ox-synctex--parsers-to-patch
+ (append org-element-greater-elements org-element-all-elements))
+
+;;; Patch all `org-element' parsers to add line number info to their
+;;; return values.
+(dolist (parser ox-synctex--parsers-to-patch)
+ (let ((parser-fn (intern (format "org-element-%s-parser"
+ (symbol-name parser)))))
+ (eval `(defadvice ,parser-fn (around ox-synctex)
+ "Advice added by `ox-synctex'."
+ ad-do-it
+ (setq ad-return-value (ox-synctex--add-line-to-element
+ ad-return-value))))))
+
+;;; Patch element->string conversion to carry through the line numbers
+;;; added above
+(defadvice org-export-transcoder (around ox-synctex)
+ ad-do-it
+ (when (and ad-return-value
+ (org-export-derived-backend-p
+ (plist-get (ad-get-arg 1) :back-end)
+ 'latex))
+ (setq ad-return-value
+ `(lambda (data contents &optional info)
+ (ox-synctex--propertize-string
+ data
+ (if info
+ (,ad-return-value data contents info)
+ ;; The plain text transcoder takes only 2 arguments;
+ ;; here contents is really info. I couldn't find a
+ ;; better way to inspect the arity of elisp
+ ;; functions...?
+ (,ad-return-value data contents)))))))
+
+;;; Patch to build the concordance once we have the export result. We
+;;; need to hack around the fact that the original function strips
+;;; text properties from its return value which we need.
+(defadvice org-export-as (around ox-synctex)
+ (cl-letf (((symbol-function 'org-no-properties)
+ (lambda (s &optional _restricted) s)))
+ ad-do-it)
+ (when (org-export-derived-backend-p (ad-get-arg 0) 'latex)
+ (with-temp-buffer
+ (insert ad-return-value)
+ (setq ox-synctex--concordance (ox-synctex--build-concordance))))
+ (setq ad-return-value (org-no-properties ad-return-value)))
+
+;;; Actually do the patching after compilation
+(defadvice org-latex-compile (around ox-synctex)
+ ad-do-it
+ (ox-synctex--patch-synctex (ad-get-arg 0))
+ ;; Some PDF viewers (eg evince) don't notice changes to the synctex
+ ;; file, so we need to poke them to reload the pdf after we've
+ ;; finished changing it.
+ (call-process dired-touch-program nil nil nil
+ (replace-regexp-in-string "\\.tex\\'" "." (ad-get-arg 0))))
+
+;;;; User-facing functions
+
+(defun ox-synctex-activate ()
+ (interactive)
+ (add-hook 'org-export-before-processing-hook
+ #'ox-synctex--before-processing-hook)
+ (ad-activate-regexp "ox-synctex"))
+
+(defun ox-synctex-deactivate ()
+ (interactive)
+ (remove-hook 'org-export-before-processing-hook
+ #'ox-synctex--before-processing-hook)
+ (ad-deactivate-regexp "ox-synctex"))
+
+(provide 'ox-synctex)
+
+;; Local Variables:
+;; lexical-binding: t
+;; End:
+
+;;; ox-synctex.el ends here
--
1.8.4.1
[-- Attachment #3: Type: text/plain, Size: 15 bytes --]
--
Aaron Ecay
next reply other threads:[~2013-10-28 18:17 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-10-28 18:17 Aaron Ecay [this message]
2013-10-29 11:11 ` [RFC] [export] synctex support for ox-latex Rasmus
2013-10-30 4:59 ` Aaron Ecay
2013-10-30 13:14 ` Rasmus
2013-11-05 10:54 ` Bastien
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.orgmode.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87fvrl1cqp.fsf@gmail.com \
--to=aaronecay@gmail.com \
--cc=emacs-orgmode@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).