org-latex-preview: Eagerly make fragment overlays

* lisp/org-latex-preview.el (org-create-latex-export,
org-latex-preview--place-images, org-latex-preview--generic-callback,
org-latex-preview--create-tex-file,
org-latex-preview-create-image-async, org-latex-preview--create,
org-latex-preview--close-previous-overlay,
org-latex-preview--make-overlay, org-latex-preview--update-overlay,
org-latex-preview-processing-face): Instead of saving buffer locations
and creating an overlay after the fragment image has been generated,
create an overlay initially and then update it when the image has been
created.  This is done by splitting the image-related code of
`org-latex-preview--make-overlay' into a new function,
`org-latex-preview--update-overlay'.  This provides a number of
advantages, primarily robustness to buffer edits during image
generation.  It also allows us to create a face to indicate
in-progress (`org-latex-preview-processing-face'), which is a nice
visual indicator.
This commit is contained in:
TEC 2023-01-03 00:29:40 +08:00
parent faccbc04e9
commit a1ea4c705d
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
1 changed files with 74 additions and 75 deletions

View File

@ -226,6 +226,10 @@ images at the same place."
:package-version '(Org . "9.0") :package-version '(Org . "9.0")
:type 'string) :type 'string)
(defface org-latex-preview-processing-face '((t :inherit shadow))
"Face applied to LaTeX fragments for which a preview is being generated."
:group 'org-faces)
(defun org-format-latex-mathml-available-p () (defun org-format-latex-mathml-available-p ()
"Return t if `org-latex-to-mathml-convert-command' is usable." "Return t if `org-latex-to-mathml-convert-command' is usable."
(save-match-data (save-match-data
@ -294,25 +298,13 @@ Note that this will also produce false postives, and
`org-element-context' should be used to verify that matches are `org-element-context' should be used to verify that matches are
indeed LaTeX fragments/environments.") indeed LaTeX fragments/environments.")
(defun org-latex-preview--make-overlay (beg end &optional path-info) (defun org-latex-preview--make-overlay (beg end)
"Build an overlay between BEG and END. "Build an overlay between BEG and END."
(dolist (o (overlays-in beg end))
If IMAGE file is specified, display it. Argument IMAGETYPE is the (when (eq (overlay-get o 'org-overlay-type)
extension of the displayed image, as a string. It defaults to 'org-latex-overlay)
\"png\"." (delete-overlay o)))
(let* ((ov (make-overlay beg end)) (let ((ov (make-overlay beg end)))
(zoom (or (plist-get org-latex-preview-options :zoom) 1.0))
(height (plist-get (cdr path-info) :height))
(depth (plist-get (cdr path-info) :depth))
(image-display
(and path-info
(list 'image
:type (plist-get (cdr path-info) :image-type)
:file (car path-info)
:height (and height (cons (* height zoom) 'em))
:ascent (if (and depth height)
(ceiling (* 100 (- 1.0 (/ depth height))))
'center)))))
(overlay-put ov 'org-overlay-type 'org-latex-overlay) (overlay-put ov 'org-overlay-type 'org-latex-overlay)
(overlay-put ov 'evaporate t) (overlay-put ov 'evaporate t)
(overlay-put ov 'modification-hooks (overlay-put ov 'modification-hooks
@ -324,19 +316,35 @@ extension of the displayed image, as a string. It defaults to
(list #'org-latex-preview-auto--insert-front-handler)) (list #'org-latex-preview-auto--insert-front-handler))
(overlay-put ov 'insert-behind-hooks (overlay-put ov 'insert-behind-hooks
(list #'org-latex-preview-auto--insert-behind-handler)) (list #'org-latex-preview-auto--insert-behind-handler))
(when path-info
(overlay-put ov 'display image-display)
(overlay-put ov 'preview-image image-display))
(if (eq (plist-get (cdr image-display) :type) 'svg)
(let ((face (if (plist-get (cdr path-info) :errors)
'error
(or (and (> beg 1)
(get-text-property (1- beg) 'face))
'default))))
(overlay-put ov 'face face))
(overlay-put ov 'face nil))
ov)) ov))
(defun org-latex-preview--update-overlay (ov path-info)
"Update the overlay OV to show the image specified by PATH-INFO."
(let* ((zoom (or (plist-get org-latex-preview-options :zoom) 1.0))
(height (plist-get (cdr path-info) :height))
(depth (plist-get (cdr path-info) :depth))
(image-display
(and path-info
(list 'image
:type (plist-get (cdr path-info) :image-type)
:file (car path-info)
:height (and height (cons (* height zoom) 'em))
:ascent (if (and depth height)
(ceiling (* 100 (- 1.0 (/ depth height))))
'center)))))
(overlay-put ov 'display image-display)
(overlay-put ov 'preview-image image-display)
(overlay-put
ov 'face
(cond
((plist-get (cdr path-info) :errors) 'error)
((eq (plist-get (cdr image-display) :type) 'svg)
(or (and (> (overlay-start ov) (point-min))
(not (eq (char-before (overlay-start ov)) ?\n))
(get-text-property (1- (overlay-start ov)) 'face))
'default))
(t nil)))))
;; Code for `org-latex-preview-auto-mode': ;; Code for `org-latex-preview-auto-mode':
;; ;;
;; The boundaries of latex preview image overlays are automatically ;; The boundaries of latex preview image overlays are automatically
@ -476,7 +484,8 @@ image. The preview image is regenerated if necessary."
(current-buffer) (current-buffer)
ov) ov)
(when-let (f (overlay-get ov 'hidden-face)) (when-let (f (overlay-get ov 'hidden-face))
(overlay-put ov 'face f) (unless (eq f 'org-latex-preview-processing-face)
(overlay-put ov 'face f))
(overlay-put ov 'hidden-face nil)) (overlay-put ov 'hidden-face nil))
(overlay-put ov 'display (overlay-get ov 'preview-image)))))) (overlay-put ov 'display (overlay-get ov 'preview-image))))))
@ -792,9 +801,7 @@ Some of the options can be changed using the variable
(let* ((processing-info (let* ((processing-info
(cdr (assq processing-type org-latex-preview-process-alist))) (cdr (assq processing-type org-latex-preview-process-alist)))
(imagetype (or (plist-get processing-info :image-output-type) "png")) (imagetype (or (plist-get processing-info :image-output-type) "png"))
document-strings fragment-info)
fragment-info
locations keys)
(save-excursion (save-excursion
(dolist (element elements) (dolist (element elements)
(let* ((beg (org-element-property :begin element)) (let* ((beg (org-element-property :begin element))
@ -828,29 +835,31 @@ Some of the options can be changed using the variable
org-latex-preview-options org-latex-preview-options
(list :foreground fg :background bg)))) (list :foreground fg :background bg))))
(if-let ((path-info (org-latex-preview--get-cached hash))) (if-let ((path-info (org-latex-preview--get-cached hash)))
(org-latex-preview-place-image beg end path-info) (org-latex-preview--update-overlay
(push (org-latex-preview--tex-styled value options) (org-latex-preview--make-overlay beg end)
document-strings) path-info)
(push (list :buffer-location (cons beg end) (push (list :string (org-latex-preview--tex-styled value options)
:overlay (org-latex-preview--make-overlay beg end)
:key hash) :key hash)
fragment-info) fragment-info)))))
(push (cons beg end) locations) (when fragment-info
(push hash keys)))))
(when locations
(org-latex-preview-create-image-async (org-latex-preview-create-image-async
processing-type processing-type
(nreverse document-strings)
(nreverse fragment-info))))) (nreverse fragment-info)))))
(defun org-latex-preview-create-image-async (processing-type preview-strings fragment-info) (defun org-latex-preview-create-image-async (processing-type fragments-info)
"Preview PREVIEW-STRINGS asynchronously with method PROCESSING-TYPE. "Preview PREVIEW-STRINGS asynchronously with method PROCESSING-TYPE.
FRAGMENT-INFO is a list of plists, where the Nth plist gives FRAGMENTS-INFO is a list of plists, each of which provides
information on the Nth fragment of PREVIEW-STRINGS. Each information on an individual fragment and should have the
FRAGMENT-INFO plist should have the following structure: following structure:
(:buffer-location (begin-pos . end-pos) :key fragment-hash) (:string fragment-string :overlay fragment-overlay :key fragment-hash)
where
- fragment-string is the literal content of the fragment
- fragment-overlay is the overlay placed for the fragment
- fragment-hash is a string that uniquely identifies the fragment
It is worth noting the FRAGMENT-INFO plists will be modified It is worth noting the FRAGMENTS-INFO plists will be modified
during processing to hold more information on the fragments." during processing to hold more information on the fragments."
(let* ((processing-type (let* ((processing-type
(or processing-type org-latex-preview-default-process)) (or processing-type org-latex-preview-default-process))
@ -860,12 +869,15 @@ during processing to hold more information on the fragments."
(error-message (or (plist-get processing-info :message) ""))) (error-message (or (plist-get processing-info :message) "")))
(dolist (program programs) (dolist (program programs)
(org-check-external-command program error-message)) (org-check-external-command program error-message))
(dolist (fragment fragments-info)
(overlay-put (plist-get fragment :overlay)
'face 'org-latex-preview-processing-face))
(let* ((extended-info (let* ((extended-info
(append processing-info (append processing-info
(list :fragments fragment-info (list :fragments fragments-info
:org-buffer (current-buffer) :org-buffer (current-buffer)
:texfile (org-latex-preview--create-tex-file :texfile (org-latex-preview--create-tex-file
processing-info preview-strings)))) processing-info fragments-info))))
(tex-compile-async (tex-compile-async
(org-latex-preview--tex-compile-async extended-info)) (org-latex-preview--tex-compile-async extended-info))
(img-extract-async (img-extract-async
@ -886,8 +898,8 @@ during processing to hold more information on the fragments."
(plist-put (cddr tex-compile-async) :failure img-extract-async)) (plist-put (cddr tex-compile-async) :failure img-extract-async))
(org-async-call tex-compile-async)))) (org-async-call tex-compile-async))))
(defun org-latex-preview--create-tex-file (processing-info preview-strings) (defun org-latex-preview--create-tex-file (processing-info fragments)
"Create a LaTeX file based on PROCESSING-INFO and PREVIEW-STRINGS. "Create a LaTeX file based on PROCESSING-INFO and FRAGMENTS.
More specifically, a preamble will be generated based on More specifically, a preamble will be generated based on
PROCESSING-INFO. Then, if `org-latex-preview-use-precompilation' is PROCESSING-INFO. Then, if `org-latex-preview-use-precompilation' is
@ -895,7 +907,7 @@ non-nil, a precompiled format file will be generated if needed
and used. Otherwise the preamble is used normally. and used. Otherwise the preamble is used normally.
Within the body of the created LaTeX file, each of Within the body of the created LaTeX file, each of
PREVIEW-STRINGS will be placed in order, wrapped within a FRAGMENTS will be placed in order, wrapped within a
\"preview\" environment. \"preview\" environment.
The path of the created LaTeX file is returned." The path of the created LaTeX file is returned."
@ -918,10 +930,10 @@ The path of the created LaTeX file is returned."
processing-info header)) processing-info header))
header)) header))
(insert "\n\\begin{document}\n") (insert "\n\\begin{document}\n")
(dolist (str preview-strings) (dolist (fragment-info fragments)
(insert (insert
"\n\\begin{preview}\n" "\n\\begin{preview}\n"
str (plist-get fragment-info :string)
"\n\\end{preview}\n")) "\n\\end{preview}\n"))
(insert "\n\\end{document}\n")) (insert "\n\\end{document}\n"))
tex-temp-name)) tex-temp-name))
@ -1038,9 +1050,9 @@ The path of the created LaTeX file is returned."
(cl-loop (cl-loop
for fragment-info in (plist-get extended-info :fragments) for fragment-info in (plist-get extended-info :fragments)
for image-file in images for image-file in images
for (beg . end) = (plist-get fragment-info :buffer-location) for ov = (plist-get fragment-info :overlay)
do (org-latex-preview-place-image do (org-latex-preview--update-overlay
beg end ov
(org-latex-preview--cache-image (org-latex-preview--cache-image
(plist-get fragment-info :key) (plist-get fragment-info :key)
image-file image-file
@ -1186,9 +1198,9 @@ listed in EXTENDED-INFO will be used."
(cl-loop (cl-loop
for fragment-info in fragments for fragment-info in fragments
for image-file = (plist-get fragment-info :path) for image-file = (plist-get fragment-info :path)
for (beg . end) = (plist-get fragment-info :buffer-location) for ov = (plist-get fragment-info :overlay)
do (org-latex-preview-place-image do (org-latex-preview--update-overlay
beg end ov
(org-latex-preview--cache-image (org-latex-preview--cache-image
(plist-get fragment-info :key) (plist-get fragment-info :key)
image-file image-file
@ -1317,19 +1329,6 @@ BLOCK-TYPE determines whether the result is placed inline or as a paragraph."
value movefile options nil processing-type)) value movefile options nil processing-type))
(org-latex-preview-place-image-link link block-type beg end value))) (org-latex-preview-place-image-link link block-type beg end value)))
;; TODO: Deleting an existing preview overlay over the same reagion is
;; wasteful. It's simpler just to update the display property of the
;; existing overlay.
(defun org-latex-preview-place-image (beg end path-info)
"Place an overlay from BEG to END showing MOVEFILE.
The overlay will be above BEG if OVERLAYS is non-nil."
(dolist (o (overlays-in beg end))
(when (eq (overlay-get o 'org-overlay-type)
'org-latex-overlay)
(delete-overlay o)))
(org-latex-preview--make-overlay beg end path-info)
(goto-char end))
(defun org-latex-preview-place-image-link (link block-type beg end value) (defun org-latex-preview-place-image-link (link block-type beg end value)
"Place then link LINK at BEG END." "Place then link LINK at BEG END."
(delete-region beg end) (delete-region beg end)