Hello Paul,

On 27/11/2025, Paul D. Nelson wrote:
>> I've seen this issue once, but I thought I already addressed it and I no
>> longer see it. I also don't recognize the behaviour you describe since
>> with these settings, calling `preview-at-point` updates the preview,
>> yeets the cursor out of the source and the preview is shown inline until
>> one enters it again whereupon it is shown in a buframe. Can you confirm
>> and/or provide more details?
>
> I use (setq preview-protect-point t), which I suspect accounts for the
> difference you observe (but let me know if otherwise).

Ah, I forgot about this mode. I fixed the issue in the attached patch
along with some other changes that you suggested (let me know if I
missed any) and other simplifications.

I've also pushed a fix to buframe that might handle the (C) issue (I
don't have multiple monitors so it's difficult for me to truly test it),
can you confirm?

> I haven't been able to reproduce this just now -- not sure what changed.
> I'll try some more over the coming days.

Let me know, but please use the attached patch if possible. I've made
some further simplifications that should also make things easier to
debug issues.

I've also removed the custom face for disabled previews (I will probably
suggest it as another patch). This is because it requires a bit more
work to make it work well with "under-construction" previews while
avoiding flickering.

Best regards,
-- Al

>From 98308a893127cc5649737dec4a5032608321659e Mon Sep 17 00:00:00 2001
From: Al Haji-Ali <[email protected]>
Date: Wed, 26 Nov 2025 12:26:40 +0100
Subject: [PATCH] New feature: preview at point

* doc/preview-latex.texi: Add docs for previewing at point.

* preview.el (preview-always-show, preview-point-where): New customizations.
(preview--frame): New variable.
(preview-replace-active-icon): Call preview-overlay-updated.
(preview-place-preview): Clear-out before toggling.
(preview-gs-flag-error, preview-mark-point, preview--open-for-replace):
Add condition on `preview-always-show`.
(preview-toggle): Update to handle values of preview-always-show and
preview-point-where and call preview-overlay-updated.
(preview-move-point): Handle `preview-always-show` and call
`preview--update-buframe`.
(preview-disabled-string): Handle `preview-leave-open-previews-visible`.
(preview-disable): Always call preview-toggle and condition deleting
files on `preview-leave-open-previews-visible`.
(preview-parse-messages): Restore point before placing preview.
(preview-region): Update documentation to return started process.
(preview--update-buframe, preview-overlay-updated): New functions.
(preview-dvi*-command): Fix case in docs.
---
 doc/preview-latex.texi |  26 ++++
 preview.el             | 275 ++++++++++++++++++++++++++++++-----------
 2 files changed, 232 insertions(+), 69 deletions(-)

diff --git a/doc/preview-latex.texi b/doc/preview-latex.texi
index 19210a35..c4121115 100644
--- a/doc/preview-latex.texi
+++ b/doc/preview-latex.texi
@@ -480,6 +480,32 @@ math (@code{$@dots{}$}), or if your usage of @code{$} conflicts with
 @previewlatex{}'s, you can turn off inline math previews.  In the
 @samp{Preview Latex} group, remove @code{textmath} from
 @code{preview-default-option-list} by customizing this variable.
+
+@item Show previews always or at point.
+
+By default, previews are always shown when available, but this can be
+disabled by setting @code{preview-always-show} to @code{nil}. In this
+case, the preview is only shown when the cursor enters the corresponding
+TeX source.
+
+@item Control placement of previews.
+
+You can set @code{preview-point-where} to determine where previews are
+placed relative to the TeX source when both are being shown. This variable
+can take the following values:
+
+@table @code
+
+@item before-string
+Show the preview before the TeX source (default).
+
+@item after-string
+Show the preview after the TeX source.
+
+@item buframe
+Show the preview in a separate frame next to the cursor. Requires the
+@code{buframe} package to be installed (available on ELPA).
+@end table
 @end itemize
 
 @node Known problems, For advanced users, Simple customization, top
diff --git a/preview.el b/preview.el
index 94812886..257baf91 100644
--- a/preview.el
+++ b/preview.el
@@ -456,6 +456,41 @@ set to `postscript'."
   :group 'preview-latex
   :type 'boolean)
 
+;;; preview-point customizations and variables.
+(defcustom preview-always-show t
+  "If non-nil, always show previews.
+
+When nil, previews are only shown when a cursor enters their source.
+See `preview-point-where' to control where they are shown."
+  :type 'boolean)
+
+(defcustom preview-point-where 'before-string
+  "Specifies where to show the preview relative to TeX source.
+
+Can be `before-string', `after-string' to show the preview at before or
+after the TeX code or `buframe' to show it in a separate frame (the
+`buframe' package must be installed).  Can also be \\='(buframe FN-POS
+FRAME-PARAMETERS BUF-PARAMETERS) where FN-POS is a position
+function (default is `buframe-position-right-of-overlay') and
+FRAME-PARAMETERS is an alist of additional frame parameters, default is
+nil and BUF-PARAMETERS is an alist of buffer local variables and their
+values."
+  :type '(choice
+          (const :tag "Before string (default)" before-string)
+          (const :tag "After string" after-string)
+          (const :tag "On frame" buframe)
+          (list :tag "On frame with explicit parameters"
+                (const buframe)
+                (choice
+                 (const :tag "Default" nil)
+                 (function :tag "Position function"))
+                (alist :tag "Frame parameters")
+                (alist :tag "Buffer parameters"))))
+
+(defvar preview--frame nil
+  "The last active preview popup frame.
+This is relevant when `preview-point-where' is set to use buframes.")
+
 (defun preview-string-expand (arg &optional separator)
   "Expand ARG as a string.
 It can already be a string.  Or it can be a list, then it is
@@ -567,7 +602,7 @@ CMD can be used to override the command line which is used as a basis.
 
 You may set the variable `preview-dvi*-command' to
 `preview-dvisvgm-command' and set `preview-dvi*-image-type' to
-`svg' to produce svg images instead of png ones."
+\\='svg to produce SVG images instead of PNG ones."
   (let* ((scale (* (/ (preview-hook-enquiry preview-scale)
                       (preview-get-magnification))
                    (with-current-buffer TeX-command-buffer
@@ -1292,7 +1327,8 @@ ready.  This behavior suppresses flicker in the appearance."
     (when (and preview-leave-open-previews-visible
                (consp img))
       ;; No "TeX icon" has been shown, so we flush manually.
-      (image-flush (car img) t))))
+      (image-flush (car img) t))
+    (preview-overlay-updated ov)))
 
 (defun preview-gs-place (ov snippet box run-buffer tempdir ps-file _imagetype)
   "Generate an image placeholder rendered over by Ghostscript.
@@ -2082,32 +2118,46 @@ If EVENT is given, it indicates the window where the event
 occured, either by being a mouse event or by directly being
 the window in question.  This may be used for cursor restoration
 purposes."
-  (let ((old-urgent (preview-remove-urgentization ov))
-        (preview-state
-         (if (if (eq arg 'toggle)
-                 (null (eq (overlay-get ov 'preview-state) 'active))
-               arg)
-             'active
-           'inactive))
-        (strings (overlay-get ov 'strings)))
-    (unless (eq (overlay-get ov 'preview-state) 'disabled)
-      (overlay-put ov 'preview-state preview-state)
-      (if (eq preview-state 'active)
-          (progn
+  (let ((old-urgent (preview-remove-urgentization ov)))
+    (unwind-protect
+        (let ((preview-state
+               (if (if (eq arg 'toggle)
+                       (null (eq (overlay-get ov 'preview-state) 'active))
+                     arg)
+                   'active
+                 'inactive))
+              (prop (if (consp preview-point-where)
+                        (car preview-point-where)
+                      preview-point-where))
+              (strings (overlay-get ov 'strings)))
+          (unless (eq (overlay-get ov 'preview-state) 'disabled)
+            (overlay-put ov 'preview-state preview-state)
             (overlay-put ov 'category 'preview-overlay)
-            (if (eq (overlay-start ov) (overlay-end ov))
-                (overlay-put ov 'before-string (car strings))
+            (if (eq preview-state 'active)
+                (progn
+                  (if (eq (overlay-start ov) (overlay-end ov))
+                      (overlay-put ov prop (car strings))
+                    (when preview-always-show
+                      (dolist (prop '(display keymap mouse-face help-echo))
+                        (overlay-put ov prop
+                                     (get-text-property 0 prop
+                                                        (car strings)))))
+                    (overlay-put ov prop nil))
+                  (overlay-put ov 'face nil))
               (dolist (prop '(display keymap mouse-face help-echo))
-                (overlay-put ov prop
-                             (get-text-property 0 prop (car strings))))
-              (overlay-put ov 'before-string nil))
-            (overlay-put ov 'face nil))
-        (dolist (prop '(display keymap mouse-face help-echo))
-          (overlay-put ov prop nil))
-        (overlay-put ov 'face 'preview-face)
-        (unless (cdr strings)
-          (setcdr strings (preview-inactive-string ov)))
-        (overlay-put ov 'before-string (cdr strings)))
+                (overlay-put ov prop nil))
+              (when preview-always-show
+                (overlay-put ov 'face 'preview-face))
+              (unless (cdr strings)
+                ;; If `preview-always-show' is nil, then we should
+                ;; always show the preview (i.e. leave it open) when the
+                ;; preview is "inactive".
+                (let ((preview-leave-open-previews-visible
+                       (or preview-leave-open-previews-visible
+                           (not preview-always-show))))
+                  (setcdr strings (preview-inactive-string ov))))
+              (overlay-put ov prop (cdr strings)))
+            (preview-overlay-updated ov)))
       (if old-urgent
           (apply #'preview-add-urgentization old-urgent))))
   (if event
@@ -2117,6 +2167,13 @@ purposes."
            event
          (posn-window (event-start event))))))
 
+(defun preview-overlay-updated (ov)
+  "Mark preview OV as updated.
+Has effect only if `preview-always-show' is nil."
+  (with-current-buffer (window-buffer)
+    ;; Only force an update if the cursor is at point
+    (preview--update-buframe (memq ov (overlays-at (point))))))
+
 (defvar preview-marker (make-marker)
   "Marker for fake intangibility.")
 
@@ -2127,13 +2184,14 @@ purposes."
 
 (defun preview-mark-point ()
   "Mark position for fake intangibility."
-  (when (eq (get-char-property (point) 'preview-state) 'active)
-    (unless preview-last-location
-      (setq preview-last-location (make-marker)))
-    (set-marker preview-last-location (point))
-    (set-marker preview-marker (point))
-    (preview-move-point))
-  (set-marker preview-marker (point)))
+  (when preview-always-show
+    (when (eq (get-char-property (point) 'preview-state) 'active)
+      (unless preview-last-location
+        (setq preview-last-location (make-marker)))
+      (set-marker preview-last-location (point))
+      (set-marker preview-marker (point))
+      (preview-move-point))
+    (set-marker preview-marker (point))))
 
 (defun preview-restore-position (ov window)
   "Tweak position after opening/closing preview.
@@ -2177,17 +2235,19 @@ overlays not in the active window."
                               (current-buffer))
                           (- pt (marker-position preview-marker))))))
           (preview-open-overlays lst)
-        (while lst
-          (setq lst
-                (if (and
-                     (eq (overlay-get (car lst) 'preview-state) 'active)
-                     (> pt (overlay-start (car lst))))
-                    (overlays-at
-                     (setq pt (if (and distance (< distance 0))
-                                  (overlay-start (car lst))
-                                (overlay-end (car lst)))))
-                  (cdr lst))))
-        (goto-char pt)))))
+        (when preview-always-show
+          (while lst
+            (setq lst
+                  (if (and
+                       (eq (overlay-get (car lst) 'preview-state) 'active)
+                       (> pt (overlay-start (car lst))))
+                      (overlays-at
+                       (setq pt (if (and distance (< distance 0))
+                                    (overlay-start (car lst))
+                                  (overlay-end (car lst)))))
+                    (cdr lst))))
+          (goto-char pt)))))
+  (preview--update-buframe))
 
 (defun preview-open-overlays (list &optional pos)
   "Open all previews in LIST, optionally restricted to enclosing POS."
@@ -2202,7 +2262,8 @@ overlays not in the active window."
 
 (defun preview--open-for-replace (beg end &rest _)
   "Make `query-replace' open preview text about to be replaced."
-  (preview-open-overlays (overlays-in beg end)))
+  (when preview-always-show
+    (preview-open-overlays (overlays-in beg end))))
 
 (defcustom preview-query-replace-reveal t
   "Make `query-replace' autoreveal previews."
@@ -2316,12 +2377,14 @@ active (`transient-mark-mode'), it is run through `preview-region'."
   "Generate a before-string for disabled preview overlay OV."
   (concat (preview-make-clickable
            (overlay-get ov 'preview-map)
-           preview-icon
+           (if preview-leave-open-previews-visible
+               (overlay-get ov 'preview-image)
+             preview-icon)
            "\
 %s regenerates preview
 %s more options"
            (lambda () (interactive) (preview-regenerate ov)))
-;; icon on separate line only for stuff starting on its own line
+          ;; icon on separate line only for stuff starting on its own line
           (with-current-buffer (overlay-buffer ov)
             (save-excursion
               (save-restriction
@@ -2341,10 +2404,10 @@ active (`transient-mark-mode'), it is run through `preview-region'."
     (overlay-put ovr 'preview-image nil))
   (overlay-put ovr 'timestamp nil)
   (setcdr (overlay-get ovr 'strings) (preview-disabled-string ovr))
+  (preview-toggle ovr)
   (unless preview-leave-open-previews-visible
-    (preview-toggle ovr))
-  (overlay-put ovr 'preview-state 'disabled)
-  (preview--delete-overlay-files ovr))
+    (preview--delete-overlay-files ovr))
+  (overlay-put ovr 'preview-state 'disabled))
 
 (defun preview--delete-overlay-files (ovr)
   "Delete files owned by OVR."
@@ -2758,8 +2821,8 @@ to the close hook."
                   place-opts)
       (overlay-put ov 'strings
                    (list (preview-active-string ov)))
-      (preview-toggle ov t)
-      (preview-clearout start end tempdir ov))))
+      (preview-clearout start end tempdir ov)
+      (preview-toggle ov preview-always-show))))
 
 (defun preview-counter-find (begin)
   "Fetch the next preceding or next preview-counters property.
@@ -3803,22 +3866,28 @@ name(\\([^)]+\\))\\)\\|\
                                           (funcall preview-find-end-function
                                                    region-beg)
                                         (point)))
-                                     (ovl (preview-place-preview
-                                           snippet
-                                           region-beg
-                                           region-end
-                                           (preview-TeX-bb box)
-                                           (cons lcounters counters)
-                                           tempdir
-                                           (cdr open-data))))
-                                (setq close-data (nconc ovl close-data))
-                                (when (and preview-protect-point
-                                           (<= region-beg point-current)
-                                           (< point-current region-end))
-                                  ;; Temporarily open the preview if it
-                                  ;; would bump the point.
-                                  (preview-toggle (car ovl))
-                                  (push (car ovl) preview-temporary-opened)))
+                                     ovl)
+                                (save-excursion
+                                  ;; Restore point to current one before
+                                  ;; placing preview
+                                  (goto-char point-current)
+                                  (setq ovl (preview-place-preview
+                                             snippet
+                                             region-beg
+                                             region-end
+                                             (preview-TeX-bb box)
+                                             (cons lcounters counters)
+                                             tempdir
+                                             (cdr open-data)))
+                                  (setq close-data (nconc ovl close-data))
+                                  (when (and preview-protect-point
+                                             preview-always-show
+                                             (<= region-beg point-current)
+                                             (< point-current region-end))
+                                    ;; Temporarily open the preview if it
+                                    ;; would bump the point.
+                                    (preview-toggle (car ovl))
+                                    (push (car ovl) preview-temporary-opened))))
                             (with-current-buffer run-buffer
                               (preview-log-error
                                (list 'error
@@ -4211,7 +4280,9 @@ The functions in this variable will each be called inside
 `preview-region' with one argument which is a string.")
 
 (defun preview-region (begin end)
-  "Run preview on region between BEGIN and END."
+  "Run preview on region between BEGIN and END.
+
+It returns the started process."
   (interactive "r")
   (let ((TeX-region-extra
          ;; Write out counter information to region.
@@ -4426,6 +4497,72 @@ If not a regular release, the date of the last change.")
           (insert "\n")))
     (error nil)))
 
+;;; buframe specific
+
+;; Buframe functions, it will be assumed that buframe is installed so
+;; that it can be required.
+(declare-function buframe-position-right-of-overlay "ext:buframe"
+                  (frame ov &optional location))
+(declare-function buframe-make-buffer "ext:buframe" (name &optional locals))
+(declare-function buframe-make "ext:buframe"
+                  (frame-or-name fn-pos buffer &optional
+                                 parent-buffer parent-frame parameters))
+(declare-function buframe-disable "ext:buframe" (frame-or-name
+                                                 &optional enable))
+
+(defun preview--update-buframe (&optional force)
+  "Show or hide a buframe popup depending on overlays at point.
+
+If OV is non-nil, use this overlay to show the buframe, or hide it if
+it was already being shown.  The frame is not updated if the
+`buframe' property has not changed, unless FORCE is non-nil."
+  (if-let* ((ov (cl-find-if
+                 (lambda (ov) (when (overlay-get ov 'buframe) ov))
+                 (overlays-at (point))))
+            (str (overlay-get ov 'buframe)))
+      (unless (and (not force)
+                   preview--frame
+                   (pcase (frame-parameter preview--frame 'auctex-preview)
+                     (`(,o . ,s) (and (eq o ov) (eq s str)))))
+        (let* ((buf (buframe-make-buffer " *auctex-preview-buffer*"
+                                         (car-safe
+                                          (cddr (cdr-safe
+                                                 preview-point-where)))))
+               (max-image-size
+                (if (integerp max-image-size)
+                    max-image-size
+                  ;; Set the size max-image-size using the current frame
+                  ;; since the popup frame will be small to begin with
+                  (* max-image-size (frame-width)))))
+          (with-current-buffer buf
+            (let (buffer-read-only)
+              (with-silent-modifications
+                (erase-buffer)
+                (insert (propertize str
+                                    ;; Remove unnecessary properties
+                                    'help-echo nil
+                                    'keymap nil
+                                    'mouse-face nil))
+                (goto-char (point-min)))))
+          (setq preview--frame
+                (buframe-make
+                 "auctex-preview"
+                 (lambda (frame)
+                   (funcall
+                    (or (car-safe (cdr-safe preview-point-where))
+                        #'buframe-position-right-of-overlay)
+                    frame
+                    ov))
+                 buf
+                 (overlay-buffer ov)
+                 (window-frame)
+                 (car-safe (cdr (cdr-safe preview-point-where)))))
+          (set-frame-parameter preview--frame 'auctex-preview
+                               (cons ov str))))
+    (when preview--frame
+      (set-frame-parameter preview--frame 'auctex-preview nil)
+      (buframe-disable preview--frame))))
+
 ;;;###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