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 v4] Inline image display as part of a new org-link-preview system
Date: Sun, 15 Sep 2024 14:57:49 -0700 [thread overview]
Message-ID: <8734m060ma.fsf@gmail.com> (raw)
In-Reply-To: <87h6ah72ui.fsf@localhost>
[-- Attachment #1: Type: text/plain, Size: 1359 bytes --]
Latest iteration of patch attached.
> In theory, we might not need to clear image cache as long as we call
> `image-flush' in `org-link-preview-clear'.
Removed (clear-image-cache) when refreshing since we run (image-flush)
unconditionally in org-link-preview-file.
> My hesitance about top-level require is that ol.el is loaded by default
> and that the preview functionality may or may not be used by the
> users. So, loading image.el ought to be optional.
No change here.
> It means that you are scheduling every single preview batch to fire at
> the same time. I think that the timers here should run sequentially -
> (1) fire the first batch without delay; (2) wait org-link-preview-delay
> and fire the next batch; (3) ...
Done, and tested preview run timings to be sure.
>> + (lambda (previews)
>> + ;; (message "queue: %d" preview-queue-size)
>> + (cl-decf preview-queue-size)
>> + (dolist (preview-spec (nreverse previews))
>> + (when-let* ((ov (cadr preview-spec))
>> + (buf (overlay-buffer ov)))
>
> ov or its buffer may not exit at the time the timer is fired.
> Because, for example, the buffer is killed, or because user edited the
> underlying link before it got displayed.
Handled by (overlay-buffer ov), as explained in the previous email.
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: 69827 bytes --]
From b70a4849b9c4ef2dca0d834ab60b2ab173e8b38b Mon Sep 17 00:00:00 2001
From: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Date: Fri, 23 Aug 2024 15:46:53 -0700
Subject: [PATCH] org-link: Customizable preview API for arbitrary link types
Add a customizable preview API for arbitrary link types. Make
inline image previews a part of the more universal org-link
preview feature. Each link type can now be previewed differently
based on a new link parameter.
* lisp/ol.el (org-link-parameters, org-link-preview-batch-size,
org-link-preview-delay, org-link-preview-overlays,
org-link-preview--get-overlays, org-link-preview--remove-overlay,
org-link-previeworg-link-preview-region, org-link-preview-clear,
org-link-preview-file, org-display-remote-inline-images,
org-image-align, org--create-inline-image,
org-display-inline-image--width, org-image--align): Add new
commands `org-link-preview', `org-link-preview-region' and
`org-link-preview-clear' for creating link previews for any kind
of link. Add new org-link parameter `:preview' for specifying how
a link type should be previewed. This link parameter is a
function called asynchronously to place previes. File links and
attachments are previewed using inline image previews as before.
Move image handling utilities from lisp/org.el to lisp/ol.el.
* testing/lisp/test-org-fold.el: Use `org-link-preview'.
* lisp/org.el (org-toggle-inline-images,
org-toggle-inline-images-command, org-display-inline-images,
org--inline-image-overlays, org-inline-image-overlays,
org-redisplay-inline-images, org-image-align,
org-display-inline-remove-overlay, org-remove-inline-images):
Obsolete and move `org-toggle-inline-images',
`org-display-inline-images' and `org-redisplay-inline-images' to
org-compat. These are obsoleted by `org-link-preview' and
`org-link-preview-region'. Remove
`org-toggle-inline-images-command'. Move the other internal
functions to org-link.
* lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to
use `org-link-preview'.
* lisp/org-keys.el: Bind `C-c C-x C-v' to new command
`org-link-preview', which has the same prefix arg behaviors as
`org-latex-preview'. In addition to these, it supports numeric
prefix args 1 and 11 to preview links with descriptions at
point/region (with 1) and across the buffer (with 11).
* lisp/org-cycle.el (org-cycle-display-inline-images): Use
`org-link-preview'.
* lisp/org-compat.el (org-display-inline-remove-overlay,
org--inline-image-overlays, org-remove-inline-images,
org-inline-image-overlays, org-display-inline-images,
org-toggle-inline-images):
* lisp/org-attach.el (org-attach-preview-file): Add new `:preview'
link parameter for links of type "attachment", set to the new
function `org-attach-preview-file'.
---
lisp/ol.el | 567 +++++++++++++++++++++++++++++++++-
lisp/org-attach.el | 11 +-
lisp/org-compat.el | 189 ++++++++++++
lisp/org-cycle.el | 10 +-
lisp/org-keys.el | 4 +-
lisp/org-plot.el | 2 +-
lisp/org.el | 534 +-------------------------------
testing/lisp/test-org-fold.el | 4 +-
8 files changed, 776 insertions(+), 545 deletions(-)
diff --git a/lisp/ol.el b/lisp/ol.el
index 52ea62d69..9e1fee980 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,16 @@ (defcustom org-link-parameters nil
The default face is `org-link'.
+`:preview'
+
+ Function to run to generate an in-buffer preview for the link. It
+ must accept three arguments:
+ - an overlay placed from the start to the end of the link.
+ - the link path, as a string.
+ - the link element
+
+ This function must return a non-nil value to indicate success.
+
`:help-echo'
String or function used as a value for the `help-echo' text
@@ -521,6 +536,80 @@ (defcustom org-link-keep-stored-after-insertion nil
:type 'boolean
:safe #'booleanp)
+(defcustom org-link-preview-delay 0.05
+ "Idle delay in seconds between link previews when using
+`org-link-preview'. Links are previewed in batches (see
+`org-link-preview-batch-size') spaced out by this delay. Set
+this to a small number for more immediate previews, but at the
+expense of higher lag."
+ :group 'org-link
+ :type 'number)
+
+(defcustom org-link-preview-batch-size 6
+ "Number of links that are previewed at once with
+`org-link-preview'. Links are previewed in batches spaced out in
+time (see `org-link-preview-delay'). Set this to a large integer
+for more immediate previews, but at the expense of higher lag."
+ :group 'org-link
+ :type 'natnum)
+
+(defcustom org-display-remote-inline-images 'skip
+ "How to display remote inline images.
+Possible values of this option are:
+
+skip Don't display remote images.
+download Always download and display remote images.
+t
+cache Display remote images, and open them in separate buffers
+ for caching. Silently update the image buffer when a file
+ change is detected."
+ :group 'org-appearance
+ :package-version '(Org . "9.7")
+ :type '(choice
+ (const :tag "Ignore remote images" skip)
+ (const :tag "Always display remote images" download)
+ (const :tag "Display and silently update remote images" cache))
+ :safe #'symbolp)
+
+(defcustom org-image-max-width 'fill-column
+ "When non-nil, limit the displayed image width.
+This setting only takes effect when `org-image-actual-width' is set to
+t or when #+ATTR* is set to t.
+
+Possible values:
+- `fill-column' :: limit width to `fill-column'
+- `window' :: limit width to window width
+- integer :: limit width to number in pixels
+- float :: limit width to that fraction of window width
+- nil :: do not limit image width"
+ :group 'org-appearance
+ :package-version '(Org . "9.7")
+ :type '(choice
+ (const :tag "Do not limit image width" nil)
+ (const :tag "Limit to `fill-column'" fill-column)
+ (const :tag "Limit to window width" window)
+ (integer :tag "Limit to a number of pixels")
+ (float :tag "Limit to a fraction of window width")))
+
+(defcustom org-image-align 'left
+ "How to align images previewed using `org-link-preview-region'.
+
+Only stand-alone image links are affected by this setting. These
+are links without surrounding text.
+
+Possible values of this option are:
+
+left Insert image at specified position.
+center Center image previews.
+right Right-align image previews."
+ :group 'org-appearance
+ :package-version '(Org . "9.7")
+ :type '(choice
+ (const :tag "Left align (or don\\='t align) image previews" left)
+ (const :tag "Center image previews" center)
+ (const :tag "Right align image previews" right))
+ :safe #'symbolp)
+
;;; Public variables
(defconst org-target-regexp (let ((border "[^<>\n\r \t]"))
@@ -649,6 +738,13 @@ (defvar org-link--insert-history nil
(defvar org-link--search-failed nil
"Non-nil when last link search failed.")
+(defvar-local org-link-preview-overlays nil)
+;; Preserve when switching modes or when restarting Org.
+;; If we clear the overlay list and later enable Or mode, the existing
+;; image overlays will never be cleared by `org-link-preview'
+;; and `org-link-preview-clear'.
+(put 'org-link-preview-overlays 'permanent-local t)
+
\f
;;; Internal Functions
@@ -881,7 +977,228 @@ (defun org-link--file-link-to-here ()
(setq desc search-desc))))
(cons link desc)))
+(defun org-link-preview--get-overlays (&optional beg end)
+ "Return link preview overlays between BEG and END."
+ (let* ((beg (or beg (point-min)))
+ (end (or end (point-max)))
+ (overlays (overlays-in beg end))
+ result)
+ (dolist (ov overlays result)
+ (when (memq ov org-link-preview-overlays)
+ (push ov result)))))
+
+(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len)
+ "Remove link-preview overlay OV if a corresponding region is modified.
+
+AFTER is true when this function is called post-change."
+ (when (and ov after)
+ (setq org-link-preview-overlays (delq ov org-link-preview-overlays))
+ ;; Clear image from cache to avoid image not updating upon
+ ;; changing on disk. See Emacs bug#59902.
+ (when-let* (((overlay-get ov 'org-image-overlay))
+ (disp (overlay-get ov 'display))
+ ((imagep disp)))
+ (image-flush disp))
+ (delete-overlay ov)))
+
\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 +1890,213 @@ (defun org-link-add-angle-brackets (s)
(unless (equal (substring s -1) ">") (setq s (concat s ">")))
s)
+;;;###autoload
+(defun org-link-preview (&optional arg beg end)
+ "Toggle display of link previews in the buffer.
+
+When region BEG..END is active, preview links in the
+region.
+
+When point is at a link, display a preview for that link only.
+Otherwise, display previews for links in current entry.
+
+With numeric prefix ARG 1, preview links with description as
+well.
+
+With prefix ARG `\\[universal-argument]', clear link previews at
+point or in the current entry.
+
+With prefix ARG `\\[universal-argument] \\[universal-argument]',
+ display link previews in the accessible portion of the
+ buffer. With numeric prefix ARG 11, do the same, but include
+ links with descriptions.
+
+With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]',
+hide all link previews in the accessible portion of the buffer.
+
+This command is designed for interactive use. From Elisp, you can
+also use `org-link-preview-region'."
+ (interactive (cons current-prefix-arg
+ (when (use-region-p)
+ (list (region-beginning) (region-end)))))
+ (let* ((include-linked
+ (cond
+ ((member arg '(nil (4) (16)) ) nil)
+ ((member arg '(1 11)) 'include-linked)
+ (t 'include-linked)))
+ (interactive? (called-interactively-p 'any))
+ (toggle-previews
+ (lambda (&optional beg end scope remove)
+ (let* ((beg (or beg (point-min)))
+ (end (or end (point-max)))
+ (old (org-link-preview--get-overlays beg end))
+ (scope (or scope (format "%d:%d" beg end))))
+ (if remove
+ (progn
+ (org-link-preview-clear beg end)
+ (when interactive?
+ (message
+ "[%s] Inline link previews turned off (removed %d images)"
+ scope (length old))))
+ (org-link-preview-region include-linked t beg end)
+ (when interactive?
+ (let ((new (org-link-preview--get-overlays beg end)))
+ (message
+ (if new
+ (format "[%s] Displaying %d images inline %s"
+ scope (length new)
+ (if include-linked "(including images with description)"
+ ""))
+ (format "[%s] No images to display inline" scope))))))))))
+ (cond
+ ;; Region selected :: display previews in region.
+ ((and beg end)
+ (funcall toggle-previews beg end "region"
+ (and (equal arg '(4)) 'remove)))
+ ;; C-u argument: clear image at point or in entry
+ ((equal arg '(4))
+ (if-let ((ov (cdr (get-char-property-and-overlay
+ (point) 'org-image-overlay))))
+ ;; clear link preview at point
+ (funcall toggle-previews
+ (overlay-start ov) (overlay-end ov)
+ "preview at point" 'remove)
+ ;; Clear link previews in entry
+ (funcall toggle-previews
+ (if (org-before-first-heading-p) (point-min)
+ (save-excursion
+ (org-with-limited-levels (org-back-to-heading t) (point))))
+ (org-with-limited-levels (org-entry-end-position))
+ "current section" 'remove)))
+ ;; C-u C-u or C-11 argument :: display images in the whole buffer.
+ ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer"))
+ ;; C-u C-u C-u argument :: unconditionally hide images in the buffer.
+ ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove))
+ ;; Argument nil or 1, no region selected :: display images in
+ ;; current section or image link at point.
+ ((and (member arg '(nil 1)) (null beg) (null end))
+ (let ((context (org-element-context)))
+ ;; toggle display of inline image link at point.
+ (if (org-element-type-p context 'link)
+ (let* ((ov (cdr-safe (get-char-property-and-overlay
+ (point) 'org-image-overlay)))
+ (remove? (and ov (memq ov org-link-preview-overlays)
+ 'remove)))
+ (funcall toggle-previews
+ (org-element-begin context)
+ (org-element-end context)
+ "image at point" remove?))
+ (let ((beg (if (org-before-first-heading-p) (point-min)
+ (save-excursion
+ (org-with-limited-levels (org-back-to-heading t) (point)))))
+ (end (org-with-limited-levels (org-entry-end-position))))
+ (funcall toggle-previews beg end "current section")))))
+ ;; Any other non-nil argument.
+ ((not (null arg)) (funcall toggle-previews beg end "region")))))
+
+(defun org-link-preview-region (&optional include-linked refresh beg end)
+ "Display link previews.
+
+A previewable link type is one that has a `:preview' link
+parameter, see `org-link-parameters'.
+
+By default, a file link or attachment is previewable if it
+follows either of these conventions:
+
+ 1. Its path is a file with an extension matching return value
+ from `image-file-name-regexp' and it has no contents.
+
+ 2. Its description consists in a single link of the previous
+ type. In this case, that link must be a well-formed plain
+ or angle link, i.e., it must have an explicit \"file\" or
+ \"attachment\" type.
+
+File links are equipped with the keymap `image-map'.
+
+When optional argument INCLUDE-LINKED is non-nil, links with a
+text description part will also be inlined. This can be nice for
+a quick look at those images, but it does not reflect what
+exported files will look like.
+
+When optional argument REFRESH is non-nil, refresh existing
+images between BEG and END. This will create new image displays
+only if necessary.
+
+BEG and END define the considered part. They default to the
+buffer boundaries with possible narrowing."
+ (interactive "P")
+ (when refresh (org-link-preview-clear beg end))
+ (org-with-point-at (or beg (point-min))
+ (let ((case-fold-search t)
+ (preview-queue (list nil)))
+ ;; Collect links to preview
+ (while (re-search-forward org-link-any-re end t)
+ (when-let* ((link (org-element-lineage
+ (save-match-data (org-element-context))
+ 'link t))
+ (linktype (org-element-property :type link))
+ (preview-func (org-link-get-parameter linktype :preview))
+ (path (and (or include-linked
+ (not (org-element-contents-begin link)))
+ (org-element-property :path link))))
+ ;; Create an overlay to hold the preview
+ (let ((ov (make-overlay
+ (org-element-begin link)
+ (progn
+ (goto-char
+ (org-element-end link))
+ (unless (eolp) (skip-chars-backward " \t"))
+ (point)))))
+ (overlay-put ov 'org-image-overlay t)
+ (overlay-put ov 'modification-hooks
+ (list 'org-link-preview--remove-overlay))
+ (push ov org-link-preview-overlays)
+ ;; Collect previews to run asynchronously, in batches
+ (if (>= (length (car-safe preview-queue)) org-link-preview-batch-size)
+ (push (list (list preview-func ov path link)) preview-queue) ;new batch
+ (push (list preview-func ov path link) (car preview-queue))))))
+ (setq preview-queue (nreverse preview-queue))
+ ;; Run preview asynchronously in batches:
+ ;; preview-queue is a list of preview batches, which is a list of specs
+ (letrec ((preview-incremental
+ (lambda ()
+ (when-let ((batch (car preview-queue)))
+ ;; spec is (preview-func overlay path link)
+ (dolist (spec (nreverse batch)) ;Preview batch
+ (when-let* ((ov (cadr spec))
+ (buf (overlay-buffer ov)))
+ (with-current-buffer buf
+ (unless (apply spec)
+ ;; Preview was unsuccessful, delete overlay
+ (delete-overlay ov)
+ (setq org-link-preview-overlays
+ (delq ov org-link-preview-overlays))))))
+ ;; Set up next idle timer
+ (pop preview-queue)
+ (when (car-safe preview-queue)
+ (run-with-idle-timer
+ (time-add (or (current-idle-time) 0) org-link-preview-delay) nil
+ preview-incremental))))))
+ (funcall preview-incremental)))))
+
+(defun org-link-preview-clear (&optional beg end)
+ "Clear link previews in region BEG to END."
+ (interactive (and (use-region-p) (list (region-beginning) (region-end))))
+ (let* ((beg (or beg (point-min)))
+ (end (or end (point-max)))
+ (overlays (overlays-in beg end)))
+ (dolist (ov overlays)
+ (when (memq ov org-link-preview-overlays)
+ (when-let ((image (overlay-get ov 'display))
+ ((imagep image)))
+ (image-flush image))
+ (setq org-link-preview-overlays (delq ov org-link-preview-overlays))
+ (delete-overlay ov)))
+ ;; Clear removed overlays.
+ (dolist (ov org-link-preview-overlays)
+ (unless (overlay-buffer ov)
+ (setq org-link-preview-overlays (delq ov org-link-preview-overlays))))))
+
\f
;;; Built-in link types
@@ -1595,7 +2119,48 @@ (defun org-link--open-elisp (path _)
(org-link-set-parameters "elisp" :follow #'org-link--open-elisp)
;;;; "file" link type
-(org-link-set-parameters "file" :complete #'org-link-complete-file)
+(org-link-set-parameters "file"
+ :complete #'org-link-complete-file
+ :preview #'org-link-preview-file)
+
+(defun org-link-preview-file (ov path link)
+ "Display image file PATH in overlay OV for LINK.
+
+LINK is the Org element being previewed.
+
+Equip each image with the keymap `image-map'.
+
+This is intended to be used as the `:preview' link property of
+file links, see `org-link-parameters'."
+ (if (not (display-graphic-p))
+ (prog1 nil
+ (message "Your Emacs does not support displaying images!"))
+ (require 'image)
+ (when-let* ((file-full (expand-file-name path))
+ (file (substitute-in-file-name file-full))
+ ((string-match-p (image-file-name-regexp) file))
+ ((file-exists-p file)))
+ (let* ((width (org-display-inline-image--width link))
+ (align (org-image--align link))
+ (image (org--create-inline-image file width)))
+ (when image ; Add image to overlay
+ ;; See bug#59902. We cannot rely
+ ;; on Emacs to update image if the file
+ ;; has changed.
+ (image-flush image)
+ (overlay-put ov 'display image)
+ (overlay-put ov 'face 'default)
+ (overlay-put ov 'keymap image-map)
+ (when align
+ (overlay-put
+ ov 'before-string
+ (propertize
+ " " 'face 'default
+ 'display
+ (pcase align
+ ("center" `(space :align-to (- center (0.5 . ,image))))
+ ("right" `(space :align-to (- right ,image)))))))
+ t)))))
;;;; "help" link type
(defun org-link--open-help (path _)
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 7a03d170e..3c4e1bfb6 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -797,9 +797,18 @@ (defun org-attach-follow (file arg)
See `org-open-file' for details about ARG."
(org-link-open-as-file (org-attach-expand file) arg))
+(defun org-attach-preview-file (ov path link)
+ "Preview attachment with PATH in overlay OV.
+
+LINK is the Org link element being previewed."
+ (org-with-point-at (org-element-begin link)
+ (org-link-preview-file
+ ov (org-attach-expand path) link)))
+
(org-link-set-parameters "attachment"
:follow #'org-attach-follow
- :complete #'org-attach-complete-link)
+ :complete #'org-attach-complete-link
+ :preview #'org-attach-preview-file)
(defun org-attach-complete-link ()
"Advise the user with the available files in the attachment directory."
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index d843216f3..242b46a86 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -783,6 +783,195 @@ (defun org-add-link-type (type &optional follow export)
(make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." "9.0")
+(declare-function org-link-preview--remove-overlay "ol")
+(declare-function org-link-preview--get-overlays "ol")
+(declare-function org-link-preview-clear "ol")
+(declare-function org-link-preview--remove-overlay "ol")
+
+(define-obsolete-function-alias 'org-display-inline-remove-overlay
+ 'org-link-preview--remove-overlay "9.8")
+(define-obsolete-function-alias 'org--inline-image-overlays
+ 'org-link-preview--get-overlays "9.8")
+(define-obsolete-function-alias 'org-remove-inline-images
+ 'org-link-preview-clear "9.8")
+(define-obsolete-variable-alias 'org-inline-image-overlays
+ 'org-link-preview-overlays "9.8")
+(defvar org-link-preview-overlays)
+(defvar org-link-abbrev-alist-local)
+(defvar org-link-abbrev-alist)
+(defvar org-link-angle-re)
+(defvar org-link-plain-re)
+(declare-function org-attach-expand "org-attach")
+(declare-function org-display-inline-image--width "org")
+(declare-function org-image--align "org")
+(declare-function org--create-inline-image "org")
+
+(make-obsolete 'org-display-inline-images
+ 'org-link-preview-region "9.8")
+;; FIXME: Unused; obsoleted; to be removed
+(defun org-display-inline-images (&optional include-linked refresh beg end)
+ "Display inline images.
+
+An inline image is a link which follows either of these
+conventions:
+
+ 1. Its path is a file with an extension matching return value
+ from `image-file-name-regexp' and it has no contents.
+
+ 2. Its description consists in a single link of the previous
+ type. In this case, that link must be a well-formed plain
+ or angle link, i.e., it must have an explicit \"file\" or
+ \"attachment\" type.
+
+Equip each image with the key-map `image-map'.
+
+When optional argument INCLUDE-LINKED is non-nil, also links with
+a text description part will be inlined. This can be nice for
+a quick look at those images, but it does not reflect what
+exported files will look like.
+
+When optional argument REFRESH is non-nil, refresh existing
+images between BEG and END. This will create new image displays
+only if necessary.
+
+BEG and END define the considered part. They default to the
+buffer boundaries with possible narrowing."
+ (interactive "P")
+ (when (display-graphic-p)
+ (when refresh
+ (org-link-preview-clear beg end)
+ (when (fboundp 'clear-image-cache) (clear-image-cache)))
+ (let ((end (or end (point-max))))
+ (org-with-point-at (or beg (point-min))
+ (let* ((case-fold-search t)
+ (file-extension-re (image-file-name-regexp))
+ (link-abbrevs (mapcar #'car
+ (append org-link-abbrev-alist-local
+ org-link-abbrev-alist)))
+ ;; Check absolute, relative file names and explicit
+ ;; "file:" links. Also check link abbreviations since
+ ;; some might expand to "file" links.
+ (file-types-re
+ (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)"
+ (if (not link-abbrevs) ""
+ (concat "\\|" (regexp-opt link-abbrevs))))))
+ (while (re-search-forward file-types-re end t)
+ (let* ((link (org-element-lineage
+ (save-match-data (org-element-context))
+ 'link t))
+ (linktype (org-element-property :type link))
+ (inner-start (match-beginning 1))
+ (path
+ (cond
+ ;; No link at point; no inline image.
+ ((not link) nil)
+ ;; File link without a description. Also handle
+ ;; INCLUDE-LINKED here since it should have
+ ;; precedence over the next case. I.e., if link
+ ;; contains filenames in both the path and the
+ ;; description, prioritize the path only when
+ ;; INCLUDE-LINKED is non-nil.
+ ((or (not (org-element-contents-begin link))
+ include-linked)
+ (and (or (equal "file" linktype)
+ (equal "attachment" linktype))
+ (org-element-property :path link)))
+ ;; Link with a description. Check if description
+ ;; is a filename. Even if Org doesn't have syntax
+ ;; for those -- clickable image -- constructs, fake
+ ;; them, as in `org-export-insert-image-links'.
+ ((not inner-start) nil)
+ (t
+ (org-with-point-at inner-start
+ (and (looking-at
+ (if (char-equal ?< (char-after inner-start))
+ org-link-angle-re
+ org-link-plain-re))
+ ;; File name must fill the whole
+ ;; description.
+ (= (org-element-contents-end link)
+ (match-end 0))
+ (progn
+ (setq linktype (match-string 1))
+ (match-string 2))))))))
+ (when (and path (string-match-p file-extension-re path))
+ (let ((file (if (equal "attachment" linktype)
+ (progn
+ (require 'org-attach)
+ (ignore-errors (org-attach-expand path)))
+ (expand-file-name path))))
+ ;; Expand environment variables.
+ (when file (setq file (substitute-in-file-name file)))
+ (when (and file (file-exists-p file))
+ (let ((width (org-display-inline-image--width link))
+ (align (org-image--align link))
+ (old (get-char-property-and-overlay
+ (org-element-begin link)
+ 'org-image-overlay)))
+ (if (and (car-safe old) refresh)
+ (image-flush (overlay-get (cdr old) 'display))
+ (let ((image (org--create-inline-image file width)))
+ (when image
+ (let ((ov (make-overlay
+ (org-element-begin link)
+ (progn
+ (goto-char
+ (org-element-end link))
+ (unless (eolp) (skip-chars-backward " \t"))
+ (point)))))
+ ;; See bug#59902. We cannot rely
+ ;; on Emacs to update image if the file
+ ;; has changed.
+ (image-flush image)
+ (overlay-put ov 'display image)
+ (overlay-put ov 'face 'default)
+ (overlay-put ov 'org-image-overlay t)
+ (overlay-put
+ ov 'modification-hooks
+ (list 'org-link-preview--remove-overlay))
+ (when (boundp 'image-map)
+ (overlay-put ov 'keymap image-map))
+ (when align
+ (overlay-put
+ ov 'before-string
+ (propertize
+ " " 'face 'default
+ 'display
+ (pcase align
+ ("center" `(space :align-to (- center (0.5 . ,image))))
+ ("right" `(space :align-to (- right ,image)))))))
+ (push ov org-inline-image-overlays))))))))))))))))
+
+(make-obsolete 'org-toggle-inline-images
+ 'org-link-preview "9.8")
+;; FIXME: Unused; obsoleted; to be removed
+(defun org-toggle-inline-images (&optional include-linked beg end)
+ "Toggle the display of inline images.
+INCLUDE-LINKED is passed to `org-display-inline-images'."
+ (interactive "P")
+ (if (org-link-preview--get-overlays beg end)
+ (progn
+ (org-link-preview-clear beg end)
+ (when (called-interactively-p 'interactive)
+ (message "Inline image display turned off")))
+ (org-display-inline-images include-linked nil beg end)
+ (when (called-interactively-p 'interactive)
+ (let ((new (org-link-preview--get-overlays beg end)))
+ (message (if new
+ (format "%d images displayed inline"
+ (length new))
+ "No images to display inline"))))))
+
+(make-obsolete 'org-redisplay-inline-images
+ 'org-link-preview "9.8")
+;; FIXME: Unused; obsoleted; to be removed
+(defun org-redisplay-inline-images ()
+ "Assure display of inline images and refresh them."
+ (interactive)
+ (org-toggle-inline-images)
+ (unless org-link-preview-overlays
+ (org-toggle-inline-images)))
+
;;;; Functions unused in Org core.
(defun org-table-recognize-table.el ()
"If there is a table.el table nearby, recognize it and move into it."
diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el
index 8a39bdb8c..1a1c916bd 100644
--- a/lisp/org-cycle.el
+++ b/lisp/org-cycle.el
@@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node))
(declare-function org-element-post-affiliated "org-element" (node))
(declare-function org-element-lineage "org-element-ast" (datum &optional types with-self))
(declare-function org-element-at-point "org-element" (&optional pom cached-only))
-(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end))
+(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end))
(declare-function org-get-tags "org" (&optional pos local fontify))
(declare-function org-subtree-end-visible-p "org" ())
(declare-function org-narrow-to-subtree "org" (&optional element))
(declare-function org-next-visible-heading "org" (arg))
(declare-function org-at-property-p "org" ())
(declare-function org-re-property "org" (property &optional literal allow-null value))
-(declare-function org-remove-inline-images "org" (&optional beg end))
+(declare-function org-link-preview-clear "ol" (&optional beg end))
(declare-function org-item-beginning-re "org" ())
(declare-function org-at-heading-p "org" (&optional invisible-not-ok))
(declare-function org-at-item-p "org" ())
@@ -817,19 +817,19 @@ (defun org-cycle-display-inline-images (state)
;; If has nested headlines, beg,end only from parent headline
;; to first child headline which reference to upper
;; let-binding `org-next-visible-heading'.
- (org-display-inline-images
+ (org-link-preview-region
nil nil
(point-min) (progn (org-next-visible-heading 1) (point)))))
('subtree
(org-with-wide-buffer
(org-narrow-to-subtree)
;; If has nested headlines, also inline display images under all sub-headlines.
- (org-display-inline-images nil nil (point-min) (point-max))))
+ (org-link-preview-region nil nil (point-min) (point-max))))
('folded
(org-with-wide-buffer
(org-narrow-to-subtree)
(if (numberp (point-max))
- (org-remove-inline-images (point-min) (point-max))
+ (org-link-preview-clear (point-min) (point-max))
(ignore)))))))
(provide 'org-cycle)
diff --git a/lisp/org-keys.el b/lisp/org-keys.el
index 1daedaae8..77cbe5c0f 100644
--- a/lisp/org-keys.el
+++ b/lisp/org-keys.el
@@ -218,7 +218,7 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence))
(declare-function org-toggle-radio-button "org" (&optional arg))
(declare-function org-toggle-comment "org" ())
(declare-function org-toggle-fixed-width "org" ())
-(declare-function org-toggle-inline-images-command "org" (&optional arg beg end))
+(declare-function org-link-preview "ol" (&optional arg beg end))
(declare-function org-latex-preview "org" (&optional arg))
(declare-function org-toggle-narrow-to-subtree "org" ())
(declare-function org-toggle-ordered-property "org" ())
@@ -618,7 +618,7 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display)
(org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock)
(org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update)
(org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview)
-(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command)
+(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview)
(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images)
(org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities)
(org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox)
diff --git a/lisp/org-plot.el b/lisp/org-plot.el
index b045344f0..836cfaffc 100644
--- a/lisp/org-plot.el
+++ b/lisp/org-plot.el
@@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac
(defun org-plot/redisplay-img-in-buffer (img-file)
"Find any overlays for IMG-FILE in the current Org buffer, and refresh them."
- (dolist (img-overlay org-inline-image-overlays)
+ (dolist (img-overlay org-link-preview-overlays)
(when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file))
(when (and (file-exists-p img-file)
(fboundp 'image-flush))
diff --git a/lisp/org.el b/lisp/org.el
index d5c1dcb35..e5cc9308f 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -5079,7 +5079,7 @@ (define-derived-mode org-mode outline-mode "Org"
;; modifications to make cache updates work reliably.
(org-unmodified
(when org-startup-with-beamer-mode (org-beamer-mode))
- (when org-startup-with-inline-images (org-display-inline-images))
+ (when org-startup-with-inline-images (org-link-preview '(16)))
(when org-startup-with-latex-preview (org-latex-preview '(16)))
(unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility))
(when org-startup-truncated (setq truncate-lines t))
@@ -15583,26 +15583,6 @@ (defcustom org-image-actual-width t
(list :tag "Use #+ATTR* or a number of pixels" (integer))
(const :tag "Use #+ATTR* or don't resize" nil)))
-(defcustom org-image-max-width 'fill-column
- "When non-nil, limit the displayed image width.
-This setting only takes effect when `org-image-actual-width' is set to
-t or when #+ATTR* is set to t.
-
-Possible values:
-- `fill-column' :: limit width to `fill-column'
-- `window' :: limit width to window width
-- integer :: limit width to number in pixels
-- float :: limit width to that fraction of window width
-- nil :: do not limit image width"
- :group 'org-appearance
- :package-version '(Org . "9.7")
- :type '(choice
- (const :tag "Do not limit image width" nil)
- (const :tag "Limit to `fill-column'" fill-column)
- (const :tag "Limit to window width" window)
- (integer :tag "Limit to a number of pixels")
- (float :tag "Limit to a fraction of window width")))
-
(defcustom org-agenda-inhibit-startup nil
"Inhibit startup when preparing agenda buffers.
When this variable is t, the initialization of the Org agenda
@@ -16649,518 +16629,6 @@ (defun org-normalize-color (value)
(format "%g" (/ value 65535.0)))
\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.44.1
next prev parent reply other threads:[~2024-09-15 21:58 UTC|newest]
Thread overview: 90+ 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 ` Karthik Chikmagalur [this message]
2024-09-17 18:16 ` [PATCH v4] " 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 ` [PATCH v11] " Karthik Chikmagalur
2024-11-25 18:11 ` Ihor Radchenko
2024-11-26 1:08 ` Karthik Chikmagalur
2024-11-26 17:18 ` Ihor Radchenko
2024-11-26 17:46 ` Ihor Radchenko
2024-11-26 18:45 ` [PATCH v12] " Karthik Chikmagalur
2024-11-27 18:47 ` Ihor Radchenko
2024-11-27 21:53 ` Karthik Chikmagalur
2024-11-26 2:08 ` Karthik Chikmagalur
2024-11-23 16:11 ` [PATCH v10] " 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=8734m060ma.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).