From 2ca8200554a33846805a31523bd17743f057236c Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Thu, 18 Jan 2024 23:59:30 -0800 Subject: [PATCH] org-latex-preview: Dynamic throttle for live previews * lisp/org-latex-preview.el (org-latex-preview-live--throttle, org-latex-preview-live-throttle, org-latex-preview-live--preview-times, org-latex-preview-live--update-times, org-latex-preview-live--record-hook, org-latex-preview-live--regenerate, org-latex-preview-live--update-props, org-latex-preview-live--src-buffer-setup, org-latex-preview-live--setup, org-latex-preview-live--teardown, org-latex-preview--create-image-async): Find the optimal throttle time for live preview updates dynamically. The variable `org-latex-preview-live--preview-times' records the last three process run times, and `org-latex-preview-live-throttle' is set to the average of these. This ensures that there are never more than two concurrent processes live-previewing a single fragment. Start another preview run process the end of the throttle period if the preview image is out of date to ensure that the state of the preview is eventually consistent with the contents of the fragment. --- lisp/org-latex-preview.el | 75 +++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/lisp/org-latex-preview.el b/lisp/org-latex-preview.el index 265e4dcbb..4ac55bdf2 100644 --- a/lisp/org-latex-preview.el +++ b/lisp/org-latex-preview.el @@ -873,8 +873,8 @@ customize the variable `org-latex-preview-live'." ;; - When the preview is regenerated, the `after-string' property of ;; the preview overlay is updated to show the new image. This ;; regeneration is modulated with a debounce -;; `org-latex-preview-live-debounce' and a throttle -;; `org-latex-preview-live-throttle'. +;; `org-latex-preview-live-debounce' and a dynamically updated +;; throttle `org-latex-preview-live-throttle'. ;; ;; - When the cursor exits the boundaries of the fragment, the ;; `after-string' property of the preview overlay is removed. @@ -938,15 +938,36 @@ environment for at least this much time." :package-version '(Org . "9.7") :type 'number) -(defcustom org-latex-preview-live-throttle 1.0 +(defvar org-latex-preview-live-throttle 1.0 "Throttle time for live LaTeX previews. When `org-latex-preview-live' is non-nil and `org-latex-preview-auto-mode' is active, live previews are -updated no more than once in this interval of time." - :group 'org-latex-preview - :package-version '(Org . "9.7") - :type 'number) +updated no more than once in this interval of time. + +Its value is updated dynamically based on the average fragment +preview time.") + +(defvar-local org-latex-preview-live--preview-times + (make-vector 3 1.0) + "Vector containing the last three preview run times in this buffer") +(defvar-local org-latex-preview-live--preview-times-index 0) + +(defun org-latex-preview-live--update-times (latest-duration) + "Update preview times given the last preview took LATEST-DURATION seconds." + (aset org-latex-preview-live--preview-times + (% (cl-incf org-latex-preview-live--preview-times-index) 3) + latest-duration) + (setq-local org-latex-preview-live-throttle + (/ (apply #'+ (append org-latex-preview-live--preview-times nil)) + 3))) + +(defun org-latex-preview-live--record-hook (exit-code _buf extended-info) + "A hook for `org-latex-preview-process-finish-functions' to track preview time. +Called with EXIT-CODE and EXTENDED-INFO from the async process." + (when (= exit-code 0) + (org-latex-preview-live--update-times + (- (float-time) (plist-get extended-info :start-time))))) (defconst org-latex-preview-live-display-type 'buffer "How to display live-updating previews of LaTeX snippets. @@ -973,22 +994,27 @@ The only currently supported option is the symbol buffer, to (setq debounce-timer nil) (apply func args)))))))) -(defun org-latex-preview-live--throttle (func timeout) - "Return a throttled FUNC with TIMEOUT applied." +(defun org-latex-preview-live--throttle (func) + "Return a throttled FUNC. + +Ensures that FUNC runs at the end of the throttle duration." (let ((waiting)) (lambda (&rest args) (unless waiting (apply func args) (setq waiting t) - (run-at-time timeout nil - (lambda () (setq waiting nil))))))) + (run-at-time + org-latex-preview-live-throttle nil + (lambda () + (setq waiting nil) + (apply func args))))))) (defun org-latex-preview-live--clearout (ov) "Clear out the live LaTeX preview for the preview overlay OV." (setq org-latex-preview-live--element-type nil) (overlay-put ov 'after-string nil)) -(defun org-latex-preview-live--regenerate (beg end _) +(defun org-latex-preview-live--regenerate (&rest _) "Regenerate the LaTeX preview overlay that overlaps BEG and END. This is meant to be run via the `after-change-functions' hook in @@ -996,10 +1022,13 @@ Org buffers when using live-updating LaTeX previews." (pcase-let ((`(,type . ,ov) (get-char-property-and-overlay (point) 'org-overlay-type))) (when (and ov (eq type 'org-latex-overlay) - (<= (overlay-start ov) beg) - (>= (overlay-end ov) end)) - (org-latex-preview-auto--regenerate-overlay ov) - (unless (overlay-buffer ov) + ;; The following checks are redundant and can make + ;; throttling inconsistent: + ;; (<= (overlay-start ov) beg) + ;; (>= (overlay-end ov) end) + (overlay-get ov 'preview-state)) + (org-latex-preview-auto--regenerate-overlay ov t) + (unless (and (overlay-buffer ov) (overlay-get ov 'preview-image)) (org-latex-preview-live--clearout ov))))) (defun org-latex-preview-live--update-props (image-spec &optional box-face) @@ -1141,8 +1170,7 @@ This is meant to be called via `org-src-mode-hook'." (overlay-end orig-ov) content)) numbering-offsets)))) - (org-latex-preview-live--throttle - org-latex-preview-live-throttle) + (org-latex-preview-live--throttle) (org-latex-preview-live--debounce org-latex-preview-live-debounce))) (add-hook 'after-change-functions org-latex-preview-live--generator 90 'local)) @@ -1174,8 +1202,7 @@ This is meant to be called via `org-src-mode-hook'." (skip-chars-backward "\n \t\r") (point)))) numbering-offsets preamble)) - (org-latex-preview-live--throttle - org-latex-preview-live-throttle) + (org-latex-preview-live--throttle) (org-latex-preview-live--debounce org-latex-preview-live-debounce))) (add-hook 'org-latex-preview-overlay-open-functions #'org-latex-preview-live--ensure-overlay nil 'local) @@ -1207,8 +1234,7 @@ See `org-latex-preview-live' for details." (setq org-latex-preview-live--docstring " ") (setq-local org-latex-preview-live--generator (thread-first #'org-latex-preview-live--regenerate - (org-latex-preview-live--throttle - org-latex-preview-live-throttle) + (org-latex-preview-live--throttle) (org-latex-preview-live--debounce org-latex-preview-live-debounce))) (when (eq org-latex-preview-live-display-type 'eldoc) @@ -1218,6 +1244,7 @@ See `org-latex-preview-live' for details." (add-hook 'org-latex-preview-overlay-close-functions #'org-latex-preview-live--clearout nil 'local) (add-hook 'org-latex-preview-overlay-open-functions #'org-latex-preview-live--ensure-overlay nil 'local) (add-hook 'after-change-functions org-latex-preview-live--generator 90 'local) + (add-hook 'org-latex-preview-process-finish-functions #'org-latex-preview-live--record-hook nil 'local) (add-hook 'org-latex-preview-overlay-update-functions #'org-latex-preview-live--update-overlay nil 'local)) (defun org-latex-preview-live--teardown () @@ -1235,6 +1262,7 @@ See `org-latex-preview-live' for details." (remove-hook 'org-latex-preview-overlay-open-functions #'org-latex-preview-live--ensure-overlay 'local) (remove-hook 'after-change-functions org-latex-preview-live--generator 'local) (remove-hook 'org-latex-preview-overlay-update-functions #'org-latex-preview-live--update-overlay 'local) + (remove-hook 'org-latex-preview-process-finish-functions #'org-latex-preview-live--record-hook 'local) (setq-local org-latex-preview-live--generator nil)) (defun org-latex-preview-clear-overlays (&optional beg end) @@ -1861,7 +1889,8 @@ Returns a list of async tasks started." :texfile (org-latex-preview--create-tex-file processing-info fragments-info appearance-options) :appearance-options appearance-options - :place-preview-p place-preview-p))) + :place-preview-p place-preview-p + :start-time (float-time)))) (tex-compile-async (org-latex-preview--tex-compile-async extended-info)) (img-extract-async