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: Tue, 10 Sep 2024 12:43:47 -0700

Latest iteration of patch attached.

>>>> +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.
>
> Sorry, I was not clear. I only referred to `display-graphic-p' check. I
> did not mean `clear-image-cache'.

Removed the display-graphic-p check.

>> That said, I have a question about `clear-image-cache' here.  Why does

[...]

>> LaTeX previews.  Images in unrelated buffers/major-modes are also
>> affected.
>
> Here is the original code:
>
> (when refresh
>       (org-remove-inline-images beg end)
>       (when (fboundp 'clear-image-cache) (clear-image-cache)))
>
> Image cache is cleared _only_ with REFRESH argument.
> I think that makes sense, right?

Yes.  But `org-link-preview-region' is always called with the REFRESH
argument set to `t' though.

>>> 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.
>
> I'd rather see them moved.
> You can later send another patch moving them into the new utils library,
> if you want.

Okay, I moved them to ol.el.  They seem out of place here, but I can
send another patch after merging this one to create org-image.el.

>> 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.
>
> Let me elaborate.
>
> In `org-link-preview-region', you set org-image-overlay property:
>
>                            +            (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)
>
> Then, you do it yet again in `org-link-preview-file', when the property
> is already there - this part is redundant.

Fixed.

>>>> +    (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.
>
> Let's not require it on top level. Maybe better do it within the preview
> function.

Moved (require 'image) to inside the `org-link-preview-file'.  I know it
doesn't affect performance in any reasonable way, but I'm usually
hesitant to do this in functions that run inside loops.

> I mostly meant calling preview-func asynchronously, while idle, spaced
> out, spending not longer than a fraction of second to call several
> preview-funcs.
> Spacing might then be controlled by the users.
>
> We might go further, and let the preview functions return a
> process. Then, we may explicitly control running sentinels just for that
> process via `accept-process-output'. But I am not sure if we need to go
> that far.

Following our other discussion in this thread, I've now implemented
batched previews via idle-timers, controlled by `org-link-preview-delay'
and `org-link-preview-batch-size'.  The default values of these
parameters needs to be tuned.  I tested a few combinations but couldn't
perceive a difference in lag or in the profiler.

The only unhandled case is when an asynchronous preview (in the sense
that preview-func is itself asynchronous) fails.  It is then the
responsibility of preview-func's callback to clean up the overlay.  This
can be done by calling org-link-preview-clear on the bounds of the link.
I think this is sufficient for now.

>From 135735097a4ab9d53f3a7ffef79b34c604cb64b9 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-batch-size,
org-link-preview-delay, 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, org-display-remote-inline-images,
org-image-align, org--create-inline-image,
org-display-inline-image--width, org-image--align): 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.  This link parameter is a
function called asynchronously to place previes.  File links and
attachments are previewed using inline image previews as before.
Move image handling utilities from lisp/org.el to lisp/ol.el.

* 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                    | 565 +++++++++++++++++++++++++++++++++-
 lisp/org-attach.el            |  11 +-
 lisp/org-compat.el            | 189 ++++++++++++
 lisp/org-cycle.el             |  10 +-
 lisp/org-keys.el              |   4 +-
 lisp/org-plot.el              |   2 +-
 lisp/org.el                   | 534 +-------------------------------
 testing/lisp/test-org-fold.el |   4 +-
 8 files changed, 774 insertions(+), 545 deletions(-)

diff --git a/lisp/ol.el b/lisp/ol.el
index 52ea62d69..dad84216b 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -82,6 +82,11 @@ (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-element-contents-end "org-element" (node))
+(declare-function org-property-or-variable-value "org" (var &optional inherit))
 
 
 ;;; Customization
@@ -171,6 +176,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
@@ -521,6 +536,80 @@ (defcustom org-link-keep-stored-after-insertion nil
   :type 'boolean
   :safe #'booleanp)
 
+(defcustom org-link-preview-delay 0.05
+  "Idle delay in seconds between link previews when using
+`org-link-preview'.  Links are previewed in batches (see
+`org-link-preview-batch-size') spaced out by this delay.  Set
+this to a small number for more immediate previews, but at the
+expense of higher lag."
+  :group 'org-link
+  :type 'number)
+
+(defcustom org-link-preview-batch-size 6
+  "Number of links that are previewed at once with
+`org-link-preview'.  Links are previewed in batches spaced out in
+time (see `org-link-preview-delay').  Set this to a large integer
+for more immediate previews, but at the expense of higher lag."
+  :group 'org-link
+  :type 'natnum)
+
+(defcustom org-display-remote-inline-images 'skip
+  "How to display remote inline images.
+Possible values of this option are:
+
+skip        Don't display remote images.
+download    Always download and display remote images.
+t
+cache       Display remote images, and open them in separate buffers
+            for caching.  Silently update the image buffer when a file
+            change is detected."
+  :group 'org-appearance
+  :package-version '(Org . "9.7")
+  :type '(choice
+         (const :tag "Ignore remote images" skip)
+         (const :tag "Always display remote images" download)
+         (const :tag "Display and silently update remote images" cache))
+  :safe #'symbolp)
+
+(defcustom org-image-max-width 'fill-column
+  "When non-nil, limit the displayed image width.
+This setting only takes effect when `org-image-actual-width' is set to
+t or when #+ATTR* is set to t.
+
+Possible values:
+- `fill-column' :: limit width to `fill-column'
+- `window'      :: limit width to window width
+- integer       :: limit width to number in pixels
+- float         :: limit width to that fraction of window width
+- nil             :: do not limit image width"
+  :group 'org-appearance
+  :package-version '(Org . "9.7")
+  :type '(choice
+          (const :tag "Do not limit image width" nil)
+          (const :tag "Limit to `fill-column'" fill-column)
+          (const :tag "Limit to window width" window)
+          (integer :tag "Limit to a number of pixels")
+          (float :tag "Limit to a fraction of window width")))
+
+(defcustom org-image-align 'left
+  "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.
+
+Possible values of this option are:
+
+left     Insert image at specified position.
+center   Center image previews.
+right    Right-align image previews."
+  :group 'org-appearance
+  :package-version '(Org . "9.7")
+  :type '(choice
+          (const :tag "Left align (or don\\='t align) image previews" left)
+         (const :tag "Center image previews" center)
+         (const :tag "Right align image previews" right))
+  :safe #'symbolp)
+
 ;;; Public variables
 
 (defconst org-target-regexp (let ((border "[^<>\n\r \t]"))
@@ -649,6 +738,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,7 +977,228 @@ (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 (delq 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)))
+
 
+
+;;;; Utilities for image preview display
+
+;; For without-x builds.
+(declare-function image-flush "image" (spec &optional frame))
+
+(defun org--create-inline-image (file width)
+  "Create image located at FILE, or return nil.
+WIDTH is the width of the image.  The image may not be created
+according to the value of `org-display-remote-inline-images'."
+  (let* ((remote? (file-remote-p file))
+        (file-or-data
+         (pcase org-display-remote-inline-images
+           ((guard (not remote?)) file)
+           (`download (with-temp-buffer
+                        (set-buffer-multibyte nil)
+                        (insert-file-contents-literally file)
+                        (buffer-string)))
+           ((or `cache `t)
+             (let ((revert-without-query '(".")))
+              (with-current-buffer (find-file-noselect file)
+                (buffer-string))))
+           (`skip nil)
+           (other
+            (message "Invalid value of `org-display-remote-inline-images': %S"
+                     other)
+            nil))))
+    (when file-or-data
+      (create-image file-or-data
+                   (and (image-type-available-p 'imagemagick)
+                        width
+                        'imagemagick)
+                   remote?
+                   :width width
+                    :max-width
+                    (pcase org-image-max-width
+                      (`fill-column (* fill-column (frame-char-width 
(selected-frame))))
+                      (`window (window-width nil t))
+                      ((pred integerp) org-image-max-width)
+                      ((pred floatp) (floor (* org-image-max-width 
(window-width nil t))))
+                      (`nil nil)
+                      (_ (error "Unsupported value of `org-image-max-width': 
%S"
+                                org-image-max-width)))
+                    :scale 1))))
+
+(declare-function org-export-read-attribute "ox"
+                  (attribute element &optional property))
+(defvar visual-fill-column-width) ; Silence compiler warning
+(defun org-display-inline-image--width (link)
+  "Determine the display width of the image LINK, in pixels.
+- When `org-image-actual-width' is t, the image's pixel width is used.
+- When `org-image-actual-width' is a number, that value will is used.
+- When `org-image-actual-width' is nil or a list, :width attribute of
+  #+attr_org or the first #+attr_...  (if it exists) is used to set the
+  image width.  A width of X% is divided by 100.  If the value is a
+  float between 0 and 2, it interpreted as that proportion of the text
+  width in the buffer.
+
+  If no :width attribute is given and `org-image-actual-width' is a
+  list with a number as the car, then that number is used as the
+  default value."
+  ;; Apply `org-image-actual-width' specifications.
+  ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified
+  ;; width.
+  (let ((org-image-actual-width (org-property-or-variable-value 
'org-image-actual-width)))
+    (cond
+     ((eq org-image-actual-width t) nil)
+     ((listp org-image-actual-width)
+      (require 'ox)
+      (let* ((par (org-element-lineage link 'paragraph))
+             ;; Try to find an attribute providing a :width.
+             ;; #+ATTR_ORG: :width ...
+             (attr-width (org-export-read-attribute :attr_org par :width))
+             (width-unreadable?
+              (lambda (value)
+                (or (not (stringp value))
+                    (unless (string= value "t")
+                      (or (not (string-match
+                              (rx bos (opt "+")
+                                  (or
+                                   ;; Number of pixels
+                                   ;; must be a lone number, not
+                                   ;; things like 4in
+                                   (seq (1+ (in "0-9")) eos)
+                                   ;; Numbers ending with %
+                                   (seq (1+ (in "0-9.")) (group-n 1 "%"))
+                                   ;; Fractions
+                                   (seq (0+ (in "0-9")) "." (1+ (in "0-9")))))
+                              value))
+                          (let ((number (string-to-number value)))
+                            (and (floatp number)
+                                 (not (match-string 1 value)) ; X%
+                                 (not (<= 0.0 number 2.0)))))))))
+             ;; #+ATTR_BACKEND: :width ...
+             (attr-other
+              (catch :found
+                (org-element-properties-map
+                 (lambda (prop _)
+                   (when (and
+                          (not (eq prop :attr_org))
+                          (string-match-p "^:attr_" (symbol-name prop))
+                          (not (funcall width-unreadable? 
(org-export-read-attribute prop par :width))))
+                     (throw :found prop)))
+                 par)))
+             (attr-width
+              (if (not (funcall width-unreadable? attr-width))
+                  attr-width
+                ;; When #+attr_org: does not have readable :width
+                (and attr-other
+                     (org-export-read-attribute attr-other par :width))))
+             (width
+              (cond
+               ;; Treat :width t as if `org-image-actual-width' were t.
+               ((string= attr-width "t") nil)
+               ;; Fallback to `org-image-actual-width' if no interprable width 
is given.
+               ((funcall width-unreadable? attr-width)
+                (car org-image-actual-width))
+               ;; Convert numeric widths to numbers, converting percentages.
+               ((string-match-p "\\`[[+]?[0-9.]+%" attr-width)
+                (/ (string-to-number attr-width) 100.0))
+               (t (string-to-number attr-width)))))
+        (if (and (floatp width) (<= 0.0 width 2.0))
+            ;; A float in [0,2] should be interpereted as this portion of
+            ;; the text width in the window.  This works well with cases like
+            ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width,
+            ;; as the "0.X" is pulled out as a float.  We use 2 as the upper
+            ;; bound as cases such as 1.2\linewidth are feasible.
+            (round (* width
+                      (window-pixel-width)
+                      (/ (or (and (bound-and-true-p visual-fill-column-mode)
+                                  (or visual-fill-column-width 
auto-fill-function))
+                             (when auto-fill-function fill-column)
+                             (- (window-text-width) 
(line-number-display-width)))
+                         (float (window-total-width)))))
+          width)))
+     ((numberp org-image-actual-width)
+      org-image-actual-width)
+     (t nil))))
+
+(defun org-image--align (link)
+  "Determine the alignment of the image LINK.
+LINK is a link object.
+
+In decreasing order of priority, this is controlled:
+- Per image by the value of `:center' or `:align' in the
+affiliated keyword `#+attr_org'.
+- By the `#+attr_html' or `#+attr_latex` keywords with valid
+  `:center' or `:align' values.
+- Globally by the user option `org-image-align'.
+
+The result is either nil or one of the strings \"left\",
+\"center\" or \"right\".
+
+\"center\" will cause the image preview to be centered, \"right\"
+will cause it to be right-aligned.  A value of \"left\" or nil
+implies no special alignment."
+  (let ((par (org-element-lineage link 'paragraph)))
+    ;; Only align when image is not surrounded by paragraph text:
+    (when (and par ; when image is not in paragraph, but in 
table/headline/etc, do not align
+               (= (org-element-begin link)
+                  (save-excursion
+                    (goto-char (org-element-contents-begin par))
+                    (skip-chars-forward "\t ")
+                    (point)))           ;account for leading space
+                                        ;before link
+               (<= (- (org-element-contents-end par)
+                     (org-element-end link))
+                  1))                  ;account for trailing newline
+                                        ;at end of paragraph
+      (save-match-data
+        ;; Look for a valid ":center t" or ":align left|center|right"
+        ;; attribute.
+        ;;
+        ;; An attr_org keyword has the highest priority, with
+        ;; any attr.* next.  Choosing between these is
+        ;; unspecified.
+        (let ((center-re ":\\(center\\)[[:space:]]+t\\b")
+              (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b")
+              attr-align)
+          (catch 'exit
+            (org-element-properties-mapc
+             (lambda (propname propval)
+               (when (and propval
+                          (string-match-p ":attr.*" (symbol-name propname)))
+                 (setq propval (car-safe propval))
+                 (when (or (string-match center-re propval)
+                           (string-match align-re propval))
+                   (setq attr-align (match-string 1 propval))
+                   (when (eq propname :attr_org)
+                     (throw 'exit t)))))
+             par))
+          (if attr-align
+              (when (member attr-align '("center" "right")) attr-align)
+            ;; No image-specific keyword, check global alignment property
+            (when (memq org-image-align '(center right))
+              (symbol-name org-image-align))))))))
+
 ;;; Public API
 
 (defun org-link-types ()
@@ -1573,6 +1890,211 @@ (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] Displaying %d images 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 (fboundp 'clear-image-cache) (clear-image-cache)))
+  (org-with-point-at (or beg (point-min))
+    (let ((case-fold-search t)
+          (preview-queue (list nil))
+          (preview-queue-size 1))
+      (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)))))
+            (overlay-put ov 'org-image-overlay t)
+            (overlay-put ov 'modification-hooks
+                         (list 'org-link-preview--remove-overlay))
+            (push ov org-link-preview-overlays)
+            ;; Collect previews to run asynchronously, in batches
+            (if (>= (length (car-safe preview-queue)) 
org-link-preview-batch-size)
+                (progn (cl-incf preview-queue-size)
+                       (push (list (list preview-func ov path link)) 
preview-queue))
+              (push (list preview-func ov path link) (car preview-queue))))))
+      ;; Run preview asynchronously in batches:
+      ;; preview-queue is a list of preview-batch, which is a list of 
preview-spec
+      (when (car preview-queue)
+        (dolist (preview-batch (nreverse preview-queue))
+          (run-with-idle-timer
+           org-link-preview-delay nil
+           (lambda (previews)
+             ;; (message "queue: %d" preview-queue-size)
+             (cl-decf preview-queue-size)
+             (dolist (preview-spec (nreverse previews)) ;spec is (preview-func 
overlay path link)
+               (when-let* ((ov (cadr preview-spec))
+                           (buf (overlay-buffer ov)))
+                 (with-current-buffer buf
+                   (unless (apply preview-spec)
+                     ;; Preview was unsuccessful, delete overlay
+                     (delete-overlay ov)
+                     (setq org-link-preview-overlays (delq ov 
org-link-preview-overlays)))))))
+           preview-batch))))))
+
+(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 +2117,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!"))
+    (require 'image)
+    (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 '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..3c4e1bfb6 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -797,9 +797,18 @@ (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-with-point-at (org-element-begin link)
+    (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..e5cc9308f 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))
@@ -15583,26 +15583,6 @@ (defcustom org-image-actual-width t
          (list :tag "Use #+ATTR* or a number of pixels" (integer))
          (const :tag "Use #+ATTR* or don't resize" nil)))
 
-(defcustom org-image-max-width 'fill-column
-  "When non-nil, limit the displayed image width.
-This setting only takes effect when `org-image-actual-width' is set to
-t or when #+ATTR* is set to t.
-
-Possible values:
-- `fill-column' :: limit width to `fill-column'
-- `window'      :: limit width to window width
-- integer       :: limit width to number in pixels
-- float         :: limit width to that fraction of window width
-- nil             :: do not limit image width"
-  :group 'org-appearance
-  :package-version '(Org . "9.7")
-  :type '(choice
-          (const :tag "Do not limit image width" nil)
-          (const :tag "Limit to `fill-column'" fill-column)
-          (const :tag "Limit to window width" window)
-          (integer :tag "Limit to a number of pixels")
-          (float :tag "Limit to a fraction of window width")))
-
 (defcustom org-agenda-inhibit-startup nil
   "Inhibit startup when preparing agenda buffers.
 When this variable is t, the initialization of the Org agenda
@@ -16649,518 +16629,6 @@ (defun org-normalize-color (value)
   (format "%g" (/ value 65535.0)))
 
 
-;; 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))
-
-(defcustom org-display-remote-inline-images 'skip
-  "How to display remote inline images.
-Possible values of this option are:
-
-skip        Don't display remote images.
-download    Always download and display remote images.
-t
-cache       Display remote images, and open them in separate buffers
-            for caching.  Silently update the image buffer when a file
-            change is detected."
-  :group 'org-appearance
-  :package-version '(Org . "9.7")
-  :type '(choice
-         (const :tag "Ignore remote images" skip)
-         (const :tag "Always display remote images" download)
-         (const :tag "Display and silently update remote images" cache))
-  :safe #'symbolp)
-
-(defcustom org-image-align 'left
-  "How to align images previewed using `org-display-inline-images'.
-
-Only stand-alone image links are affected by this setting.  These
-are links without surrounding text.
-
-Possible values of this option are:
-
-left     Insert image at specified position.
-center   Center image previews.
-right    Right-align image previews."
-  :group 'org-appearance
-  :package-version '(Org . "9.7")
-  :type '(choice
-          (const :tag "Left align (or don\\='t align) image previews" left)
-         (const :tag "Center image previews" center)
-         (const :tag "Right align image previews" right))
-  :safe #'symbolp)
-
-(defun org--create-inline-image (file width)
-  "Create image located at FILE, or return nil.
-WIDTH is the width of the image.  The image may not be created
-according to the value of `org-display-remote-inline-images'."
-  (let* ((remote? (file-remote-p file))
-        (file-or-data
-         (pcase org-display-remote-inline-images
-           ((guard (not remote?)) file)
-           (`download (with-temp-buffer
-                        (set-buffer-multibyte nil)
-                        (insert-file-contents-literally file)
-                        (buffer-string)))
-           ((or `cache `t)
-             (let ((revert-without-query '(".")))
-              (with-current-buffer (find-file-noselect file)
-                (buffer-string))))
-           (`skip nil)
-           (other
-            (message "Invalid value of `org-display-remote-inline-images': %S"
-                     other)
-            nil))))
-    (when file-or-data
-      (create-image file-or-data
-                   (and (image-type-available-p 'imagemagick)
-                        width
-                        'imagemagick)
-                   remote?
-                   :width width
-                    :max-width
-                    (pcase org-image-max-width
-                      (`fill-column (* fill-column (frame-char-width 
(selected-frame))))
-                      (`window (window-width nil t))
-                      ((pred integerp) org-image-max-width)
-                      ((pred floatp) (floor (* org-image-max-width 
(window-width nil t))))
-                      (`nil nil)
-                      (_ (error "Unsupported value of `org-image-max-width': 
%S"
-                                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
-(defun org-display-inline-image--width (link)
-  "Determine the display width of the image LINK, in pixels.
-- When `org-image-actual-width' is t, the image's pixel width is used.
-- When `org-image-actual-width' is a number, that value will is used.
-- When `org-image-actual-width' is nil or a list, :width attribute of
-  #+attr_org or the first #+attr_...  (if it exists) is used to set the
-  image width.  A width of X% is divided by 100.  If the value is a
-  float between 0 and 2, it interpreted as that proportion of the text
-  width in the buffer.
-
-  If no :width attribute is given and `org-image-actual-width' is a
-  list with a number as the car, then that number is used as the
-  default value."
-  ;; Apply `org-image-actual-width' specifications.
-  ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified
-  ;; width.
-  (let ((org-image-actual-width (org-property-or-variable-value 
'org-image-actual-width)))
-    (cond
-     ((eq org-image-actual-width t) nil)
-     ((listp org-image-actual-width)
-      (require 'ox)
-      (let* ((par (org-element-lineage link 'paragraph))
-             ;; Try to find an attribute providing a :width.
-             ;; #+ATTR_ORG: :width ...
-             (attr-width (org-export-read-attribute :attr_org par :width))
-             (width-unreadable?
-              (lambda (value)
-                (or (not (stringp value))
-                    (unless (string= value "t")
-                      (or (not (string-match
-                              (rx bos (opt "+")
-                                  (or
-                                   ;; Number of pixels
-                                   ;; must be a lone number, not
-                                   ;; things like 4in
-                                   (seq (1+ (in "0-9")) eos)
-                                   ;; Numbers ending with %
-                                   (seq (1+ (in "0-9.")) (group-n 1 "%"))
-                                   ;; Fractions
-                                   (seq (0+ (in "0-9")) "." (1+ (in "0-9")))))
-                              value))
-                          (let ((number (string-to-number value)))
-                            (and (floatp number)
-                                 (not (match-string 1 value)) ; X%
-                                 (not (<= 0.0 number 2.0)))))))))
-             ;; #+ATTR_BACKEND: :width ...
-             (attr-other
-              (catch :found
-                (org-element-properties-map
-                 (lambda (prop _)
-                   (when (and
-                          (not (eq prop :attr_org))
-                          (string-match-p "^:attr_" (symbol-name prop))
-                          (not (funcall width-unreadable? 
(org-export-read-attribute prop par :width))))
-                     (throw :found prop)))
-                 par)))
-             (attr-width
-              (if (not (funcall width-unreadable? attr-width))
-                  attr-width
-                ;; When #+attr_org: does not have readable :width
-                (and attr-other
-                     (org-export-read-attribute attr-other par :width))))
-             (width
-              (cond
-               ;; Treat :width t as if `org-image-actual-width' were t.
-               ((string= attr-width "t") nil)
-               ;; Fallback to `org-image-actual-width' if no interprable width 
is given.
-               ((funcall width-unreadable? attr-width)
-                (car org-image-actual-width))
-               ;; Convert numeric widths to numbers, converting percentages.
-               ((string-match-p "\\`[[+]?[0-9.]+%" attr-width)
-                (/ (string-to-number attr-width) 100.0))
-               (t (string-to-number attr-width)))))
-        (if (and (floatp width) (<= 0.0 width 2.0))
-            ;; A float in [0,2] should be interpereted as this portion of
-            ;; the text width in the window.  This works well with cases like
-            ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width,
-            ;; as the "0.X" is pulled out as a float.  We use 2 as the upper
-            ;; bound as cases such as 1.2\linewidth are feasible.
-            (round (* width
-                      (window-pixel-width)
-                      (/ (or (and (bound-and-true-p visual-fill-column-mode)
-                                  (or visual-fill-column-width 
auto-fill-function))
-                             (when auto-fill-function fill-column)
-                             (- (window-text-width) 
(line-number-display-width)))
-                         (float (window-total-width)))))
-          width)))
-     ((numberp org-image-actual-width)
-      org-image-actual-width)
-     (t nil))))
-
-(defun org-image--align (link)
-  "Determine the alignment of the image LINK.
-LINK is a link object.
-
-In decreasing order of priority, this is controlled:
-- Per image by the value of `:center' or `:align' in the
-affiliated keyword `#+attr_org'.
-- By the `#+attr_html' or `#+attr_latex` keywords with valid
-  `:center' or `:align' values.
-- Globally by the user option `org-image-align'.
-
-The result is either nil or one of the strings \"left\",
-\"center\" or \"right\".
-
-\"center\" will cause the image preview to be centered, \"right\"
-will cause it to be right-aligned.  A value of \"left\" or nil
-implies no special alignment."
-  (let ((par (org-element-lineage link 'paragraph)))
-    ;; Only align when image is not surrounded by paragraph text:
-    (when (and par ; when image is not in paragraph, but in 
table/headline/etc, do not align
-               (= (org-element-begin link)
-                  (save-excursion
-                    (goto-char (org-element-contents-begin par))
-                    (skip-chars-forward "\t ")
-                    (point)))           ;account for leading space
-                                        ;before link
-               (<= (- (org-element-contents-end par)
-                     (org-element-end link))
-                  1))                  ;account for trailing newline
-                                        ;at end of paragraph
-      (save-match-data
-        ;; Look for a valid ":center t" or ":align left|center|right"
-        ;; attribute.
-        ;;
-        ;; An attr_org keyword has the highest priority, with
-        ;; any attr.* next.  Choosing between these is
-        ;; unspecified.
-        (let ((center-re ":\\(center\\)[[:space:]]+t\\b")
-              (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b")
-              attr-align)
-          (catch 'exit
-            (org-element-properties-mapc
-             (lambda (propname propval)
-               (when (and propval
-                          (string-match-p ":attr.*" (symbol-name propname)))
-                 (setq propval (car-safe propval))
-                 (when (or (string-match center-re propval)
-                           (string-match align-re propval))
-                   (setq attr-align (match-string 1 propval))
-                   (when (eq propname :attr_org)
-                     (throw 'exit t)))))
-             par))
-          (if attr-align
-              (when (member attr-align '("center" "right")) attr-align)
-            ;; No image-specific keyword, check global alignment property
-            (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]