emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
To: Ihor Radchenko <yantar92@posteo.net>
Cc: stardiviner <numbchild@gmail.com>, Org mode <emacs-orgmode@gnu.org>
Subject: Re: [PATCH v11] Inline image display as part of a new org-link-preview system
Date: Sun, 24 Nov 2024 19:17:10 -0800	[thread overview]
Message-ID: <87bjy4c895.fsf@gmail.com> (raw)
In-Reply-To: <87a5dq9bt9.fsf@localhost>

[-- Attachment #1: Type: text/plain, Size: 716 bytes --]

>> Ihor, do you need anything else from me, or want me to try something
>> else here?  This patch is held up because I'm not able to get "make
>> docs" to run.  (make output is in my previous email.)
>
> For the purposes of the inline image display patch, see the attached pdf
> generated using your latest org-manual patch.
> We need to get rid of the description parts of the info link or rewrite
> the surrounding text a bit to make things readable in PDF version of the
> manual.

I've attached the latest version of the patchset, with the link
descriptions in the manual fixed for PDF readability.  I can't test it
here so please let me know if it looks good.

I've also rebased the commits onto main.

Karthik

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-org-link-Customizable-preview-API-for-arbitrary-link.patch --]
[-- Type: text/x-patch, Size: 75484 bytes --]

From e02a53dfaa40113bc3a3df241a542dcbe7bb6f62 Mon Sep 17 00:00:00 2001
From: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Date: Fri, 23 Aug 2024 15:46:53 -0700
Subject: [PATCH 1/2] 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--timer,
org-link-preview--queue, org-link-preview-overlays,
org-link-preview--get-overlays, org-link-preview--remove-overlay,
org-link-preview, org-link-preview-region,
org-link-preview--process-queue, 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,
org-startup-with-inline-images, org-startup-with-link-previews,
org-startup-options): 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.  Rename `org-startup-with-inline-images' to
`org-startup-with-link-previews'.  Add new STARTUP options for
link previews to `org-startup-options': "linkpreviews" and
"nolinkpreviews".

* 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,
org-cycle-display-link-previews, org-cycle-inline-images-display,
org-cycle-inline-link-previews): Use `org-link-preview' and
`org-link-preview-region'.  Rename inline-images functions and user
options to their link-previews equivalents:

- `org-cycle-display-inline-images' to `org-cycle-display-link-previews'
- `org-cycle-inline-images-display' to `org-cycle-inline-link-previews'

* 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                    | 602 +++++++++++++++++++++++++++++++++-
 lisp/org-attach.el            |  11 +-
 lisp/org-compat.el            | 185 +++++++++++
 lisp/org-cycle.el             |  31 +-
 lisp/org-keys.el              |   7 +-
 lisp/org-plot.el              |   2 +-
 lisp/org.el                   | 554 +------------------------------
 testing/lisp/test-org-fold.el |   4 +-
 8 files changed, 838 insertions(+), 558 deletions(-)

diff --git a/lisp/ol.el b/lisp/ol.el
index ea01e2254..1480c4719 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))
 
 \f
 ;;; Customization
@@ -171,6 +176,18 @@ (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 syntax node for the link
+
+  This function must return a non-nil value to indicate success.
+  A return value of nil implies that the preview failed, and the
+  overlay placed on the link will be removed.
+
 `:help-echo'
 
   String or function used as a value for the `help-echo' text
@@ -521,6 +538,81 @@ (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 asynchronously, 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 +741,29 @@ (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)
+
+(defvar-local org-link-preview--timer nil
+  "Timer for previewing Org links in buffer.
+
+This timer creates previews for specs in
+`org-link-preview--queue'.")
+
+(defvar-local org-link-preview--queue nil
+  "Queue of pending previews for Org links in buffer.
+
+Each element of this queue is a list of the form
+
+(PREVIEW-FUNC OVERLAY PATH LINK)
+
+where PREVIEW-FUNC places a preview of PATH using OVERLAY.  LINK
+is the Org element being previewed.")
+
 \f
 ;;; Internal Functions
 
@@ -881,7 +996,227 @@ (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* ((disp (overlay-get ov 'display))
+                ((imagep disp)))
+      (image-flush disp))
+    (delete-overlay ov)))
+
 \f
+
+;;;; 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 +1908,230 @@ (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, also preview links with description in
+the active region, at point or in the current section.
+
+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")))))
+
+;;;###autoload
+(defun org-link-preview-refresh ()
+  "Assure display of link previews in buffer and refresh them."
+  (interactive)
+  (org-link-preview-region nil t (point-min) (point-max)))
+
+(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))
+  (org-with-point-at (or beg (point-min))
+    (let ((case-fold-search t)
+          preview-queue)
+      ;; Collect links to preview
+      (while (re-search-forward org-link-any-re end t)
+        (when-let*
+            ((link (org-element-lineage (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 'modification-hooks
+                         (list 'org-link-preview--remove-overlay))
+            (push ov org-link-preview-overlays)
+            (push (list preview-func ov path link) preview-queue))))
+      ;; Collect previews in buffer-local LIFO preview queue
+      (setq org-link-preview--queue
+            (nconc (nreverse preview-queue) org-link-preview--queue))
+      ;; Run preview possibly asynchronously
+      (when org-link-preview--queue
+        (org-link-preview--process-queue (current-buffer))))))
+
+(defun org-link-preview--process-queue (org-buffer)
+  "Preview pending Org link previews in ORG-BUFFER.
+
+Previews are generated from the specs in
+`org-link-preview--queue', which see."
+  (and (buffer-live-p org-buffer)
+       (with-current-buffer org-buffer
+         (cl-loop
+          for spec in org-link-preview--queue
+          for ov = (cadr spec)    ;SPEC is (preview-func ov path link)
+          for count from org-link-preview-batch-size above 0
+          do (pop org-link-preview--queue)
+          if (overlay-buffer ov) do
+          (if (apply spec)
+              (overlay-put ov 'org-image-overlay t)
+            ;; Preview was unsuccessful, delete overlay
+            (delete-overlay ov)
+            (setq org-link-preview-overlays
+                  (delq ov org-link-preview-overlays)))
+          else do (cl-incf count) end
+          finally do
+          (setq org-link-preview--timer
+                (and org-link-preview--queue
+                     (run-with-idle-timer
+                      (time-add (or (current-idle-time) 0)
+                                org-link-preview-delay)
+                      nil #'org-link-preview--process-queue org-buffer)))))))
+
+(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)
+        ;; Remove pending preview tasks between BEG and END
+        (when-let ((spec (cl-find ov org-link-preview--queue
+                                  :key #'cadr)))
+          (setq org-link-preview--queue (delq spec org-link-preview--queue)))
+        ;; Remove placed overlays between BEG and END
+        (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))))))
+
 \f
 ;;; Built-in link types
 
@@ -1595,7 +2154,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 d4393324f..4fe06cbf4 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 05fe13457..20caafb2b 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -1010,6 +1010,191 @@ (define-obsolete-function-alias 'org-open-link-from-string
 (define-obsolete-function-alias 'org-add-angle-brackets
   'org-link-add-angle-brackets "9.3")
 
+(declare-function org-link-preview--remove-overlay "ol"
+                  (ov after beg end &optional len))
+(declare-function org-link-preview--get-overlays "ol" (&optional beg end))
+(declare-function org-link-preview-clear "ol" (&optional beg end))
+(declare-function org-link-preview--remove-overlay "ol"
+                  (ov after beg end &optional len))
+(declare-function org-attach-expand "org-attach" (file))
+(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))
+
+(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-function-alias 'org-redisplay-inline-images
+  'org-link-preview-refresh "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)
+
+(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")
+(declare-function org-link-preview-region "ol")
+;; 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-link-preview-region 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"))))))
+
+
 ;; The function was made obsolete by commit 65399674d5 of 2013-02-22.
 ;; This make-obsolete call was added 2016-09-01.
 (make-obsolete 'org-capture-import-remember-templates
diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el
index 8a39bdb8c..97a545393 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" ())
@@ -217,7 +217,7 @@ (defcustom org-cycle-pre-hook nil
 (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees
                             org-cycle-show-empty-lines
                             org-cycle-optimize-window-after-visibility-change
-                            org-cycle-display-inline-images)
+                            org-cycle-display-link-previews)
   "Hook that is run after `org-cycle' has changed the buffer visibility.
 The function(s) in this hook must accept a single argument which indicates
 the new state that was set by the most recent `org-cycle' command.  The
@@ -237,11 +237,15 @@ (defcustom org-cycle-open-archived-trees nil
   :group 'org-cycle
   :type 'boolean)
 
-(defcustom org-cycle-inline-images-display nil
-  "Non-nil means auto display inline images under subtree when cycling."
+(defvaralias 'org-cycle-inline-images-display
+  'org-cycle-link-previews-display
+  "Non-nil means auto display inline images under subtree when cycling.")
+
+(defcustom org-cycle-link-previews-display nil
+  "Non-nil means auto display link previews under subtree when cycling."
   :group 'org-startup
   :group 'org-cycle
-  :package-version '(Org . "9.6")
+  :package-version '(Org . "9.8")
   :type 'boolean)
 
 (defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook)
@@ -804,12 +808,15 @@ (defun org-cycle-hide-archived-subtrees (state)
 		       "Subtree is archived and stays closed.  Use \
 `\\[org-cycle-force-archived]' to cycle it anyway."))))))
 
-(defun org-cycle-display-inline-images (state)
+(defalias 'org-cycle-inline-images-display
+  'org-cycle-display-link-previews)
+
+(defun org-cycle-display-link-previews (state)
   "Auto display inline images under subtree when cycling.
-It works when `org-cycle-inline-images-display' is non-nil.
+It works when `org-cycle-link-previews-display' is non-nil.
 STATE is the current outline visibility state.  It should be one of
 symbols `content', `all', `folded', `children', or `subtree'."
-  (when org-cycle-inline-images-display
+  (when org-cycle-link-previews-display
     (pcase state
       ('children
        (org-with-wide-buffer
@@ -817,19 +824,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 f6de753af..3f07a8818 100644
--- a/lisp/org-keys.el
+++ b/lisp/org-keys.el
@@ -218,7 +218,8 @@ (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-link-preview-refresh "ol" ())
 (declare-function org-latex-preview "org" (&optional arg))
 (declare-function org-toggle-narrow-to-subtree "org" ())
 (declare-function org-toggle-ordered-property "org" ())
@@ -652,8 +653,8 @@ (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-M-v") #'org-redisplay-inline-images)
+(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-link-preview-refresh)
 (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)
 (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button)
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 1e9057935..def094a9a 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -1160,14 +1160,22 @@ (defcustom org-startup-shrink-all-tables nil
   :package-version '(Org . "9.2")
   :safe #'booleanp)
 
-(defcustom org-startup-with-inline-images nil
+(defvaralias 'org-startup-with-inline-images
+  'org-startup-with-link-previews
   "Non-nil means show inline images when loading a new Org file.
 This can also be configured on a per-file basis by adding one of
 the following lines anywhere in the buffer:
    #+STARTUP: inlineimages
-   #+STARTUP: noinlineimages"
+   #+STARTUP: noinlineimages")
+
+(defcustom org-startup-with-link-previews nil
+  "Non-nil means show link previews when loading a new Org file.
+This can also be configured on a per-file basis by adding one of
+the following lines anywhere in the buffer:
+   #+STARTUP: linkpreviews
+   #+STARTUP: nolinkpreviews"
   :group 'org-startup
-  :version "24.1"
+  :version "29.4"
   :type 'boolean)
 
 (defcustom org-startup-with-latex-preview nil
@@ -4153,8 +4161,10 @@ (defconst org-startup-options
     ("shrink" org-startup-shrink-all-tables t)
     ("descriptivelinks" org-link-descriptive t)
     ("literallinks" org-link-descriptive nil)
-    ("inlineimages" org-startup-with-inline-images t)
-    ("noinlineimages" org-startup-with-inline-images nil)
+    ("inlineimages" org-startup-with-link-previews t)
+    ("noinlineimages" org-startup-with-link-previews nil)
+    ("linkpreviews" org-startup-with-link-previews t)
+    ("nolinkpreviews" org-startup-with-link-previews nil)
     ("latexpreview" org-startup-with-latex-preview t)
     ("nolatexpreview" org-startup-with-latex-preview nil)
     ("customtime" org-display-custom-times t)
@@ -5079,7 +5089,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))
@@ -15601,26 +15611,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
@@ -16667,518 +16657,6 @@ (defun org-normalize-color (value)
   (format "%g" (/ value 65535.0)))
 
 \f
-;; 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.46.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --]
[-- Type: text/x-patch, Size: 19818 bytes --]

From 3fb7dcff71e0786770230c69cea8550270412ecc Mon Sep 17 00:00:00 2001
From: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Date: Sun, 24 Nov 2024 19:14:28 -0800
Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types

* etc/ORG-NEWS: Mention new commands and options for link preview.

* doc/org-manual.org: Add sections on
- previewing external links (Images and link previews), and
- adding the link preview feature to other link types (Adding
Hyperlink preview).
---
 doc/org-manual.org | 291 ++++++++++++++++++++++++++++++---------------
 etc/ORG-NEWS       |  93 +++++++++++++--
 2 files changed, 279 insertions(+), 105 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 451fd72cd..e904b27db 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -11778,7 +11778,75 @@ ** Literal Examples
 the end of the current line.  Then the label is stored as a link
 =(label)=, for retrieval with {{{kbd(C-c C-l)}}}.
 
-** Images
+** Images and link previews
+:PROPERTIES:
+:DESCRIPTION: Preview links in the buffer.
+:END:
+
+Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers.  By
+default, only image links[fn::Image links are =file:= and
+=attachment:= links to existing image files; Emacs should be able to
+display the linked images (see ~image-types~ variable)] can be
+previewed inline, where the images are displayed in place of the link
+path.
+
+You can customize the previews as described in the [[*Adding Hyperlink
+preview]] section.  Link previews do not have to display images -- any
+kind of [[info:elisp#Overlay Properties][display decoration]] can be used.
+
+You can preview the supported link types in the whole buffer, in the
+current section, active region or at point with the following commands: 
+
+- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) ::
+
+  #+kindex: C-c C-x C-v
+  #+findex: org-link-preview
+  Create inline previews for external links in the active region, the
+  link at point or in the current section.  With a prefix argument,
+  clear link previews at point or in the current entry.  With a double
+  prefix argument, preview all links in the buffer.  With triple
+  prefix argument, hide previews for all links in the buffer.
+
+  By default, only links without descriptions are previewed.  You
+  can force displaying previews for all supported links (including
+  links with descriptions) using a numeric argument of ~1~.  This
+  will toggle all previews in the active region, the link at point
+  or the current section.  A numeric prefix argument of ~11~ will
+  toggle previews in the whole buffer instead.
+
+  #+vindex: org-startup-with-link-previews
+  Org mode can display link previews automatically when opening
+  buffers.  Either customize ~org-startup-with-link-previews~ or use
+  the =#+STARTUP: linkpreviews= keyword to enable previews for that
+  specific buffer when opening it.  =#+STARTUP: nolinkpreviews= will
+  disable previews on startup in the buffer.
+
+- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) ::
+
+  #+kindex: C-c C-x C-M-v
+  #+findex: org-link-preview-refresh
+  Assure inline display of external link previews in the whole buffer
+  and refresh them.
+
+- (~org-link-preview-region~) ::
+
+  #+findex: org-link-preview-region
+  Create inline previews for external links in the active region, or
+  the buffer.  With a prefix argument, also preview links with a text
+  description part.
+
+- (~org-link-preview-clear~) ::
+
+  #+findex: org-link-preview-clear
+  Clear external link previews in the active region, or the buffer.
+
+#+vindex: org-cycle-inline-images-display
+Link previews can also be displayed when cycling the folding state.
+When the custom option ~org-cycle-link-previews-display~ is set,
+supported link types under the subtree (including images) will be
+displayed automatically.
+
+*** Images
 :PROPERTIES:
 :DESCRIPTION: Display an image.
 :END:
@@ -11803,102 +11871,76 @@ ** Images
 [[./img/a.jpg]]
 #+end_example
 
-Such images can be displayed within the buffer with the following
-command:
-
-- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) ::
-
-  #+kindex: C-c C-x C-v
-  #+findex: org-toggle-inline-images-command
-  Toggle the inline display of linked images in current section or at
-  point.  With a prefix argument, toggle inline images in the whole
-  buffer.  With double prefix argument, hide all the images in buffer.
-
-  By default, only the image links without description are displayed.
-  You can force displaying all the images using numeric argument to
-  toggle all the images in current section (~1~ argument) or the whole
-  buffer (~11~ argument).
-
-  #+vindex: org-startup-with-inline-images
-  You can ask for inline images to be displayed at
-  startup by configuring the variable
-  ~org-startup-with-inline-images~[fn:: The variable
-  ~org-startup-with-inline-images~ can be set within a buffer with the
-  =STARTUP= options =inlineimages= and =noinlineimages=.].
-
-
-  #+vindex: org-image-actual-width
-  #+vindex: org-image-max-width
-  #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property
-  By default, Org mode displays inline images according to their
-  actual width, but no wider than ~fill-column~ characters.
-
-  You can customize the displayed image width using
-  ~org-image-actual-width~ variable (globally) or
-  =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can
-  be customized in Emacs >= 24.1, built with ImageMagick support.].
-  Their value can be the following:
-  - (default) Non-~nil~, use the actual width of images when inlining
-    them.  If the actual width is too wide, limit it according to
-    ~org-image-max-width~.
-  - When set to a number, use ImageMagick (when available) to set the
-    image's width to this value.
-  - When set to a number in a list, try to get the width from any
-    =#+ATTR.*= keyword if it matches a width specification like:
-    #+begin_example
-    ,#+ATTR_HTML: :width 300px
-    #+end_example
-    and fall back on that number if none is found.
-  - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword
-    and fall back on the original width or ~org-image-max-width~ if
-    none is found.
-
-  ~org-image-max-width~ limits the maximum displayed image width, but
-  only when the image width is not set explicitly.  Possible maximum
-  width can be set to:
-  - (default) ~fill-column~, limit width to ~fill-column~ number of
-    characters.
-  - ~window~, limit width to current window width.
-  - integer number, limit width to that specified number of pixels.
-  - ~nil~, do not limit the width.
-
-  #+vindex: org-image-align
-  Org mode can left-align, center or right-align the display of inline
-  images.  This setting is controlled (globally) by ~org-image-align~.
-  Only standalone images are affected, corresponding to links with no
-  surrounding text in their paragraph except for whitespace.  Its
-  value can be the following:
-  - (default) The symbol ~left~, which inserts the image where the
-    link appears in the buffer.
-  - The symbol ~center~, which will preview links centered in the
-    Emacs window.
-  - The symbol ~right~, which will preview links right-aligned in the
-    Emacs window.
-
-  Inline image alignment can be specified for each link using the
-  =#+ATTR.*= keyword if it matches an alignment specification like:
+When [[*Images and link previews][link previews]] are displayed as images, the image size and
+alignment can be further customized.
+
+#+vindex: org-image-actual-width
+#+vindex: org-image-max-width
+#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property
+By default, Org mode displays inline images according to their
+actual width, but no wider than ~fill-column~ characters.
+
+You can customize the displayed image width using
+~org-image-actual-width~ variable (globally) or
+=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can
+be customized in Emacs >= 24.1, built with ImageMagick support.].
+Their value can be the following:
+- (default) Non-~nil~, use the actual width of images when inlining
+  them.  If the actual width is too wide, limit it according to
+  ~org-image-max-width~.
+- When set to a number, use ImageMagick (when available) to set the
+  image's width to this value.
+- When set to a number in a list, try to get the width from any
+  =#+ATTR.*= keyword if it matches a width specification like:
   #+begin_example
-  ,#+ATTR_HTML: :align center
+  ,#+ATTR_HTML: :width 300px
   #+end_example
-  Org will use the alignment specification from any =#+ATTR.*=
-  keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG=
-  (if present) will override the others.  For instance, this link
-  #+begin_example
-  ,#+ATTR_HTML: :align right
-  ,#+ATTR_ORG: :align center
-  [[/path/to/image/file.png]]
-  #+end_example
-  will be displayed centered in Emacs but exported right-aligned to
-  HTML.
-
-  When =#+ATTR_ORG= is not set, inline image alignment is also read
-  from the =:center= attribute supported by some export backends (like
-  HTML, LaTeX and Beamer.)
+  and fall back on that number if none is found.
+- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword
+  and fall back on the original width or ~org-image-max-width~ if
+  none is found.
+
+~org-image-max-width~ limits the maximum displayed image width, but
+only when the image width is not set explicitly.  Possible maximum
+width can be set to:
+- (default) ~fill-column~, limit width to ~fill-column~ number of
+  characters.
+- ~window~, limit width to current window width.
+- integer number, limit width to that specified number of pixels.
+- ~nil~, do not limit the width.
+
+#+vindex: org-image-align
+Org mode can left-align, center or right-align the display of inline
+images.  This setting is controlled (globally) by ~org-image-align~.
+Only standalone images are affected, corresponding to links with no
+surrounding text in their paragraph except for whitespace.  Its
+value can be the following:
+- (default) The symbol ~left~, which inserts the image where the
+  link appears in the buffer.
+- The symbol ~center~, which will preview links centered in the
+  Emacs window.
+- The symbol ~right~, which will preview links right-aligned in the
+  Emacs window.
+
+Inline image alignment can be specified for each link using the
+=#+ATTR.*= keyword if it matches an alignment specification like:
+#+begin_example
+,#+ATTR_HTML: :align center
+#+end_example
+Org will use the alignment specification from any =#+ATTR.*=
+keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG=
+(if present) will override the others.  For instance, this link
+#+begin_example
+,#+ATTR_HTML: :align right
+,#+ATTR_ORG: :align center
+[[/path/to/image/file.png]]
+#+end_example
+will be displayed centered in Emacs but exported right-aligned to
+HTML.
 
-#+vindex: org-cycle-inline-images-display
-Inline images can also be displayed when cycling the folding state.
-When custom option ~org-cycle-inline-images-display~ is set, the
-visible inline images under subtree will be displayed automatically.
+When =#+ATTR_ORG= is not set, inline image alignment is also read from
+the =:center= attribute supported by some export backends (like HTML,
+LaTeX and Beamer.)
 
 ** Captions
 :PROPERTIES:
@@ -21837,6 +21879,67 @@ ** Adding Hyperlink Types
    topic.  It also provides a default description.  The function
    ~org-insert-link~ can insert it back into an Org buffer later on.
 
+** Adding Hyperlink preview
+:PROPERTIES:
+:DESCRIPTION: Preview behavior for new hyperlink types.
+:END:
+#+cindex: hyperlinks, adding preview behavior
+
+By default, Org supports previewing external links for links ot type
+=file= and =attachment= that point to image files. (See [[*Images]].)
+
+Support for previewing other link types inline can be added to Org in
+the following way:
+
+1. Add a =:preview= link parameter to the link type using
+   ~org-link-set-parameters~.  As an example, here we add previews for
+   the =docview= link type.
+
+   #+begin_src emacs-lisp
+(org-link-set-parameters
+ "docview" :preview #'org-link-docview-preview)
+   #+end_src
+
+2. The value of the =:preview= parameter must be a function that
+   accepts three arguments:
+   - an overlay placed from the start to the end of the link,
+   - the link path, as a string, and
+   - the syntax node for the link.
+   It must return a non-nil value to indicate preview success.  A
+   value of =nil= implies that the preview failed, and the overlay
+   placed on the link will be removed.
+
+   In our example, we use the =convert= program (part of the
+   =imagemagick= suite of tools) to create the thumbnail that is
+   displayed inline.
+
+   #+begin_src emacs-lisp
+(defun org-link-docview-preview (ov path _elem)
+  "Preview file at PATH in overlay OV.
+
+_ELEM is the syntax node of the link element."
+  (when (executable-find "convert")
+    (let* ((path (expand-file-name (substitute-in-file-name path)))
+           (output-file (expand-file-name (concat "org-docview-preview-"
+                                                  (substring (sha1 path) 0 10)
+                                                  ".png")
+                                          temporary-file-directory)))
+      ;; Create or find preview for path
+      (when (or (file-readable-p output-file)
+                (= 0 (call-process
+                      "convert"
+                      nil (get-buffer-create "*Org Docview Preview Output*") nil
+                      "-thumbnail" "x320" "-colorspace" "rgb"
+                      "-background" "white" "-alpha" "remove" "-strip"
+                      (concat path "[0]") output-file)))
+        ;; If preview image is available, display it via the overlay
+        (overlay-put ov 'display (create-image output-file))))))
+   #+end_src
+
+3. Now previews of docview links for supported document types (PDF,
+   djvu) are generated (along with image file previews) when calling
+   ~org-link-preview~.
+
 ** Adding Export Backends
 :PROPERTIES:
 :DESCRIPTION: How to write new export backends.
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 92bfe3501..08857962b 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole
 buffer (or narrowed part of the buffer).  With prefix argument, it
 also forced displaying image links with description.
 
-Now, =C-c C-x C-v= is bound to a new command
-~org-toggle-inline-images-command~, which uses different defaults:
+Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which
+uses different defaults:
 
-1. By default, it toggles image at point or, if there is no image at
-   point, images in current entry
+1. When the region is active, images in the region are previewed
 
-2. When region is active, it is honored
+2. Otherwise, if there is an image at point, it is toggled.  If there
+   is no image at point, images in the current entry are previewed
 
-3. =C-u= argument changed its meaning.  Now, it forces toggling images
-   in the whole buffer
+3. With the =C-u= argument, image previews in the active region or at
+   point are cleared instead
 
-4. =C-u C-u= argument unconditionally hides all the images in buffer
+4. The =C-u C-u= argument unconditionally shows all images in the
+   accessible portion of the buffer
 
-5. Displaying images over links with description can be forced using
+5. The =C-u C-u C-u= argument unconditionally clears all images in the
+   accessible portion of the buffer
+
+6. Displaying images over links with description can be forced using
    numeric argument:
    - ~C-u 1~ for toggling all images at point/current entry
    - ~C-u 11~ for toggling all images in buffer
 
-The old ~org-toggle-inline-images~ command is still available.  You
-can bind it back to =C-c C-x C-v= by adding the following to you config:
+(The first five of these prefix arg behaviors are the same as that of
+the ~org-latex-preview~ command.)
+
+In addition to images, ~org-link-preview~ can also be used to preview
+Org links of all types for which preview behavior is defined, see
+[[#link-preview][previews for arbitrary link types]].
+
+The old ~org-toggle-inline-images~ command is obsolete but still
+available.  You can bind it back to =C-c C-x C-v= by adding the
+following to your config:
 #+begin_src emacs-lisp
 (eval-after-load 'org-keys
   (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images))
@@ -76,6 +88,16 @@ now have diary timestamps included as well.
 # We list the most important features, and the features that may
 # require user action to be used.
 
+*** All Org link types can be previewed
+:PROPERTIES:
+:CUSTOM_ID: link-preview
+:END:
+
+Org links support a new parameter =:preview= that can be used to
+preview arbitrary link types.  The value of this parameter should be a
+function that is called to preview links of the corresponding type
+(see ~org-link-parameters~).
+
 *** Alignment of image previews can be customized
 
 This is not a new feature.  It has been added in Org 9.7, but not
@@ -120,6 +142,17 @@ See the new [[info:org#Repeating commands]["Repeating commands"]] section in Org
 # adding new customizations, or changing the interpretation of the
 # existing customizations.
 
+*** New option ~org-link-preview-batch-size~
+
+Org link previews are generated asynchronously and a few at a time, in
+batches.  This option controls the number of links that are previewed
+in each batch.
+
+*** New option ~org-link-preview-delay~
+
+Org link previews are generated asynchronously.  This option controls
+the minimum idle time in seconds between previews of batches of links.
+
 *** Allow disabling macro replacement during export
 
 New custom option ~org-export-replace-macros~ controls whether Org
@@ -162,6 +195,26 @@ bibliography format requires them to be written in title-case.
 
 # This also includes changes in function behavior from Elisp perspective.
 
+*** New command ~org-link-preview~ to preview Org links
+
+This command replaces ~org-toggle-inline-images~, which is now
+obsolete.
+
+*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer
+
+This command replaces ~org-display-inline-images~, which is now
+obsolete.
+
+*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer
+
+This command replaces ~org-remove-inline-images~, which is now
+obsolete.
+
+*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer
+
+This command replaces ~org-redisplay-inline-images~, which is now
+obsolete.
+
 *** ob-sqlite: Added ability to open a database in readonly mode
 
 Added option :readonly to ob-sqlite.
@@ -204,6 +257,24 @@ leave extra prompts after evaluation, and skipping the prompt
 filtering can be more robust for such languages (as this avoids
 removing false positive prompts).
 
+** Removed or renamed functions and variables
+
+*** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~
+
+Inline image previews in Org mode are now provided by the more general
+link previews feature.  The behavior with regards to image links is
+unchanged.
+
+*** ~org-cycle-inline-images-display~ is renamed to ~org-cycle-link-previews-display~
+
+The behavior is unchanged, except in that the new variable now affects
+previews of supported link types besides image links.
+
+*** ~org-startup-with-inline-images~ is renamed to ~org-startup-with-link-previews~
+
+The behavior is unchanged, except in that the new variable now affects
+previews of supported link types besides image links.
+
 ** Miscellaneous
 *** Org mode no longer prevents =flyspell= from spell-checking inside =LOGBOOK= drawers
 
-- 
2.46.1


  reply	other threads:[~2024-11-25  3:34 UTC|newest]

Thread overview: 82+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-05-15  3:28 [PATCH] add a function to only refresh inline images under current headline instead of global buffer Christopher M. Miles
2023-05-15 11:08 ` Ihor Radchenko
2023-05-15 13:01   ` Christopher M. Miles
2023-05-15 14:00   ` [PATCH v2] " Christopher M. Miles
2023-05-16  9:17     ` Ihor Radchenko
2023-05-16 12:18       ` Christopher M. Miles
2023-07-24 11:25         ` Ihor Radchenko
2023-08-01  4:40           ` [PATCH v3] " Christopher M. Miles
2023-08-01  8:04             ` Ihor Radchenko
2023-08-01 12:17               ` [PATCH v3.1] " Christopher M. Miles
2023-08-01 14:09                 ` Ihor Radchenko
2023-08-01 15:22                   ` Christopher M. Miles
2023-08-01 15:46                   ` Christopher M. Miles
2023-08-02 16:08                     ` Ihor Radchenko
2023-08-04  6:30                       ` Christopher M. Miles
2023-08-02  7:26                 ` Ihor Radchenko
2023-08-02 15:44                   ` Christopher M. Miles
2023-08-04  8:20                     ` Ihor Radchenko
2023-08-05  5:28                       ` [PATCH v3.2] " Christopher M. Miles
2024-07-22 10:46                         ` Ihor Radchenko
2024-08-01 22:58                           ` [PATCH v4.0] " stardiviner
     [not found]                           ` <66a8b73b.170a0220.383476.996e@mx.google.com>
2024-08-12 10:18                             ` Ihor Radchenko
2024-08-14  2:04                               ` stardiviner
2024-08-18 10:27                                 ` Ihor Radchenko
2024-08-20  2:02                                   ` Karthik Chikmagalur
2024-08-20 15:43                                     ` Karthik Chikmagalur
2024-08-20 18:19                                       ` Ihor Radchenko
2024-08-20 19:46                                         ` Karthik Chikmagalur
2024-08-22 13:19                                           ` Ihor Radchenko
2024-08-23  6:04                                             ` Karthik Chikmagalur
2024-08-23 23:36                                             ` [PATCH v1] Inline image display as part of a new org-link-preview system Karthik Chikmagalur
2024-08-24  1:00                                               ` Karthik Chikmagalur
2024-08-31 14:22                                               ` Ihor Radchenko
2024-08-31 16:41                                                 ` Karthik Chikmagalur
2024-08-31 16:53                                                   ` Ihor Radchenko
2024-08-31 22:37                                                     ` [PATCH v2] " Karthik Chikmagalur
2024-09-01 13:06                                                       ` Ihor Radchenko
2024-09-02 20:13                                                         ` [PATCH v3] " Karthik Chikmagalur
2024-09-08  7:43                                                           ` Ihor Radchenko
2024-09-09  3:21                                                             ` Karthik Chikmagalur
2024-09-09  6:06                                                               ` Ihor Radchenko
2024-09-09  6:30                                                                 ` Karthik Chikmagalur
2024-09-09 16:47                                                                   ` Ihor Radchenko
2024-09-09 19:14                                                                     ` Karthik Chikmagalur
2024-09-10 16:57                                                                       ` Ihor Radchenko
2024-09-10 19:53                                                                         ` Karthik Chikmagalur
2024-09-15  7:51                                                                           ` Ihor Radchenko
2024-09-09 21:45                                                                 ` Karthik Chikmagalur
2024-09-10 16:58                                                                   ` Ihor Radchenko
2024-09-10 17:38                                                                     ` Karthik Chikmagalur
2024-09-10 18:34                                                                       ` Ihor Radchenko
2024-09-10 19:43                                                             ` Karthik Chikmagalur
2024-09-15  8:12                                                               ` Ihor Radchenko
2024-09-15 20:50                                                                 ` Karthik Chikmagalur
2024-09-15 21:57                                                                 ` [PATCH v4] " Karthik Chikmagalur
2024-09-17 18:16                                                                   ` Ihor Radchenko
2024-09-18  1:44                                                                     ` [PATCH v5] " Karthik Chikmagalur
2024-09-21 10:11                                                                       ` Ihor Radchenko
2024-09-22 22:00                                                                         ` [PATCH v6] " Karthik Chikmagalur
2024-10-03 17:03                                                                           ` Ihor Radchenko
2024-10-08  0:07                                                                             ` [PATCH v7] " Karthik Chikmagalur
2024-10-10 17:15                                                                               ` Ihor Radchenko
2024-10-10 21:23                                                                                 ` Karthik Chikmagalur
2024-10-12  8:15                                                                                   ` Ihor Radchenko
2024-10-28  6:13                                                                                     ` [PATCH v8] " Karthik Chikmagalur
2024-10-28 18:16                                                                                       ` Ihor Radchenko
2024-10-28 21:53                                                                                         ` [PATCH v9] " Karthik Chikmagalur
2024-10-29 18:46                                                                                           ` Ihor Radchenko
2024-10-29 20:45                                                                                             ` [PATCH v10] " Karthik Chikmagalur
2024-10-30 17:32                                                                                               ` Ihor Radchenko
2024-10-30 19:50                                                                                                 ` Karthik Chikmagalur
2024-10-31 18:16                                                                                                   ` Ihor Radchenko
2024-11-14  5:43                                                                                                     ` Karthik Chikmagalur
2024-11-14 17:14                                                                                                       ` Ihor Radchenko
2024-11-14 22:10                                                                                                         ` Karthik Chikmagalur
2024-11-21  3:46                                                                                                           ` Karthik Chikmagalur
2024-11-23 16:04                                                                                                             ` Ihor Radchenko
2024-11-25  3:17                                                                                                               ` Karthik Chikmagalur [this message]
2024-11-23 16:11                                                                                                           ` Ihor Radchenko
2024-08-18 10:34                                 ` [FR] Automatically display images in resutls of evaluation (was: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer) Ihor Radchenko
     [not found]                                   ` <66c54dfc.a70a0220.3c823a.2899@mx.google.com>
2024-08-22 13:06                                     ` [FR] Automatically display images in resutls of evaluation Ihor Radchenko
     [not found]                                       ` <66c89411.170a0220.3255c1.0cd5@mx.google.com>
     [not found]                                         ` <87zfor7b04.fsf@localhost>
     [not found]                                           ` <CAL1eYuLOsaS43ahueN4uWiCn+Ykp=p_-t9dzAypKdy1en_53BQ@mail.gmail.com>
2024-09-15 13:33                                             ` [PATCH v3] " Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87bjy4c895.fsf@gmail.com \
    --to=karthikchikmagalur@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    --cc=numbchild@gmail.com \
    --cc=yantar92@posteo.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).