Hello Paul,

On 03/04/2026, Paul D. Nelson wrote:
> Thanks, I tried testing it with a relatively recent Emacs master, with
> Emacs -Q + AUCTeX, and the config:
>
>   (setq preview-visibility-style 'always)
>   (setq preview-at-point-placement 'before-string)
>   Open tex file, M-x preview-automatic-mode

Apologies, I somehow forgot to test it with the default GS previews.

I am attaching a new patch that should work with all previews (a couple
of fixes should be adopted regardless of preview-automatic). I also
adopted a better approach that should be more robust, i.e., prevent
hanging of Emacs which you experienced.

Note: A main issue (which thankfully revealed a few other issues) was
caused by `preview-reraise-error` being called with a nil process in
`preview-gs-filter`. I believe this is a design flaw in the function
since the argument seemingly serves two purposes: the documented one
being that if non-nil to remove the process from "Compilation", the
undocumented one and the one I was using it for is indicating which
process the function is being called from. I implemented what I
considered to be a hack by selecting the process buffer before calling
the function with nil inside `preview-gs-filter` (otherwise, the
function has no way of knowing the correct context of the call, and I
have no way of knowing if the error should be silenced or not). I would
much rather change the arguments to be
(defun preview-reraise-error (process &optional remove-from-compilation))

with the first always indicating the "context" of the function call, but
I know we should be careful when changing public(?)  function
interfaces. Another option, for consistency, is to always call
`preview-reraise-error` from the process buffer regardless of its
parameters.

-- Al

>From 90628a337dba2c5868a7db4a8552db1ab21a3935 Mon Sep 17 00:00:00 2001
From: Al Haji-Ali <[email protected]>
Date: Tue, 31 Mar 2026 14:13:50 +0100
Subject: [PATCH] New feature: preview-automatic

Code derived from preview-auto.el (Copyright 2024 FSF, by Paul
D. Nelson), adapted and integrated here.

* doc/preview-latex.texi: Add docs for preview-automatic
* preview.el (preview-silent-errors): New local variable.
(preview-log-error, preview-reraise-error): Silence errors
conditionally.
(preview-automatic-function, preview-automatic-delay): New user options
(preview-handle-modification): Update previews automatically.
(preview-automatic-mode): New mode.
(preview-automatic--update-1, preview-automatic-update,
preview-automatic--after-change, preview-automatic--after-process): New
functions.
(preview--delete-overlay-files): Allow excepting a file.
(preview-gs-transact): Fix issue with multiple filenames.
(preview-gs-filter): Call preview-reraise-error from process buffer.
---
 doc/preview-latex.texi |   3 +
 preview.el             | 167 +++++++++++++++++++++++++++++++++++++----
 2 files changed, 155 insertions(+), 15 deletions(-)

diff --git a/doc/preview-latex.texi b/doc/preview-latex.texi
index 0f3c63b1..a38d030d 100644
--- a/doc/preview-latex.texi
+++ b/doc/preview-latex.texi
@@ -481,6 +481,9 @@ math (@code{$@dots{}$}), or if your usage of @code{$} conflicts with
 @samp{Preview Latex} group, remove @code{textmath} from
 @code{preview-default-option-list} by customizing this variable.
 
+See also @code{preview-automatic-mode} to enable automatic updating or
+generation of previews.
+
 @item Customize where previews are shown
 
 Set @code{preview-visibility-style} to control when previews appear:
diff --git a/preview.el b/preview.el
index 37da8f1d..196b72d7 100644
--- a/preview.el
+++ b/preview.el
@@ -372,6 +372,10 @@ See also `preview-gs-command'."
   "List of overlays to convert using gs.
 Buffer-local to the appropriate TeX process buffer.")
 
+(defvar-local preview-silent-errors nil
+  "When non-nil, do not signal preview errors nor display output buffer.
+This variable should be set in the process buffer.")
+
 (defvar-local preview-gs-outstanding nil
   "Overlays currently processed.")
 
@@ -699,7 +703,8 @@ is to be used."
         (insert-before-markers
          (format "%s: %s\n"
                  context (error-message-string err)))
-        (display-buffer (current-buffer)))))
+        (unless preview-silent-errors
+          (display-buffer (current-buffer))))))
   (setq preview-error-condition err))
 
 (defun preview-reraise-error (&optional process)
@@ -708,7 +713,12 @@ Makes sure that PROCESS is removed from the \"Compilation\"
 tag in the mode line."
   (when preview-error-condition
     (unwind-protect
-        (signal (car preview-error-condition) (cdr preview-error-condition))
+        (unless (buffer-local-value 'preview-silent-errors
+                                    (or (and process
+                                             (process-buffer process))
+                                        (current-buffer)))
+          (signal (car preview-error-condition)
+                  (cdr preview-error-condition)))
       (setq preview-error-condition nil
             compilation-in-progress (delq process compilation-in-progress)))))
 
@@ -825,8 +835,10 @@ Gets the usual PROCESS and STRING parameters, see
         (setq preview-gs-answer (substring preview-gs-answer pos))
         (condition-case err
             (preview-gs-transact process answer)
-          (error (preview-log-error err "Ghostscript filter" process))))))
-  (preview-reraise-error))
+          (error (preview-log-error err "Ghostscript filter" process)))))
+    ;; Call `preview-reraise-error' from the process buffer since we do
+    ;; not pass the `process'.
+    (preview-reraise-error)))
 
 (defun preview-gs-restart ()
   "Start a new Ghostscript conversion process."
@@ -1583,14 +1595,10 @@ given as ANSWER."
         (when queued
           (let* ((bbox (aref queued 0))
                  (filenames (overlay-get ov 'filenames))
-                 (oldfile (nth 0 filenames))
-                 (newfile (nth 1 filenames)))
+                 (newfile (car (last filenames))))
             (if have-error
                 (preview-gs-flag-error ov answer)
-              (condition-case nil
-                  (preview-delete-file oldfile)
-                (file-error nil))
-              (overlay-put ov 'filenames (cdr filenames))
+              (preview--delete-overlay-files ov newfile)
               (preview-replace-active-icon
                ov
                (preview-create-icon (car newfile)
@@ -2173,11 +2181,15 @@ for definition of OV, AFTER-CHANGE, BEG, END and LENGTH."
     (preview-register-change ov)))
 
 (defun preview-handle-modification
-  (ov after-change _beg _end &optional _length)
+    (ov after-change _beg _end &optional _length)
   "Hook function for `modification-hooks' property.
 See info node `(elisp) Overlay Properties' for
 definition of OV, AFTER-CHANGE, BEG, END and LENGTH."
-  (unless after-change
+  (if after-change
+      (when (buffer-local-value 'preview-automatic-mode
+                                (overlay-buffer ov))
+        (preview-automatic-update (overlay-buffer ov)
+                                  (overlay-start ov)))
     (preview-register-change ov)))
 
 (defun preview--string (ov use-icon helpstring &optional click1)
@@ -2521,11 +2533,13 @@ active (`transient-mark-mode'), it is run through `preview-region'."
       (preview--delete-overlay-files ovr))
     (overlay-put ovr 'preview-state 'disabled)))
 
-(defun preview--delete-overlay-files (ovr)
+(defun preview--delete-overlay-files (ovr &optional except)
   "Delete files owned by OVR."
   (let ((filenames (overlay-get ovr 'filenames)))
-    (overlay-put ovr 'filenames nil)
-    (dolist (filename filenames)
+    (overlay-put ovr 'filenames (when (and except
+                                           (memq except filenames))
+                                  (list except)))
+    (dolist (filename (delq except filenames))
       (condition-case nil
           (preview-delete-file filename)
         (file-error nil)))))
@@ -4675,6 +4689,129 @@ See `preview-at-point-placement'."))))
         (set-frame-parameter old-frame 'auctex-preview nil)
         (buframe-disable old-frame)))))
 
+;;; preview-automatic -- Auto-preview
+
+(defcustom preview-automatic-function nil
+  "Function to determine if a preview should be created at point.
+
+When `preview-automatic-mode' is enabled and this variable is non-nil,
+it should be a function that is is called with no arguments at (point)
+when there is not a preview already.  If the function returns a non-nil
+value, `preview-at-point' will be called.
+
+If the function returns a cons, it should be the (BEGIN . END), which
+will be used as arguments for `preview-region'."
+  :type 'symbol
+  :group 'preview-latex
+  :package-version '(auctex . "14.2.0"))
+
+;;;###autoload
+(define-minor-mode preview-automatic-mode
+  "Enable automatic refreshing and generation of previews.
+When enabled, existing previews are automatically updated when their
+text is changed.  Moreover, previews are automatically created whenever
+`preview-automatic-function' returns a non-nil value at point."
+  :group 'preview-latex
+  :init-value nil
+  (if preview-automatic-mode
+      (progn
+        (add-hook 'after-change-functions
+                  #'preview-automatic--after-change nil t)
+        (preview-automatic-update (current-buffer) (point)))
+    (remove-hook 'after-change-functions
+                 #'preview-automatic--after-change t)))
+
+(defcustom preview-automatic-delay 0.1
+  "Delay in seconds for automatic preview timer."
+  :type 'number
+  :group 'preview-latex
+  :package-version '(auctex . "14.2.0"))
+
+(defun preview-automatic--update-1 (buffer pt)
+  "Update preview at PT in BUFFER."
+  (when (buffer-live-p buffer)
+    (with-current-buffer buffer
+      (if-let* ((cur-process
+                 (or (get-buffer-process (TeX-process-buffer-name
+                                          (TeX-region-file)))
+                     (get-buffer-process (TeX-process-buffer-name
+                                          (TeX-master-file))))))
+          (progn
+            (when (eq (process-get cur-process 'preview-automatic) t)
+              ;; This was an `preview-automatic' process, therefore we
+              ;; kill it and eventually restart it.
+              (ignore-errors (TeX-kill-job))
+              (setq preview-abort-flag t))
+            (process-put cur-process 'preview-automatic (list buffer pt))
+            (add-function :after (process-sentinel cur-process)
+                          #'preview-automatic--after-process
+                          '((name . preview-automatic)
+                            (depth . -99))))
+        (when-let* ((region
+                     (save-excursion
+                       (goto-char pt)
+                       (let* ((cur (cl-find-if
+                                    (lambda (ov)
+                                      (eq (overlay-get ov 'category)
+                                          'preview-overlay))
+                                    (overlays-at (point))))
+                              (region (or
+                                       (and cur
+                                            (eq
+                                             (overlay-get cur 'preview-state)
+                                             'disabled))
+                                       (and (not cur)
+                                            preview-automatic-function
+                                            (funcall
+                                             preview-automatic-function)))))
+                         (when region
+                           (if (consp region)
+                               region
+                             (cons (preview-next-border t)
+                                   (preview-next-border nil))))))))
+          (let ((TeX-suppress-compilation-message t)
+                (save-silently t)
+                (noninteractive t)
+                (inhibit-message t)
+                message-log-max)
+            (when-let* ((process (preview-region (car region)
+                                                 (cdr region))))
+              (process-put process 'preview-automatic t)
+              (with-current-buffer (process-buffer process)
+                (setq-local preview-silent-errors t
+                            preview-locating-previews-message nil)))))))))
+
+(if (require 'timeout nil t)
+    (defalias 'preview-automatic-update
+      (timeout-debounced-func 'preview-automatic--update-1
+                              'preview-automatic-delay))
+  ;; `timeout' is not available, define debounced
+  ;; `preview-automatic-update' manually.
+  (defvar preview-automatic--update-timer nil
+    "Timer used for debouncing preview-automatic-update.")
+  (defun preview-automatic-update (buffer pt)
+    (:documentation (documentation 'preview-automatic--update-1))
+    (when preview-automatic--update-timer
+      (cancel-timer preview-automatic--update-timer)
+      (setq preview-automatic--update-timer nil))
+
+    (setq preview-automatic--update-timer
+          (run-with-idle-timer
+           preview-automatic-delay nil
+           #'preview-automatic--update-1
+           buffer pt))))
+
+(defun preview-automatic--after-change (&rest _)
+  "Ensure a preview is updated after buffer change, if needed."
+  (preview-automatic-update (current-buffer) (point)))
+
+(defun preview-automatic--after-process (process _)
+  "Ensure a preview is updated after a process terminates, if needed."
+  (when-let* ((args (process-get process 'preview-automatic)))
+    (when (consp args)
+      (process-put process 'preview-automatic t)
+      (apply #'preview-automatic-update args))))
+
 ;;;###autoload
 (defun preview-report-bug () "Report a bug in the preview-latex package."
        (interactive)
-- 
2.50.1 (Apple Git-155)

_______________________________________________
bug-auctex mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/bug-auctex

Reply via email to