emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v3] Inline image display as part of a new org-link-preview sy


From: Karthik Chikmagalur
Subject: Re: [PATCH v3] Inline image display as part of a new org-link-preview system
Date: Mon, 02 Sep 2024 13:13:17 -0700

New patch version attached.  Not all your points were addressed, please
check my comments below.

>> Subject: [PATCH] org-link: Move inline image display to org-link
>
> The patch does a lot more than merely "moving" things around. Please,
> name it accordingly. Maybe something like
>
>    New customizable preview API for arbitrary link types

Renamed commit.

>> +(declare-function org-attach-expand "org-attach" (file))
>
> Some of these declares are now redundant.

Only one declare was redundant (org-attach-expand), fixed.

>> +     ;; C-u argument: clear image at point or in entry
>> +     ((equal arg '(4))
>> +      (if (get-char-property (point) 'org-image-overlay)
>> +          ;; clear link preview at point
>> +          (when-let ((context (org-element-context))
>> +                     ((org-element-type-p context 'link)))
>
> It will be more reliable to clear across overlay boundaries rather than
> assuming that the overlay covers exactly one link.

Fixed.

>> +BEG and END define the considered part.  They default to the
>> +buffer boundaries with possible narrowing."
>> +  (interactive "P")
>> +  (when (display-graphic-p)
>
> Do we need it here? You check graphics both here and later in the
> preview function. We may probably drop the check herein.

I moved the `refresh' clause out of the display-graphic-p check, but I
think it's needed.  Otherwise this line will run once per previewed
image instead of once per call to `org-link-preview':

(when (fboundp 'clear-image-cache) (clear-image-cache))

clearing newly placed previews (within the same run) from the cache.

That said, I have a question about `clear-image-cache' here.  Why does
Org clear the Emacs frame's entire image cache every time
`org-link-preview-region' (or the previous `org-display-inline-images')
is called?  This seems overreaching.  There are many situations even
within Org mode where we want to avoid the extra file access that
clearing the image cache will cause, like in a buffer with hundreds of
LaTeX previews.  Images in unrelated buffers/major-modes are also
affected.

>> +      (let* ((width (org-display-inline-image--width link))
>> +         (align (org-image--align link))
>> +             (image (org--create-inline-image file width)))
>
> I am wondering why you left these functions in org.el. Why not moving
> them here?

They seemed out of place in ol.el, so my plan was the following:
1. This patch: Implement org-link-preview.
2. Another patch: Move image creation/manipulation/geometry functions
from org into an org-image (or org-image-utils) library.  Require it in
ol.

However, I can move them wherever you want, let me know.

>> +        (when image            ; Add image to overlay
>> ...
>> +      (overlay-put ov 'org-image-overlay t)
>
> This is redundant, isn't it?

I don't know how create-image can fail.  Suppose a .jpg file exists but
is corrupted or does not have image/jpeg data.  Then create-image
returns nil, as does `org--create-inline-image'.  This check guards
against trying to preview an "image" that's nil.

>> +      (when (boundp 'image-map)
>
> What if not bound? Why not simply (require 'image)?

Just unmodified old code.  I've require'd image in ol now, let me know
if I should do it differently.

> ... and ORG-NEWS entry.
>
> You also need to fixup the manual where it talks about image previews.

Yes, I will do both at the end, after the changes are final and before
merging.

>> +`:preview'
>> +
>> +  Function to run to generate an in-buffer preview for the link.  It
>> +  must accept three arguments:
>> +  - an overlay placed from the start to the end of the link.
>> +  - the link path, as a string.
>> +  - the link element
>> +  This function must return a non-nil value to indicate success.
>
> I am wondering whether asynchronicity should be implemented on high
> level rather than inside individual link preview functions.
> We may, for example, first create the necessary overlays and indicate
> that they are "processing..." first, and then call the actual preview
> functions asynchronously.
>
> If we leave asynchronous handling to individual previews, they will have
> no way to handle queuing large number of link previews, and we may arrive
> at the common situation with network processes when they schedule and
> finish around the same time, hanging Emacs while it is handling
> a bunch of process sentinels.

I'm not sure how to solve this problem.  Here's what I'm picturing --
note that this doesn't actually avoid the process sentinel pile-up:

The preview provider should return its callback function to Org so Org
can run it at its convenience.

Org runs (funcall #'preview-func ov path link)

preview-func returns either
1. t, if preview is synchronous and successful,
2. nil, if preview is synchronous but failed,
3. A callback-func, if preview is asynchronous.  In this case the
preview-func starts the network request or async job.

Org adds callback-func to the queue of callback-funcs for this run, and
runs them spaced out on a timer or something.  Preview success depends
on whether the callback-func returns t or nil.

Implementing something like this should be easy with org-async, included
in the LaTeX preview patchset.

But the process sentinels will run independent of Org's callback queue.
The callbacks only place the resulting image (or other metadata) on the
overlays.  The bottleneck here is not the display engine, so this
doesn't solve the problem.

Can you give me some more details of what you have in mind?

Karthik
>From 7c2df91fb6f41e24af8ee5bee452784e896149f8 Mon Sep 17 00:00:00 2001
From: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Date: Fri, 23 Aug 2024 15:46:53 -0700
Subject: [PATCH] org-link: Customizable preview API for arbitrary link types

Add a customizable preview API for arbitrary link types.  Make
inline image previews a part of the more universal org-link
preview feature.  Each link type can now be previewed differently
based on a new link parameter.

* lisp/ol.el (org-link-parameters, org-link-preview-overlays,
org-link-preview--get-overlays, org-link-preview--remove-overlay,
org-link-previeworg-link-preview-region, org-link-preview-clear,
org-link-preview-file): Add new commands `org-link-preview',
`org-link-preview-region' and `org-link-preview-clear' for
creating link previews for any kind of link.  Add new org-link
parameter `:preview' for specifying how a link type should be
previewed.  File links and attachments are previewed using inline
image previews as before.

* testing/lisp/test-org-fold.el: Use `org-link-preview'.

* lisp/org.el (org-toggle-inline-images,
org-toggle-inline-images-command, org-display-inline-images,
org--inline-image-overlays, org-inline-image-overlays,
org-redisplay-inline-images, org-image-align,
org-display-inline-remove-overlay, org-remove-inline-images):
Obsolete and move `org-toggle-inline-images',
`org-display-inline-images' and `org-redisplay-inline-images' to
org-compat.  These are obsoleted by `org-link-preview' and
`org-link-preview-region'.  Remove
`org-toggle-inline-images-command'.  Move the other internal
functions to org-link.

* lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to
use `org-link-preview'.

* lisp/org-keys.el: Bind `C-c C-x C-v' to new command
`org-link-preview', which has the same prefix arg behaviors as
`org-latex-preview'.  In addition to these, it supports numeric
prefix args 1 and 11 to preview links with descriptions at
point/region (with 1) and across the buffer (with 11).

* lisp/org-cycle.el (org-cycle-display-inline-images): Use
`org-link-preview'.

* lisp/org-compat.el (org-display-inline-remove-overlay,
org--inline-image-overlays, org-remove-inline-images,
org-inline-image-overlays, org-display-inline-images,
org-toggle-inline-images):

* lisp/org-attach.el (org-attach-preview-file): Add new `:preview'
link parameter for links of type "attachment", set to the new
function `org-attach-preview-file'.
---
 lisp/ol.el                    | 277 ++++++++++++++++++++++++++++++++-
 lisp/org-attach.el            |  10 +-
 lisp/org-compat.el            | 189 +++++++++++++++++++++++
 lisp/org-cycle.el             |  10 +-
 lisp/org-keys.el              |   4 +-
 lisp/org-plot.el              |   2 +-
 lisp/org.el                   | 283 +---------------------------------
 testing/lisp/test-org-fold.el |   4 +-
 8 files changed, 486 insertions(+), 293 deletions(-)

diff --git a/lisp/ol.el b/lisp/ol.el
index 52ea62d69..f06fe5a34 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -33,6 +33,7 @@ (org-assert-version)
 (require 'org-compat)
 (require 'org-macs)
 (require 'org-fold)
+(require 'image)
 
 (defvar clean-buffer-list-kill-buffer-names)
 (defvar org-agenda-buffer-name)
@@ -82,6 +83,12 @@ (declare-function org-src-source-buffer "org-src" ())
 (declare-function org-src-source-type "org-src" ())
 (declare-function org-time-stamp-format "org" (&optional long inactive))
 (declare-function outline-next-heading "outline" ())
+(declare-function image-flush "image" (spec &optional frame))
+(declare-function org-entry-end-position "org" ())
+(declare-function org-element-contents-begin "org-element" (node))
+(declare-function org-display-inline-image--width "org" (link))
+(declare-function org-image--align "org" (link))
+(declare-function org--create-inline-image "org" (file width))
 
 
 ;;; Customization
@@ -171,6 +178,16 @@ (defcustom org-link-parameters nil
 
   The default face is `org-link'.
 
+`:preview'
+
+  Function to run to generate an in-buffer preview for the link.  It
+  must accept three arguments:
+  - an overlay placed from the start to the end of the link.
+  - the link path, as a string.
+  - the link element
+
+  This function must return a non-nil value to indicate success.
+
 `:help-echo'
 
   String or function used as a value for the `help-echo' text
@@ -649,6 +666,13 @@ (defvar org-link--insert-history nil
 (defvar org-link--search-failed nil
   "Non-nil when last link search failed.")
 
+(defvar-local org-link-preview-overlays nil)
+;; Preserve when switching modes or when restarting Org.
+;; If we clear the overlay list and later enable Or mode, the existing
+;; image overlays will never be cleared by `org-link-preview'
+;; and `org-link-preview-clear'.
+(put 'org-link-preview-overlays 'permanent-local t)
+
 
 ;;; Internal Functions
 
@@ -881,6 +905,30 @@ (defun org-link--file-link-to-here ()
          (setq desc search-desc))))
     (cons link desc)))
 
+(defun org-link-preview--get-overlays (&optional beg end)
+  "Return link preview overlays between BEG and END."
+  (let* ((beg (or beg (point-min)))
+         (end (or end (point-max)))
+         (overlays (overlays-in beg end))
+         result)
+    (dolist (ov overlays result)
+      (when (memq ov org-link-preview-overlays)
+        (push ov result)))))
+
+(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len)
+  "Remove link-preview overlay OV if a corresponding region is modified.
+
+AFTER is true when this function is called post-change."
+  (when (and ov after)
+    (setq org-link-preview-overlays (delete ov org-link-preview-overlays))
+    ;; Clear image from cache to avoid image not updating upon
+    ;; changing on disk.  See Emacs bug#59902.
+    (when-let* (((overlay-get ov 'org-image-overlay))
+                (disp (overlay-get ov 'display))
+                ((imagep disp)))
+      (image-flush disp))
+    (delete-overlay ov)))
+
 
 ;;; Public API
 
@@ -1573,6 +1621,192 @@ (defun org-link-add-angle-brackets (s)
   (unless (equal (substring s -1) ">") (setq s (concat s ">")))
   s)
 
+;;;###autoload
+(defun org-link-preview (&optional arg beg end)
+  "Toggle display of link previews in the buffer.
+
+When region BEG..END is active, preview links in the
+region.
+
+When point is at a link, display a preview for that link only.
+Otherwise, display previews for links in current entry.
+
+With numeric prefix ARG 1, preview links with description as
+well.
+
+With prefix ARG `\\[universal-argument]', clear link previews at
+point or in the current entry.
+
+With prefix ARG `\\[universal-argument] \\[universal-argument]',
+ display link previews in the accessible portion of the
+ buffer.  With numeric prefix ARG 11, do the same, but include
+ links with descriptions.
+
+With prefix ARG `\\[universal-argument] \\[universal-argument] 
\\[universal-argument]',
+hide all link previews in the accessible portion of the buffer.
+
+This command is designed for interactive use.  From Elisp, you can
+also use `org-link-preview-region'."
+  (interactive (cons current-prefix-arg
+                     (when (use-region-p)
+                       (list (region-beginning) (region-end)))))
+  (let* ((include-linked
+          (cond
+           ((member arg '(nil (4) (16)) ) nil)
+           ((member arg '(1 11)) 'include-linked)
+           (t 'include-linked)))
+         (interactive? (called-interactively-p 'any))
+         (toggle-previews
+          (lambda (&optional beg end scope remove)
+            (let* ((beg (or beg (point-min)))
+                   (end (or end (point-max)))
+                   (old (org-link-preview--get-overlays beg end))
+                   (scope (or scope (format "%d:%d" beg end))))
+              (if remove
+                  (progn
+                    (org-link-preview-clear beg end)
+                    (when interactive?
+                      (message
+                       "[%s] Inline link previews turned off (removed %d 
images)"
+                       scope (length old))))
+               (org-link-preview-region include-linked t beg end)
+                (when interactive?
+                  (let ((new (org-link-preview--get-overlays beg end)))
+                    (message
+                     (if new
+                        (format "[%s] %d images displayed inline %s"
+                                scope (length new)
+                                 (if include-linked "(including images with 
description)"
+                                   ""))
+                      (format "[%s] No images to display inline" 
scope))))))))))
+    (cond
+     ;; Region selected :: display previews in region.
+     ((and beg end)
+      (funcall toggle-previews beg end "region"
+               (and (equal arg '(4)) 'remove)))
+     ;; C-u argument: clear image at point or in entry
+     ((equal arg '(4))
+      (if-let ((ov (cdr (get-char-property-and-overlay
+                         (point) 'org-image-overlay))))
+          ;; clear link preview at point
+          (funcall toggle-previews
+                   (overlay-start ov) (overlay-end ov)
+                   "preview at point" 'remove)
+        ;; Clear link previews in entry
+        (funcall toggle-previews
+                 (if (org-before-first-heading-p) (point-min)
+                   (save-excursion
+                     (org-with-limited-levels (org-back-to-heading t) 
(point))))
+                 (org-with-limited-levels (org-entry-end-position))
+                 "current section" 'remove)))
+     ;; C-u C-u or C-11 argument :: display images in the whole buffer.
+     ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer"))
+     ;; C-u C-u C-u argument :: unconditionally hide images in the buffer.
+     ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove))
+     ;; Argument nil or 1, no region selected :: display images in
+     ;; current section or image link at point.
+     ((and (member arg '(nil 1)) (null beg) (null end))
+      (let ((context (org-element-context)))
+        ;; toggle display of inline image link at point.
+        (if (org-element-type-p context 'link)
+            (let* ((ov (cdr-safe (get-char-property-and-overlay
+                                  (point) 'org-image-overlay)))
+                   (remove? (and ov (memq ov org-link-preview-overlays)
+                                 'remove)))
+              (funcall toggle-previews
+                       (org-element-begin context)
+                       (org-element-end context)
+                       "image at point" remove?))
+          (let ((beg (if (org-before-first-heading-p) (point-min)
+                      (save-excursion
+                        (org-with-limited-levels (org-back-to-heading t) 
(point)))))
+                (end (org-with-limited-levels (org-entry-end-position))))
+            (funcall toggle-previews beg end "current section")))))
+     ;; Any other non-nil argument.
+     ((not (null arg)) (funcall toggle-previews beg end "region")))))
+
+(defun org-link-preview-region (&optional include-linked refresh beg end)
+  "Display link previews.
+
+A previewable link type is one that has a `:preview' link
+parameter, see `org-link-parameters'.
+
+By default, a file link or attachment is previewable if it
+follows either of these conventions:
+
+  1. Its path is a file with an extension matching return value
+     from `image-file-name-regexp' and it has no contents.
+
+  2. Its description consists in a single link of the previous
+     type.  In this case, that link must be a well-formed plain
+     or angle link, i.e., it must have an explicit \"file\" or
+     \"attachment\" type.
+
+File links are equipped with the keymap `image-map'.
+
+When optional argument INCLUDE-LINKED is non-nil, links with a
+text description part will also be inlined.  This can be nice for
+a quick look at those images, but it does not reflect what
+exported files will look like.
+
+When optional argument REFRESH is non-nil, refresh existing
+images between BEG and END.  This will create new image displays
+only if necessary.
+
+BEG and END define the considered part.  They default to the
+buffer boundaries with possible narrowing."
+  (interactive "P")
+  (when refresh (org-link-preview-clear beg end))
+  (when (display-graphic-p)
+    (when (fboundp 'clear-image-cache) (clear-image-cache)))
+  (org-with-point-at (or beg (point-min))
+    (let ((case-fold-search t))
+      (while (re-search-forward org-link-any-re end t)
+        (when-let* ((link (org-element-lineage
+                           (save-match-data (org-element-context))
+                           'link t))
+                    (linktype (org-element-property :type link))
+                    (preview-func (org-link-get-parameter linktype :preview))
+                    (path (and (or include-linked
+                                   (not (org-element-contents-begin link)))
+                               (org-element-property :path link))))
+          ;; Create an overlay to hold the preview
+          (let ((ov (make-overlay
+                     (org-element-begin link)
+                     (progn
+                      (goto-char
+                       (org-element-end link))
+                      (unless (eolp) (skip-chars-backward " \t"))
+                      (point)))))
+            ;; TODO: Change this overlay property to `org-link-preview' 
everywhere.
+            (overlay-put ov 'org-image-overlay t)
+            (overlay-put ov 'modification-hooks
+                         (list 'org-link-preview--remove-overlay))
+            ;; call preview function for link type
+            (if (funcall preview-func ov path link)
+              (when (overlay-buffer ov)
+                (push ov org-link-preview-overlays))
+              ;; Preview was unsuccessful, delete overlay
+              (delete-overlay ov))))))))
+
+(defun org-link-preview-clear (&optional beg end)
+  "Clear link previews in region BEG to END."
+  (interactive (and (use-region-p) (list (region-beginning) (region-end))))
+  (let* ((beg (or beg (point-min)))
+         (end (or end (point-max)))
+         (overlays (overlays-in beg end)))
+    (dolist (ov overlays)
+      (when (memq ov org-link-preview-overlays)
+        (when-let ((image (overlay-get ov 'display))
+                   ((imagep image)))
+          (image-flush image))
+        (setq org-link-preview-overlays (delq ov org-link-preview-overlays))
+        (delete-overlay ov)))
+    ;; Clear removed overlays.
+    (dolist (ov org-link-preview-overlays)
+      (unless (overlay-buffer ov)
+        (setq org-link-preview-overlays (delq ov 
org-link-preview-overlays))))))
+
 
 ;;; Built-in link types
 
@@ -1595,7 +1829,48 @@ (defun org-link--open-elisp (path _)
 (org-link-set-parameters "elisp" :follow #'org-link--open-elisp)
 
 ;;;; "file" link type
-(org-link-set-parameters "file" :complete #'org-link-complete-file)
+(org-link-set-parameters "file"
+                         :complete #'org-link-complete-file
+                         :preview #'org-link-preview-file)
+
+(defun org-link-preview-file (ov path link)
+  "Display image file PATH in overlay OV for LINK.
+
+LINK is the Org element being previewed.
+
+Equip each image with the keymap `image-map'.
+
+This is intended to be used as the `:preview' link property of
+file links, see `org-link-parameters'."
+  (if (not (display-graphic-p))
+      (prog1 nil
+        (message "Your Emacs does not support displaying images!"))
+    (when-let* ((file-full (expand-file-name path))
+                (file (substitute-in-file-name file-full))
+                ((string-match-p (image-file-name-regexp) file))
+                ((file-exists-p file)))
+      (let* ((width (org-display-inline-image--width link))
+            (align (org-image--align link))
+             (image (org--create-inline-image file width)))
+        (when image            ; Add image to overlay
+         ;; See bug#59902.  We cannot rely
+          ;; on Emacs to update image if the file
+          ;; has changed.
+          (image-flush image)
+         (overlay-put ov 'display image)
+         (overlay-put ov 'face 'default)
+         (overlay-put ov 'org-image-overlay t)
+         (overlay-put ov 'keymap image-map)
+          (when align
+            (overlay-put
+             ov 'before-string
+             (propertize
+              " " 'face 'default
+              'display
+              (pcase align
+                ("center" `(space :align-to (- center (0.5 . ,image))))
+                ("right"  `(space :align-to (- right ,image)))))))
+          t)))))
 
 ;;;; "help" link type
 (defun org-link--open-help (path _)
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 7a03d170e..4b0887fcd 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -797,9 +797,17 @@ (defun org-attach-follow (file arg)
 See `org-open-file' for details about ARG."
   (org-link-open-as-file (org-attach-expand file) arg))
 
+(defun org-attach-preview-file (ov path link)
+  "Preview attachment with PATH in overlay OV.
+
+LINK is the Org link element being previewed."
+  (org-link-preview-file
+   ov (org-attach-expand path) link))
+
 (org-link-set-parameters "attachment"
                         :follow #'org-attach-follow
-                         :complete #'org-attach-complete-link)
+                         :complete #'org-attach-complete-link
+                         :preview #'org-attach-preview-file)
 
 (defun org-attach-complete-link ()
   "Advise the user with the available files in the attachment directory."
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index d843216f3..242b46a86 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -783,6 +783,195 @@ (defun org-add-link-type (type &optional follow export)
 
 (make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." 
"9.0")
 
+(declare-function org-link-preview--remove-overlay "ol")
+(declare-function org-link-preview--get-overlays "ol")
+(declare-function org-link-preview-clear "ol")
+(declare-function org-link-preview--remove-overlay "ol")
+
+(define-obsolete-function-alias 'org-display-inline-remove-overlay
+  'org-link-preview--remove-overlay "9.8")
+(define-obsolete-function-alias 'org--inline-image-overlays
+  'org-link-preview--get-overlays "9.8")
+(define-obsolete-function-alias 'org-remove-inline-images
+  'org-link-preview-clear "9.8")
+(define-obsolete-variable-alias 'org-inline-image-overlays
+  'org-link-preview-overlays "9.8")
+(defvar org-link-preview-overlays)
+(defvar org-link-abbrev-alist-local)
+(defvar org-link-abbrev-alist)
+(defvar org-link-angle-re)
+(defvar org-link-plain-re)
+(declare-function org-attach-expand "org-attach")
+(declare-function org-display-inline-image--width "org")
+(declare-function org-image--align "org")
+(declare-function org--create-inline-image "org")
+
+(make-obsolete 'org-display-inline-images
+               'org-link-preview-region "9.8")
+;; FIXME: Unused; obsoleted; to be removed
+(defun org-display-inline-images (&optional include-linked refresh beg end)
+  "Display inline images.
+
+An inline image is a link which follows either of these
+conventions:
+
+  1. Its path is a file with an extension matching return value
+     from `image-file-name-regexp' and it has no contents.
+
+  2. Its description consists in a single link of the previous
+     type.  In this case, that link must be a well-formed plain
+     or angle link, i.e., it must have an explicit \"file\" or
+     \"attachment\" type.
+
+Equip each image with the key-map `image-map'.
+
+When optional argument INCLUDE-LINKED is non-nil, also links with
+a text description part will be inlined.  This can be nice for
+a quick look at those images, but it does not reflect what
+exported files will look like.
+
+When optional argument REFRESH is non-nil, refresh existing
+images between BEG and END.  This will create new image displays
+only if necessary.
+
+BEG and END define the considered part.  They default to the
+buffer boundaries with possible narrowing."
+  (interactive "P")
+  (when (display-graphic-p)
+    (when refresh
+      (org-link-preview-clear beg end)
+      (when (fboundp 'clear-image-cache) (clear-image-cache)))
+    (let ((end (or end (point-max))))
+      (org-with-point-at (or beg (point-min))
+       (let* ((case-fold-search t)
+              (file-extension-re (image-file-name-regexp))
+              (link-abbrevs (mapcar #'car
+                                    (append org-link-abbrev-alist-local
+                                            org-link-abbrev-alist)))
+              ;; Check absolute, relative file names and explicit
+              ;; "file:" links.  Also check link abbreviations since
+              ;; some might expand to "file" links.
+              (file-types-re
+               (format 
"\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)"
+                       (if (not link-abbrevs) ""
+                         (concat "\\|" (regexp-opt link-abbrevs))))))
+         (while (re-search-forward file-types-re end t)
+           (let* ((link (org-element-lineage
+                         (save-match-data (org-element-context))
+                         'link t))
+                   (linktype (org-element-property :type link))
+                  (inner-start (match-beginning 1))
+                  (path
+                   (cond
+                    ;; No link at point; no inline image.
+                    ((not link) nil)
+                    ;; File link without a description.  Also handle
+                    ;; INCLUDE-LINKED here since it should have
+                    ;; precedence over the next case.  I.e., if link
+                    ;; contains filenames in both the path and the
+                    ;; description, prioritize the path only when
+                    ;; INCLUDE-LINKED is non-nil.
+                    ((or (not (org-element-contents-begin link))
+                         include-linked)
+                     (and (or (equal "file" linktype)
+                               (equal "attachment" linktype))
+                          (org-element-property :path link)))
+                    ;; Link with a description.  Check if description
+                    ;; is a filename.  Even if Org doesn't have syntax
+                    ;; for those -- clickable image -- constructs, fake
+                    ;; them, as in `org-export-insert-image-links'.
+                    ((not inner-start) nil)
+                    (t
+                     (org-with-point-at inner-start
+                       (and (looking-at
+                             (if (char-equal ?< (char-after inner-start))
+                                 org-link-angle-re
+                               org-link-plain-re))
+                            ;; File name must fill the whole
+                            ;; description.
+                            (= (org-element-contents-end link)
+                               (match-end 0))
+                            (progn
+                               (setq linktype (match-string 1))
+                               (match-string 2))))))))
+             (when (and path (string-match-p file-extension-re path))
+               (let ((file (if (equal "attachment" linktype)
+                               (progn
+                                  (require 'org-attach)
+                                 (ignore-errors (org-attach-expand path)))
+                              (expand-file-name path))))
+                  ;; Expand environment variables.
+                  (when file (setq file (substitute-in-file-name file)))
+                 (when (and file (file-exists-p file))
+                   (let ((width (org-display-inline-image--width link))
+                         (align (org-image--align link))
+                          (old (get-char-property-and-overlay
+                               (org-element-begin link)
+                               'org-image-overlay)))
+                     (if (and (car-safe old) refresh)
+                          (image-flush (overlay-get (cdr old) 'display))
+                       (let ((image (org--create-inline-image file width)))
+                         (when image
+                           (let ((ov (make-overlay
+                                      (org-element-begin link)
+                                      (progn
+                                        (goto-char
+                                         (org-element-end link))
+                                        (unless (eolp) (skip-chars-backward " 
\t"))
+                                        (point)))))
+                              ;; See bug#59902.  We cannot rely
+                              ;; on Emacs to update image if the file
+                              ;; has changed.
+                              (image-flush image)
+                             (overlay-put ov 'display image)
+                             (overlay-put ov 'face 'default)
+                             (overlay-put ov 'org-image-overlay t)
+                             (overlay-put
+                              ov 'modification-hooks
+                              (list 'org-link-preview--remove-overlay))
+                             (when (boundp 'image-map)
+                               (overlay-put ov 'keymap image-map))
+                              (when align
+                                (overlay-put
+                                 ov 'before-string
+                                 (propertize
+                                  " " 'face 'default
+                                  'display
+                                  (pcase align
+                                    ("center" `(space :align-to (- center (0.5 
. ,image))))
+                                    ("right"  `(space :align-to (- right 
,image)))))))
+                             (push ov org-inline-image-overlays))))))))))))))))
+
+(make-obsolete 'org-toggle-inline-images
+               'org-link-preview "9.8")
+;; FIXME: Unused; obsoleted; to be removed
+(defun org-toggle-inline-images (&optional include-linked beg end)
+  "Toggle the display of inline images.
+INCLUDE-LINKED is passed to `org-display-inline-images'."
+  (interactive "P")
+  (if (org-link-preview--get-overlays beg end)
+      (progn
+        (org-link-preview-clear beg end)
+        (when (called-interactively-p 'interactive)
+         (message "Inline image display turned off")))
+    (org-display-inline-images include-linked nil beg end)
+    (when (called-interactively-p 'interactive)
+      (let ((new (org-link-preview--get-overlays beg end)))
+        (message (if new
+                    (format "%d images displayed inline"
+                            (length new))
+                  "No images to display inline"))))))
+
+(make-obsolete 'org-redisplay-inline-images
+               'org-link-preview "9.8")
+;; FIXME: Unused; obsoleted; to be removed
+(defun org-redisplay-inline-images ()
+  "Assure display of inline images and refresh them."
+  (interactive)
+  (org-toggle-inline-images)
+  (unless org-link-preview-overlays
+    (org-toggle-inline-images)))
+
 ;;;; Functions unused in Org core.
 (defun org-table-recognize-table.el ()
   "If there is a table.el table nearby, recognize it and move into it."
diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el
index 8a39bdb8c..1a1c916bd 100644
--- a/lisp/org-cycle.el
+++ b/lisp/org-cycle.el
@@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" 
(property node))
 (declare-function org-element-post-affiliated "org-element" (node))
 (declare-function org-element-lineage "org-element-ast" (datum &optional types 
with-self))
 (declare-function org-element-at-point "org-element" (&optional pom 
cached-only))
-(declare-function org-display-inline-images "org" (&optional include-linked 
refresh beg end))
+(declare-function org-link-preview-region "ol" (&optional include-linked 
refresh beg end))
 (declare-function org-get-tags "org" (&optional pos local fontify))
 (declare-function org-subtree-end-visible-p "org" ())
 (declare-function org-narrow-to-subtree "org" (&optional element))
 (declare-function org-next-visible-heading "org" (arg))
 (declare-function org-at-property-p "org" ())
 (declare-function org-re-property "org" (property &optional literal allow-null 
value))
-(declare-function org-remove-inline-images "org" (&optional beg end))
+(declare-function org-link-preview-clear "ol" (&optional beg end))
 (declare-function org-item-beginning-re "org" ())
 (declare-function org-at-heading-p "org" (&optional invisible-not-ok))
 (declare-function org-at-item-p "org" ())
@@ -817,19 +817,19 @@ (defun org-cycle-display-inline-images (state)
         ;; If has nested headlines, beg,end only from parent headline
         ;; to first child headline which reference to upper
         ;; let-binding `org-next-visible-heading'.
-        (org-display-inline-images
+        (org-link-preview-region
          nil nil
          (point-min) (progn (org-next-visible-heading 1) (point)))))
       ('subtree
        (org-with-wide-buffer
         (org-narrow-to-subtree)
         ;; If has nested headlines, also inline display images under all 
sub-headlines.
-        (org-display-inline-images nil nil (point-min) (point-max))))
+        (org-link-preview-region nil nil (point-min) (point-max))))
       ('folded
        (org-with-wide-buffer
         (org-narrow-to-subtree)
         (if (numberp (point-max))
-            (org-remove-inline-images (point-min) (point-max))
+            (org-link-preview-clear (point-min) (point-max))
           (ignore)))))))
 
 (provide 'org-cycle)
diff --git a/lisp/org-keys.el b/lisp/org-keys.el
index 1daedaae8..77cbe5c0f 100644
--- a/lisp/org-keys.el
+++ b/lisp/org-keys.el
@@ -218,7 +218,7 @@ (declare-function org-toggle-checkbox "org" (&optional 
toggle-presence))
 (declare-function org-toggle-radio-button "org" (&optional arg))
 (declare-function org-toggle-comment "org" ())
 (declare-function org-toggle-fixed-width "org" ())
-(declare-function org-toggle-inline-images-command "org" (&optional arg beg 
end))
+(declare-function org-link-preview "ol" (&optional arg beg end))
 (declare-function org-latex-preview "org" (&optional arg))
 (declare-function org-toggle-narrow-to-subtree "org" ())
 (declare-function org-toggle-ordered-property "org" ())
@@ -618,7 +618,7 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") 
#'org-clock-display)
 (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock)
 (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update)
 (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview)
-(org-defkey org-mode-map (kbd "C-c C-x C-v") 
#'org-toggle-inline-images-command)
+(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview)
 (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images)
 (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities)
 (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox)
diff --git a/lisp/org-plot.el b/lisp/org-plot.el
index b045344f0..836cfaffc 100644
--- a/lisp/org-plot.el
+++ b/lisp/org-plot.el
@@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols 
params &optional prefac
 
 (defun org-plot/redisplay-img-in-buffer (img-file)
   "Find any overlays for IMG-FILE in the current Org buffer, and refresh them."
-  (dolist (img-overlay org-inline-image-overlays)
+  (dolist (img-overlay org-link-preview-overlays)
     (when (string= img-file (plist-get (cdr (overlay-get img-overlay 
'display)) :file))
       (when (and (file-exists-p img-file)
                  (fboundp 'image-flush))
diff --git a/lisp/org.el b/lisp/org.el
index d5c1dcb35..091a09344 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -5079,7 +5079,7 @@ (define-derived-mode org-mode outline-mode "Org"
     ;; modifications to make cache updates work reliably.
     (org-unmodified
      (when org-startup-with-beamer-mode (org-beamer-mode))
-     (when org-startup-with-inline-images (org-display-inline-images))
+     (when org-startup-with-inline-images (org-link-preview '(16)))
      (when org-startup-with-latex-preview (org-latex-preview '(16)))
      (unless org-inhibit-startup-visibility-stuff 
(org-cycle-set-startup-visibility))
      (when org-startup-truncated (setq truncate-lines t))
@@ -16651,126 +16651,6 @@ (defun org-normalize-color (value)
 
 ;; Image display
 
-(defvar-local org-inline-image-overlays nil)
-;; Preserve when switching modes or when restarting Org.
-;; If we clear the overlay list and later enable Or mode, the existing
-;; image overlays will never be cleared by `org-toggle-inline-images'
-;; and `org-toggle-inline-images-command'.
-(put 'org-inline-image-overlays 'permanent-local t)
-
-(defun org--inline-image-overlays (&optional beg end)
-  "Return image overlays between BEG and END."
-  (let* ((beg (or beg (point-min)))
-         (end (or end (point-max)))
-         (overlays (overlays-in beg end))
-         result)
-    (dolist (ov overlays result)
-      (when (memq ov org-inline-image-overlays)
-        (push ov result)))))
-
-(defun org-toggle-inline-images-command (&optional arg beg end)
-  "Toggle display of inline images without description at point.
-
-When point is at an image link, toggle displaying that image.
-Otherwise, toggle displaying images in current entry.
-
-When region BEG..END is active, toggle displaying images in the
-region.
-
-With numeric prefix ARG 1, display images with description as well.
-
-With prefix ARG `\\[universal-argument]', toggle displaying images in
-the accessible portion of the buffer.  With numeric prefix ARG 11, do
-the same, but include images with description.
-
-With prefix ARG `\\[universal-argument] \\[universal-argument]', hide
-all the images in accessible portion of the buffer.
-
-This command is designed for interactive use.  From Elisp, you can
-also use `org-toggle-inline-images'."
-  (interactive (cons current-prefix-arg
-                     (when (use-region-p)
-                       (list (region-beginning) (region-end)))))
-  (let* ((include-linked
-          (cond
-           ((member arg '(nil (4) (16)) ) nil)
-           ((member arg '(1 11)) 'include-linked)
-           (t 'include-linked)))
-         (interactive? (called-interactively-p 'any))
-         (toggle-images
-          (lambda (&optional beg end scope force-remove)
-            (let* ((beg (or beg (point-min)))
-                   (end (or end (point-max)))
-                   (old (org--inline-image-overlays beg end))
-                   (scope (or scope (format "%d:%d" beg end))))
-              (if (or old force-remove)
-                  (progn
-                    (org-remove-inline-images beg end)
-                    (when interactive?
-                      (message
-                       "[%s] Inline image display turned off (removed %d 
images)"
-                       scope (length old))))
-               (org-display-inline-images include-linked t beg end)
-                (when interactive?
-                  (let ((new (org--inline-image-overlays beg end)))
-                    (message
-                     (if new
-                        (format "[%s] %d images displayed inline %s"
-                                scope (length new)
-                                 (if include-linked "(including images with 
description)"
-                                   ""))
-                      (format "[%s] No images to display inline" 
scope))))))))))
-    (cond
-     ((not (display-graphic-p))
-      (message "Your Emacs does not support displaying images!"))
-     ;; Region selected :: toggle images in region.
-     ((and beg end) (funcall toggle-images beg end "region"))
-     ;; C-u or C-11 argument :: toggle images in the whole buffer.
-     ((member arg '(11 (4))) (funcall toggle-images nil nil "buffer"))
-     ;; C-u C-u argument :: unconditionally hide images in the buffer.
-     ((equal arg '(16)) (funcall toggle-images nil nil "buffer" 'remove))
-     ;; Argument nil or 1, no region selected :: toggle (display or hide
-     ;; dwim) images in current section or image link at point.
-     ((and (member arg '(nil 1)) (null beg) (null end))
-      (let ((context (org-element-context)))
-        ;; toggle display of inline image link at point.
-        (if (org-element-type-p context 'link)
-            (funcall toggle-images
-                     (org-element-begin context)
-                     (org-element-end context)
-                     "image at point")
-          (let ((beg (if (org-before-first-heading-p) (point-min)
-                      (save-excursion
-                        (org-with-limited-levels (org-back-to-heading t) 
(point)))))
-                (end (org-with-limited-levels (org-entry-end-position))))
-            (funcall toggle-images beg end "current section")))))
-     ;; Any other non-nil argument.
-     ((not (null arg)) (funcall toggle-images beg end "region")))))
-
-(defun org-toggle-inline-images (&optional include-linked beg end)
-  "Toggle the display of inline images.
-INCLUDE-LINKED is passed to `org-display-inline-images'."
-  (interactive "P")
-  (if (org--inline-image-overlays beg end)
-      (progn
-        (org-remove-inline-images beg end)
-        (when (called-interactively-p 'interactive)
-         (message "Inline image display turned off")))
-    (org-display-inline-images include-linked nil beg end)
-    (when (called-interactively-p 'interactive)
-      (let ((new (org--inline-image-overlays beg end)))
-        (message (if new
-                    (format "%d images displayed inline"
-                            (length new))
-                  "No images to display inline"))))))
-
-(defun org-redisplay-inline-images ()
-  "Assure display of inline images and refresh them."
-  (interactive)
-  (org-toggle-inline-images)
-  (unless org-inline-image-overlays
-    (org-toggle-inline-images)))
-
 ;; For without-x builds.
 (declare-function image-flush "image" (spec &optional frame))
 
@@ -16793,7 +16673,7 @@ (defcustom org-display-remote-inline-images 'skip
   :safe #'symbolp)
 
 (defcustom org-image-align 'left
-  "How to align images previewed using `org-display-inline-images'.
+  "How to align images previewed using `org-link-preview-region'.
 
 Only stand-alone image links are affected by this setting.  These
 are links without surrounding text.
@@ -16850,139 +16730,6 @@ (defun org--create-inline-image (file width)
                                 org-image-max-width)))
                     :scale 1))))
 
-(defun org-display-inline-images (&optional include-linked refresh beg end)
-  "Display inline images.
-
-An inline image is a link which follows either of these
-conventions:
-
-  1. Its path is a file with an extension matching return value
-     from `image-file-name-regexp' and it has no contents.
-
-  2. Its description consists in a single link of the previous
-     type.  In this case, that link must be a well-formed plain
-     or angle link, i.e., it must have an explicit \"file\" or
-     \"attachment\" type.
-
-Equip each image with the key-map `image-map'.
-
-When optional argument INCLUDE-LINKED is non-nil, also links with
-a text description part will be inlined.  This can be nice for
-a quick look at those images, but it does not reflect what
-exported files will look like.
-
-When optional argument REFRESH is non-nil, refresh existing
-images between BEG and END.  This will create new image displays
-only if necessary.
-
-BEG and END define the considered part.  They default to the
-buffer boundaries with possible narrowing."
-  (interactive "P")
-  (when (display-graphic-p)
-    (when refresh
-      (org-remove-inline-images beg end)
-      (when (fboundp 'clear-image-cache) (clear-image-cache)))
-    (let ((end (or end (point-max))))
-      (org-with-point-at (or beg (point-min))
-       (let* ((case-fold-search t)
-              (file-extension-re (image-file-name-regexp))
-              (link-abbrevs (mapcar #'car
-                                    (append org-link-abbrev-alist-local
-                                            org-link-abbrev-alist)))
-              ;; Check absolute, relative file names and explicit
-              ;; "file:" links.  Also check link abbreviations since
-              ;; some might expand to "file" links.
-              (file-types-re
-               (format 
"\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)"
-                       (if (not link-abbrevs) ""
-                         (concat "\\|" (regexp-opt link-abbrevs))))))
-         (while (re-search-forward file-types-re end t)
-           (let* ((link (org-element-lineage
-                         (save-match-data (org-element-context))
-                         'link t))
-                   (linktype (org-element-property :type link))
-                  (inner-start (match-beginning 1))
-                  (path
-                   (cond
-                    ;; No link at point; no inline image.
-                    ((not link) nil)
-                    ;; File link without a description.  Also handle
-                    ;; INCLUDE-LINKED here since it should have
-                    ;; precedence over the next case.  I.e., if link
-                    ;; contains filenames in both the path and the
-                    ;; description, prioritize the path only when
-                    ;; INCLUDE-LINKED is non-nil.
-                    ((or (not (org-element-contents-begin link))
-                         include-linked)
-                     (and (or (equal "file" linktype)
-                               (equal "attachment" linktype))
-                          (org-element-property :path link)))
-                    ;; Link with a description.  Check if description
-                    ;; is a filename.  Even if Org doesn't have syntax
-                    ;; for those -- clickable image -- constructs, fake
-                    ;; them, as in `org-export-insert-image-links'.
-                    ((not inner-start) nil)
-                    (t
-                     (org-with-point-at inner-start
-                       (and (looking-at
-                             (if (char-equal ?< (char-after inner-start))
-                                 org-link-angle-re
-                               org-link-plain-re))
-                            ;; File name must fill the whole
-                            ;; description.
-                            (= (org-element-contents-end link)
-                               (match-end 0))
-                            (progn
-                               (setq linktype (match-string 1))
-                               (match-string 2))))))))
-             (when (and path (string-match-p file-extension-re path))
-               (let ((file (if (equal "attachment" linktype)
-                               (progn
-                                  (require 'org-attach)
-                                 (ignore-errors (org-attach-expand path)))
-                              (expand-file-name path))))
-                  ;; Expand environment variables.
-                  (when file (setq file (substitute-in-file-name file)))
-                 (when (and file (file-exists-p file))
-                   (let ((width (org-display-inline-image--width link))
-                         (align (org-image--align link))
-                          (old (get-char-property-and-overlay
-                               (org-element-begin link)
-                               'org-image-overlay)))
-                     (if (and (car-safe old) refresh)
-                          (image-flush (overlay-get (cdr old) 'display))
-                       (let ((image (org--create-inline-image file width)))
-                         (when image
-                           (let ((ov (make-overlay
-                                      (org-element-begin link)
-                                      (progn
-                                        (goto-char
-                                         (org-element-end link))
-                                        (unless (eolp) (skip-chars-backward " 
\t"))
-                                        (point)))))
-                              ;; See bug#59902.  We cannot rely
-                              ;; on Emacs to update image if the file
-                              ;; has changed.
-                              (image-flush image)
-                             (overlay-put ov 'display image)
-                             (overlay-put ov 'face 'default)
-                             (overlay-put ov 'org-image-overlay t)
-                             (overlay-put
-                              ov 'modification-hooks
-                              (list 'org-display-inline-remove-overlay))
-                             (when (boundp 'image-map)
-                               (overlay-put ov 'keymap image-map))
-                              (when align
-                                (overlay-put
-                                 ov 'before-string
-                                 (propertize
-                                  " " 'face 'default
-                                  'display
-                                  (pcase align
-                                    ("center" `(space :align-to (- center (0.5 
. ,image))))
-                                    ("right"  `(space :align-to (- right 
,image)))))))
-                             (push ov org-inline-image-overlays))))))))))))))))
-
 (declare-function org-export-read-attribute "ox"
                   (attribute element &optional property))
 (defvar visual-fill-column-width) ; Silence compiler warning
@@ -17135,32 +16882,6 @@ (defun org-image--align (link)
             (when (memq org-image-align '(center right))
               (symbol-name org-image-align))))))))
 
-
-(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len)
-  "Remove inline-display overlay if a corresponding region is modified."
-  (when (and ov after)
-    (setq org-inline-image-overlays (delete ov org-inline-image-overlays))
-    ;; Clear image from cache to avoid image not updating upon
-    ;; changing on disk.  See Emacs bug#59902.
-    (when (overlay-get ov 'org-image-overlay)
-      (image-flush (overlay-get ov 'display)))
-    (delete-overlay ov)))
-
-(defun org-remove-inline-images (&optional beg end)
-  "Remove inline display of images."
-  (interactive)
-  (let* ((beg (or beg (point-min)))
-         (end (or end (point-max)))
-         (overlays (overlays-in beg end)))
-    (dolist (ov overlays)
-      (when (memq ov org-inline-image-overlays)
-        (setq org-inline-image-overlays (delq ov org-inline-image-overlays))
-        (delete-overlay ov)))
-    ;; Clear removed overlays.
-    (dolist (ov org-inline-image-overlays)
-      (unless (overlay-buffer ov)
-        (setq org-inline-image-overlays (delq ov 
org-inline-image-overlays))))))
-
 (defvar org-self-insert-command-undo-counter 0)
 (defvar org-speed-command nil)
 
diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el
index f58642be6..809738f6c 100644
--- a/testing/lisp/test-org-fold.el
+++ b/testing/lisp/test-org-fold.el
@@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images 
()
       (org-show-subtree)
       (org-fold-subtree t)
       (run-hook-with-args 'org-cycle-hook 'folded)
-      (should-not org-inline-image-overlays)
+      (should-not org-link-preview-overlays)
       (should-not
        (cl-every
         (lambda (ov) (overlay-get ov 'org-image-overlay))
         (overlays-in (point-min) (point-max))))
       (org-show-subtree)
       (run-hook-with-args 'org-cycle-hook 'subtree)
-      (should org-inline-image-overlays)
+      (should org-link-preview-overlays)
       (should
        (cl-every
         (lambda (ov) (overlay-get ov 'org-image-overlay))
-- 
2.44.1


reply via email to

[Prev in Thread] Current Thread [Next in Thread]