├── README.org
├── orgdiff-screenshot.png
└── orgdiff.el
/README.org:
--------------------------------------------------------------------------------
1 | #+title: OrgDiff
2 | #+author: tecosaur
3 |
4 | #+html:
5 | #+html: 
6 |
7 | As we're all aware, one of the lovely things about working in plaintext is that
8 | you can run version control over your documents. =git diff= does a perfectly
9 | passable job at showing you how your Org files has changed, but what about when
10 | you want something richer? Well =latexdiff= is brilliant for LaTeX files, and you
11 | can export Org to LaTeX! However, it's a bit of a hassle, you need to
12 | 1. Check out an old revision of your repository
13 | 2. Export your Org file to LaTeX
14 | 3. Fast-forward to the newer version
15 | 4. Do /another/ Org export to LaTeX
16 | 5. Run =latexdiff= on the two =.tex= files
17 | 6. Compile to a PDF, and view it
18 |
19 | Gah! This is sounding like a bit of a pain. That's where *OrgDiff* comes in. It
20 | automates all of the above, and provides a few other quality of life
21 | improvements, namely:
22 | + Commit selection via the venerable Magit
23 | + Parallel exporting of the two Org files to TeX
24 | + Descriptive forms of the main =latexdiff= options, accessible via a Transient interface
25 | + Automatic TeX compiler-upgrading. I.e., if one document needs LuaLaTeX and the
26 | other needs XeLaTeX, then we need to use LuaLaTeX to compile the =latexdiff= result
27 |
28 | Along with that we provide some more basic options, like comparing any two Org
29 | files without git, just creating the =.tex= diff, or running ~ediff~ on the Org
30 | files.
31 |
32 | [[file:orgdiff-screenshot.png]]
33 |
34 | Do note that while this seems to work, it's quite early on in development, and
35 | it shows in the code (lack of documentation, etc.). It seems perfectly usable
36 | though 🙂.
37 |
--------------------------------------------------------------------------------
/orgdiff-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tecosaur/orgdiff/e5e2d6e57a5e996ae0fdffc4b55522d9ee6fb476/orgdiff-screenshot.png
--------------------------------------------------------------------------------
/orgdiff.el:
--------------------------------------------------------------------------------
1 | ;;; orgdiff.el --- Intelligently generate Org document diffs -*- lexical-binding: t; -*-
2 | ;;
3 | ;; Copyright (C) 2021 TEC
4 | ;;
5 | ;; Author: TEC
6 | ;; Maintainer: TEC
7 | ;; Created: June 24, 2021
8 | ;; Modified: June 24, 2021
9 | ;; Version: 0.0.1
10 | ;; Keywords: tools
11 | ;; Homepage: https://github.com/tec/orgdiff
12 | ;; Package-Requires: ((emacs "26.3") (transient "0.2.0") (magit "3.1.0"))
13 | ;;
14 | ;; This file is not part of GNU Emacs.
15 | ;;
16 | ;;; Commentary:
17 | ;;
18 | ;; Intelligently generate Org document diffs
19 | ;;
20 | ;;; Code:
21 |
22 | (eval-when-compile
23 | (require 'cl-lib)
24 | (require 'eieio))
25 |
26 | (require 'ox)
27 | (require 'ox-latex)
28 | (require 'transient)
29 | (require 'magit)
30 |
31 | ;; Customisations
32 |
33 | (defgroup orgdiff nil
34 | "Generating Org document diffs."
35 | :group 'org
36 | :prefix "orgdiff-")
37 |
38 | (defcustom orgdiff-latexdiff-postprocess-hooks nil
39 | "Post-processing functions which are run on the latexdiff result."
40 | :type 'hook)
41 |
42 | (defcustom orgdiff-latexdiff-executable "latexdiff"
43 | "Path to the latexdiff executable to use on the generated tex files."
44 | :type 'string)
45 |
46 | (defcustom orgdiff-latex-compiler-priorities
47 | '("pdflatex" "xelatex" "lualatex")
48 | "A list of compiler priorities, ascending.
49 | If two documents require different compilers, the higher priority
50 | compiler will be used."
51 | :type '(repeat string))
52 |
53 | (defcustom orgdiff-latex-compile-command
54 | (delq nil (list "latexmk"
55 | "-f"
56 | "-pdf"
57 | "-%compiler"
58 | (and (string-match-p "-shell-escape" (car org-latex-pdf-process))
59 | "-shell-escape")
60 | "-interaction=nonstopmode"
61 | "%texfile"))
62 | "Compile command as a list in the form (\"CMD\" \"ARGS\"...).
63 | \"%compiler\" is replaced with the requested compiler, and
64 | \"%texfile\" replaced with the path to the .tex file."
65 | :type '(repeat string))
66 |
67 | ;; Helper variables
68 |
69 | (defvar orgdiff--base-dir nil "The current directory for diffing files.")
70 | (defvar orgdiff--rev1 nil "The first revision.")
71 |
72 | (defvar orgdiff--export-processes nil)
73 | (defvar orgdiff--latexdiff-file-1 nil)
74 | (defvar orgdiff--latexdiff-file-2 nil)
75 | (defvar orgdiff--latexdiff-flatten nil)
76 |
77 | (defvar orgdiff--rev1dir nil)
78 | (defvar orgdiff--rev2dir nil)
79 |
80 | (defvar orgdiff--rev1file nil)
81 | (defvar orgdiff--rev2file nil)
82 | (defvar orgdiff--difffile nil)
83 |
84 | (defvar orgdiff--export-processes nil)
85 |
86 | ;; Transient setup
87 |
88 | (defclass orgdiff--transient-lisp-variable-formatted (transient-variable)
89 | ((reader :initform #'transient-lisp-variable--reader)
90 | (always-read :initform t)
91 | (set-value :initarg :set-value :initform #'set))
92 | "Class used for Lisp variables, modified version of `transient-lisp-variable'.")
93 |
94 | (cl-defmethod transient-init-value ((obj orgdiff--transient-lisp-variable-formatted))
95 | (oset obj value (symbol-value (oref obj variable))))
96 |
97 | (cl-defmethod transient-infix-set ((obj orgdiff--transient-lisp-variable-formatted) value)
98 | (funcall (oref obj set-value)
99 | (oref obj variable)
100 | (oset obj value value)))
101 |
102 | (cl-defmethod transient-format-description ((obj orgdiff--transient-lisp-variable-formatted))
103 | (or (oref obj description)
104 | (symbol-name (oref obj variable))))
105 |
106 | (cl-defmethod transient-format-value ((obj orgdiff--transient-lisp-variable-formatted))
107 | (let ((val (oref obj value)))
108 | (pcase val
109 | ('t (propertize "yes" 'face 'transient-argument))
110 | ('nil (propertize "no" 'face 'transient-unreachable))
111 | (_ (propertize (prin1-to-string val) 'face 'transient-value)))))
112 |
113 | (cl-defmethod transient-prompt ((obj orgdiff--transient-lisp-variable-formatted))
114 | (format "Set %s: " (oref obj variable)))
115 |
116 | (defun orgdiff--transient-lisp-variable-formatted--reader (prompt initial-input _history)
117 | (read--expression prompt initial-input))
118 |
119 | (eval-when-compile
120 | (defmacro orgdiff--define-infix (key name description type default
121 | &rest reader)
122 | "Define infix with KEY, NAME, DESCRIPTION, TYPE, DEFAULT and READER as arguments."
123 | (declare (indent 5) (doc-string 3))
124 | (let ((infix-var (intern (format "orgdiff-%s" name))))
125 | `(progn
126 | (defcustom ,infix-var ,default
127 | ,(concat description ".")
128 | :type ,type
129 | :group 'orgdiff)
130 | (transient-define-infix ,(intern (format "orgdiff--set-%s" name)) ()
131 | ,(format "Set `%s' from a popup buffer." infix-var)
132 | :class 'orgdiff--transient-lisp-variable-formatted
133 | :variable ',infix-var
134 | :key ,key
135 | :description ,description
136 | :argument ,(format "--%s" name)
137 | :reader (lambda (&rest _) ,@reader))))))
138 |
139 | (orgdiff--define-infix
140 | "-1" file-1 "First file"
141 | '(choice string boolean) nil
142 | (read-file-name "First file: " nil nil t nil
143 | (lambda (f)
144 | (or (equal (file-name-extension f) "org")
145 | (string-match-p "/$" f)))))
146 |
147 | (orgdiff--define-infix
148 | "-2" file-2 "Second file"
149 | '(choice string boolean) nil
150 | (read-file-name "Second file: " nil orgdiff-file-2 t nil
151 | (lambda (f)
152 | (or (and (equal (file-name-extension f) "org")
153 | (message f)
154 | (not (string= f orgdiff-file-1)))
155 | (string-match-p "/$" f)))))
156 |
157 | (orgdiff--define-infix
158 | "-r" git-revisions "Git revision"
159 | '(choice string boolean) nil
160 | (run-at-time
161 | nil nil
162 | (lambda ()
163 | (transient--suspend-override)
164 | (magit-log-select
165 | (lambda (r1)
166 | (let ((r1tag (magit-rev-name r1)))
167 | (when r1tag (setq r1tag (replace-regexp-in-string "tags/" "" r1tag)))
168 | (setq orgdiff--rev1 (or r1tag r1)))
169 | (magit-log-select
170 | (lambda (r2)
171 | (let ((r2tag (magit-rev-name r2)))
172 | (when r2tag (setq r2tag (replace-regexp-in-string "tags/" "" r2tag)))
173 | (setq r2 (or r2tag r2)))
174 | (setq orgdiff-git-revisions (substring-no-properties (concat orgdiff--rev1 ".." r2)))
175 | (transient--resume-override)
176 | (orgdiff--transient))
177 | (concat (propertize "Second revision (" 'face 'transient-heading)
178 | (propertize "newer" 'face 'transient-argument)
179 | (propertize "): " 'face 'transient-heading)
180 | "type %p to select commit at point, %q to use the current working state instead")
181 | (lambda ()
182 | (setq orgdiff-git-revisions (substring-no-properties orgdiff--rev1))
183 | (transient--resume-override)
184 | (orgdiff--transient))))
185 | (concat (propertize "First revision (" 'face 'transient-heading)
186 | (propertize "older" 'face 'transient-argument)
187 | (propertize "): " 'face 'transient-heading)
188 | "type %p to select commit at point, %q to not compare any revisions")
189 | (lambda ()
190 | (transient--resume-override)
191 | (orgdiff--transient)))))
192 | nil)
193 |
194 | ;;;###autoload
195 | (defun orgdiff ()
196 | "Generate a diff of two Org files."
197 | (interactive)
198 |
199 | (setq orgdiff--base-dir
200 | (with-temp-buffer
201 | (if (= 0 (call-process "git" nil t nil "rev-parse" "--show-toplevel"))
202 | (string-trim (buffer-string))
203 | default-directory)))
204 |
205 | (unless (and orgdiff-file-1 (string-prefix-p orgdiff--base-dir
206 | (expand-file-name orgdiff-file-1)))
207 | (setq orgdiff-file-1
208 | (if (and (stringp buffer-file-name) (string= ".org" (file-name-extension buffer-file-name)))
209 | buffer-file-name
210 | (car (directory-files orgdiff--base-dir t "\\.org$" nil 1)))))
211 |
212 | (when (and orgdiff-file-2
213 | (or (string= orgdiff-file-1 orgdiff-file-2)
214 | (not (string-prefix-p orgdiff--base-dir
215 | (expand-file-name orgdiff-file-2)))))
216 | (setq orgdiff-file-2 nil))
217 |
218 | (if (= 0 (call-process "git" nil nil nil "rev-parse" "--git-dir")) ; are we in a git dir?
219 | (if (memq major-mode '(magit-status-mode magit-log-mode))
220 | ;; If in a magit buffer, set git revisions from state
221 | (setq orgdiff-git-revisions
222 | (let* ((commits (magit-region-values '(commit branch) t))
223 | (range (if commits
224 | (concat (car (last commits)) ".." (car commits))
225 | (magit-branch-or-commit-at-point))))
226 | (and range (substring-no-properties range))))
227 | ;; Otherwise, just verify that the refs are valid
228 | (unless (or (null orgdiff-git-revisions)
229 | (cl-every #'magit-rev-verify-commit
230 | (split-string orgdiff-git-revisions "\\.\\.")))
231 | (setq orgdiff-git-revisions nil)))
232 | (setq orgdiff-git-revisions nil))
233 |
234 | (orgdiff--transient))
235 |
236 | (transient-define-prefix orgdiff--transient ()
237 | ["Files"
238 | (orgdiff--set-file-1)
239 | (orgdiff--set-file-2)]
240 | ["Git"
241 | :if (lambda () (= 0 (call-process "git" nil nil nil "rev-parse" "--git-dir")))
242 | (orgdiff--set-git-revisions)]
243 | ["Action"
244 | ("s" "swap files" orgdiff--swap-files)
245 | ("e" "ediff" orgdiff--ediff)
246 | ("l" "latexdiff" orgdiff--latexdiff)])
247 |
248 | (defun orgdiff--swap-files (&optional no-transient)
249 | (interactive)
250 | (let ((old1 orgdiff-file-1))
251 | (setq orgdiff-file-1 orgdiff-file-2
252 | orgdiff-file-2 old1))
253 | (unless no-transient
254 | (orgdiff--transient)))
255 |
256 | ;;; simple diff
257 |
258 | (defun orgdiff--ediff ()
259 | (interactive)
260 | (when (and orgdiff-file-2 (not orgdiff-file-1))
261 | (orgdiff--swap-files t))
262 | (orgdiff--extract-revisions)
263 | (ediff-files orgdiff--rev1file orgdiff--rev2file))
264 |
265 | ;;; latexdiff
266 |
267 | (defun orgdiff--latexdiff ()
268 | (interactive)
269 | (when orgdiff--export-processes
270 | (user-error "A latexdiff is currently in progress. Try `orgdiff--latexdiff-abort' if something seems wrong."))
271 | (when (and orgdiff-file-2 (not orgdiff-file-1))
272 | (orgdiff--swap-files t))
273 | (setq orgdiff--latexdiff-file-1
274 | (and orgdiff-file-1
275 | (concat (file-name-sans-extension orgdiff-file-1) ".tex"))
276 | orgdiff--latexdiff-file-2
277 | (and orgdiff-file-2
278 | (concat (file-name-sans-extension orgdiff-file-2) ".tex"))
279 | orgdiff--latexdiff-flatten
280 | (and (or orgdiff-git-revisions
281 | (not (and orgdiff-file-1 orgdiff-file-2
282 | (string= (file-name-directory orgdiff-file-1)
283 | (file-name-directory orgdiff-file-2)))))
284 | t))
285 | (orgdiff--latexdiff-transient))
286 |
287 | (defface orgdiff--latexdiff-red
288 | '((t :foreground "#cd3d3e"))
289 | "Used to preview latexdiff's red.")
290 |
291 | (defface orgdiff--latexdiff-blue
292 | '((t :foreground "#4078f2"))
293 | "Used to preview latexdiff's red.")
294 |
295 | (defvar orgdiff--latexdiff-flags
296 | `(("--type"
297 | (UNDERLINE .
298 | ,(concat (propertize "discarded" 'face '((:strike-through t) orgdiff--latexdiff-red))
299 | " " (propertize "added" 'face 'orgdiff--latexdiff-blue)
300 | " " (propertize "(default)" 'face 'shadow)))
301 | (CTRADITIONAL .
302 | ,(concat (propertize "[.."'face 'orgdiff--latexdiff-red)
303 | (propertize "1"'face '((:height 0.6) orgdiff--latexdiff-red))
304 | (propertize "]"'face 'orgdiff--latexdiff-red)
305 | " " (propertize "added" 'face '((:height 1.15) variable-pitch orgdiff--latexdiff-blue))))
306 | (TRADITIONAL .
307 | ,(concat "[.." (propertize "1" 'face '(:height 0.6)) "]"
308 | " " (propertize "added" 'face '((:height 1.15) variable-pitch))))
309 | (CFONT .
310 | ,(concat (propertize "discarded" 'face '((:height 0.7) orgdiff--latexdiff-red))
311 | " " (propertize "added" 'face '((:height 1.15) variable-pitch orgdiff--latexdiff-blue))))
312 | (FONTSTRIKE .
313 | ,(concat (propertize "discarded" 'face '((:strike-through t) small))
314 | " " (propertize "added" 'face '((:height 1.15) variable-pitch))))
315 | (CHANGEBAR . ,(propertize "no markup, change marks in margins" 'face 'font-lock-doc-face))
316 | (CCHANGEBAR .
317 | ,(concat (propertize "CHANGEBAR" 'face 'font-lock-function-name-face)
318 | (propertize " + " 'face 'font-lock-comment-face)
319 | (propertize "discarded" 'face 'orgdiff--latexdiff-red)
320 | " " (propertize "added" 'face 'orgdiff--latexdiff-blue)))
321 | (CFONTCHBAR .
322 | ,(concat (propertize "CHANGEBAR" 'face 'font-lock-function-name-face)
323 | (propertize " + " 'face 'font-lock-comment-face)
324 | (propertize "discarded" 'face '(small orgdiff--latexdiff-red))
325 | " " (propertize "added" 'face '((:height 1.15) variable-pitch orgdiff--latexdiff-blue))))
326 | (CULINECHBAR .
327 | ,(concat (propertize "CHANGEBAR" 'face 'font-lock-function-name-face)
328 | (propertize " + " 'face 'font-lock-comment-face)
329 | (propertize "discarded" 'face '((:strike-through t) orgdiff--latexdiff-red))
330 | " " (propertize "added" 'face 'orgdiff--latexdiff-blue)))
331 | (INVISIBLE . "added")
332 | (BOLD .
333 | ,(propertize "added" 'face 'bold))
334 | (PDFCOMMENT .
335 | ,(concat (propertize "discarded text in PDF comment, " 'face 'font-lock-doc-face) (propertize "added" 'face 'underline))))
336 | ("--subtype"
337 | (SAFE . ,(propertize "No additional markup (default, reccomended)" 'face 'font-lock-doc-face))
338 | (MARGIN . ,(propertize "Mark start and end of changed block with symbol in margin" 'face 'font-lock-doc-face))
339 | (COLOR .
340 | ,(concat (propertize "deleted passages" 'face 'orgdiff--latexdiff-red)
341 | " " (propertize "added passages" 'face 'orgdiff--latexdiff-blue)))
342 | (ZLABEL . ,(propertize "Highlight changed pages, requires post-processing" 'face 'font-lock-doc-face))
343 | (ONLYCHANGEDPAGE . ,(propertize "(Also) Highlights changed pages, no post-processing but dodgy floats" 'face 'font-lock-doc-face)))
344 | ("--floattype"
345 | (FLOATSAFE . ,(concat "Treat as main text, but skip begin/end commands" (propertize " (default)" 'face 'shadow)))
346 | (TRADITIONALSAFE . "Make deleted environments small and wrapped in brackets. Use with TRADITIONAL/CTRADITIONAL")
347 | (IDENTICAL . "Treat floats exactly the same as the main text"))
348 | ("--math-markup"
349 | (off . "Supress markup in math environments. Only show new version")
350 | (whole . "Any change causes the whole equation to be marked as changed")
351 | (coarse . ,(concat "Coarse granularity. Use when content and order being changed" (propertize " (default)" 'face 'shadow)))
352 | (fine . "Detect and mark up small changes. Suitable if minor changes (e.g. typo fixes) are expected"))
353 | ("--graphics-markup"
354 | (off . "No highlighting for figures")
355 | (new-only . ,(concat "Surround new/changed figures with a blue frame" (propertize " (default)" 'face 'shadow)))
356 | (both . "Surround new/changed figures with a blue frame, and shrink and cross out deleted figures")))
357 | "LaTeXdiff flags and recognised values they may take.")
358 |
359 | (defun orgdiff--latexdiff-prompt-flag (flag)
360 | (let* ((max-key-width
361 | (thread-last (cdr (assoc flag orgdiff--latexdiff-flags))
362 | (mapcar #'car)
363 | (mapcar #'symbol-name)
364 | (mapcar #'length)
365 | (seq-max)))
366 | (options
367 | (mapcar (lambda (opt)
368 | (concat (propertize (symbol-name (car opt)) 'face 'transient-value)
369 | (make-string (- max-key-width (length (symbol-name (car opt))) -2) ? )
370 | (cdr opt)))
371 | (cdr (assoc flag orgdiff--latexdiff-flags))))
372 | (selection
373 | (completing-read (concat flag ": ")
374 | options
375 | nil t)))
376 | (with-temp-buffer
377 | (insert selection)
378 | (goto-char (point-min))
379 | (intern (buffer-substring-no-properties
380 | (point-min)
381 | (cdr (bounds-of-thing-at-point 'word)))))))
382 |
383 | (orgdiff--define-infix
384 | "-A" latexdiff-async "Export Org to TeX asyncronously"
385 | 'boolean nil
386 | (not orgdiff-latexdiff-async))
387 | (orgdiff--define-infix
388 | "-F" latexdiff-flatten "Flatten includes"
389 | 'boolean nil
390 | (not orgdiff-latexdiff-flatten))
391 | (orgdiff--define-infix
392 | "-S" latexdiff-allow-spaces "Allow spaces"
393 | 'boolean nil
394 | (not orgdiff-latexdiff-allow-spaces))
395 |
396 | (orgdiff--define-infix
397 | "-d" latexdiff-skip-deleted "Omit all deleted content"
398 | 'boolean nil
399 | (not orgdiff-latexdiff-skip-deleted))
400 | (orgdiff--define-infix
401 | "-t" latexdiff-type "Type"
402 | 'symbol 'UNDERLINE
403 | (orgdiff--latexdiff-prompt-flag "--type"))
404 | (orgdiff--define-infix
405 | "-s" latexdiff-subtype "Sub-type"
406 | 'symbol 'SAFE
407 | (orgdiff--latexdiff-prompt-flag "--subtype"))
408 | (orgdiff--define-infix
409 | "-f" latexdiff-floattype "Float type"
410 | 'symbol 'FLOATSAFE
411 | (orgdiff--latexdiff-prompt-flag "--floattype"))
412 | (orgdiff--define-infix
413 | "-m" latexdiff-math-markup "Math markup"
414 | 'symbol 'coarse
415 | (orgdiff--latexdiff-prompt-flag "--math-markup"))
416 | (orgdiff--define-infix
417 | "-g" latexdiff-graphics-markup "Graphics markup"
418 | 'symbol 'new-only
419 | (orgdiff--latexdiff-prompt-flag "--graphics-markup"))
420 | (orgdiff--define-infix
421 | "-c" latexdiff-citation-markup "Citation markup"
422 | 'boolean t
423 | (not orgdiff-latexdiff-citation-markup))
424 |
425 | (transient-define-prefix orgdiff--latexdiff-transient ()
426 | ["Processing"
427 | (orgdiff--set-latexdiff-async)
428 | (orgdiff--set-latexdiff-flatten)
429 | (orgdiff--set-latexdiff-allow-spaces)
430 | ;; Filter script
431 | ;; Preamble file
432 | ;; Cleanup output # custom
433 | ]
434 | ["Style"
435 | (orgdiff--set-latexdiff-skip-deleted)
436 | (orgdiff--set-latexdiff-type)
437 | (orgdiff--set-latexdiff-subtype)
438 | (orgdiff--set-latexdiff-floattype)
439 | (orgdiff--set-latexdiff-graphics-markup)
440 | (orgdiff--set-latexdiff-math-markup)
441 | (orgdiff--set-latexdiff-citation-markup)
442 | ]
443 | ["Action"
444 | ("l" "run latexdiff, compile, and open PDF" orgdiff--latexdiff--action-pdf-open)
445 | ("L" "run latexdiff only" orgdiff--latexdiff--action-latex)
446 | ;; ("P" "PDF" orgdiff--latexdiff--action-pdf)
447 | ;; ("p" "run latexdiff, and compile" orgdiff--latexdiff--action-latex)
448 | ])
449 |
450 | (defvar orgdiff--latexdiff--completion-action nil)
451 |
452 | (defun orgdiff--latexdiff--action-latex ()
453 | (interactive)
454 | (setq orgdiff--latexdiff--completion-action '(:format latex :action nil))
455 | (orgdiff--latexdiff-execute-pt1))
456 |
457 | (defun orgdiff--latexdiff--action-latex-open ()
458 | (interactive)
459 | (setq orgdiff--latexdiff--completion-action '(:format latex :action open))
460 | (orgdiff--latexdiff-execute-pt1))
461 |
462 | (defun orgdiff--latexdiff--action-pdf ()
463 | (interactive)
464 | (setq orgdiff--latexdiff--completion-action '(:format pdf :action nil))
465 | (orgdiff--latexdiff-execute-pt1))
466 |
467 | (defun orgdiff--latexdiff--action-pdf-open ()
468 | (interactive)
469 | (setq orgdiff--latexdiff--completion-action '(:format pdf :action open))
470 | (orgdiff--latexdiff-execute-pt1))
471 |
472 | ;;; Actually building the diff
473 |
474 | (defun orgdiff--latexdiff-execute-pt1 ()
475 | (orgdiff--extract-revisions)
476 | (orgdiff--latex-create-from-org)
477 | (unless (executable-find orgdiff-latexdiff-executable)
478 | (user-error "Could not locate the latexdiff executable!"))
479 | (orgdiff--latexdiff-wait-for-export-then #'orgdiff--latexdiff-execute-pt2))
480 |
481 | (defun orgdiff--latexdiff-execute-pt2 ()
482 | (unless
483 | (and (file-exists-p (concat (file-name-sans-extension orgdiff--rev1file) ".tex"))
484 | (file-exists-p (concat (file-name-sans-extension orgdiff--rev2file) ".tex")))
485 | (user-error "Error! Org files were not sucessfully exported to LaTeX"))
486 | (orgdiff--latexdiff-expand)
487 | (orgdiff--latexdiff-do-diff)
488 | (pcase (plist-get orgdiff--latexdiff--completion-action :format)
489 | ('latex
490 | (let ((dest (concat (file-name-directory orgdiff-file-1)
491 | (file-name-nondirectory orgdiff--difffile))))
492 | (rename-file orgdiff--difffile dest t)
493 | (when (eq 'open (plist-get orgdiff--latexdiff--completion-action :action))
494 | (find-file-other-window dest))))
495 | ('pdf
496 | (orgdiff--latexdiff-compile)
497 | (orgdiff--latexdiff-wait-for-export-then #'orgdiff--latexdiff-handle-pdf))))
498 |
499 | (defun orgdiff--extract-revisions ()
500 | (setq orgdiff--rev1dir nil
501 | orgdiff--rev2dir nil)
502 | (when orgdiff-git-revisions
503 | (message "%s%s" (propertize "Orgdiff" 'face 'bold) ": Checking out revisions...")
504 | (let ((revisions (split-string orgdiff-git-revisions "\\.\\."))
505 | (default-directory (string-trim (shell-command-to-string "git rev-parse --show-toplevel"))))
506 | (setq orgdiff--rev1dir (make-temp-file "orgdiff-" t))
507 | ;; I could use some sort of in-emacs piping, but I'd rather not
508 | (call-process "sh" nil nil nil "-c" (format "git archive --format=tar %s | tar -xf - -C %s" (car revisions) (shell-quote-argument orgdiff--rev1dir)))
509 | (when (cdr revisions)
510 | (setq orgdiff--rev2dir (make-temp-file "orgdiff-" t))
511 | (call-process "sh" nil nil nil "-c" (format "git archive --format=tar %s | tar -xf - -C %s" (cadr revisions) (shell-quote-argument orgdiff--rev2dir))))))
512 | (setq orgdiff--rev1file
513 | (if orgdiff--rev1dir
514 | (expand-file-name (file-relative-name orgdiff-file-1 orgdiff--base-dir)
515 | orgdiff--rev1dir)
516 | orgdiff-file-1)
517 | orgdiff--rev2file
518 | (if orgdiff--rev2dir
519 | (expand-file-name (file-relative-name (or orgdiff-file-2 orgdiff-file-1) orgdiff--base-dir)
520 | orgdiff--rev2dir)
521 | (or orgdiff-file-2 orgdiff-file-1))))
522 |
523 | (defun orgdiff--latex-create-from-org ()
524 | (dolist (file (list orgdiff--rev1file orgdiff--rev2file))
525 | (with-temp-buffer
526 | (setq buffer-file-name file
527 | default-directory (file-name-directory file))
528 | (insert-file-contents file)
529 | (unless orgdiff-latexdiff-async
530 | (message "%s%s%s%s" (propertize "Orgdiff" 'face 'bold) ": Exporting " file " to TeX"))
531 | (let ((inhibit-message t))
532 | (org-mode)
533 | (org-latex-export-to-latex orgdiff-latexdiff-async)
534 | (when orgdiff-latexdiff-async
535 | (push (caddar org-export-stack-contents) orgdiff--export-processes))
536 | (set-buffer-modified-p nil))))
537 | (message "%s%s" (propertize "Orgdiff" 'face 'bold) ": Exporting Org files to TeX..."))
538 |
539 | (defun orgdiff--latexdiff-wait-for-export-then (then)
540 | (if orgdiff--export-processes
541 | (progn
542 | (setq orgdiff--export-processes
543 | (delq nil (mapcar
544 | (lambda (proc)
545 | (if (memq (process-status proc) '(exit signal failed)) nil proc))
546 | orgdiff--export-processes)))
547 | (run-at-time 0.5 nil #'orgdiff--latexdiff-wait-for-export-then then))
548 | (funcall then)))
549 |
550 | (defun orgdiff--latexdiff-abort ()
551 | (interactive)
552 | (when orgdiff--export-processes
553 | (ignore-errors (mapcar #'kill-process orgdiff--export-processes))
554 | (message "%s processes aborted" (length orgdiff--export-processes))
555 | (setq orgdiff--export-processes nil)
556 | (org-export-stack-clear)))
557 |
558 | (defun orgdiff--latexdiff-expand ()
559 | (when orgdiff--latexdiff-flatten
560 | (message "%s%s" (propertize "Orgdiff" 'face 'bold) ": Flattening tex files...")
561 | (call-process "latexpand" nil nil nil orgdiff--rev1file)
562 | (call-process "latexpand" nil nil nil orgdiff--rev2file)))
563 |
564 | (defun orgdiff--latexdiff-do-diff ()
565 | (message "%s%s" (propertize "Orgdiff" 'face 'bold) ": latexdiff-ing tex files...")
566 | (with-temp-buffer
567 | (apply #'call-process orgdiff-latexdiff-executable nil '(t nil) nil
568 | (delq nil (list
569 | "-t" (symbol-name orgdiff-latexdiff-type)
570 | "-s" (symbol-name orgdiff-latexdiff-subtype)
571 | "-f" (symbol-name orgdiff-latexdiff-floattype)
572 | (and orgdiff-latexdiff-allow-spaces "--allow-spaces")
573 | (and orgdiff-latexdiff-skip-deleted "--no-del")
574 | (if orgdiff-latexdiff-citation-markup
575 | "--enable-citation-markup" "--disable-citation-markup")
576 | (format "--math-markup=%s" orgdiff-latexdiff-math-markup)
577 | (format "--graphics-markup=%s" orgdiff-latexdiff-graphics-markup)
578 | (expand-file-name (concat (file-name-sans-extension orgdiff--rev1file) ".tex"))
579 | (expand-file-name (concat (file-name-sans-extension orgdiff--rev2file) ".tex")))))
580 | (setq orgdiff--difffile
581 | (concat
582 | (file-name-directory orgdiff--rev2file)
583 | (if (string= (file-name-nondirectory orgdiff--rev1file) (file-name-nondirectory orgdiff--rev2file))
584 | (file-name-base orgdiff--rev1file)
585 | (concat (file-name-base orgdiff--rev1file)
586 | "-diff-"
587 | (file-name-base orgdiff--rev2file)))
588 | (when orgdiff-git-revisions
589 | (concat "-"
590 | (car (split-string orgdiff-git-revisions "\\.\\."))
591 | ":"
592 | (or (cadr (split-string orgdiff-git-revisions "\\.\\."))
593 | "current")))
594 | ".tex"))
595 | (run-hooks 'orgdiff-latexdiff-postprocess-hooks)
596 | (let (before-save-hook after-save-hook write-file-functions)
597 | (setq buffer-file-name orgdiff--difffile)
598 | (save-buffer 0))))
599 |
600 | (defun orgdiff--latexdiff-compile ()
601 | (let* ((compilers (mapcar
602 | (lambda (file)
603 | (with-temp-buffer
604 | (insert-file-contents file)
605 | (when (search-forward
606 | (string-trim-right (format org-latex-compiler-file-string "") "\n")
607 | nil t)
608 | (buffer-substring-no-properties (point) (progn (end-of-line) (point))))))
609 | (list (concat (file-name-sans-extension orgdiff--rev1file) ".tex")
610 | (concat (file-name-sans-extension orgdiff--rev2file) ".tex"))))
611 | (compiler (nth (max (cl-position (car compilers) orgdiff-latex-compiler-priorities :test #'equal)
612 | (cl-position (cadr compilers) orgdiff-latex-compiler-priorities :test #'equal))
613 | orgdiff-latex-compiler-priorities))
614 | (compile-command
615 | (mapcar
616 | (lambda (arg)
617 | (thread-last arg
618 | (replace-regexp-in-string "%compiler" compiler)
619 | (replace-regexp-in-string "%texfile"
620 | (expand-file-name orgdiff--difffile))))
621 | orgdiff-latex-compile-command))
622 | (default-directory (file-name-directory orgdiff--difffile)))
623 | (message "%s%s" (propertize "Orgdiff" 'face 'bold) ": compiling diff file...")
624 | (push
625 | (apply #'start-process "orgdiff--latexdiff-compile" "*orgdiff--latexdiff-compile*" compile-command)
626 | orgdiff--export-processes)))
627 |
628 | (defun orgdiff--latexdiff-handle-pdf ()
629 | (message "%s%s" (propertize "Orgdiff" 'face 'bold) ": diff created.")
630 | ;; Cleanup
631 | (when org-latex-remove-logfiles
632 | (mapc #'delete-file
633 | (directory-files
634 | (file-name-directory orgdiff--difffile)
635 | t
636 | (concat (regexp-quote (file-name-base orgdiff--difffile))
637 | "\\(?:\\.[0-9]+\\)?\\."
638 | (regexp-opt org-latex-logfiles-extensions))
639 | t)))
640 | ;; Move the PDF to the right place
641 | (let ((pdf-file (concat (file-name-sans-extension orgdiff--difffile) ".pdf"))
642 | (dest (concat (file-name-directory orgdiff-file-1)
643 | (file-name-base orgdiff--difffile)
644 | ".pdf")))
645 | (unless (file-exists-p pdf-file)
646 | (user-error "Error! Diff PDF was not produced"))
647 | (rename-file pdf-file dest t)
648 | (when (eq 'open (plist-get orgdiff--latexdiff--completion-action :action))
649 | (find-file-other-window dest))))
650 |
651 | (provide 'orgdiff)
652 | ;;; orgdiff.el ends here
653 |
--------------------------------------------------------------------------------