forked from mirrors/org-mode
org-latex-preview: Add org-preview-latex-auto-mode
* lisp/org-latex-preview.el (org-latex-preview--make-overlay): Add overlay hooks that correct for any edits made inside the overlay, and store a copy of the image specification in the "preview-image" slot. Also return the created overlay, so it can be used in other functions. (org-latex-preview--from-overlay-p, org-latex-preview--marker, org-latex-preview--inhibit): New variables to keep track of overlay state when using auto-mode. (org-latex-preview--handle-pre-cursor, org-latex-preview--handle-post-cursor, org-latex-preview--move-into): Detect when the cursor is entering or leaving a preview overlay, and trigger the appropriate action. (org-latex-preview--open-this-overlay, org-latex-preview--close-previous-overlay): When cursor has entered/left a preview overlay, show the text/image as appropriate. (org-latex-preview--handle-insert): Create a dummy preview overlay when a new LaTeX fragment is created. (org-latex-preview--insert-front-handler, org-preview--insert-behind-handler): Extend preview overlay boundaries when their content changes. (org-latex-preview-auto-mode): A new minor mode for automatic opening/closing of preview overlays, and regeneration. (org-latex-preview-auto-generate-p): Variable which affects the regeneration behaviour of `org-latex-preview-auto-mode'.
This commit is contained in:
parent
239f3bab04
commit
b014c81a3f
|
@ -276,6 +276,17 @@ This requires the LaTeX package \"mylatexformat\" to be installed."
|
|||
:package-version '(Org . "9.7")
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom org-latex-preview-auto-generate t
|
||||
"Whether `org-latex-preview-auto-mode' should apply to new fragments.
|
||||
|
||||
When non-nil, newly inserted/typed LaTeX fragments and
|
||||
environments will be automatically previewed. Otherwise, only
|
||||
existing LaTeX previews will be automatically hidden/shown on
|
||||
cursor movement and regenerated after edits."
|
||||
:group 'org-latex
|
||||
:package-version '(Org . "9.7")
|
||||
:type 'boolean)
|
||||
|
||||
(defconst org-latex-preview--tentative-math-re
|
||||
"\\$\\|\\\\[([]\\|^[ \t]*\\\\begin{[A-Za-z0-9*]+}"
|
||||
"Regexp whith will match all instances of LaTeX math.
|
||||
|
@ -309,8 +320,13 @@ extension of the displayed image, as a string. It defaults to
|
|||
(when after-p
|
||||
(overlay-put o 'preview-state 'modified)
|
||||
(overlay-put o 'display nil) ))))
|
||||
(overlay-put ov 'insert-in-front-hooks
|
||||
(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 '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
|
||||
|
@ -318,7 +334,246 @@ extension of the displayed image, as a string. It defaults to
|
|||
(get-text-property (1- beg) 'face))
|
||||
'default))))
|
||||
(overlay-put ov 'face face))
|
||||
(overlay-put ov 'face nil))))
|
||||
(overlay-put ov 'face nil))
|
||||
ov))
|
||||
|
||||
;; Code for `org-latex-preview-auto-mode':
|
||||
;;
|
||||
;; The boundaries of latex preview image overlays are automatically
|
||||
;; extended to track changes in the underlying text by the functions
|
||||
;; `org-latex-preview-auto--insert-front-handler' and
|
||||
;; `org-latex-preview-auto--insert-behind-handler'. These are placed in the
|
||||
;; `insert-in-front-hooks' and `insert-behind-hooks' properties of the
|
||||
;; iamge overlays. See (info "(elisp) Overlay Properties").
|
||||
;;
|
||||
;; This code examines the previous and current cursor
|
||||
;; positions after each command. It uses the variables
|
||||
;; `org-latex-preview-auto--from-overlay' and `org-latex-preview-auto--marker' to track
|
||||
;; this.
|
||||
;;
|
||||
;; If the cursor has moved out of or into a latex preview overlay,
|
||||
;; the overlay is changed to display or hide its image respectively.
|
||||
;; The functions `org-latex-preview-auto--handle-pre-cursor' and
|
||||
;; `org-latex-preview-auto--handle-post-cursor' do this. These are palced in
|
||||
;; `pre-command-hook' and `post-command-hook' respectively.
|
||||
;;
|
||||
;; When the cursor positions pre- and post-command are inside an
|
||||
;; overlay, it uses the overlay property `view-text' to check if the
|
||||
;; source and destination overlays are distinct. If they are it shows
|
||||
;; and hides images as appropriate.
|
||||
;;
|
||||
;; If the latex fragment text for an existing overlay is modified, a
|
||||
;; new preview image will be generated automatically. The
|
||||
;; modification state of the overlay is stored in the overlay property
|
||||
;; `preview-state', and the function
|
||||
;; `org-latex-preview-auto--close-previous-overlay' handles the recompilation.
|
||||
;;
|
||||
;; When the user option `org-latex-preview-auto-generate' is
|
||||
;; non-nil, previews are auto-generated for latex fragments as the
|
||||
;; user types them. This work is handled by
|
||||
;; `org-latex-preview-auto--handle-insert', which is placed in
|
||||
;; `post-self-insert-hook'. It does this by placing dummy overlays
|
||||
;; that don't display images, but are marked as having been modified.
|
||||
|
||||
(defvar-local org-latex-preview-auto--from-overlay nil
|
||||
"Whether the cursor if starting from within a preview overlay.")
|
||||
(defvar-local org-latex-preview-auto--marker (make-marker)
|
||||
"Marker to keep track of the previous cursor position.
|
||||
This helps with tracking cursor movement into and out of preview overlays.")
|
||||
(defvar-local org-latex-preview-auto--inhibit nil
|
||||
"Delay the state machine that decides to auto-generate preview fragments.")
|
||||
|
||||
(defsubst org-latex-preview-auto--move-into (ov)
|
||||
"Adjust column when moving into the overlay OV from below."
|
||||
(when (> (marker-position org-latex-preview-auto--marker)
|
||||
(line-end-position))
|
||||
(goto-char (overlay-end ov))
|
||||
(goto-char (max (line-beginning-position)
|
||||
(overlay-start ov)))))
|
||||
|
||||
(defun org-latex-preview-auto--handle-pre-cursor ()
|
||||
"Record the previous state of the cursor position.
|
||||
|
||||
This keeps track of the cursor relative to the positions of
|
||||
Org latex preview overlays.
|
||||
|
||||
This is intended to be placed in `pre-command-hook'."
|
||||
(if org-latex-preview-auto--inhibit
|
||||
(setq org-latex-preview-auto--inhibit nil)
|
||||
(setq org-latex-preview-auto--from-overlay
|
||||
(eq (get-char-property (point) 'org-overlay-type)
|
||||
'org-latex-overlay))
|
||||
(set-marker org-latex-preview-auto--marker (point))))
|
||||
|
||||
(defun org-latex-preview-auto--handle-post-cursor ()
|
||||
"Toggle or generate LaTeX previews based on cursor movement.
|
||||
|
||||
If the cursor is moving into a preview overlay, \"open\" it to
|
||||
display the underlying latex fragment. If the cursor is moving
|
||||
out of a preview overlay, show the image again or generate a new
|
||||
one as appropriate.
|
||||
|
||||
See `org-latex-preview-auto-generate' to customize this behavior.
|
||||
|
||||
This is intended to be placed in `post-command-hook'."
|
||||
(let ((into-overlay-p (eq (get-char-property (point) 'org-overlay-type)
|
||||
'org-latex-overlay)))
|
||||
(cond
|
||||
((and into-overlay-p org-latex-preview-auto--from-overlay)
|
||||
(unless (get-char-property (point) 'view-text)
|
||||
;; Jumped from overlay to overlay
|
||||
(org-latex-preview-auto--close-previous-overlay)
|
||||
(org-latex-preview-auto--open-this-overlay)))
|
||||
((and into-overlay-p (not org-latex-preview-auto--from-overlay))
|
||||
;; Moved into overlay
|
||||
(org-latex-preview-auto--open-this-overlay))
|
||||
(org-latex-preview-auto--from-overlay
|
||||
;; Moved out of overlay
|
||||
(org-latex-preview-auto--close-previous-overlay)))
|
||||
(set-marker org-latex-preview-auto--marker (point))))
|
||||
|
||||
(defun org-latex-preview-auto--open-this-overlay ()
|
||||
"Open Org latex preview image overlays.
|
||||
|
||||
If there is a latex preview image overlay at point, hide the
|
||||
image and display its text."
|
||||
(dolist (ov (overlays-at (point)))
|
||||
(when (eq (overlay-get ov 'org-overlay-type)
|
||||
'org-latex-overlay)
|
||||
(overlay-put ov 'display nil)
|
||||
(overlay-put ov 'view-text t)
|
||||
(when-let ((f (overlay-get ov 'face)))
|
||||
(overlay-put ov 'hidden-face f)
|
||||
(overlay-put ov 'face nil))
|
||||
(org-latex-preview-auto--move-into ov)
|
||||
(setq org-latex-preview-auto--from-overlay nil))))
|
||||
|
||||
(defun org-latex-preview-auto--close-previous-overlay ()
|
||||
"Close Org latex preview image overlays.
|
||||
|
||||
If there is a latex preview image overlay at the previously
|
||||
recorded cursor position, hide its text and display the
|
||||
image. The preview image is regenerated if necessary."
|
||||
(dolist (ov (overlays-at (marker-position org-latex-preview-auto--marker)))
|
||||
(when (eq (overlay-get ov 'org-overlay-type) 'org-latex-overlay)
|
||||
(overlay-put ov 'view-text nil)
|
||||
(if (eq (overlay-get ov 'preview-state) 'modified)
|
||||
;; It may seem odd to use an timer for this action, but by
|
||||
;; introducing a brief window for Emacs to deal with input
|
||||
;; events triggered during prior processing the perceptible
|
||||
;; delay is reduced. Setting an 0.05s timer isn't
|
||||
;; necesarily the optimal duration, but from a little
|
||||
;; testing it appears to be fairly reasonable.
|
||||
(run-at-time
|
||||
0.05 nil
|
||||
(lambda (buf ov)
|
||||
(with-current-buffer buf
|
||||
(org-latex-preview--create
|
||||
org-latex-preview-default-process
|
||||
(org-latex-preview-collect-fragments
|
||||
(overlay-start ov)
|
||||
(overlay-end ov)))))
|
||||
(current-buffer)
|
||||
ov)
|
||||
(when-let (f (overlay-get ov 'hidden-face))
|
||||
(overlay-put ov 'face f)
|
||||
(overlay-put ov 'hidden-face nil))
|
||||
(overlay-put ov 'display (overlay-get ov 'preview-image))))))
|
||||
|
||||
(defun org-latex-preview-auto--handle-insert ()
|
||||
"Set up a dummy overlay on the latex fragment around point.
|
||||
|
||||
This is called when inserting text, provided there isn't already
|
||||
a preview overlay in place. See
|
||||
`org-latex-preview-auto-generate' for details. The dummy
|
||||
overlay is marked as modified, so that moving out of the fragment
|
||||
causes latex compilation (if required) and the preview image to
|
||||
be displayed.
|
||||
|
||||
This function is intended to be added to `post-self-insert-hook'."
|
||||
(when (and org-latex-preview-auto-generate
|
||||
(> (point) (+ (point-min) 3)))
|
||||
;; Because we rely on font-lock information to detect
|
||||
;; LaTeX fragments, we need to check for two special cases
|
||||
;; where a LaTeX fragment may have just been formed,
|
||||
;; but font-lock may not have applied the faces we need yet
|
||||
;; (e.g. due to `jit-lock-mode').
|
||||
(cond
|
||||
((save-excursion
|
||||
;; We use `re-search-forward', because under certain
|
||||
;; conditions `re-search-backward' can modify the buffer.
|
||||
;; Believe me, I am more confused that you are.
|
||||
(goto-char (- (point) 2))
|
||||
(re-search-forward "\\=\\\\[])]" (+ (point) 2) t))
|
||||
(font-lock-ensure
|
||||
(save-excursion
|
||||
(re-search-backward "\\\\[[([]" nil t) (point))
|
||||
(point)))
|
||||
((save-excursion
|
||||
(goto-char (- (point) 3))
|
||||
(re-search-forward "\\=\\\\[[([].\\\\[])]" (+ (point) 5) t))
|
||||
(font-lock-ensure (- (point) 3) (+ (point) 2))))
|
||||
(and-let* ((p (1- (point)))
|
||||
((not (eq (get-char-property p 'org-overlay-type) 'org-latex-overlay)))
|
||||
(faces (get-text-property p 'face))
|
||||
(face-list (if (listp faces) faces (list faces)))
|
||||
((memq 'font-latex-math-face face-list))
|
||||
(element (org-element-context))
|
||||
(beg (org-element-property :begin element))
|
||||
(end (org-element-property :end element))
|
||||
(ov (org-latex-preview--make-overlay beg end)))
|
||||
(overlay-put ov 'preview-state 'modified)
|
||||
(overlay-put ov 'view-text t)
|
||||
(setq org-latex-preview-auto--inhibit t
|
||||
org-latex-preview-auto--from-overlay t)
|
||||
(set-marker org-latex-preview-auto--marker p))))
|
||||
|
||||
(defun org-latex-preview-auto--insert-front-handler
|
||||
(ov after-p _beg end &optional _length)
|
||||
"Extend Org LaTeX preview text boundaries when editing previews.
|
||||
|
||||
OV is the overlay displaying the preview. For the meaning of
|
||||
AFTER-P, END and the other arguments, see the
|
||||
`modification-hooks' property of overlays in the Elisp
|
||||
manual: (elisp) Overlay Properties."
|
||||
(when after-p
|
||||
(unless undo-in-progress
|
||||
(if (eq (overlay-get ov 'preview-state) 'active)
|
||||
(move-overlay ov end (overlay-end ov))))))
|
||||
|
||||
(defun org-latex-preview-auto--insert-behind-handler
|
||||
(ov after-p beg _end &optional _length)
|
||||
"Extend Org LaTeX preview text boundaries when editing previews.
|
||||
|
||||
OV is the overlay displaying the preview. For the meaning of
|
||||
AFTER-P, BEG and the other arguments, see the
|
||||
`modification-hooks' property of overlays in the Elisp
|
||||
manual: (elisp) Overlay Properties."
|
||||
(when after-p
|
||||
(unless undo-in-progress
|
||||
(if (eq (overlay-get ov 'preview-state) 'active)
|
||||
(move-overlay ov (overlay-end ov) beg)))))
|
||||
|
||||
(define-minor-mode org-latex-preview-auto-mode
|
||||
"Minor mode to automatically preview LaTeX fragments.
|
||||
|
||||
When LaTeX preview auto mode is on, LaTeX fragments in Org buffers are
|
||||
automatically previewed after being inserted, and hidden when the
|
||||
cursor moves into them. This allows one to seamlessly edit and
|
||||
preview LaTeX in Org buffers.
|
||||
|
||||
To enable auto-toggling of the preview images without auto-generating
|
||||
them or vice-versa, customize the variable `org-latex-preview-auto-generate'."
|
||||
:global nil
|
||||
(if org-latex-preview-auto-mode
|
||||
(progn
|
||||
(add-hook 'pre-command-hook #'org-latex-preview-auto--handle-pre-cursor nil 'local)
|
||||
(org-latex-preview-auto--handle-pre-cursor) ; Invoke setup before the hook even fires.
|
||||
(add-hook 'post-command-hook #'org-latex-preview-auto--handle-post-cursor nil 'local)
|
||||
(add-hook 'post-self-insert-hook #'org-latex-preview-auto--handle-insert nil 'local))
|
||||
(remove-hook 'pre-command-hook #'org-latex-preview-auto--handle-pre-cursor 'local)
|
||||
(remove-hook 'post-command-hook #'org-latex-preview-auto--handle-post-cursor 'local)
|
||||
(remove-hook 'post-self-insert-hook #'org-latex-preview-auto--handle-insert 'local)))
|
||||
|
||||
(defun org-latex-preview-clear-overlays (&optional beg end)
|
||||
"Remove all overlays with LaTeX fragment images in current buffer.
|
||||
|
|
Loading…
Reference in New Issue