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'.
This commit is contained in:
Karthik Chikmagalur 2024-08-23 15:46:53 -07:00 committed by Ihor Radchenko
parent 4ff4828944
commit 9cce40c94f
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
8 changed files with 839 additions and 558 deletions

View File

@ -82,6 +82,11 @@
(declare-function org-src-source-type "org-src" ()) (declare-function org-src-source-type "org-src" ())
(declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function org-time-stamp-format "org" (&optional long inactive))
(declare-function outline-next-heading "outline" ()) (declare-function outline-next-heading "outline" ())
(declare-function image-flush "image" (spec &optional frame))
(declare-function org-entry-end-position "org" ())
(declare-function org-element-contents-begin "org-element" (node))
(declare-function org-element-contents-end "org-element" (node))
(declare-function org-property-or-variable-value "org" (var &optional inherit))
;;; Customization ;;; Customization
@ -171,6 +176,18 @@ link.
The default face is `org-link'. 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' `:help-echo'
String or function used as a value for the `help-echo' text String or function used as a value for the `help-echo' text
@ -521,6 +538,81 @@ links more efficient."
:type 'boolean :type 'boolean
:safe #'booleanp) :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 ;;; Public variables
(defconst org-target-regexp (let ((border "[^<>\n\r \t]")) (defconst org-target-regexp (let ((border "[^<>\n\r \t]"))
@ -649,6 +741,29 @@ exact and fuzzy text search.")
(defvar org-link--search-failed nil (defvar org-link--search-failed nil
"Non-nil when last link search failed.") "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.")
;;; Internal Functions ;;; Internal Functions
@ -881,7 +996,227 @@ Return t when a link has been stored in `org-link-store-props'."
(setq desc search-desc)))) (setq desc search-desc))))
(cons link 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)))
;;;; 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 ;;; Public API
(defun org-link-types () (defun org-link-types ()
@ -1573,6 +1908,231 @@ If there is no description, use the link target."
(unless (equal (substring s -1) ">") (setq s (concat s ">"))) (unless (equal (substring s -1) ">") (setq s (concat s ">")))
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)
(forward-char -1) ;ensure we are on the link
(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))))))
;;; Built-in link types ;;; Built-in link types
@ -1595,7 +2155,48 @@ PATH is the sexp to evaluate, as a string."
(org-link-set-parameters "elisp" :follow #'org-link--open-elisp) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp)
;;;; "file" link type ;;;; "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 ;;;; "help" link type
(defun org-link--open-help (path _) (defun org-link--open-help (path _)

View File

@ -797,9 +797,18 @@ It is meant to be added to `org-export-before-parsing-hook'."
See `org-open-file' for details about ARG." See `org-open-file' for details about ARG."
(org-link-open-as-file (org-attach-expand file) 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" (org-link-set-parameters "attachment"
:follow #'org-attach-follow :follow #'org-attach-follow
:complete #'org-attach-complete-link) :complete #'org-attach-complete-link
:preview #'org-attach-preview-file)
(defun org-attach-complete-link () (defun org-attach-complete-link ()
"Advise the user with the available files in the attachment directory." "Advise the user with the available files in the attachment directory."

View File

@ -1010,6 +1010,191 @@ use of this function is for the stuck project list."
(define-obsolete-function-alias 'org-add-angle-brackets (define-obsolete-function-alias 'org-add-angle-brackets
'org-link-add-angle-brackets "9.3") '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. ;; The function was made obsolete by commit 65399674d5 of 2013-02-22.
;; This make-obsolete call was added 2016-09-01. ;; This make-obsolete call was added 2016-09-01.
(make-obsolete 'org-capture-import-remember-templates (make-obsolete 'org-capture-import-remember-templates

View File

@ -40,14 +40,14 @@
(declare-function org-element-post-affiliated "org-element" (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-lineage "org-element-ast" (datum &optional types with-self))
(declare-function org-element-at-point "org-element" (&optional pom cached-only)) (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-get-tags "org" (&optional pos local fontify))
(declare-function org-subtree-end-visible-p "org" ()) (declare-function org-subtree-end-visible-p "org" ())
(declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-narrow-to-subtree "org" (&optional element))
(declare-function org-next-visible-heading "org" (arg)) (declare-function org-next-visible-heading "org" (arg))
(declare-function org-at-property-p "org" ()) (declare-function org-at-property-p "org" ())
(declare-function org-re-property "org" (property &optional literal allow-null value)) (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-item-beginning-re "org" ())
(declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-heading-p "org" (&optional invisible-not-ok))
(declare-function org-at-item-p "org" ()) (declare-function org-at-item-p "org" ())
@ -217,7 +217,7 @@ the values `folded', `children', or `subtree'."
(defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees
org-cycle-show-empty-lines org-cycle-show-empty-lines
org-cycle-optimize-window-after-visibility-change 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. "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 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 the new state that was set by the most recent `org-cycle' command. The
@ -237,11 +237,15 @@ normal outline commands like `show-all', but not with the cycling commands."
:group 'org-cycle :group 'org-cycle
:type 'boolean) :type 'boolean)
(defcustom org-cycle-inline-images-display nil (defvaralias 'org-cycle-inline-images-display
"Non-nil means auto display inline images under subtree when cycling." '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-startup
:group 'org-cycle :group 'org-cycle
:package-version '(Org . "9.6") :package-version '(Org . "9.8")
:type 'boolean) :type 'boolean)
(defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook) (defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook)
@ -804,12 +808,15 @@ STATE should be one of the symbols listed in the docstring of
"Subtree is archived and stays closed. Use \ "Subtree is archived and stays closed. Use \
`\\[org-cycle-force-archived]' to cycle it anyway.")))))) `\\[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. "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 STATE is the current outline visibility state. It should be one of
symbols `content', `all', `folded', `children', or `subtree'." symbols `content', `all', `folded', `children', or `subtree'."
(when org-cycle-inline-images-display (when org-cycle-link-previews-display
(pcase state (pcase state
('children ('children
(org-with-wide-buffer (org-with-wide-buffer
@ -817,19 +824,19 @@ symbols `content', `all', `folded', `children', or `subtree'."
;; If has nested headlines, beg,end only from parent headline ;; If has nested headlines, beg,end only from parent headline
;; to first child headline which reference to upper ;; to first child headline which reference to upper
;; let-binding `org-next-visible-heading'. ;; let-binding `org-next-visible-heading'.
(org-display-inline-images (org-link-preview-region
nil nil nil nil
(point-min) (progn (org-next-visible-heading 1) (point))))) (point-min) (progn (org-next-visible-heading 1) (point)))))
('subtree ('subtree
(org-with-wide-buffer (org-with-wide-buffer
(org-narrow-to-subtree) (org-narrow-to-subtree)
;; If has nested headlines, also inline display images under all sub-headlines. ;; 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 ('folded
(org-with-wide-buffer (org-with-wide-buffer
(org-narrow-to-subtree) (org-narrow-to-subtree)
(if (numberp (point-max)) (if (numberp (point-max))
(org-remove-inline-images (point-min) (point-max)) (org-link-preview-clear (point-min) (point-max))
(ignore))))))) (ignore)))))))
(provide 'org-cycle) (provide 'org-cycle)

View File

@ -218,7 +218,8 @@
(declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-radio-button "org" (&optional arg))
(declare-function org-toggle-comment "org" ()) (declare-function org-toggle-comment "org" ())
(declare-function org-toggle-fixed-width "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-latex-preview "org" (&optional arg))
(declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-narrow-to-subtree "org" ())
(declare-function org-toggle-ordered-property "org" ()) (declare-function org-toggle-ordered-property "org" ())
@ -652,8 +653,8 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names."
(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 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-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-l") #'org-latex-preview)
(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview)
(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x 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 \\") #'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-b") #'org-toggle-checkbox)
(org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button)

View File

@ -633,7 +633,7 @@ manner suitable for prepending to a user-specified script."
(defun org-plot/redisplay-img-in-buffer (img-file) (defun org-plot/redisplay-img-in-buffer (img-file)
"Find any overlays for IMG-FILE in the current Org buffer, and refresh them." "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 (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file))
(when (and (file-exists-p img-file) (when (and (file-exists-p img-file)
(fboundp 'image-flush)) (fboundp 'image-flush))

View File

@ -1160,14 +1160,22 @@ the following lines anywhere in the buffer:
:package-version '(Org . "9.2") :package-version '(Org . "9.2")
:safe #'booleanp) :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. "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 This can also be configured on a per-file basis by adding one of
the following lines anywhere in the buffer: the following lines anywhere in the buffer:
#+STARTUP: inlineimages #+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 :group 'org-startup
:version "24.1" :version "29.4"
:type 'boolean) :type 'boolean)
(defcustom org-startup-with-latex-preview nil (defcustom org-startup-with-latex-preview nil
@ -4153,8 +4161,10 @@ After a match, the following groups carry important information:
("shrink" org-startup-shrink-all-tables t) ("shrink" org-startup-shrink-all-tables t)
("descriptivelinks" org-link-descriptive t) ("descriptivelinks" org-link-descriptive t)
("literallinks" org-link-descriptive nil) ("literallinks" org-link-descriptive nil)
("inlineimages" org-startup-with-inline-images t) ("inlineimages" org-startup-with-link-previews t)
("noinlineimages" org-startup-with-inline-images nil) ("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) ("latexpreview" org-startup-with-latex-preview t)
("nolatexpreview" org-startup-with-latex-preview nil) ("nolatexpreview" org-startup-with-latex-preview nil)
("customtime" org-display-custom-times t) ("customtime" org-display-custom-times t)
@ -5079,7 +5089,7 @@ The following commands are available:
;; modifications to make cache updates work reliably. ;; modifications to make cache updates work reliably.
(org-unmodified (org-unmodified
(when org-startup-with-beamer-mode (org-beamer-mode)) (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))) (when org-startup-with-latex-preview (org-latex-preview '(16)))
(unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility))
(when org-startup-truncated (setq truncate-lines t)) (when org-startup-truncated (setq truncate-lines t))
@ -15601,26 +15611,6 @@ This requires Emacs >= 24.1, built with imagemagick support."
(list :tag "Use #+ATTR* or a number of pixels" (integer)) (list :tag "Use #+ATTR* or a number of pixels" (integer))
(const :tag "Use #+ATTR* or don't resize" nil))) (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 (defcustom org-agenda-inhibit-startup nil
"Inhibit startup when preparing agenda buffers. "Inhibit startup when preparing agenda buffers.
When this variable is t, the initialization of the Org agenda When this variable is t, the initialization of the Org agenda
@ -16667,518 +16657,6 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML."
(format "%g" (/ value 65535.0))) (format "%g" (/ value 65535.0)))
;; Image display
(defvar-local org-inline-image-overlays nil)
;; Preserve when switching modes or when restarting Org.
;; If we clear the overlay list and later enable Or mode, the existing
;; image overlays will never be cleared by `org-toggle-inline-images'
;; and `org-toggle-inline-images-command'.
(put 'org-inline-image-overlays 'permanent-local t)
(defun org--inline-image-overlays (&optional beg end)
"Return image overlays between BEG and END."
(let* ((beg (or beg (point-min)))
(end (or end (point-max)))
(overlays (overlays-in beg end))
result)
(dolist (ov overlays result)
(when (memq ov org-inline-image-overlays)
(push ov result)))))
(defun org-toggle-inline-images-command (&optional arg beg end)
"Toggle display of inline images without description at point.
When point is at an image link, toggle displaying that image.
Otherwise, toggle displaying images in current entry.
When region BEG..END is active, toggle displaying images in the
region.
With numeric prefix ARG 1, display images with description as well.
With prefix ARG `\\[universal-argument]', toggle displaying images in
the accessible portion of the buffer. With numeric prefix ARG 11, do
the same, but include images with description.
With prefix ARG `\\[universal-argument] \\[universal-argument]', hide
all the images in accessible portion of the buffer.
This command is designed for interactive use. From Elisp, you can
also use `org-toggle-inline-images'."
(interactive (cons current-prefix-arg
(when (use-region-p)
(list (region-beginning) (region-end)))))
(let* ((include-linked
(cond
((member arg '(nil (4) (16)) ) nil)
((member arg '(1 11)) 'include-linked)
(t 'include-linked)))
(interactive? (called-interactively-p 'any))
(toggle-images
(lambda (&optional beg end scope force-remove)
(let* ((beg (or beg (point-min)))
(end (or end (point-max)))
(old (org--inline-image-overlays beg end))
(scope (or scope (format "%d:%d" beg end))))
(if (or old force-remove)
(progn
(org-remove-inline-images beg end)
(when interactive?
(message
"[%s] Inline image display turned off (removed %d images)"
scope (length old))))
(org-display-inline-images include-linked t beg end)
(when interactive?
(let ((new (org--inline-image-overlays beg end)))
(message
(if new
(format "[%s] %d images displayed inline %s"
scope (length new)
(if include-linked "(including images with description)"
""))
(format "[%s] No images to display inline" scope))))))))))
(cond
((not (display-graphic-p))
(message "Your Emacs does not support displaying images!"))
;; Region selected :: toggle images in region.
((and beg end) (funcall toggle-images beg end "region"))
;; C-u or C-11 argument :: toggle images in the whole buffer.
((member arg '(11 (4))) (funcall toggle-images nil nil "buffer"))
;; C-u C-u argument :: unconditionally hide images in the buffer.
((equal arg '(16)) (funcall toggle-images nil nil "buffer" 'remove))
;; Argument nil or 1, no region selected :: toggle (display or hide
;; dwim) images in current section or image link at point.
((and (member arg '(nil 1)) (null beg) (null end))
(let ((context (org-element-context)))
;; toggle display of inline image link at point.
(if (org-element-type-p context 'link)
(funcall toggle-images
(org-element-begin context)
(org-element-end context)
"image at point")
(let ((beg (if (org-before-first-heading-p) (point-min)
(save-excursion
(org-with-limited-levels (org-back-to-heading t) (point)))))
(end (org-with-limited-levels (org-entry-end-position))))
(funcall toggle-images beg end "current section")))))
;; Any other non-nil argument.
((not (null arg)) (funcall toggle-images beg end "region")))))
(defun org-toggle-inline-images (&optional include-linked beg end)
"Toggle the display of inline images.
INCLUDE-LINKED is passed to `org-display-inline-images'."
(interactive "P")
(if (org--inline-image-overlays beg end)
(progn
(org-remove-inline-images beg end)
(when (called-interactively-p 'interactive)
(message "Inline image display turned off")))
(org-display-inline-images include-linked nil beg end)
(when (called-interactively-p 'interactive)
(let ((new (org--inline-image-overlays beg end)))
(message (if new
(format "%d images displayed inline"
(length new))
"No images to display inline"))))))
(defun org-redisplay-inline-images ()
"Assure display of inline images and refresh them."
(interactive)
(org-toggle-inline-images)
(unless org-inline-image-overlays
(org-toggle-inline-images)))
;; For without-x builds.
(declare-function image-flush "image" (spec &optional frame))
(defcustom org-display-remote-inline-images 'skip
"How to display remote inline images.
Possible values of this option are:
skip Don't display remote images.
download Always download and display remote images.
t
cache Display remote images, and open them in separate buffers
for caching. Silently update the image buffer when a file
change is detected."
:group 'org-appearance
:package-version '(Org . "9.7")
:type '(choice
(const :tag "Ignore remote images" skip)
(const :tag "Always display remote images" download)
(const :tag "Display and silently update remote images" cache))
:safe #'symbolp)
(defcustom org-image-align 'left
"How to align images previewed using `org-display-inline-images'.
Only stand-alone image links are affected by this setting. These
are links without surrounding text.
Possible values of this option are:
left Insert image at specified position.
center Center image previews.
right Right-align image previews."
:group 'org-appearance
:package-version '(Org . "9.7")
:type '(choice
(const :tag "Left align (or don\\='t align) image previews" left)
(const :tag "Center image previews" center)
(const :tag "Right align image previews" right))
:safe #'symbolp)
(defun org--create-inline-image (file width)
"Create image located at FILE, or return nil.
WIDTH is the width of the image. The image may not be created
according to the value of `org-display-remote-inline-images'."
(let* ((remote? (file-remote-p file))
(file-or-data
(pcase org-display-remote-inline-images
((guard (not remote?)) file)
(`download (with-temp-buffer
(set-buffer-multibyte nil)
(insert-file-contents-literally file)
(buffer-string)))
((or `cache `t)
(let ((revert-without-query '(".")))
(with-current-buffer (find-file-noselect file)
(buffer-string))))
(`skip nil)
(other
(message "Invalid value of `org-display-remote-inline-images': %S"
other)
nil))))
(when file-or-data
(create-image file-or-data
(and (image-type-available-p 'imagemagick)
width
'imagemagick)
remote?
:width width
:max-width
(pcase org-image-max-width
(`fill-column (* fill-column (frame-char-width (selected-frame))))
(`window (window-width nil t))
((pred integerp) org-image-max-width)
((pred floatp) (floor (* org-image-max-width (window-width nil t))))
(`nil nil)
(_ (error "Unsupported value of `org-image-max-width': %S"
org-image-max-width)))
:scale 1))))
(defun org-display-inline-images (&optional include-linked refresh beg end)
"Display inline images.
An inline image is a link which follows either of these
conventions:
1. Its path is a file with an extension matching return value
from `image-file-name-regexp' and it has no contents.
2. Its description consists in a single link of the previous
type. In this case, that link must be a well-formed plain
or angle link, i.e., it must have an explicit \"file\" or
\"attachment\" type.
Equip each image with the key-map `image-map'.
When optional argument INCLUDE-LINKED is non-nil, also links with
a text description part will be inlined. This can be nice for
a quick look at those images, but it does not reflect what
exported files will look like.
When optional argument REFRESH is non-nil, refresh existing
images between BEG and END. This will create new image displays
only if necessary.
BEG and END define the considered part. They default to the
buffer boundaries with possible narrowing."
(interactive "P")
(when (display-graphic-p)
(when refresh
(org-remove-inline-images beg end)
(when (fboundp 'clear-image-cache) (clear-image-cache)))
(let ((end (or end (point-max))))
(org-with-point-at (or beg (point-min))
(let* ((case-fold-search t)
(file-extension-re (image-file-name-regexp))
(link-abbrevs (mapcar #'car
(append org-link-abbrev-alist-local
org-link-abbrev-alist)))
;; Check absolute, relative file names and explicit
;; "file:" links. Also check link abbreviations since
;; some might expand to "file" links.
(file-types-re
(format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)"
(if (not link-abbrevs) ""
(concat "\\|" (regexp-opt link-abbrevs))))))
(while (re-search-forward file-types-re end t)
(let* ((link (org-element-lineage
(save-match-data (org-element-context))
'link t))
(linktype (org-element-property :type link))
(inner-start (match-beginning 1))
(path
(cond
;; No link at point; no inline image.
((not link) nil)
;; File link without a description. Also handle
;; INCLUDE-LINKED here since it should have
;; precedence over the next case. I.e., if link
;; contains filenames in both the path and the
;; description, prioritize the path only when
;; INCLUDE-LINKED is non-nil.
((or (not (org-element-contents-begin link))
include-linked)
(and (or (equal "file" linktype)
(equal "attachment" linktype))
(org-element-property :path link)))
;; Link with a description. Check if description
;; is a filename. Even if Org doesn't have syntax
;; for those -- clickable image -- constructs, fake
;; them, as in `org-export-insert-image-links'.
((not inner-start) nil)
(t
(org-with-point-at inner-start
(and (looking-at
(if (char-equal ?< (char-after inner-start))
org-link-angle-re
org-link-plain-re))
;; File name must fill the whole
;; description.
(= (org-element-contents-end link)
(match-end 0))
(progn
(setq linktype (match-string 1))
(match-string 2))))))))
(when (and path (string-match-p file-extension-re path))
(let ((file (if (equal "attachment" linktype)
(progn
(require 'org-attach)
(ignore-errors (org-attach-expand path)))
(expand-file-name path))))
;; Expand environment variables.
(when file (setq file (substitute-in-file-name file)))
(when (and file (file-exists-p file))
(let ((width (org-display-inline-image--width link))
(align (org-image--align link))
(old (get-char-property-and-overlay
(org-element-begin link)
'org-image-overlay)))
(if (and (car-safe old) refresh)
(image-flush (overlay-get (cdr old) 'display))
(let ((image (org--create-inline-image file width)))
(when image
(let ((ov (make-overlay
(org-element-begin link)
(progn
(goto-char
(org-element-end link))
(unless (eolp) (skip-chars-backward " \t"))
(point)))))
;; See bug#59902. We cannot rely
;; on Emacs to update image if the file
;; has changed.
(image-flush image)
(overlay-put ov 'display image)
(overlay-put ov 'face 'default)
(overlay-put ov 'org-image-overlay t)
(overlay-put
ov 'modification-hooks
(list 'org-display-inline-remove-overlay))
(when (boundp 'image-map)
(overlay-put ov 'keymap image-map))
(when align
(overlay-put
ov 'before-string
(propertize
" " 'face 'default
'display
(pcase align
("center" `(space :align-to (- center (0.5 . ,image))))
("right" `(space :align-to (- right ,image)))))))
(push ov org-inline-image-overlays))))))))))))))))
(declare-function org-export-read-attribute "ox"
(attribute element &optional property))
(defvar visual-fill-column-width) ; Silence compiler warning
(defun org-display-inline-image--width (link)
"Determine the display width of the image LINK, in pixels.
- When `org-image-actual-width' is t, the image's pixel width is used.
- When `org-image-actual-width' is a number, that value will is used.
- When `org-image-actual-width' is nil or a list, :width attribute of
#+attr_org or the first #+attr_... (if it exists) is used to set the
image width. A width of X% is divided by 100. If the value is a
float between 0 and 2, it interpreted as that proportion of the text
width in the buffer.
If no :width attribute is given and `org-image-actual-width' is a
list with a number as the car, then that number is used as the
default value."
;; Apply `org-image-actual-width' specifications.
;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified
;; width.
(let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width)))
(cond
((eq org-image-actual-width t) nil)
((listp org-image-actual-width)
(require 'ox)
(let* ((par (org-element-lineage link 'paragraph))
;; Try to find an attribute providing a :width.
;; #+ATTR_ORG: :width ...
(attr-width (org-export-read-attribute :attr_org par :width))
(width-unreadable?
(lambda (value)
(or (not (stringp value))
(unless (string= value "t")
(or (not (string-match
(rx bos (opt "+")
(or
;; Number of pixels
;; must be a lone number, not
;; things like 4in
(seq (1+ (in "0-9")) eos)
;; Numbers ending with %
(seq (1+ (in "0-9.")) (group-n 1 "%"))
;; Fractions
(seq (0+ (in "0-9")) "." (1+ (in "0-9")))))
value))
(let ((number (string-to-number value)))
(and (floatp number)
(not (match-string 1 value)) ; X%
(not (<= 0.0 number 2.0)))))))))
;; #+ATTR_BACKEND: :width ...
(attr-other
(catch :found
(org-element-properties-map
(lambda (prop _)
(when (and
(not (eq prop :attr_org))
(string-match-p "^:attr_" (symbol-name prop))
(not (funcall width-unreadable? (org-export-read-attribute prop par :width))))
(throw :found prop)))
par)))
(attr-width
(if (not (funcall width-unreadable? attr-width))
attr-width
;; When #+attr_org: does not have readable :width
(and attr-other
(org-export-read-attribute attr-other par :width))))
(width
(cond
;; Treat :width t as if `org-image-actual-width' were t.
((string= attr-width "t") nil)
;; Fallback to `org-image-actual-width' if no interprable width is given.
((funcall width-unreadable? attr-width)
(car org-image-actual-width))
;; Convert numeric widths to numbers, converting percentages.
((string-match-p "\\`[[+]?[0-9.]+%" attr-width)
(/ (string-to-number attr-width) 100.0))
(t (string-to-number attr-width)))))
(if (and (floatp width) (<= 0.0 width 2.0))
;; A float in [0,2] should be interpereted as this portion of
;; the text width in the window. This works well with cases like
;; #+attr_latex: :width 0.X\{line,page,column,etc.}width,
;; as the "0.X" is pulled out as a float. We use 2 as the upper
;; bound as cases such as 1.2\linewidth are feasible.
(round (* width
(window-pixel-width)
(/ (or (and (bound-and-true-p visual-fill-column-mode)
(or visual-fill-column-width auto-fill-function))
(when auto-fill-function fill-column)
(- (window-text-width) (line-number-display-width)))
(float (window-total-width)))))
width)))
((numberp org-image-actual-width)
org-image-actual-width)
(t nil))))
(defun org-image--align (link)
"Determine the alignment of the image LINK.
LINK is a link object.
In decreasing order of priority, this is controlled:
- Per image by the value of `:center' or `:align' in the
affiliated keyword `#+attr_org'.
- By the `#+attr_html' or `#+attr_latex` keywords with valid
`:center' or `:align' values.
- Globally by the user option `org-image-align'.
The result is either nil or one of the strings \"left\",
\"center\" or \"right\".
\"center\" will cause the image preview to be centered, \"right\"
will cause it to be right-aligned. A value of \"left\" or nil
implies no special alignment."
(let ((par (org-element-lineage link 'paragraph)))
;; Only align when image is not surrounded by paragraph text:
(when (and par ; when image is not in paragraph, but in table/headline/etc, do not align
(= (org-element-begin link)
(save-excursion
(goto-char (org-element-contents-begin par))
(skip-chars-forward "\t ")
(point))) ;account for leading space
;before link
(<= (- (org-element-contents-end par)
(org-element-end link))
1)) ;account for trailing newline
;at end of paragraph
(save-match-data
;; Look for a valid ":center t" or ":align left|center|right"
;; attribute.
;;
;; An attr_org keyword has the highest priority, with
;; any attr.* next. Choosing between these is
;; unspecified.
(let ((center-re ":\\(center\\)[[:space:]]+t\\b")
(align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b")
attr-align)
(catch 'exit
(org-element-properties-mapc
(lambda (propname propval)
(when (and propval
(string-match-p ":attr.*" (symbol-name propname)))
(setq propval (car-safe propval))
(when (or (string-match center-re propval)
(string-match align-re propval))
(setq attr-align (match-string 1 propval))
(when (eq propname :attr_org)
(throw 'exit t)))))
par))
(if attr-align
(when (member attr-align '("center" "right")) attr-align)
;; No image-specific keyword, check global alignment property
(when (memq org-image-align '(center right))
(symbol-name org-image-align))))))))
(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len)
"Remove inline-display overlay if a corresponding region is modified."
(when (and ov after)
(setq org-inline-image-overlays (delete ov org-inline-image-overlays))
;; Clear image from cache to avoid image not updating upon
;; changing on disk. See Emacs bug#59902.
(when (overlay-get ov 'org-image-overlay)
(image-flush (overlay-get ov 'display)))
(delete-overlay ov)))
(defun org-remove-inline-images (&optional beg end)
"Remove inline display of images."
(interactive)
(let* ((beg (or beg (point-min)))
(end (or end (point-max)))
(overlays (overlays-in beg end)))
(dolist (ov overlays)
(when (memq ov org-inline-image-overlays)
(setq org-inline-image-overlays (delq ov org-inline-image-overlays))
(delete-overlay ov)))
;; Clear removed overlays.
(dolist (ov org-inline-image-overlays)
(unless (overlay-buffer ov)
(setq org-inline-image-overlays (delq ov org-inline-image-overlays))))))
(defvar org-self-insert-command-undo-counter 0) (defvar org-self-insert-command-undo-counter 0)
(defvar org-speed-command nil) (defvar org-speed-command nil)

View File

@ -716,14 +716,14 @@ Unfolded Paragraph.
(org-show-subtree) (org-show-subtree)
(org-fold-subtree t) (org-fold-subtree t)
(run-hook-with-args 'org-cycle-hook 'folded) (run-hook-with-args 'org-cycle-hook 'folded)
(should-not org-inline-image-overlays) (should-not org-link-preview-overlays)
(should-not (should-not
(cl-every (cl-every
(lambda (ov) (overlay-get ov 'org-image-overlay)) (lambda (ov) (overlay-get ov 'org-image-overlay))
(overlays-in (point-min) (point-max)))) (overlays-in (point-min) (point-max))))
(org-show-subtree) (org-show-subtree)
(run-hook-with-args 'org-cycle-hook 'subtree) (run-hook-with-args 'org-cycle-hook 'subtree)
(should org-inline-image-overlays) (should org-link-preview-overlays)
(should (should
(cl-every (cl-every
(lambda (ov) (overlay-get ov 'org-image-overlay)) (lambda (ov) (overlay-get ov 'org-image-overlay))