As discussed, I've now made the changes to introduce two customizations (I am open to changing the names):
- `preview-always-show`: default t, when set to nil it essentially has the preview-point behaviour (See below). - `preview-point-where`: default 'before-string, can be set to 'after-string and 'buframe as well. As an added bonus, this splitting of customizations forced me to rethink the implementation and now it is greatly simplified with smaller changes to the existing code. Also, I wasn't sure if I should respect `preview-auto-reveal` when `preview-always-show` is nil, so I've done that for now. This means that to truly get the same behaviour as preview-point, set `preview-auto-reveal` to t. Otherwise, the default value of that variable only shows the preview on specific key strokes. I've also decided to remove the automatic previewing code for now. Since the two features are orthogonal, I will send that code as a separate patch to not confuse things. Feedback is welcome, -- Al
>From 52f070df6561aabd32e1ededb97f13befd2f87ef Mon Sep 17 00:00:00 2001 From: Al Haji-Ali <[email protected]> Date: Wed, 19 Nov 2025 22:52:37 +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-disabled-face): New face for disabled previews. (preview--frame): New variable. (preview-gs-sentinel, preview-gs-transact, preview-dvi*-place-all, preview-place-preview): Call `preview-overlay-updated'. (preview-gs-flag-error, preview-mark-point, preview--open-for-replace): Add condition on `preview-always-show`. (preview-ascent-from-bb): Return 'center when 'preview-point-where' is 'buframe. (preview-toggle): Update to handle values of preview-always-show and preview-point-where. (preview-move-point): Handle `preview-always-show` and call `preview--update-buframe`. (preview-disabled-string): Handle `preview-leave-open-previews-visible` and add `preview-disabled-face`. (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. --- doc/preview-latex.texi | 26 +++ preview.el | 365 ++++++++++++++++++++++++++++++----------- 2 files changed, 293 insertions(+), 98 deletions(-) diff --git a/doc/preview-latex.texi b/doc/preview-latex.texi index 19210a3506..c41211156c 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 9481288675..ebb4f43860 100644 --- a/preview.el +++ b/preview.el @@ -456,6 +456,42 @@ 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" before-string) + (const :tag "After string (default)" after-string) + (const :tag "On frame" buframe) + (list :tag "On frame with explicit parameters" + (function :tag "Position function") + (alist :tag "Frame parameters") + (alist :tag "Buffer parameters")))) + +(defface preview-disabled-face + '((t (:inherit shadow))) + "Face used when preview is disabled." + :group 'preview) + +(defvar preview--frame nil + "The last active preview popup frame.") + (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 @@ -709,6 +745,8 @@ and tries to restart Ghostscript if necessary." (let* ((err (concat preview-gs-answer "\n" (process-name process) " " string)) (ov (preview-gs-behead-outstanding err))) + (when ov + (preview-overlay-updated ov)) (when (and (null ov) preview-gs-queue) (save-excursion (goto-char (if (marker-buffer (process-mark process)) @@ -1430,16 +1468,16 @@ Try \\[ps-run-start] \\[ps-run-buffer] and \ (ps-open (let ((string (concat - (mapconcat #'shell-quote-argument - (append (list - preview-gs-command - outfile) - preview-gs-command-line) - " ") - "\nGS>" - preview-gs-init-string - (aref (overlay-get ov 'queued) 1) - err))) + (mapconcat #'shell-quote-argument + (append (list + preview-gs-command + outfile) + preview-gs-command-line) + " ") + "\nGS>" + preview-gs-init-string + (aref (overlay-get ov 'queued) 1) + err))) (lambda () (interactive "@") (preview-mouse-open-error string)))) (str (preview-make-clickable @@ -1463,7 +1501,8 @@ Try \\[ps-run-start] \\[ps-run-buffer] and \ (apply #'preview-mouse-open-eps args))]))))))) (overlay-put ov 'strings (cons str str)) - (preview-toggle ov))) + (if preview-always-show + (preview-toggle ov)))) (defun preview-gs-transact (process answer) "Work off Ghostscript transaction. @@ -1494,7 +1533,8 @@ given as ANSWER." (preview-ascent-from-bb bbox) (aref preview-colors 2)))) - (overlay-put ov 'queued nil))))) + (overlay-put ov 'queued nil) + (preview-overlay-updated ov))))) (while (and (< (length preview-gs-outstanding) preview-gs-outstanding-limit) (setq ov (pop preview-gs-queue))) @@ -1706,19 +1746,24 @@ icon is cached in the property list of the SYMBOL." (defun preview-ascent-from-bb (bb) "This calculates the image ascent from its bounding box. The bounding box BB needs to be a 4-component vector of -numbers (can be float if available)." +numbers (can be float if available). + +If `preview-point-where' is set to \\='buframe, this simply returns +\\='center." ;; baseline is at 1in from the top of letter paper (11in), so it is ;; at 10in from the bottom precisely, which is 720 in PostScript ;; coordinates. If our bounding box has its bottom not above this ;; line, and its top above, we can calculate a useful ascent value. ;; If not, something is amiss. We just use 100 in that case. - - (let ((bottom (aref bb 1)) - (top (aref bb 3))) - (if (and (<= bottom 720) - (> top 720)) - (round (* 100.0 (/ (- top 720.0) (- top bottom)))) - 100))) + (if (or (eq preview-point-where 'buframe) + (eq (car-safe preview-point-where) 'buframe)) + 'center + (let ((bottom (aref bb 1)) + (top (aref bb 3))) + (if (and (<= bottom 720) + (> top 720)) + (round (* 100.0 (/ (- top 720.0) (- top bottom)))) + 100)))) (defface preview-face '((((background dark)) (:background "dark slate gray")) @@ -2082,32 +2127,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 - (overlay-put ov 'category 'preview-overlay) - (if (eq (overlay-start ov) (overlay-end ov)) - (overlay-put ov 'before-string (car strings)) + (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 (or (and (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) + (if (eq preview-state 'active) + (progn + (overlay-put ov 'category 'preview-overlay) + (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--update-buframe ov t))) (if old-urgent (apply #'preview-add-urgentization old-urgent)))) (if event @@ -2127,13 +2186,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 +2237,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 +2264,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." @@ -2314,20 +2377,25 @@ active (`transient-mark-mode'), it is run through `preview-region'." (defun preview-disabled-string (ov) "Generate a before-string for disabled preview overlay OV." - (concat (preview-make-clickable - (overlay-get ov 'preview-map) - preview-icon - "\ + (let ((ret (concat (preview-make-clickable + (overlay-get ov 'preview-map) + (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 - (with-current-buffer (overlay-buffer ov) - (save-excursion - (save-restriction - (widen) - (goto-char (overlay-start ov)) - (if (bolp) "\n" "")))))) + (lambda () (interactive) (preview-regenerate ov))) + ;; icon on separate line only for stuff starting on its own line + (with-current-buffer (overlay-buffer ov) + (save-excursion + (save-restriction + (widen) + (goto-char (overlay-start ov)) + (if (bolp) "\n" ""))))))) + (if preview-leave-open-previews-visible + (propertize ret 'face 'preview-disabled-face) + ret))) (defun preview-disable (ovr) "Change overlay behaviour of OVR after source edits." @@ -2341,10 +2409,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." @@ -2618,7 +2686,8 @@ Deletes the dvi file when finished." (preview-ascent-from-bb (aref queued 0)) (aref preview-colors 2))) - (overlay-put ov 'queued nil)) + (overlay-put ov 'queued nil) + (preview-overlay-updated ov)) (push filename oldfiles) ;; Do note modify `filenames' if we are not replacing ;; it, to avoid orphaning files. The filenames will be @@ -2759,7 +2828,8 @@ to the close hook." (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-overlay-updated ov)))) (defun preview-counter-find (begin) "Fetch the next preceding or next preview-counters property. @@ -3803,22 +3873,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 +4287,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 +4504,97 @@ 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 ov force) + "Show or hide a buframe popup. + +Search for overlays at point having a non-nil \\='buframe property, or +hide it otherwise. If OV is non-nil, this overlay is used to show the +buframe, or hide it if it was already being shown. + +The frame is not updated if \\='buframe property has not changed, unless +FORCE is non-nil." + (if-let* ((ov (or 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 + (eq (cdr (frame-parameter preview--frame + 'auctex-preview)) + 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 (and preview--frame + (or (null ov) + ;; Do not disable the buframe if it's showing another + ;; overview. + (eq ov (car-safe (frame-parameter preview--frame + 'auctex-preview))))) + (set-frame-parameter preview--frame 'auctex-preview nil) + (buframe-disable preview--frame)))) + +;;; preview-point +(defun preview-overlay-updated (ov) + "Mark preview OV as updated. +Has effect only if `preview-always-show' is nil." + (when (and (not preview-always-show) + (let ((pt (and + (overlay-buffer ov) + (with-current-buffer (overlay-buffer ov) + (point))))) + (and ;; If pt is inside the overlay + (eq (overlay-buffer ov) (window-buffer)) + (>= pt (overlay-start ov)) + (< pt (overlay-end ov))))) + (preview-toggle ov))) + ;;;###autoload (defun preview-report-bug () "Report a bug in the preview-latex package." (interactive) -- 2.39.5 (Apple Git-154)
_______________________________________________ bug-auctex mailing list [email protected] https://lists.gnu.org/mailman/listinfo/bug-auctex
