From a1ea4c705d974bc2a5b47b02859627584436839a Mon Sep 17 00:00:00 2001 From: TEC Date: Tue, 3 Jan 2023 00:29:40 +0800 Subject: [PATCH] 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. --- lisp/org-latex-preview.el | 149 +++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/lisp/org-latex-preview.el b/lisp/org-latex-preview.el index 570fff4b6..829ec68d3 100644 --- a/lisp/org-latex-preview.el +++ b/lisp/org-latex-preview.el @@ -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)