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")
: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 ()
"Return t if `org-latex-to-mathml-convert-command' is usable."
(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
indeed LaTeX fragments/environments.")
(defun org-latex-preview--make-overlay (beg end &optional path-info)
"Build an overlay between BEG and END.
If IMAGE file is specified, display it. Argument IMAGETYPE is the
extension of the displayed image, as a string. It defaults to
\"png\"."
(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)))))
(defun org-latex-preview--make-overlay (beg end)
"Build an overlay between BEG and END."
(dolist (o (overlays-in beg end))
(when (eq (overlay-get o 'org-overlay-type)
'org-latex-overlay)
(delete-overlay o)))
(let ((ov (make-overlay beg end)))
(overlay-put ov 'org-overlay-type 'org-latex-overlay)
(overlay-put ov 'evaporate t)
(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))
(overlay-put ov 'insert-behind-hooks
(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))
(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':
;;
;; The boundaries of latex preview image overlays are automatically
@ -476,7 +484,8 @@ image. The preview image is regenerated if necessary."
(current-buffer)
ov)
(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 'display (overlay-get ov 'preview-image))))))
@ -792,9 +801,7 @@ Some of the options can be changed using the variable
(let* ((processing-info
(cdr (assq processing-type org-latex-preview-process-alist)))
(imagetype (or (plist-get processing-info :image-output-type) "png"))
document-strings
fragment-info
locations keys)
fragment-info)
(save-excursion
(dolist (element elements)
(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
(list :foreground fg :background bg))))
(if-let ((path-info (org-latex-preview--get-cached hash)))
(org-latex-preview-place-image beg end path-info)
(push (org-latex-preview--tex-styled value options)
document-strings)
(push (list :buffer-location (cons beg end)
(org-latex-preview--update-overlay
(org-latex-preview--make-overlay beg end)
path-info)
(push (list :string (org-latex-preview--tex-styled value options)
:overlay (org-latex-preview--make-overlay beg end)
:key hash)
fragment-info)
(push (cons beg end) locations)
(push hash keys)))))
(when locations
fragment-info)))))
(when fragment-info
(org-latex-preview-create-image-async
processing-type
(nreverse document-strings)
(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.
FRAGMENT-INFO is a list of plists, where the Nth plist gives
information on the Nth fragment of PREVIEW-STRINGS. Each
FRAGMENT-INFO plist should have the following structure:
(:buffer-location (begin-pos . end-pos) :key fragment-hash)
FRAGMENTS-INFO is a list of plists, each of which provides
information on an individual fragment and should have the
following structure:
(: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."
(let* ((processing-type
(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) "")))
(dolist (program programs)
(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
(append processing-info
(list :fragments fragment-info
(list :fragments fragments-info
:org-buffer (current-buffer)
:texfile (org-latex-preview--create-tex-file
processing-info preview-strings))))
processing-info fragments-info))))
(tex-compile-async
(org-latex-preview--tex-compile-async extended-info))
(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))
(org-async-call tex-compile-async))))
(defun org-latex-preview--create-tex-file (processing-info preview-strings)
"Create a LaTeX file based on PROCESSING-INFO and PREVIEW-STRINGS.
(defun org-latex-preview--create-tex-file (processing-info fragments)
"Create a LaTeX file based on PROCESSING-INFO and FRAGMENTS.
More specifically, a preamble will be generated based on
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.
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.
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))
header))
(insert "\n\\begin{document}\n")
(dolist (str preview-strings)
(dolist (fragment-info fragments)
(insert
"\n\\begin{preview}\n"
str
(plist-get fragment-info :string)
"\n\\end{preview}\n"))
(insert "\n\\end{document}\n"))
tex-temp-name))
@ -1038,9 +1050,9 @@ The path of the created LaTeX file is returned."
(cl-loop
for fragment-info in (plist-get extended-info :fragments)
for image-file in images
for (beg . end) = (plist-get fragment-info :buffer-location)
do (org-latex-preview-place-image
beg end
for ov = (plist-get fragment-info :overlay)
do (org-latex-preview--update-overlay
ov
(org-latex-preview--cache-image
(plist-get fragment-info :key)
image-file
@ -1186,9 +1198,9 @@ listed in EXTENDED-INFO will be used."
(cl-loop
for fragment-info in fragments
for image-file = (plist-get fragment-info :path)
for (beg . end) = (plist-get fragment-info :buffer-location)
do (org-latex-preview-place-image
beg end
for ov = (plist-get fragment-info :overlay)
do (org-latex-preview--update-overlay
ov
(org-latex-preview--cache-image
(plist-get fragment-info :key)
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))
(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)
"Place then link LINK at BEG END."
(delete-region beg end)