Automatic Latex Preview Toggling in org-mode

Feb 1, 2017   #configuration  #org-mode  #emacs 

In my org-mode setup, initially I used to preview the whole buffer all the time. However, as my files grew bigger the waiting time for this has become prohibitively expensive. Since then I have set it up to continuously recompile the file in the background each time the file is saved. It works pretty well since instead of spawning a new async process on each save, I have a single background process which recomiles in sync and thus needs not reload all the emacs on each recompile. Overall, it takes a couple of seconds to see the updated pdf after each save.

Recently however, I updated to org-mode 9.3 and I noticed that the image creation has gotten significantly faster, and the caching has gotten better - to the point of loading instantly the images from previous sessions. This has led me searching again for a way to toggle the images whenever the cursor falls on them, like in auctex. I wanted this feature for a long while now, bug never bothered to write it myself.

This time however, I found the solution premade by John Kitchin. It essentially worked quite well but had a few bugs that irritated me. I took it upon me to fix them, and attached below is the version with the fixes, as well as a bit of refactoring.

The major points: - Before deleting an overlay, make sure that there is still a latex fragment at the current location. - Don’t move the point (cursor) when entering a fragment. - Refactoring of the latex fragment test and surroungins (I know there is also org-inside-LaTeX-fragment-p, but it doesn’t detect when located on the first character of a fragment, and this one does).

To use, add the following to your org-mode-hook

    (add-hook 'post-command-hook 'kk/org-latex-fragment-toggle t)

And add the following code somewhere in init.el.

  (defvar kk/org-latex-fragment-last nil
    "Holds last fragment/environment you were on.")

  (defun kk/org-in-latex-fragment-p ()
    "Return the point where the latex fragment begins, if inside
  a latex fragment. Else return false"
    (let* ((el (org-element-context))
           (el-type (car el)))
      (and (or (eq 'latex-fragment el-type) (eq 'latex-environment el-type))
          (org-element-property :begin el))))

  (defun kk/org-latex-fragment-toggle ()
    "Toggle a latex fragment image "
    (and (eq 'org-mode major-mode)
	 (let ((begin (kk/org-in-latex-fragment-p)))
            ;; were on a fragment and now on a new fragment
              ;; fragment we were on
              ;; and are on a fragment now

              ;; but not on the last one this is a little tricky. as you edit the
              ;; fragment, it is not equal to the last one. We use the begin
              ;; property which is less likely to change for the comparison.
              (not (and kk/org-latex-fragment-last
			(= begin
             ;; go back to last one and put image back, provided there is still a fragment there
               (goto-char kk/org-latex-fragment-last)
               (when (kk/org-in-latex-fragment-p) (org-preview-latex-fragment))

               ;; now remove current image
               (goto-char begin)
               (let ((ov (loop for ov in (org--list-latex-overlays)
				(<= (overlay-start ov) (point))
				(>= (overlay-end ov) (point)))
                               return ov)))
		 (when ov
                   (delete-overlay ov)))
               ;; and save new fragment
               (setq kk/org-latex-fragment-last begin)))

            ;; were on a fragment and now are not on a fragment
              ;; not on a fragment now
              (not begin)
              ;; but we were on one
             ;; put image back on, provided that there is still a fragment here.
               (goto-char kk/org-latex-fragment-last)
               (when (kk/org-in-latex-fragment-p) (org-preview-latex-fragment)))

             ;; unset last fragment
             (setq kk/org-latex-fragment-last nil))

            ;; were not on a fragment, and now are
              ;; we were not one one
              (not kk/org-latex-fragment-last)
              ;; but now we are
             ;; remove image
               (goto-char begin)
               (let ((ov (loop for ov in (org--list-latex-overlays)
				(<= (overlay-start ov) (point))
				(>= (overlay-end ov) (point)))
                               return ov)))
		 (when ov
                   (delete-overlay ov))))
             (setq kk/org-latex-fragment-last begin))))))