* [PATCH] add a function to only refresh inline images under current headline instead of global buffer @ 2023-05-15 3:28 Christopher M. Miles 2023-05-15 11:08 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-05-15 3:28 UTC (permalink / raw) To: Org mode [-- Attachment #1: Type: text/plain, Size: 2369 bytes --] I found a lot of third-part Emacs packages refresh Org source block image result using the API function like this: #+begin_src emacs-lisp ;; Automatically refresh inline images. (add-hook 'org-babel-after-execute-hook (defun ob-dall-e--refresh-inline-images () (when org-inline-image-overlays (org-redisplay-inline-images)))) #+end_src The `org-redisplay-inline-images' will refresh whole buffer inline images. When the buffer is a big Org file, and not all inline images are display already by default (still image file links under fold status). Invoking `org-redisplay-inline-images' will cause Emacs suspend a long time. So I suggest to add an variant local function of `org-redisplay-inline-images' which named `org-redisplay-inline-images-under-headline' that only redisplay inline images under current headline to solve the issue. Here is the diff code prototype, Ihor, can you review it? If it's ok, I will send patch update then. #+begin_src diff (defun org-redisplay-inline-images () - "Assure display of inline images and refresh them." + "Assure display of global all inline images in buffer and refresh them. + +NOTE: This function will refresh whole buffer inline images, if +you only want to refresh inline images under headline, suggest to +use `org-redisplay-inline-images-under-headline' in your hook or advice." (interactive) (org-toggle-inline-images) (unless org-inline-image-overlays (org-toggle-inline-images))) +(defun org-redisplay-inline-images-under-headline () + "Assure display of images under current headline and refresh them. +This function is the suggested to be used in hook or advice." + (interactive) + (org-with-wide-buffer + (org-narrow-to-subtree) + ;; 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 + nil nil + (point-min) (progn (org-next-visible-heading 1) (point))))) + #+end_src -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2023-05-15 11:08 UTC (permalink / raw) To: numbchild; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: > The `org-redisplay-inline-images' will refresh whole buffer inline > images. When the buffer is a big Org file, and not all inline images are > display already by default (still image file links under fold status). > Invoking `org-redisplay-inline-images' will cause Emacs suspend a long > time. > > So I suggest to add an variant local function of > `org-redisplay-inline-images' which named > `org-redisplay-inline-images-under-headline' that only redisplay inline > images under current headline to solve the issue. > > Here is the diff code prototype, Ihor, can you review it? If it's ok, I > will send patch update then. I'd prefer something more closely resembling `org-latex-preview' approach with prefix arguments: Toggle preview of the LaTeX fragment at point. If the cursor is on a LaTeX fragment, create the image and overlay it over the source code, if there is none. Remove it otherwise. If there is no fragment at point, display images for all fragments in the current section. With an active region, display images for all fragments in the region. With a C-u prefix argument ARG, clear images for all fragments in the current section. With a C-u C-u prefix argument ARG, display image for all fragments in the buffer. With a C-u C-u C-u prefix argument ARG, clear image for all fragments in the buffer. `org-toggle-inline-images' should also be changed. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 1 sibling, 0 replies; 90+ messages in thread From: Christopher M. Miles @ 2023-05-15 13:01 UTC (permalink / raw) To: Ihor Radchenko; +Cc: numbchild, Org mode [-- Attachment #1: Type: text/plain, Size: 2019 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >> The `org-redisplay-inline-images' will refresh whole buffer inline >> images. When the buffer is a big Org file, and not all inline images are >> display already by default (still image file links under fold status). >> Invoking `org-redisplay-inline-images' will cause Emacs suspend a long >> time. >> >> So I suggest to add an variant local function of >> `org-redisplay-inline-images' which named >> `org-redisplay-inline-images-under-headline' that only redisplay inline >> images under current headline to solve the issue. >> >> Here is the diff code prototype, Ihor, can you review it? If it's ok, I >> will send patch update then. > > I'd prefer something more closely resembling `org-latex-preview' > approach with prefix arguments: > > Toggle preview of the LaTeX fragment at point. > > If the cursor is on a LaTeX fragment, create the image and > overlay it over the source code, if there is none. Remove it > otherwise. If there is no fragment at point, display images for > all fragments in the current section. With an active region, > display images for all fragments in the region. > > With a C-u prefix argument ARG, clear images for all fragments > in the current section. > > With a C-u C-u prefix argument ARG, display image for all > fragments in the buffer. > > With a C-u C-u C-u prefix argument ARG, clear image for all > fragments in the buffer. > > `org-toggle-inline-images' should also be changed. I agree this solution. I will check out latex fragment preview source code whether I can implement this. -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* [PATCH v2] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-05-15 11:08 ` Ihor Radchenko 2023-05-15 13:01 ` Christopher M. Miles @ 2023-05-15 14:00 ` Christopher M. Miles 2023-05-16 9:17 ` Ihor Radchenko 1 sibling, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-05-15 14:00 UTC (permalink / raw) To: Ihor Radchenko; +Cc: numbchild, Org mode [-- Attachment #1: Type: text/plain, Size: 5505 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >> The `org-redisplay-inline-images' will refresh whole buffer inline >> images. When the buffer is a big Org file, and not all inline images are >> display already by default (still image file links under fold status). >> Invoking `org-redisplay-inline-images' will cause Emacs suspend a long >> time. >> >> So I suggest to add an variant local function of >> `org-redisplay-inline-images' which named >> `org-redisplay-inline-images-under-headline' that only redisplay inline >> images under current headline to solve the issue. >> >> Here is the diff code prototype, Ihor, can you review it? If it's ok, I >> will send patch update then. > > I'd prefer something more closely resembling `org-latex-preview' > approach with prefix arguments: > > Toggle preview of the LaTeX fragment at point. > > If the cursor is on a LaTeX fragment, create the image and > overlay it over the source code, if there is none. Remove it > otherwise. If there is no fragment at point, display images for > all fragments in the current section. With an active region, > display images for all fragments in the region. > > With a C-u prefix argument ARG, clear images for all fragments > in the current section. > > With a C-u C-u prefix argument ARG, display image for all > fragments in the buffer. > > With a C-u C-u C-u prefix argument ARG, clear image for all > fragments in the buffer. > > `org-toggle-inline-images' should also be changed. Here is the source code of `org-toggle-inline-images'. I implement this by reference `org-latex-preview'. I have not write testing, but only manually tested with Edebug and on actual Org buffer displaying and disable inline images. The command works fine. Ihor, can you review the code? #+begin_src emacs-lisp (defun org-toggle-inline-images (&optional arg include-linked beg end) "Toggle the display of inline images at point. INCLUDE-LINKED is passed to `org-display-inline-images'. If cursor is on an inline image link, display the inline image. If there is none, remove it otherwise. If there is no inline image link at point, display all inline images in the current section. With an active region, display inline images in the region. With a `\\[universal-argument]' prefix argument ARG, clear inline images in the current section. With a `\\[universal-argument] \\[universal-argument]' prefix argument ARG, display all inline images in the buffer. With a `\\[universal-argument] \\[universal-argument] \ \\[universal-argument]' prefix argument ARG, clear all inline images in the buffer." (interactive "P") (cond ((not (display-graphic-p)) nil) ;; Clear whole buffer inline images. ((equal arg '(64)) (org-remove-inline-images (point-min) (point-max)) (message "Inline images preview disabled in buffer.")) ;; Display whole buffer inline images. ((equal arg '(16)) (message "Displaying all inline images in buffer...") (org-display-inline-images include-linked nil (point-min) (point-max)) (message "Displaying all inline images in buffer... done.")) ;; Clear current section. ((equal arg '(4)) (let* ((beg (if (use-region-p) (region-beginning) (if (org-before-first-heading-p) (point-min) (save-excursion (org-with-limited-levels (org-back-to-heading t) (point)))))) (end (if (use-region-p) (region-end) (org-with-limited-levels (org-entry-end-position)))) (inline-images (org--inline-image-overlays beg end))) (org-remove-inline-images beg end) (message "%d inline images display removed." (length inline-images)))) ;; Display region selected inline images. ((use-region-p) (message "Displaying inline images in region...") (org-display-inline-images include-linked t (region-beginning) (region-end)) (message "Displaying inline images in region... done.")) ;; Toggle display of inline image link at point. ((let ((context (org-element-context))) (and (memq (org-element-type context) '(link)) (let ((beg (org-element-property :begin context)) (end (org-element-property :end context))) (if (org--inline-image-overlays beg end) (progn (org-remove-inline-images beg end) (message "Display inline image at point removed.")) (org-display-inline-images include-linked t beg end) (message "Displaying inline image at point ... done.")) t)))) ;; Display inline images under current section. (t (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)))) (message "Displaying inline images in section...") (org-display-inline-images include-linked t beg end) (message "Displaying inline images in section... done."))))) #+end_src -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v2] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2023-05-16 9:17 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> I'd prefer something more closely resembling `org-latex-preview' >> approach with prefix arguments: >> ... >> `org-toggle-inline-images' should also be changed. > > Here is the source code of `org-toggle-inline-images'. I implement this > by reference `org-latex-preview'. I have not write testing, but only > manually tested with Edebug and on actual Org buffer displaying and > disable inline images. The command works fine. Ihor, can you review the code? Unlike `org-latex-preview', here we need to (1) respect active region; (2) keep the backward compatibility for INCLUDE-LINKED. For (1), for example, it would make sense to respect region when prefix argument is C-u and clear images only there. For (2), we need to at least allow toggling images with description using some prefix argument (previously, any prefix argument would do). -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v2] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-05-16 9:17 ` Ihor Radchenko @ 2023-05-16 12:18 ` Christopher M. Miles 2023-07-24 11:25 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-05-16 12:18 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1: Type: text/plain, Size: 3125 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >>> I'd prefer something more closely resembling `org-latex-preview' >>> approach with prefix arguments: >>> ... >>> `org-toggle-inline-images' should also be changed. >> >> Here is the source code of `org-toggle-inline-images'. I implement this >> by reference `org-latex-preview'. I have not write testing, but only >> manually tested with Edebug and on actual Org buffer displaying and >> disable inline images. The command works fine. Ihor, can you review the code? > > Unlike `org-latex-preview', here we need to (1) respect active region; > (2) keep the backward compatibility for INCLUDE-LINKED. > > For (1), for example, it would make sense to respect region when prefix > argument is C-u and clear images only there. For (2), we need to at > least allow toggling images with description using some prefix argument > (previously, any prefix argument would do). I indeed implemented all same behavior like `org-latex-preview' in new `org-toggle-inline-images'. For (1), org-latex-preview has a `cond' logic to toggle preview in region. Here is the code from `org-latex-preview'. ((use-region-p) (message "Creating LaTeX previews in region...") (org--latex-preview-region (region-beginning) (region-end)) (message "Creating LaTeX previews in region... done.")) + ;; Clear current section. ((equal arg '(4)) (org-clear-latex-preview (if (use-region-p) (region-beginning) (if (org-before-first-heading-p) (point-min) (save-excursion (org-with-limited-levels (org-back-to-heading t) (point))))) (if (use-region-p) (region-end) (org-with-limited-levels (org-entry-end-position))))) And I also implement the toggle inline images in region logic in `org-toggle-inline-images' as bellowing: ;; Display region selected inline images. ((use-region-p) (message "Displaying inline images in region...") (org-display-inline-images include-linked t (region-beginning) (region-end)) (message "Displaying inline images in region... done.")) + ;; Clear current section. ((equal arg '(4)) (org-clear-latex-preview (if (use-region-p) (region-beginning) (if (org-before-first-heading-p) (point-min) (save-excursion (org-with-limited-levels (org-back-to-heading t) (point))))) (if (use-region-p) (region-end) (org-with-limited-levels (org-entry-end-position))))) ------------------------------------------------------------------------ For (2), It's working. The code passed the parameter `include-linked' to `org-display-inline-images'. So they works in any case of [C-u] prefix. Maybe you want to eval the patch code to test. -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v2] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2023-07-24 11:25 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> Unlike `org-latex-preview', here we need to (1) respect active region; >> (2) keep the backward compatibility for INCLUDE-LINKED. >> >> For (1), for example, it would make sense to respect region when prefix >> argument is C-u and clear images only there. For (2), we need to at >> least allow toggling images with description using some prefix argument >> (previously, any prefix argument would do). > > I indeed implemented all same behavior like `org-latex-preview' in new > `org-toggle-inline-images'. > > ... > And I also implement the toggle inline images in region logic in > `org-toggle-inline-images' as bellowing: > > ;; Display region selected inline images. > ((use-region-p) > (message "Displaying inline images in region...") > (org-display-inline-images include-linked t (region-beginning) (region-end)) > (message "Displaying inline images in region... done.")) This is not a toggle. This is unconditional refresh. > For (2), It's working. The code passed the parameter `include-linked' to > `org-display-inline-images'. So they works in any case of [C-u] prefix. My concern is that previously C-u M-x org-toggle-inline-images would "display links with a text description part". With your patch, it is no longer the case because INCLUDE-LINKED is not affected by the prefix argument. And there is no clean way to allow INCLUDE-LINKED while keeping consistency with latex preview commands. What we might do here is making a new defcustom that will control whether linked images should be displayed. Then, something like C-1 org-toggle-inline-images could toggle that defcustom and refresh all the image previews in buffer (if any). WDYT? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* [PATCH v3] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-07-24 11:25 ` Ihor Radchenko @ 2023-08-01 4:40 ` Christopher M. Miles 2023-08-01 8:04 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-08-01 4:40 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1.1: Type: text/plain, Size: 3372 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >>> Unlike `org-latex-preview', here we need to (1) respect active region; >>> (2) keep the backward compatibility for INCLUDE-LINKED. >>> >>> For (1), for example, it would make sense to respect region when prefix >>> argument is C-u and clear images only there. For (2), we need to at >>> least allow toggling images with description using some prefix argument >>> (previously, any prefix argument would do). >> >> I indeed implemented all same behavior like `org-latex-preview' in new >> `org-toggle-inline-images'. >> >> ... >> And I also implement the toggle inline images in region logic in >> `org-toggle-inline-images' as bellowing: >> >> ;; Display region selected inline images. >> ((use-region-p) >> (message "Displaying inline images in region...") >> (org-display-inline-images include-linked t (region-beginning) (region-end)) >> (message "Displaying inline images in region... done.")) > > This is not a toggle. This is unconditional refresh. > Yes, indeed it's a unconditional refresh. It does not match the meaning of word "toggle". Seems need to detect whether has image overlays in region. I added this detection in new patch. Like bellowing: #+begin_src emacs-lisp ... ((use-region-p) (if (seq-contains-p (mapcar (lambda (ov) (plist-get (overlay-properties ov) 'org-image-overlay)) (overlays-in beg end)) t) (progn (org-remove-inline-images beg end) (message "Inline images in region removed.")) (message "Displaying inline images in region...") (org-display-inline-images include-linked nil (region-beginning) (region-end)) (message "Displaying inline images in region... done."))) .... #+end_src >> For (2), It's working. The code passed the parameter `include-linked' to >> `org-display-inline-images'. So they works in any case of [C-u] prefix. > > My concern is that previously C-u M-x org-toggle-inline-images would > "display links with a text description part". With your patch, it is no > longer the case because INCLUDE-LINKED is not affected by the prefix > argument. > > And there is no clean way to allow INCLUDE-LINKED while keeping > consistency with latex preview commands. About the INCLUDE-LINKED argument, I don't know how to process it. In theory, it should be handled by function org-display-inline-images instead of org-toggle-inline-images. If you have improvements on it, can you add code on my patch? > > What we might do here is making a new defcustom that will control > whether linked images should be displayed. Then, something like C-1 > org-toggle-inline-images could toggle that defcustom and refresh all the > image previews in buffer (if any). > > WDYT? Refreshing all image previews in buffer is same as old behavior. My patch's purpose is to improve function org-toggle-inline-images behavior which refresh only in current level scope instead of whole buffer with lot of inline images especially heavy and suspend Emacs. Because function org-toggle-inline-images is used in of lot Emacs ob-* related packages on after babel execution hook. Old behavior caused execute one source block need to refresh whole buffer all images. Instead of only current headline images, or just results image. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1.2: 0001-org-Improve-inline-images-displaying-like-LaTeX-prev.patch --] [-- Type: text/x-patch, Size: 5252 bytes --] From 635624cb8446791b9e39f2803077ac9fa6d17225 Mon Sep 17 00:00:00 2001 From: stardiviner <numbchild@gmail.com> Date: Mon, 22 May 2023 16:25:33 +0800 Subject: [PATCH] org: Improve inline images displaying like LaTeX previewing * lisp/org.el (org-toggle-inline-images): Implement LaTeX previewing same logic in inline images toggle displaying. --- lisp/org.el | 92 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index e72cf056a..f847a42e7 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -16160,22 +16160,84 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." (when (memq ov org-inline-image-overlays) (push ov result))))) -(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'." +(defun org-toggle-inline-images (&optional arg include-linked beg end) + "Toggle the display of inline images at point. +INCLUDE-LINKED is passed to `org-display-inline-images'. + +If cursor is on an inline image link, display the inline image. +If there is none, remove it otherwise. +If there is no inline image link at point, display all inline images in the current section. +With an active region, display inline images in the region. + +With a `\\[universal-argument]' prefix argument ARG, clear inline +images in the current section. + +With a `\\[universal-argument] \\[universal-argument]' prefix + argument ARG, display all inline images in the buffer. + +With a `\\[universal-argument] \\[universal-argument] \ +\\[universal-argument]' prefix argument ARG, clear all inline +images in the buffer." (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")))))) + (cond + ((not (display-graphic-p)) nil) + ;; Clear whole buffer inline images. + ((equal arg '(64)) + (org-remove-inline-images (point-min) (point-max)) + (message "Inline images preview disabled in buffer.")) + ;; Display whole buffer inline images. + ((equal arg '(16)) + (message "Displaying all inline images in buffer...") + (org-display-inline-images include-linked nil (point-min) (point-max)) + (message "Displaying all inline images in buffer... done.")) + ;; Clear current section. + ((equal arg '(4)) + (let* ((beg (if (use-region-p) + (region-beginning) + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))))) + (end (if (use-region-p) + (region-end) + (org-with-limited-levels (org-entry-end-position)))) + (inline-images (org--inline-image-overlays beg end))) + (org-remove-inline-images beg end) + (message "%d inline images display removed." (length inline-images)))) + ;; Display region selected inline images. + ((use-region-p) + (if (seq-contains-p + (mapcar + (lambda (ov) + (plist-get (overlay-properties ov) 'org-image-overlay)) + (overlays-in beg end)) + t) + (progn + (org-remove-inline-images beg end) + (message "Inline images in region removed.")) + (message "Displaying inline images in region...") + (org-display-inline-images include-linked nil (region-beginning) (region-end)) + (message "Displaying inline images in region... done."))) + ;; Toggle display of inline image link at point. + ((let ((context (org-element-context))) + (and (memq (org-element-type context) '(link)) + (let ((beg (org-element-property :begin context)) + (end (org-element-property :end context))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "Display inline image at point removed.")) + (org-display-inline-images include-linked t beg end) + (message "Displaying inline image at point ... done.")) + t)))) + ;; Display inline images under current section. + (t + (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)))) + (message "Displaying inline images in section...") + (org-display-inline-images include-linked t beg end) + (message "Displaying inline images in section... done."))))) (defun org-redisplay-inline-images () "Assure display of inline images and refresh them." -- 2.39.2 (Apple Git-143) [-- Attachment #1.3: Type: text/plain, Size: 267 bytes --] -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2023-08-01 8:04 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> This is not a toggle. This is unconditional refresh. >> > Yes, indeed it's a unconditional refresh. It does not match the meaning of word "toggle". > > Seems need to detect whether has image overlays in region. I added this > detection in new patch. Like bellowing: > > #+begin_src emacs-lisp > ... > ((use-region-p) > (if (seq-contains-p > (mapcar > (lambda (ov) > (plist-get (overlay-properties ov) 'org-image-overlay)) > (overlays-in beg end)) > t) You can just use `org--inline-image-overlays'. >> And there is no clean way to allow INCLUDE-LINKED while keeping >> consistency with latex preview commands. > > About the INCLUDE-LINKED argument, I don't know how to process it. In > theory, it should be handled by function org-display-inline-images > instead of org-toggle-inline-images. If you have improvements on it, can > you add code on my patch? The problem here is backwards compatibility. `org-toggle-inline-images' is bound to C-c C-x C-v and people may be used to C-u C-c C-x C-v displaying linked images like [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] I will need to think more how to approach this. >> What we might do here is making a new defcustom that will control >> whether linked images should be displayed. Then, something like C-1 >> org-toggle-inline-images could toggle that defcustom and refresh all the >> image previews in buffer (if any). >> >> WDYT? > > Refreshing all image previews in buffer is same as old behavior. Sure. But the idea of this specific C-1 prefix argument is to toggle the hypothetical defcustom `org-inline-images-include-linked'. If we flip it we may need to remove/add linked image previews or otherwise risk users being confused by the defcustom not taking effect. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-01 8:04 ` Ihor Radchenko @ 2023-08-01 12:17 ` Christopher M. Miles 2023-08-01 14:09 ` Ihor Radchenko 2023-08-02 7:26 ` Ihor Radchenko 0 siblings, 2 replies; 90+ messages in thread From: Christopher M. Miles @ 2023-08-01 12:17 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1.1: Type: text/plain, Size: 2342 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >>> This is not a toggle. This is unconditional refresh. >>> >> Yes, indeed it's a unconditional refresh. It does not match the meaning of word "toggle". >> >> Seems need to detect whether has image overlays in region. I added this >> detection in new patch. Like bellowing: >> >> #+begin_src emacs-lisp >> ... >> ((use-region-p) >> (if (seq-contains-p >> (mapcar >> (lambda (ov) >> (plist-get (overlay-properties ov) 'org-image-overlay)) >> (overlays-in beg end)) >> t) > > You can just use `org--inline-image-overlays'. > Aha, I forget this API function, I applied in new patch. >>> And there is no clean way to allow INCLUDE-LINKED while keeping >>> consistency with latex preview commands. >> >> About the INCLUDE-LINKED argument, I don't know how to process it. In >> theory, it should be handled by function org-display-inline-images >> instead of org-toggle-inline-images. If you have improvements on it, can >> you add code on my patch? > > The problem here is backwards compatibility. `org-toggle-inline-images' > is bound to C-c C-x C-v and people may be used to C-u C-c C-x C-v > displaying linked images like > > [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] > > I will need to think more how to approach this. > Indeed. >>> What we might do here is making a new defcustom that will control >>> whether linked images should be displayed. Then, something like C-1 >>> org-toggle-inline-images could toggle that defcustom and refresh all the >>> image previews in buffer (if any). >>> >>> WDYT? >> >> Refreshing all image previews in buffer is same as old behavior. > > Sure. But the idea of this specific C-1 prefix argument is to toggle the > hypothetical defcustom `org-inline-images-include-linked'. If we flip it > we may need to remove/add linked image previews or otherwise risk users > being confused by the defcustom not taking effect. I checked source code, don't know where to insert this functionality. Is it be in `org-display-inline-images` or somewhere else? I will find time to checking code whether can add this in another patch. If you have plan for this, let me know. I think current patch is ready for merging now. WDYT? [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1.2: 0001-org-Improve-inline-images-displaying-like-LaTeX-prev.patch --] [-- Type: text/x-patch, Size: 5168 bytes --] From 2f68f0172dc5e452c05a9d254eab8ae797bcd15b Mon Sep 17 00:00:00 2001 From: stardiviner <numbchild@gmail.com> Date: Mon, 22 May 2023 16:25:33 +0800 Subject: [PATCH] org: Improve inline images displaying like LaTeX previewing * lisp/org.el (org-toggle-inline-images): Implement LaTeX previewing same logic in inline images toggle displaying. --- lisp/org.el | 89 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index e72cf056a..04f713d26 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -16160,22 +16160,81 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." (when (memq ov org-inline-image-overlays) (push ov result))))) -(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'." +(defun org-toggle-inline-images (&optional arg include-linked beg end) + "Toggle the display of inline images at point. +INCLUDE-LINKED is passed to `org-display-inline-images'. + +If cursor is on an inline image link, display the inline image. +If there is none, remove it otherwise. +If there is no inline image link at point, display all inline images in the current section. +With an active region, display inline images in the region. + +With a `\\[universal-argument]' prefix argument ARG, clear inline +images in the current section. + +With a `\\[universal-argument] \\[universal-argument]' prefix + argument ARG, display all inline images in the buffer. + +With a `\\[universal-argument] \\[universal-argument] \ +\\[universal-argument]' prefix argument ARG, clear all inline +images in the buffer." (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")))))) + (cond + ((not (display-graphic-p)) nil) + ;; Clear whole buffer inline images. + ((equal arg '(64)) + (org-remove-inline-images (point-min) (point-max)) + (message "Inline images preview disabled in buffer.")) + ;; Display whole buffer inline images. + ((equal arg '(16)) + (message "Displaying all inline images in buffer...") + (org-display-inline-images include-linked nil (point-min) (point-max)) + (message "Displaying all inline images in buffer... done.")) + ;; Clear current section. + ((equal arg '(4)) + (let* ((beg (if (use-region-p) + (region-beginning) + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))))) + (end (if (use-region-p) + (region-end) + (org-with-limited-levels (org-entry-end-position)))) + (inline-images (org--inline-image-overlays beg end))) + (org-remove-inline-images beg end) + (message "%d inline images display removed." (length inline-images)))) + ;; Display region selected inline images. + ((use-region-p) + (let ((beg (region-beginning)) + (end (region-end))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "Inline images in region removed.")) + (message "Displaying inline images in region...") + (org-display-inline-images include-linked t beg end) + (message "Displaying inline images in region... done.")))) + ;; Toggle display of inline image link at point. + ((let ((context (org-element-context))) + (and (memq (org-element-type context) '(link)) + (let ((beg (org-element-property :begin context)) + (end (org-element-property :end context))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "Display inline image at point removed.")) + (org-display-inline-images include-linked t beg end) + (message "Displaying inline image at point ... done.")) + t)))) + ;; Display inline images under current section. + (t + (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)))) + (message "Displaying inline images in section...") + (org-display-inline-images include-linked t beg end) + (message "Displaying inline images in section... done."))))) (defun org-redisplay-inline-images () "Assure display of inline images and refresh them." -- 2.39.2 (Apple Git-143) [-- Attachment #1.3: Type: text/plain, Size: 267 bytes --] -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 7:26 ` Ihor Radchenko 1 sibling, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2023-08-01 14:09 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: > I think current patch is ready for merging now. WDYT? No, unfortunately. With your patch, it will become impossible to display linked images using interactive command. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-01 14:09 ` Ihor Radchenko @ 2023-08-01 15:22 ` Christopher M. Miles 2023-08-01 15:46 ` Christopher M. Miles 1 sibling, 0 replies; 90+ messages in thread From: Christopher M. Miles @ 2023-08-01 15:22 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1: Type: text/plain, Size: 716 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >> I think current patch is ready for merging now. WDYT? > > No, unfortunately. > With your patch, it will become impossible to display linked images > using interactive command. I still need time to study how to add support for this. Most likely I can't implement it by myself. Would you modify my patch to add support for this? -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 1 sibling, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-08-01 15:46 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1: Type: text/plain, Size: 978 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >> I think current patch is ready for merging now. WDYT? > > No, unfortunately. > With your patch, it will become impossible to display linked images > using interactive command. Does this code match the needs? #+begin_src emacs-lisp ;; [M-1] / [C-1] argument for linked images like: ;; [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] ((equal arg 1) (setq org-inline-images-include-linked (not org-inline-images-include-linked)) (let ((current-prefix-arg nil)) (org-toggle-inline-images nil org-inline-images-include-linked))) #+end_src -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-01 15:46 ` Christopher M. Miles @ 2023-08-02 16:08 ` Ihor Radchenko 2023-08-04 6:30 ` Christopher M. Miles 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2023-08-02 16:08 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> With your patch, it will become impossible to display linked images >> using interactive command. > > Does this code match the needs? Please check my latest reply (https://list.orgmode.org/87o7jpoqfl.fsf@localhost/T/#m25fbb254635512a7bf86d368bf93a24a1c2fb3f1). I think we may go away without introducing an extra defcustom. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-02 16:08 ` Ihor Radchenko @ 2023-08-04 6:30 ` Christopher M. Miles 0 siblings, 0 replies; 90+ messages in thread From: Christopher M. Miles @ 2023-08-04 6:30 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1: Type: text/plain, Size: 881 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >>> With your patch, it will become impossible to display linked images >>> using interactive command. >> >> Does this code match the needs? > > Please check my latest reply (https://list.orgmode.org/87o7jpoqfl.fsf@localhost/T/#m25fbb254635512a7bf86d368bf93a24a1c2fb3f1). > I think we may go away without introducing an extra defcustom. After yesterday night studying source code, I still can't figure out solution. So I pass this patch to you. Wait you to finish the unfinished part of path. -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-01 12:17 ` [PATCH v3.1] " Christopher M. Miles 2023-08-01 14:09 ` Ihor Radchenko @ 2023-08-02 7:26 ` Ihor Radchenko 2023-08-02 15:44 ` Christopher M. Miles 1 sibling, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2023-08-02 7:26 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> The problem here is backwards compatibility. `org-toggle-inline-images' >> is bound to C-c C-x C-v and people may be used to C-u C-c C-x C-v >> displaying linked images like >> >> [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] >> >> I will need to think more how to approach this. >> > > Indeed. Another problem with your change is that the order of arguments changed. If there is some Elisp doing something like (org-toggle-inline-images INCLUDE-LINKED), your patch will break the code. I think that instead of changing the existing function, we can convert your patch into a new function `org-toggle-inline-images-command' that will be free to alter the argument order. We can then re-bind that function in org-keys to make it used by default, but only interactively. As for displaying linked images, what about something like C-1 C-c C-x C-v being equivalent of C-c C-x C-v + INCLUDE-LINKED=t (display in current section/region, with linked) and C-11 C-c C-x C-v being equivalent of C-u C-u C-c C-x C-v + INCLUDE-LINKED=t (display in the whole buffer, with linked) > +(defun org-toggle-inline-images (&optional arg include-linked beg end) > + "Toggle the display of inline images at point. > +INCLUDE-LINKED is passed to `org-display-inline-images'. > + > +If cursor is on an inline image link, display the inline image. > +If there is none, remove it otherwise. I do not quite understand what the last line is trying to say. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-02 7:26 ` Ihor Radchenko @ 2023-08-02 15:44 ` Christopher M. Miles 2023-08-04 8:20 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-08-02 15:44 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1: Type: text/plain, Size: 2590 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >>> The problem here is backwards compatibility. `org-toggle-inline-images' >>> is bound to C-c C-x C-v and people may be used to C-u C-c C-x C-v >>> displaying linked images like >>> >>> [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] >>> >>> I will need to think more how to approach this. >>> >> >> Indeed. > > Another problem with your change is that the order of arguments changed. > If there is some Elisp doing something like > (org-toggle-inline-images INCLUDE-LINKED), your patch will break the > code. > > I think that instead of changing the existing function, we can convert > your patch into a new function `org-toggle-inline-images-command' that > will be free to alter the argument order. We can then re-bind that > function in org-keys to make it used by default, but only interactively. > I don't think so, the patch main purpose is for improve image refreshing after babel result image displaying. Because the function is hooked by other ob-* packages. Another purpose is headline level images displaying. So modifying this function is the only way. > As for displaying linked images, what about something like > > > C-1 C-c C-x C-v being equivalent of > C-c C-x C-v + INCLUDE-LINKED=t > (display in current section/region, with linked) > > and > > C-11 C-c C-x C-v being equivalent of > C-u C-u C-c C-x C-v + INCLUDE-LINKED=t > (display in the whole buffer, with linked) > Don't know, I have not use it this way. I think INCLUDE-LINKED is just a option argument for function org-display-inline-images. No need to be available for toggle in interactive command. An option like org-inline-images-include-linked is enough. >> +(defun org-toggle-inline-images (&optional arg include-linked beg end) >> + "Toggle the display of inline images at point. >> +INCLUDE-LINKED is passed to `org-display-inline-images'. >> + >> +If cursor is on an inline image link, display the inline image. >> +If there is none, remove it otherwise. > > I do not quite understand what the last line is trying to say. Emmm, I forget the meaning of it. I read the docstring again, seems it can be deleted. I will delete it in my patch. -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3.1] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2023-08-04 8:20 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> I think that instead of changing the existing function, we can convert >> your patch into a new function `org-toggle-inline-images-command' that >> will be free to alter the argument order. We can then re-bind that >> function in org-keys to make it used by default, but only interactively. >> > I don't think so, the patch main purpose is for improve image refreshing > after babel result image displaying. Because the function is hooked by > other ob-* packages. Another purpose is headline level images displaying. > So modifying this function is the only way. Fair point. Then, for backward compatibility, we may treat any non-nil, non-list (like '(4), '(16), '(64)), non-number (like 1, 11) value as INCLUDE-LINKED. This way, the existing calls like (org-toggle-inline-images t) will not be broken. Also, the signature should be (defun org-toggle-inline-images (&optional arg beg end include-linked) So that the meaning of the second and third argument is unchanged. >> As for displaying linked images, what about something like >> >> >> C-1 C-c C-x C-v being equivalent of >> C-c C-x C-v + INCLUDE-LINKED=t >> (display in current section/region, with linked) >> >> and >> >> C-11 C-c C-x C-v being equivalent of >> C-u C-u C-c C-x C-v + INCLUDE-LINKED=t >> (display in the whole buffer, with linked) >> > Don't know, I have not use it this way. I think INCLUDE-LINKED is just a > option argument for function org-display-inline-images. No need to be > available for toggle in interactive command. An option like > org-inline-images-include-linked is enough. Sorry, but ignoring backwards compatibility is not an option. If you personally haven't used some feature, it does not mean that others also didn't. We can make limited compromises, but should try our best not to break things that were working before. See https://bzg.fr/en/the-software-maintainers-pledge/ >>> +If cursor is on an inline image link, display the inline image. >>> +If there is none, remove it otherwise. >> >> I do not quite understand what the last line is trying to say. > > Emmm, I forget the meaning of it. I read the docstring again, seems it > can be deleted. I will delete it in my patch. > After yesterday night studying source code, I still can't figure out > solution. So I pass this patch to you. Wait you to finish the unfinished > part of path. I hope that now you have enough information to update the patch. Let me know if not. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* [PATCH v3.2] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2023-08-04 8:20 ` Ihor Radchenko @ 2023-08-05 5:28 ` Christopher M. Miles 2024-07-22 10:46 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Christopher M. Miles @ 2023-08-05 5:28 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Christopher M. Miles, Org mode [-- Attachment #1.1: Type: text/plain, Size: 3247 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > "Christopher M. Miles" <numbchild@gmail.com> writes: > >>> I think that instead of changing the existing function, we can convert >>> your patch into a new function `org-toggle-inline-images-command' that >>> will be free to alter the argument order. We can then re-bind that >>> function in org-keys to make it used by default, but only interactively. >>> >> I don't think so, the patch main purpose is for improve image refreshing >> after babel result image displaying. Because the function is hooked by >> other ob-* packages. Another purpose is headline level images displaying. >> So modifying this function is the only way. > > Fair point. > Then, for backward compatibility, we may treat any non-nil, non-list > (like '(4), '(16), '(64)), non-number (like 1, 11) value as > INCLUDE-LINKED. This way, the existing calls like > (org-toggle-inline-images t) will not be broken. > > Also, the signature should be > > (defun org-toggle-inline-images (&optional arg beg end include-linked) > > So that the meaning of the second and third argument is unchanged. > I prefer this compromise result. I updated the patch, Please review it whether it's correct. >>> As for displaying linked images, what about something like >>> >>> >>> C-1 C-c C-x C-v being equivalent of >>> C-c C-x C-v + INCLUDE-LINKED=t >>> (display in current section/region, with linked) >>> >>> and >>> >>> C-11 C-c C-x C-v being equivalent of >>> C-u C-u C-c C-x C-v + INCLUDE-LINKED=t >>> (display in the whole buffer, with linked) >>> >> Don't know, I have not use it this way. I think INCLUDE-LINKED is just a >> option argument for function org-display-inline-images. No need to be >> available for toggle in interactive command. An option like >> org-inline-images-include-linked is enough. > > Sorry, but ignoring backwards compatibility is not an option. > If you personally haven't used some feature, it does not mean that > others also didn't. > > We can make limited compromises, but should try our best not to break > things that were working before. > See https://bzg.fr/en/the-software-maintainers-pledge/ > I agree with backward compatibility is very important. I did a source code statistics researching of using the function org-toggle-inline-images at two places: - GitHub: https://github.com/search?q=org-toggle-inline-images&ref=opensearch&type=code&p=3 (Only package scimax incoke this function with INCLUDE-LINKED argument t) - My installed Emacs packages, NO package invoke this function with argument INCLUDE-LINKED. So lucky this change will not affect lot. >>>> +If cursor is on an inline image link, display the inline image. >>>> +If there is none, remove it otherwise. >>> >>> I do not quite understand what the last line is trying to say. >> >> Emmm, I forget the meaning of it. I read the docstring again, seems it >> can be deleted. I will delete it in my patch. > >> After yesterday night studying source code, I still can't figure out >> solution. So I pass this patch to you. Wait you to finish the unfinished >> part of path. > > I hope that now you have enough information to update the patch. > Let me know if not. If this patch is still not ok, really hope you can edit my patch. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1.2: 0001-org-Improve-inline-images-displaying-like-LaTeX-prev.patch --] [-- Type: text/x-patch, Size: 6281 bytes --] From a2707a0f92c76188c3ebb412fe2b57788b50ca9a Mon Sep 17 00:00:00 2001 From: stardiviner <numbchild@gmail.com> Date: Mon, 22 May 2023 16:25:33 +0800 Subject: [PATCH] org: Improve inline images displaying like LaTeX previewing * lisp/org.el (org-toggle-inline-images): Implement LaTeX previewing same logic in inline images toggle displaying. --- lisp/org-keys.el | 2 +- lisp/org.el | 95 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 38fac57d8..7b3b84af4 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -204,7 +204,7 @@ (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 "org" (&optional include-linked beg end)) +(declare-function org-toggle-inline-images "org" (&optional arg beg end include-linked)) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) diff --git a/lisp/org.el b/lisp/org.el index e72cf056a..c01952b97 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -16160,22 +16160,87 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." (when (memq ov org-inline-image-overlays) (push ov result))))) -(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'." +(defun org-toggle-inline-images (&optional arg beg end include-linked) + "Toggle the display of inline images at point. +INCLUDE-LINKED is passed to `org-display-inline-images'. + +If cursor is on an inline image link, display the inline image. +If there is no inline image link at point, display all inline images in the current section. +With an active region, display inline images in the region. + +With a `\\[universal-argument]' prefix argument ARG, clear inline +images in the current section. + +With a `\\[universal-argument] \\[universal-argument]' prefix + argument ARG, display all inline images in the buffer. + +With a `\\[universal-argument] \\[universal-argument] \ +\\[universal-argument]' prefix argument ARG, clear all inline +images in the buffer." (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")))))) + (cond + ((not (display-graphic-p)) nil) + ;; Clear whole buffer inline images. + ((equal arg '(64)) + (org-remove-inline-images (point-min) (point-max)) + (message "Inline images preview disabled in buffer.")) + ;; Display whole buffer inline images. + ((equal arg '(16)) + (message "Displaying all inline images in buffer...") + (let ((include-linked t)) ; assume INCLUDE-LINKED be t here for backward compatibility. + (org-display-inline-images include-linked nil (point-min) (point-max))) + (message "Displaying all inline images in buffer... done.")) + ;; Clear current section. + ((equal arg '(4)) + (let* ((beg (if (use-region-p) + (region-beginning) + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))))) + (end (if (use-region-p) + (region-end) + (org-with-limited-levels (org-entry-end-position)))) + (inline-images (org--inline-image-overlays beg end))) + (org-remove-inline-images beg end) + (message "%d inline images display removed." (length inline-images)))) + ;; [M-1] / [C-1] argument for linked images like: + ;; [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] + ((equal arg 1) + (let ((current-prefix-arg nil) + (include-linked t)) ; assume INCLUDE-LINKED be t here for backward compatibility. + (org-toggle-inline-images nil nil nil include-linked))) + ;; Display region selected inline images. + ((use-region-p) + (let ((beg (region-beginning)) + (end (region-end))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "Inline images in region removed.")) + (message "Displaying inline images in region...") + (org-display-inline-images include-linked t beg end) + (message "Displaying inline images in region... done.")))) + ;; Toggle display of inline image link at point. + ((let ((context (org-element-context))) + (and (memq (org-element-type context) '(link)) + (let ((beg (org-element-property :begin context)) + (end (org-element-property :end context))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "Display inline image at point removed.")) + (org-display-inline-images include-linked t beg end) + (message "Displaying inline image at point ... done.")) + t)))) + ;; Display inline images under current section. + (t + (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)))) + (message "Displaying inline images in section...") + (org-display-inline-images include-linked t beg end) + (message "Displaying inline images in section... done."))))) (defun org-redisplay-inline-images () "Assure display of inline images and refresh them." -- 2.39.2 (Apple Git-143) [-- Attachment #1.3: Type: text/plain, Size: 267 bytes --] -- [ stardiviner ] I try to make every word tell the meaning that I want to express without misunderstanding. Blog: https://stardiviner.github.io/ IRC(libera.chat, freenode): stardiviner, Matrix: stardiviner GPG: F09F650D7D674819892591401B5DF1C95AE89AC3 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 487 bytes --] ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v3.2] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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> 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-07-22 10:46 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode "Christopher M. Miles" <numbchild@gmail.com> writes: >> Then, for backward compatibility, we may treat any non-nil, non-list >> (like '(4), '(16), '(64)), non-number (like 1, 11) value as >> INCLUDE-LINKED. This way, the existing calls like >> (org-toggle-inline-images t) will not be broken. > ... > I prefer this compromise result. > I updated the patch, Please review it whether it's correct. I think that C-u C-u unconditionally including linked images is confusing. What about using my idea with M-1/M-11 to be interpreted as INCLUDE-LINKED=t? Also, you did not document in the docstring what happens if ARG is something like t. > I did a source code statistics researching of using the function org-toggle-inline-images at two places: > > - GitHub: https://github.com/search?q=org-toggle-inline-images&ref=opensearch&type=code&p=3 > (Only package scimax incoke this function with INCLUDE-LINKED argument t) > - My installed Emacs packages, NO package invoke this function with argument INCLUDE-LINKED. > > So lucky this change will not affect lot. I have found user configs using the argument: https://github.com/search?q=%22%28org-toggle-inline-images+t%29%22&type=code https://github.com/search?q=%22%28org-toggle-inline-images+%27%22&type=code So, please do as I advised - treat non-special ARG values as INCLUDE-LINKED. Also, looking at the proposed prefix arguments, I feel that it might not necessarily be the best idea to copy over what org-latex-preview does exactly. org-latex-preview is not a "toggle", unlike org-toggle-inline-images What about the following treatment of ARG: 1. No argument, no region selected :: toggle (display or hide dwim) images in current section 2. No argument, region selected: toggle images in region 3. C-u argument :: toggle images in the whole buffer 4. C-u C-u argument, no region selected :: unconditionally hide images in the buffer 5. M-1 argument, no region selected :: display images in current section with INCLUDE-LINKED 6. M-1 argument, region selected :: ... in region ... 7. M-11 argument :: ... in the whole buffer ... 8. Any other argument :: treat as INCLUDE-LINKED = t And please document all the new arguments in the manual and etc/ORG-NEWS file. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-07-22 10:46 ` Ihor Radchenko @ 2024-08-01 22:58 ` stardiviner [not found] ` <66a8b73b.170a0220.383476.996e@mx.google.com> 1 sibling, 0 replies; 90+ messages in thread From: stardiviner @ 2024-08-01 22:58 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Org mode [-- Attachment #1.1: Type: text/plain, Size: 3178 bytes --] I followed you upper 8 conditions to re-write my patch. Except the 8. condition I'm not sure I understand correctly. And I extend 1. condition to support the inline image link at point toggle displaying. [stardiviner] <Hack this world!> GPG key ID: 47C32433 IRC(freeenode): stardiviner Twitter: @numbchild Key fingerprint = 9BAA 92BC CDDD B9EF 3B36 CB99 B8C4 B8E5 47C3 2433 Blog: http://stardiviner.github.io/ On Mon, Jul 22, 2024 at 6:44 PM Ihor Radchenko <yantar92@posteo.net> wrote: > "Christopher M. Miles" <numbchild@gmail.com> writes: > > >> Then, for backward compatibility, we may treat any non-nil, non-list > >> (like '(4), '(16), '(64)), non-number (like 1, 11) value as > >> INCLUDE-LINKED. This way, the existing calls like > >> (org-toggle-inline-images t) will not be broken. > > ... > > I prefer this compromise result. > > I updated the patch, Please review it whether it's correct. > > I think that C-u C-u unconditionally including linked images is > confusing. > > What about using my idea with M-1/M-11 to be interpreted as > INCLUDE-LINKED=t? > > Also, you did not document in the docstring what happens if ARG is > something like t. > > > I did a source code statistics researching of using the function > org-toggle-inline-images at two places: > > > > - GitHub: > https://github.com/search?q=org-toggle-inline-images&ref=opensearch&type=code&p=3 > > (Only package scimax incoke this function with INCLUDE-LINKED argument t) > > - My installed Emacs packages, NO package invoke this function with > argument INCLUDE-LINKED. > > > > So lucky this change will not affect lot. > > I have found user configs using the argument: > > > https://github.com/search?q=%22%28org-toggle-inline-images+t%29%22&type=code > https://github.com/search?q=%22%28org-toggle-inline-images+%27%22&type=code > > So, please do as I advised - treat non-special ARG values as > INCLUDE-LINKED. > > Also, looking at the proposed prefix arguments, I feel that it might not > necessarily be the best idea to copy over what org-latex-preview does > exactly. org-latex-preview is not a "toggle", unlike > org-toggle-inline-images > > What about the following treatment of ARG: > > 1. No argument, no region selected :: toggle (display or hide dwim) images > in current section > 2. No argument, region selected: toggle images in region > 3. C-u argument :: toggle images in the whole buffer > 4. C-u C-u argument, no region selected :: unconditionally hide images in > the buffer > 5. M-1 argument, no region selected :: display images in current section > with INCLUDE-LINKED > 6. M-1 argument, region selected :: ... in region ... > 7. M-11 argument :: ... in the whole buffer ... > 8. Any other argument :: treat as INCLUDE-LINKED = t > > And please document all the new arguments in the manual and etc/ORG-NEWS > file. > > -- > Ihor Radchenko // yantar92, > Org mode contributor, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> > [-- Attachment #1.2: Type: text/html, Size: 4784 bytes --] [-- Attachment #2: 0001-org-Implement-conditionally-inline-images-displaying.patch --] [-- Type: application/octet-stream, Size: 8511 bytes --] From 63c482d0c3782dcb328581f1d56275a131e78610 Mon Sep 17 00:00:00 2001 From: stardiviner <numbchild@gmail.com> Date: Mon, 22 May 2023 16:25:33 +0800 Subject: [PATCH] org: Implement conditionally inline images displaying logic * lisp/org.el (org-toggle-inline-images): Implement conditionally inline images displaying logic based on universal-argument. --- etc/ORG-NEWS | 5 ++ lisp/org-keys.el | 2 +- lisp/org.el | 117 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 108 insertions(+), 16 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 8a2a1ec0e..2798fb131 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -113,6 +113,11 @@ accept the INFO channel and return a string. This makes it possible to dynamically generate the content of the resulting ~<head>~ tag in the resulting HTML document. +*** ~org-toggle-inline-images~ implement conditionally inline images displaying logic + +You can display or hide inline images at point, region, or current +section through universal-argument. + ** Miscellaneous *** ~org-refile~ now saves current position to Org mark ring when jumping to heading diff --git a/lisp/org-keys.el b/lisp/org-keys.el index edd4059fc..52608b747 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,7 @@ (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 "org" (&optional include-linked beg end)) +(declare-function org-toggle-inline-images "org" (&optional arg beg end include-linked)) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) diff --git a/lisp/org.el b/lisp/org.el index 362f0c158..83e440632 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -16665,22 +16665,109 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML." (when (memq ov org-inline-image-overlays) (push ov result))))) -(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'." +(defun org-toggle-inline-images (&optional arg beg end) + "Toggle the display of inline images at point. + +The parameter ARG from `\\[universal-argument]' is used as condition in bellowing \"argument\". + +1. No argument, no region selected :: toggle (display or hide dwim) images in current section or image link at point +2. No argument, region selected :: toggle images in region +3. C-u argument :: toggle images in the whole buffer +4. C-u C-u argument, no region selected :: unconditionally hide images in the buffer +5. M-1 argument, no region selected :: display images in current section with INCLUDE-LINKED +6. M-1 argument, region selected :: ... in region ... +7. M-11 argument :: ... in the whole buffer ... +8. Any other argument :: treat as INCLUDE-LINKED = t" (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")))))) + (cond + ((not (display-graphic-p)) (message "Your Emacs does not support displaying images!")) + ;; 1. No argument, no region selected :: toggle (display or hide dwim) images in current section or image link at point. + ((and (null arg) (not (use-region-p))) + (let ((context (org-element-context)) + (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))) + (include-linked nil)) + ;; toggle display of inline image link at point. + (if (memq (org-element-type context) '(link)) + (let ((beg (org-element-property :begin context)) + (end (org-element-property :end context))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "Remove inline image at point.")) + (org-display-inline-images include-linked t beg end) + (message "Display inline image at point ... done."))) + (if (org--inline-image-overlays beg end) + (org-remove-inline-images beg end) + (message "Display inline images in section...") + (org-display-inline-images include-linked t beg end) + (message "Display inline images in section... done."))))) + ;; 2. No argument, region selected :: toggle images in region. + ((and (null arg) (use-region-p)) + (let* ((beg (region-beginning)) + (end (region-end)) + (include-linked nil) + (inline-images (org--inline-image-overlays beg end))) + (if (org--inline-image-overlays beg end) + (progn + (org-remove-inline-images beg end) + (message "%d inline images display removed." (length inline-images))) + (message "Display inline images displayed in region...") + (org-display-inline-images include-linked t beg end) + (message "Display inline images displayed in region... done.")))) + ;; 3. C-u argument :: toggle images in the whole buffer. + ((equal arg '(4)) + (let ((include-linked nil)) + (if (org--inline-image-overlays (point-min) (point-max)) + (org-remove-inline-images (point-min) (point-max)) + (message "Display all inline images in buffer...") + (org-display-inline-images include-linked nil (point-min) (point-max)) + (message "Display all inline images in buffer... done.")))) + ;; 4. C-u C-u argument, no region selected :: unconditionally hide images in the buffer. + ((and (equal arg '(16)) (not (use-region-p))) + (org-remove-inline-images (point-min) (point-max)) + (message "Remove all inline images in buffer displaying.")) + ;; 5. M-1 argument, no region selected :: display images in current section with `INCLUDE-LINKED'. + ((and (equal arg 1) (not (use-region-p))) + (let ((context (org-element-context)) + (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))) + (include-linked t)) + (message "Display inline images in section...") + (org-display-inline-images include-linked t beg end) + (message "Display inline images in section... done."))) + ;; 6. M-1 argument, region selected :: ... in region ... + ;; [M-1] / [C-1] argument for linked images like: + ;; [[https://orgmode.org/resources/img/org-mode-unicorn.svg][description]] + ((and (equal arg 1) (use-region-p)) + (let* ((beg (region-beginning)) + (end (region-end)) + (include-linked t) + (inline-images (org--inline-image-overlays beg end))) + (org-display-inline-images include-linked t beg end) + (message "%d inline images displayed in region... done." (length inline-images)))) + ;; 7. M-11 argument :: ... in the whole buffer ... + ((equal arg '11) + (let ((beg (point-min)) + (end (point-max)) + (include-linked t)) + (message "Display inline images in buffer...") + (org-display-inline-images include-linked t beg end) + (message "Display inline images in buffer... done."))) + ;; 8. Any other argument :: treat ARG as INCLUDE-LINKED = t + ((not (null arg)) + (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))) + (include-linked arg)) + (message "Display inline images in section...") + (org-display-inline-images include-linked t beg end) + (message "Display inline images in section... done."))))) (defun org-redisplay-inline-images () "Assure display of inline images and refresh them." -- 2.39.3 (Apple Git-146) ^ permalink raw reply related [flat|nested] 90+ messages in thread
[parent not found: <66a8b73b.170a0220.383476.996e@mx.google.com>]
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer [not found] ` <66a8b73b.170a0220.383476.996e@mx.google.com> @ 2024-08-12 10:18 ` Ihor Radchenko 2024-08-14 2:04 ` stardiviner 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-12 10:18 UTC (permalink / raw) To: Christopher M. Miles; +Cc: Org mode [-- Attachment #1: Type: text/plain, Size: 1522 bytes --] "Christopher M. Miles" <numbchild@gmail.com> writes: >> What about the following treatment of ARG: >> >> 1. No argument, no region selected :: toggle (display or hide dwim) images in current section >> 2. No argument, region selected: toggle images in region >> 3. C-u argument :: toggle images in the whole buffer >> 4. C-u C-u argument, no region selected :: unconditionally hide images in the buffer >> 5. M-1 argument, no region selected :: display images in current section with INCLUDE-LINKED >> 6. M-1 argument, region selected :: ... in region ... >> 7. M-11 argument :: ... in the whole buffer ... >> 8. Any other argument :: treat as INCLUDE-LINKED = t >> >> And please document all the new arguments in the manual and etc/ORG-NEWS file. > > I followed you upper 8 conditions to re-write my patch. > Except the 8. condition I'm not sure I understand correctly. > And I extend 1. condition to support the inline image link at point toggle displaying. > ... I do not like the code repetitions in the patch and relying upon `use-region-p' even for non-interactive use. I am attaching a complete rewrite of your idea. Please let me know if my patch does everything you want to include into the command. Note that I went with a new idea of introducing a branch new function instead of changing `org-toggle-inline-images'. This way, existing users of `org-toggle-inline-images' will not be affected at all. We are just changing the default C-c C-x C-v binding. This way, the breakage is a little as possible. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-toggle-inline-images-command-New-command-for-C-c.patch --] [-- Type: text/x-patch, Size: 10393 bytes --] From 0a7b2a8850013be3ca916f4e5c67ac68b94feb25 Mon Sep 17 00:00:00 2001 Message-ID: <0a7b2a8850013be3ca916f4e5c67ac68b94feb25.1723457647.git.yantar92@posteo.net> From: Ihor Radchenko <yantar92@posteo.net> Date: Mon, 12 Aug 2024 12:11:17 +0200 Subject: [PATCH] org-toggle-inline-images-command: New command for C-c C-x C-v binding * lisp/org.el (org-toggle-inline-images-command): New command to toggle images. Unlike the old `org-toggle-inline-image', it is more flexible and default to toggling images at point/current entry. * lisp/org-keys.el (org-mode-map): Bind C-c C-x C-v to the new command. * doc/org-manual.org (Images): Update the manual describing the new command behaviour. * etc/ORG-NEWS (=C-c C-x C-v= command toggling inline image display has been reworked): Document the breaking change. Link: https://list.orgmode.org/6461a84b.a70a0220.b6d36.5d00@mx.google.com/ --- doc/org-manual.org | 17 +++++++--- etc/ORG-NEWS | 31 ++++++++++++++++++ lisp/org-keys.el | 4 +-- lisp/org.el | 82 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 126 insertions(+), 8 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 6cf51ebcac..9365c66b10 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11806,14 +11806,21 @@ ** Images Such images can be displayed within the buffer with the following command: -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images~) :: +- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images + #+findex: org-toggle-inline-images-command + Toggle the inline display of linked images in current section or at + point. With a prefix argument, toggle inline images in the whole + buffer. With double prefix argument, hide all the images in buffer. + + By default, only the image links without description are displayed. + You can force displaying all the images using numeric argument to + toggle all the images in current section (~1~ argument) or the whole + buffer (~11~ argument). + #+vindex: org-startup-with-inline-images - Toggle the inline display of linked images. When called with a - prefix argument, also display images that do have a link - description. You can ask for inline images to be displayed at + You can ask for inline images to be displayed at startup by configuring the variable ~org-startup-with-inline-images~[fn:: The variable ~org-startup-with-inline-images~ can be set within a buffer with the diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 6f858f3ca7..533965311a 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -18,6 +18,37 @@ Please send Org bug reports to mailto:emacs-orgmode@gnu.org. # require user action for most Org mode users. # Sorted from most important to least important. +*** =C-c C-x C-v= command toggling inline image display has been reworked + +Previously, =C-c C-x C-v= always toggled image display in the whole +buffer (or narrowed part of the buffer). With prefix argument, it +also forced displaying image links with description. + +Now, =C-c C-x C-v= is bound to a new command +~org-toggle-inline-images-command~, which uses different defaults: + +1. By default, it toggles image at point or, if there is no image at + point, images in current entry + +2. When region is active, it is honored + +3. =C-u= argument changed its meaning. Now, it forces toggling images + in the whole buffer + +4. =C-u C-u= argument unconditionally hides all the images in buffer + +5. Displaying images over links with description can be forced using + numeric argument: + - ~C-u 1~ for toggling all images at point/current entry + - ~C-u 11~ for toggling all images in buffer + +The old ~org-toggle-inline-images~ command is still available. You +can bind it back to =C-c C-x C-v= by adding the following to you config: +#+begin_src emacs-lisp +(eval-after-load 'org-keys + (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) +#+end_src + *** Org mode may throw an error when attempting to include remote unsafe resource noninteractively Previously, when ~org-resource-download-policy~ is ~ask~ (default), diff --git a/lisp/org-keys.el b/lisp/org-keys.el index edd4059fc6..1daedaae8c 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 "org" (&optional include-linked beg end)) +(declare-function org-toggle-inline-images-command "org" (&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) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) diff --git a/lisp/org.el b/lisp/org.el index bcab7ca860..39167c656f 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -16654,7 +16654,8 @@ (defun org-normalize-color (value) (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'. +;; 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) @@ -16667,6 +16668,85 @@ (defun org--inline-image-overlays (&optional beg end) (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'." -- 2.45.2 [-- Attachment #3: Type: text/plain, Size: 224 bytes --] -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-08-12 10:18 ` Ihor Radchenko @ 2024-08-14 2:04 ` stardiviner 2024-08-18 10:27 ` 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 0 siblings, 2 replies; 90+ messages in thread From: stardiviner @ 2024-08-14 2:04 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Org mode [-- Attachment #1: Type: text/plain, Size: 3145 bytes --] I agree with your patch solution. I have tested fine. Thanks for your re-write. I have an idea, would you like to add an function in bellowing: #+begin_src emacs-lisp (defun org-toggle-inline-images-in-results () "Toggle inline images in babel source block results." (save-excursion ;; [C-c C-v C-o] `org-babel-open-src-block-result' (let ((begin (org-babel-where-is-src-block-result)) (end (progn (end-of-line) (skip-chars-forward " \r\t\n") (looking-at org-link-bracket-re) ;; (org-babel-read-result) (point)))) (org-toggle-inline-images-command nil begin end)))) (add-hook 'org-babel-after-execute-hook 'org-toggle-inline-images-in-results) #+end_src WDYT? after all I originally propose this patch for this purpose to improve the babel result inline image toggle displaying performance. [stardiviner] <Hack this world!> GPG key ID: 47C32433 IRC(freeenode): stardiviner Twitter: @numbchild Key fingerprint = 9BAA 92BC CDDD B9EF 3B36 CB99 B8C4 B8E5 47C3 2433 Blog: http://stardiviner.github.io/ On Mon, Aug 12, 2024 at 6:17 PM Ihor Radchenko <yantar92@posteo.net> wrote: > "Christopher M. Miles" <numbchild@gmail.com> writes: > > >> What about the following treatment of ARG: > >> > >> 1. No argument, no region selected :: toggle (display or hide dwim) > images in current section > >> 2. No argument, region selected: toggle images in region > >> 3. C-u argument :: toggle images in the whole buffer > >> 4. C-u C-u argument, no region selected :: unconditionally hide images > in the buffer > >> 5. M-1 argument, no region selected :: display images in current > section with INCLUDE-LINKED > >> 6. M-1 argument, region selected :: ... in region ... > >> 7. M-11 argument :: ... in the whole buffer ... > >> 8. Any other argument :: treat as INCLUDE-LINKED = t > >> > >> And please document all the new arguments in the manual and > etc/ORG-NEWS file. > > > > I followed you upper 8 conditions to re-write my patch. > > Except the 8. condition I'm not sure I understand correctly. > > And I extend 1. condition to support the inline image link at point > toggle displaying. > > ... > > I do not like the code repetitions in the patch and relying upon > `use-region-p' even for non-interactive use. > > I am attaching a complete rewrite of your idea. > Please let me know if my patch does everything you want to include into > the command. > > Note that I went with a new idea of introducing a branch new function > instead of changing `org-toggle-inline-images'. This way, existing > users of `org-toggle-inline-images' will not be affected at all. We are > just changing the default C-c C-x C-v binding. This way, the breakage > is a little as possible. > > > -- > Ihor Radchenko // yantar92, > Org mode contributor, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> > [-- Attachment #2: Type: text/html, Size: 4549 bytes --] ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-08-14 2:04 ` stardiviner @ 2024-08-18 10:27 ` Ihor Radchenko 2024-08-20 2:02 ` Karthik Chikmagalur 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 1 sibling, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-18 10:27 UTC (permalink / raw) To: stardiviner; +Cc: Org mode stardiviner <numbchild@gmail.com> writes: > I agree with your patch solution. I have tested fine. Thanks for your > re-write. Applied, onto main. https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=d51dc4aa29 -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-08-18 10:27 ` Ihor Radchenko @ 2024-08-20 2:02 ` Karthik Chikmagalur 2024-08-20 15:43 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-20 2:02 UTC (permalink / raw) To: Ihor Radchenko, stardiviner; +Cc: Org mode > stardiviner <numbchild@gmail.com> writes: > >> I agree with your patch solution. I have tested fine. Thanks for your >> re-write. > > Applied, onto main. > https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=d51dc4aa29 I just noticed this change, and had a couple of suggestions. Right now the commands org-latex-preview and org-toggle-inline-images-command do similar things -- placing overlays over LaTeX fragments or image links respectively. But their regular and prefix arg behaviors are different: | Calling convention | org-latex-preview | org-toggle-inline-images-command | |--------------------+-------------------------------+-----------------------------------| | C-c C-x C-[l,v] | toggle preview at point | toggle preview at point | | | or preview current entry | or toggle in current entry | | With C-u | Clear previews in section | Toggle previews in whole buffer | | With C-u C-u | Preview whole buffer | Hide all previews in whole buffer | | With C-u C-u C-u | Hide previews in whole buffer | - | I think this is confusing, and it will help to stick to a single convention for previewing "things" in Org. Of the two, I prefer org-latex-preview's behavior and have been using a custom library [1] to toggle images this way, but so long as it is consistent I would be okay with either set of behaviors. Karthik [1]: https://github.com/karthink/org-image-preview ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-08-20 2:02 ` Karthik Chikmagalur @ 2024-08-20 15:43 ` Karthik Chikmagalur 2024-08-20 18:19 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-20 15:43 UTC (permalink / raw) To: Ihor Radchenko, stardiviner; +Cc: Org mode > | Calling convention | org-latex-preview | org-toggle-inline-images-command | > |--------------------+-------------------------------+-----------------------------------| > | C-c C-x C-[l,v] | toggle preview at point | toggle preview at point | > | | or preview current entry | or toggle in current entry | > | With C-u | Clear previews in section | Toggle previews in whole buffer | > | With C-u C-u | Preview whole buffer | Hide all previews in whole buffer | > | With C-u C-u C-u | Hide previews in whole buffer | - | > > I think this is confusing, and it will help to stick to a single > convention for previewing "things" in Org. Of the two, I prefer > org-latex-preview's behavior and have been using a custom library [1] to > toggle images this way, but so long as it is consistent I would be okay > with either set of behaviors. Ihor: Based on this, what do you think of the following behavior for org-toggle-inline-images-command? C-c C-x C-v : toggle link preview at point or unconditionally preview active region (not toggle) or unconditionally preview current entry/section (not toggle) With C-u : Clear preview in active region (not toggle) or clear preview in current entry/section (not toggle) With C-u C-u : Unconditionally preview whole buffer (not toggle) With C-u C-u C-u: Clear previews in whole buffer With C-u 1 : Same as C-c C-x C-v, but including images for links with descriptions With C-u 11 : Same as C-u C-u, but including images for links with descriptions This is much closer to the behavior of org-latex-preview. Since all these behaviors are new I think it makes sense to be as consistent as possible for "preview" behavior in Org. ---- Separately, are you interested in separating the link preview code into an org-image-preview library? This will include the feature of registering link preview handlers for different kinds of links, such as video thumbnails for local video links and youtube links, etc. I already have this working for a couple of years now in org-image-preview.el (linked in previous email), but hadn't submitted it yet since the previews are synchronous. I was waiting for org-async to be added along with the LaTeX preview patch so I could submit an async version. But there appears to be activity around image previews right now so I can submit it and work on making it async later. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-08-20 15:43 ` Karthik Chikmagalur @ 2024-08-20 18:19 ` Ihor Radchenko 2024-08-20 19:46 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-20 18:19 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > Ihor: Based on this, what do you think of the following behavior for > org-toggle-inline-images-command? > > C-c C-x C-v : toggle link preview at point > or unconditionally preview active region (not toggle) > or unconditionally preview current entry/section (not toggle) > > With C-u : Clear preview in active region (not toggle) > or clear preview in current entry/section (not toggle) > > With C-u C-u : Unconditionally preview whole buffer (not toggle) > > With C-u C-u C-u: Clear previews in whole buffer > > With C-u 1 : Same as C-c C-x C-v, but including images for links with descriptions > With C-u 11 : Same as C-u C-u, but including images for links with descriptions > > This is much closer to the behavior of org-latex-preview. Since all > these behaviors are new I think it makes sense to be as consistent as > possible for "preview" behavior in Org. I agree. Do you want to create a patch to that effect? > Separately, are you interested in separating the link preview code into > an org-image-preview library? This will include the feature of > registering link preview handlers for different kinds of links, such as > video thumbnails for local video links and youtube links, etc. I > already have this working for a couple of years now in > org-image-preview.el (linked in previous email), but hadn't submitted it > yet since the previews are synchronous. I was waiting for org-async to > be added along with the LaTeX preview patch so I could submit an async > version. But there appears to be activity around image previews right > now so I can submit it and work on making it async later. This sounds like a reasonable addition, yes. Maybe even hook into link parameters. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 2024-08-20 18:19 ` Ihor Radchenko @ 2024-08-20 19:46 ` Karthik Chikmagalur 2024-08-22 13:19 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-20 19:46 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >> This is much closer to the behavior of org-latex-preview. Since all >> these behaviors are new I think it makes sense to be as consistent as >> possible for "preview" behavior in Org. > > I agree. > Do you want to create a patch to that effect? I'll send a patch. >> Separately, are you interested in separating the link preview code into >> an org-image-preview library? This will include the feature of >> registering link preview handlers for different kinds of links, such as >> video thumbnails for local video links and youtube links, etc. I >> already have this working for a couple of years now in >> org-image-preview.el (linked in previous email), but hadn't submitted it >> yet since the previews are synchronous. I was waiting for org-async to >> be added along with the LaTeX preview patch so I could submit an async >> version. But there appears to be activity around image previews right >> now so I can submit it and work on making it async later. > > This sounds like a reasonable addition, yes. > Maybe even hook into link parameters. Yes, the idea is to add an org-link-parameter named ":preview" so each link type can register how it should be previewed. When omitted, we fall back to a default (only for file links) that's the current image preview behavior. More fine-grained behavior may be desired in the future -- for example, web url links to YouTube should be previewed differently from web url links to arXiv, but for now this will be the responsibility of the single :preview function in org-link-parameters. Would you like this to be part of org.el or should I spin out an org-image-preview.el feature+library? Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-08-22 13:19 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> Maybe even hook into link parameters. > > Yes, the idea is to add an org-link-parameter named ":preview" so each > link type can register how it should be previewed. When omitted, we > fall back to a default (only for file links) that's the current > image preview behavior. > > More fine-grained behavior may be desired in the future -- for example, > web url links to YouTube should be previewed differently from web url > links to arXiv, but for now this will be the responsibility of the > single :preview function in org-link-parameters. This is not a problem. It is possible (and also Elispy) to use advices: (add-function :before-until (org-link-get-parameter "id" :follow) #'my-function) We may want to document this fact better though. > Would you like this to be part of org.el or should I spin out an > org-image-preview.el feature+library? Spin out will be easier when for my rebase :) -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4.0] Re: [PATCH] add a function to only refresh inline images under current headline instead of global buffer 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 1 sibling, 0 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-23 6:04 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode > This is not a problem. > It is possible (and also Elispy) to use advices: > > (add-function :before-until (org-link-get-parameter "id" :follow) #'my-function) > > We may want to document this fact better though. I've noticed that org-link-parameters that are functions, like the :follow, :export and :activate-func parameters, are given very specific arguments. For example: `:activate-func' Function to run at the end of Font Lock activation. It must accept four arguments: - the buffer position at the start of the link, - the buffer position at its end, - the path, as a string, - a boolean, non-nil when the link has brackets. This is even though these functions can gather any information about the link using org-element. This suggests that we have something specific in mind for what these functions should do. Along the same lines, my plan is to add a :preview (or :preview-func) parameter that looks like this: `:preview' Function to run when generating an in-buffer preview for the link. It must accept two arguments: - an overlay placed from the start to the end of the link. - the path, as a string. (I can add more arguments if it proves necessary.) The idea is that the preview function can specify an image as the overlay's display property, or add some other overlay property with suitable content. For example, for notmuch links you could specify a before-string that adds relevant metadata from the email that's not part of the link path, like a date/sender. My question is: is it okay to assume that preview actions for all link types should work by placing an overlay over the link? Or is it too restrictive to pass an overlay? A more general version could leave it to the :preview function to create the overlay, or preview it in some other way entirely, such as by adding text properties instead of an overlay. One reason I would like to restrict it to an overlay is that I think a :preview feature should not modify the buffer in any way, as adding text properties will. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* [PATCH v1] Inline image display as part of a new org-link-preview system 2024-08-22 13:19 ` Ihor Radchenko 2024-08-23 6:04 ` Karthik Chikmagalur @ 2024-08-23 23:36 ` Karthik Chikmagalur 2024-08-24 1:00 ` Karthik Chikmagalur 2024-08-31 14:22 ` Ihor Radchenko 1 sibling, 2 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-23 23:36 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 3217 bytes --] >> Would you like this to be part of org.el or should I spin out an >> org-image-preview.el feature+library? > > Spin out will be easier when for my rebase :) Please find attached the draft of a patch implementing a general link-preview system. ---- The idea is the following: 1. Introduce the :preview link parameter. This is a function that is passed an overlay placed over a link (along with some other link details) and should adorn the overlay however the link should be previewed. Typically this is by adding an image to the overlay, but it can be more general. This has a few advantages over the current system: - The code is much more modular. - Previewing image links is no longer "special": all links can have custom preview behavior. Users or package authors can specify how they want each link type to be previewed. - About 300 lines of code have been moved out of org.el, with more removals to come (details below). 2. A :preview function for links of type "file" or "attachment" is provided -- it is the old org-display-inline-images function, but broken up into a couple of more modular functions. - Note: the new preview behavior for image files should be identical, no changes or breaking changes are expected. 3. Introduce a new org-link-preview command. This command has the same prefix arg behaviors as org-latex-preview, and previews each link using its :preview function. This is a drop-in replacement for org-toggle-inline-images-command and org-toggle-inline-images. org-toggle-inline-images-command has been removed, and org-toggle-inline-images has been moved to org-compat. There are a couple of other commands for use from elisp: org-link-preview-region and org-link-preview-clear. Advantages: - Uniform preview behavior across Org preview types (links, latex) - Can preview individual links, links in the section or across the buffer. - Can preview, refresh existing previews or disable them independently using different prefix arg behaviors (no need to toggle/run twice). 4. Bind C-c C-x C-v to org-link-preview. ---- Organization: I didn't create a new feature/library: All the link-preview code is part of ol.el. I think it fits well there. I can move the code for creating and examining images from org.el to a new org-image.el or org-image-utils.el library if required. ---- Not yet handled or final: - Link abbreviations: I'm using org-link-any-re to find links, and this appears to be handling link abbreviations fine. So there is no special code to handle link abbreviations. But I'm not sure. - Updating NEWS and the manual. I can do this at the end, before merging. - The calling convention for the :preview function is not final. Right now it is given the overlay, the link type and the link path. But other link details can be required -- for example, image links need to read the org keywords for the image width and alignment, so I end up calling (org-element-context) again inside the :preview function. ---- I'll send a short example of using org-link-preview to asynchronously preview youtube links as thumbnails soon. Karthik [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-link-Move-inline-image-display-to-org-link.patch --] [-- Type: text/x-patch, Size: 45086 bytes --] From 93b7e50a50d7c6f72439169e5647a840c6e04bcc Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH 2/2] org-link: Move inline image display to org-link Make inline image previews a part of a more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-previeworg-link-preview-region, org-link-preview-clear, org-link-preview-file): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. File links and attachments are previewed using inline image previews as before. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. * 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: Add new `:preview' link parameter for links of type "attachment". --- lisp/ol.el | 290 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 4 +- lisp/org-compat.el | 189 ++++++++++++++++++++++ lisp/org-cycle.el | 10 +- lisp/org-keys.el | 4 +- lisp/org-plot.el | 2 +- lisp/org.el | 283 +-------------------------------- testing/lisp/test-org-fold.el | 4 +- 8 files changed, 493 insertions(+), 293 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..78543f8c7 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,13 @@ (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-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)) \f ;;; Customization @@ -171,6 +178,14 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run when generating 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 type, as a string. + - the path, as a string. + `:help-echo' String or function used as a value for the `help-echo' text @@ -649,6 +664,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,6 +903,28 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delete ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when (overlay-get ov 'org-image-overlay) + (image-flush (overlay-get ov 'display))) + (delete-overlay ov))) + \f ;;; Public API @@ -1573,6 +1617,195 @@ (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-images + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] %d images displayed inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ((not (display-graphic-p)) + (message "Your Emacs does not support displaying images!")) + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-images beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if (get-char-property (point) 'org-image-overlay) + ;; clear link preview at point + (when-let ((context (org-element-context)) + ((org-element-type-p context 'link))) + (funcall toggle-images + (org-element-begin context) + (org-element-end context) + "preview at point" 'remove)) + ;; Clear link previews in entry + (funcall toggle-images + (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-images nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-images 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-images + (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-images beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-images 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 (display-graphic-p) + (when refresh (org-link-preview-clear beg end)) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t)) + (while (re-search-forward org-link-any-re end t) + (when-let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; TODO: Change this overlay property to `org-link-preview' everywhere. + (overlay-put ov 'org-image-overlay t) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + ;; call preview function for link type + (funcall preview-func ov linktype path) + ;; If overlay still exists, add it to the list + (when (overlay-buffer ov) + (push ov org-link-preview-overlays)))))))) + +(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 +1828,62 @@ (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 linktype path) + "Display image file PATH in overlay OV. + +LINKTYPE is the Org link type used to preview PATH, either +\"file\" or \"attachment\". + +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-let ((file-full + (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path))) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((link (org-element-lineage + (save-excursion + (goto-char (overlay-start ov)) + (save-match-data (org-element-context))) + 'link t)) + (width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (if (not image) + ;; Image not available, clean up overlay + (delete-overlay ov) + ;; Add image to overlay: + + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (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))))))))) + ;; file or image not available, clean up overlay + (delete-overlay ov))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index 7a03d170e..7d90dae0b 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -797,9 +797,11 @@ (defun org-attach-follow (file arg) See `org-open-file' for details about ARG." (org-link-open-as-file (org-attach-expand file) arg)) +(declare-function org-link-preview-file "org-link-preview") (org-link-set-parameters "attachment" :follow #'org-attach-follow - :complete #'org-attach-complete-link) + :complete #'org-attach-complete-link + :preview #'org-link-preview-file) (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." diff --git a/lisp/org-compat.el b/lisp/org-compat.el index d843216f3..242b46a86 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -783,6 +783,195 @@ (defun org-add-link-type (type &optional follow export) (make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." "9.0") +(declare-function org-link-preview--remove-overlay "ol") +(declare-function org-link-preview--get-overlays "ol") +(declare-function org-link-preview-clear "ol") +(declare-function org-link-preview--remove-overlay "ol") + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) +(declare-function org-attach-expand "org-attach") +(declare-function org-display-inline-image--width "org") +(declare-function org-image--align "org") +(declare-function org--create-inline-image "org") + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-display-inline-images include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + +(make-obsolete 'org-redisplay-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-redisplay-inline-images () + "Assure display of inline images and refresh them." + (interactive) + (org-toggle-inline-images) + (unless org-link-preview-overlays + (org-toggle-inline-images))) + ;;;; Functions unused in Org core. (defun org-table-recognize-table.el () "If there is a table.el table nearby, recognize it and move into it." diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..1a1c916bd 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -817,19 +817,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 1daedaae8..77cbe5c0f 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,7 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,7 +618,7 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index d5c1dcb35..091a09344 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -5079,7 +5079,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -16651,126 +16651,6 @@ (defun org-normalize-color (value) \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)) @@ -16793,7 +16673,7 @@ (defcustom org-display-remote-inline-images 'skip :safe #'symbolp) (defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. + "How to align images previewed using `org-link-preview-region'. Only stand-alone image links are affected by this setting. These are links without surrounding text. @@ -16850,139 +16730,6 @@ (defun org--create-inline-image (file width) org-image-max-width))) :scale 1)))) -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - (declare-function org-export-read-attribute "ox" (attribute element &optional property)) (defvar visual-fill-column-width) ; Silence compiler warning @@ -17135,32 +16882,6 @@ (defun org-image--align (link) (when (memq org-image-align '(center right)) (symbol-name org-image-align)))))))) - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.44.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v1] Inline image display as part of a new org-link-preview system 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 1 sibling, 0 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-24 1:00 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode > I'll send a short example of using org-link-preview to asynchronously > preview youtube links as thumbnails soon. Here is a prototype of asynchronous org-link-preview of Youtube links in Org mode: https://share.karthinks.com/ol-preview-web.el Essentially I defined an async fetcher `ol-preview-web-link', and registered it as a previewer for https links: (org-link-set-parameters "https" :preview #'ol-preview-web-link) Here's a demo of this in action, previewing image files and links together with M-x org-link-preview: https://share.karthinks.com/org-link-preview-demo.mp4 Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v1] Inline image display as part of a new org-link-preview system 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 1 sibling, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-31 14:22 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> Spin out will be easier when for my rebase :) > > Please find attached the draft of a patch implementing a general > link-preview system. Thanks! > Not yet handled or final: > > - Link abbreviations: I'm using org-link-any-re to find links, and this > appears to be handling link abbreviations fine. So there is no > special code to handle link abbreviations. But I'm not sure. > - The calling convention for the :preview function is not final. Right > now it is given the overlay, the link type and the link path. But > other link details can be required -- for example, image links need to > read the org keywords for the image width and alignment, so I end up > calling (org-element-context) again inside the :preview function. We may simply pass the link object to :preview function. > Subject: [PATCH 2/2] org-link: Move inline image display to org-link Is there [PATCH 1/2]? > * 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'. Didn't we discuss changes to the behavior? > +`:preview' > + > + Function to run when generating 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 type, as a string. > + - the path, as a string. Maybe we can ask the function to return non-nil when the preview is going to happen. The current approach with "overlay deleted then no preview" is not ideal. > +(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))))) I know that this is a copy-paste, but we may utilize 'org-image-overlay + `org-find-overlays' here. > +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) > + "Remove link-preview overlay OV if a corresponding region is modified. > + > +AFTER is true when this function is called post-change." > + (when (and ov after) > + (setq org-link-preview-overlays (delete ov org-link-preview-overlays)) > + ;; Clear image from cache to avoid image not updating upon > + ;; changing on disk. See Emacs bug#59902. > + (when (overlay-get ov 'org-image-overlay) > + (image-flush (overlay-get ov 'display))) > + (delete-overlay ov))) It implies that every :preview function _must_ put an image as 'display property. If it does not, we will run into errors. But should it? Also, when if preview is asynchronous, and we run this function when the image is not yet assigned to the overlay? > + (cond > + ((not (display-graphic-p)) > + (message "Your Emacs does not support displaying images!")) May some third-party previews not require graphic? > +(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))) In asynchronous previews, some overlays may be deleted already. We should be careful with this. > +(defun org-link-preview-file (ov linktype path) > + "Display image file PATH in overlay OV. > + > +LINKTYPE is the Org link type used to preview PATH, either > +\"file\" or \"attachment\". > + > +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-let ((file-full > + (if (equal "attachment" linktype) > + (progn > + (require 'org-attach) > + (ignore-errors (org-attach-expand path))) > + (expand-file-name path))) I'd rather put this part into org-attach, as a separate function that calls `org-link-preview-file'. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v1] Inline image display as part of a new org-link-preview system 2024-08-31 14:22 ` Ihor Radchenko @ 2024-08-31 16:41 ` Karthik Chikmagalur 2024-08-31 16:53 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-31 16:41 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode Thanks Ihor. Before I send the next iteration of the patch, I'd like your input on some decisions marked with "Q:" below. >> Not yet handled or final: >> >> - Link abbreviations: I'm using org-link-any-re to find links, and this >> appears to be handling link abbreviations fine. So there is no >> special code to handle link abbreviations. But I'm not sure. > >> - The calling convention for the :preview function is not final. Right >> now it is given the overlay, the link type and the link path. But >> other link details can be required -- for example, image links need to >> read the org keywords for the image width and alignment, so I end up >> calling (org-element-context) again inside the :preview function. > > We may simply pass the link object to :preview function. If we pass the link object instead of the overlay, it will be the preview function's job to create overlays as needed. This overlay should have special properties (like `org-image-overlay') for previewing to work correctly. Q: Is this okay? Or did you mean we can pass both the link and the overlay, like this: (funcall preview-func ov link) and let the previewer figure out the link type and path details? >> Subject: [PATCH 2/2] org-link: Move inline image display to org-link > > Is there [PATCH 1/2]? There is no [PATCH 1/2], sorry about the naming. What I sent is the complete patch. >> * 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'. > > Didn't we discuss changes to the behavior? Yes, those changes have been implemented, please see the docstring for org-link-preview. The behavior is identical to that of org-latex-preview, but in addition the 1 and the 11 numeric prefix args are handled specially. >> +`:preview' >> + >> + Function to run when generating 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 type, as a string. >> + - the path, as a string. > > Maybe we can ask the function to return non-nil when the preview is > going to happen. The current approach with "overlay deleted then no > preview" is not ideal. Good idea. >> +(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))))) > > I know that this is a copy-paste, but we may utilize 'org-image-overlay > + `org-find-overlays' here. Noted. >> +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) >> + "Remove link-preview overlay OV if a corresponding region is modified. >> + >> +AFTER is true when this function is called post-change." >> + (when (and ov after) >> + (setq org-link-preview-overlays (delete ov org-link-preview-overlays)) >> + ;; Clear image from cache to avoid image not updating upon >> + ;; changing on disk. See Emacs bug#59902. >> + (when (overlay-get ov 'org-image-overlay) >> + (image-flush (overlay-get ov 'display))) >> + (delete-overlay ov))) > > It implies that every :preview function _must_ put an image as 'display > property. If it does not, we will run into errors. But should it? This was an oversight, I meant to add (when-let ((disp (overlay-get ov 'display)) ((imagep disp))) (image-flush disp)) Will fix. > Also, when if preview is asynchronous, and we run this function when the > image is not yet assigned to the overlay? Handled by the above, plus the fact that we delete the overlay. It's up to the async callback to handle the case where the overlay no longer exists. >> + (cond >> + ((not (display-graphic-p)) >> + (message "Your Emacs does not support displaying images!")) > > May some third-party previews not require graphic? Good point, will handle. >> +(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))) > > In asynchronous previews, some overlays may be deleted already. We > should be careful with this. Noted. >> +(defun org-link-preview-file (ov linktype path) >> + "Display image file PATH in overlay OV. >> + >> +LINKTYPE is the Org link type used to preview PATH, either >> +\"file\" or \"attachment\". >> + >> +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-let ((file-full >> + (if (equal "attachment" linktype) >> + (progn >> + (require 'org-attach) >> + (ignore-errors (org-attach-expand path))) >> + (expand-file-name path))) > > I'd rather put this part into org-attach, as a separate function that > calls `org-link-preview-file'. Q: I don't follow. Right now `org-link-preview-file' is the :preview org-link-parameter of file links and attachments. Could you explain how this indirection should work instead? Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v1] Inline image display as part of a new org-link-preview system 2024-08-31 16:41 ` Karthik Chikmagalur @ 2024-08-31 16:53 ` Ihor Radchenko 2024-08-31 22:37 ` [PATCH v2] " Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-31 16:53 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> We may simply pass the link object to :preview function. > > If we pass the link object instead of the overlay, it will be the > preview function's job to create overlays as needed. This overlay > should have special properties (like `org-image-overlay') for previewing > to work correctly. > > Q: Is this okay? Or did you mean we can pass both the link and the > overlay, like this: > > (funcall preview-func ov link) > > and let the previewer figure out the link type and path details? No, I meant to pass link object in addition to other parameters: (funcall preview-func ov path link) We need to pass PATH because we use special rules to derive it. It is not simply LINK :path property. >>> * 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'. >> >> Didn't we discuss changes to the behavior? > > Yes, those changes have been implemented, please see the docstring for > org-link-preview. The behavior is identical to that of > org-latex-preview, but in addition the 1 and the 11 numeric prefix args > are handled specially. Then, the commit message is not accurate. >>> + (if-let ((file-full >>> + (if (equal "attachment" linktype) >>> + (progn >>> + (require 'org-attach) >>> + (ignore-errors (org-attach-expand path))) >>> + (expand-file-name path))) >> >> I'd rather put this part into org-attach, as a separate function that >> calls `org-link-preview-file'. > > Q: I don't follow. Right now `org-link-preview-file' is the :preview > org-link-parameter of file links and attachments. Could you explain how > this indirection should work instead? What I meant is that org-attach will define a custom `org-attach-preview-file' function that will compute the filename, and then internally call `org-link-preview-file'. Then, attachment: link will have :preview set to `org-attach-preview-file'. This way, you will not need to require org-attach from ol and ol from org-attach simultaneously. (no cyclic dependencies, please) -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v2] Inline image display as part of a new org-link-preview system 2024-08-31 16:53 ` Ihor Radchenko @ 2024-08-31 22:37 ` Karthik Chikmagalur 2024-09-01 13:06 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-08-31 22:37 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 3046 bytes --] v2 of the patch is attached. I incorporated most of your suggested changes including in the commit message, except for a couple that I talk about below. First, here's an updated description of the proposed API: 1. You can register a previewer with each type of Org link in the following way: (org-link-set-parameters "file" :preview #'org-link-preview-file) (org-link-set-parameters "https" :preview #'my/org-link-preview-web) 2. This previewer is called by Org with three parameters: an overlay, the link path being previewed and the link element: (funcall #'org-link-preview-file ov path link) - To preview the link, the previewer must update the overlay ov as needed, for example by placing an image as its `display' property. It should not modify the buffer contents. - The previewer must return a non-nil value to indicate preview success. If the preview fails, it should return nil. That's it. >> Q: I don't follow. Right now `org-link-preview-file' is the :preview >> org-link-parameter of file links and attachments. Could you explain how >> this indirection should work instead? > > What I meant is that org-attach will define a custom > `org-attach-preview-file' function that will compute the filename, and > then internally call `org-link-preview-file'. Then, attachment: link > will have :preview set to `org-attach-preview-file'. This way, you will > not need to require org-attach from ol and ol from org-attach > simultaneously. (no cyclic dependencies, please) Done. > Then, the commit message is not accurate. Fixed. > I know that this is a copy-paste, but > we may utilize 'org-image-overlay > + `org-find-overlays' here. I decided not to do this since the logic in org-link-preview--remove-overlay is different: it checks for the existence of overlays in the maintained list org-link-preview-overlays instead of checking for overlays in the buffer. > It implies that every :preview function _must_ put an image as 'display > property. If it does not, we will run into errors. But should it? Fixed. > Also, when if preview is asynchronous, and we run this function when the > image is not yet assigned to the overlay? > In asynchronous previews, some overlays may be deleted already. We > should be careful with this. No changes here. When the async callback runs, the state of the buffer can have changed. There's nothing Org can do about that. The callback has access to the overlay, so it will have to use a construct like (and (overlay-buffer ov) (add-preview-to link)) If the preview fails, there will be an undecorated overlay hanging around, I'm not sure what to do about it. The async function can run `(delete-overlay ov)', but that breaks the abstraction boundary we're trying to create. (It will also have to remove the overlay from `org-link-preview-overlays'.) It can't run `org-link-preview-clear' on the link, since the link element's bounds might be invalid. I'm assuming that the :begin and :end property of the link are integers and not markers. Karthik [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-link-Move-inline-image-display-to-org-link.patch --] [-- Type: text/x-patch, Size: 45064 bytes --] From d60d5bef78841d548cfbcdca3a461cb9c52e4314 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: Move inline image display to org-link Make inline image previews a part of a more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-previeworg-link-preview-region, org-link-preview-clear, org-link-preview-file): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. File links and attachments are previewed using inline image previews as before. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images): Use `org-link-preview'. * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el: Add new `:preview' link parameter for links of type "attachment". --- lisp/ol.el | 280 ++++++++++++++++++++++++++++++++- lisp/org-attach.el | 10 +- lisp/org-compat.el | 189 +++++++++++++++++++++++ lisp/org-cycle.el | 10 +- lisp/org-keys.el | 4 +- lisp/org-plot.el | 2 +- lisp/org.el | 283 +--------------------------------- testing/lisp/test-org-fold.el | 4 +- 8 files changed, 489 insertions(+), 293 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..baed73daa 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,13 @@ (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-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)) \f ;;; Customization @@ -171,6 +178,16 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run to generate an in-buffer preview for the link. It + must accept three arguments: + - an overlay placed from the start to the end of the link. + - the link path, as a string. + - the link element + + This function must return a non-nil value to indicate success. + `:help-echo' String or function used as a value for the `help-echo' text @@ -649,6 +666,13 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + \f ;;; Internal Functions @@ -881,6 +905,30 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delete ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* (((overlay-get ov 'org-image-overlay)) + (disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f ;;; Public API @@ -1573,6 +1621,194 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, preview links with description as +well. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] %d images displayed inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if (get-char-property (point) 'org-image-overlay) + ;; clear link preview at point + (when-let ((context (org-element-context)) + ((org-element-type-p context 'link))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "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 (display-graphic-p) + (when refresh (org-link-preview-clear beg end)) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t)) + (while (re-search-forward org-link-any-re end t) + (when-let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; TODO: Change this overlay property to `org-link-preview' everywhere. + (overlay-put ov 'org-image-overlay t) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + ;; call preview function for link type + (if (funcall preview-func ov path link) + (when (overlay-buffer ov) + (push ov org-link-preview-overlays)) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov)))))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +1831,49 @@ (defun org-link--open-elisp (path _) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) ;;;; "file" link type -(org-link-set-parameters "file" :complete #'org-link-complete-file) +(org-link-set-parameters "file" + :complete #'org-link-complete-file + :preview #'org-link-preview-file) + +(defun org-link-preview-file (ov path link) + "Display image file PATH in overlay OV for LINK. + +LINK is the Org element being previewed. + +Equip each image with the keymap `image-map'. + +This is intended to be used as the `:preview' link property of +file links, see `org-link-parameters'." + (if (not (display-graphic-p)) + (prog1 nil + (message "Your Emacs does not support displaying images!")) + (when-let* ((file-full (expand-file-name path)) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (when image ; Add image to overlay + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (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))))))) + t))))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index 7a03d170e..4b0887fcd 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -797,9 +797,17 @@ (defun org-attach-follow (file arg) See `org-open-file' for details about ARG." (org-link-open-as-file (org-attach-expand file) arg)) +(defun org-attach-preview-file (ov path link) + "Preview attachment with PATH in overlay OV. + +LINK is the Org link element being previewed." + (org-link-preview-file + ov (org-attach-expand path) link)) + (org-link-set-parameters "attachment" :follow #'org-attach-follow - :complete #'org-attach-complete-link) + :complete #'org-attach-complete-link + :preview #'org-attach-preview-file) (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." diff --git a/lisp/org-compat.el b/lisp/org-compat.el index d843216f3..242b46a86 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -783,6 +783,195 @@ (defun org-add-link-type (type &optional follow export) (make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." "9.0") +(declare-function org-link-preview--remove-overlay "ol") +(declare-function org-link-preview--get-overlays "ol") +(declare-function org-link-preview-clear "ol") +(declare-function org-link-preview--remove-overlay "ol") + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) +(declare-function org-attach-expand "org-attach") +(declare-function org-display-inline-image--width "org") +(declare-function org-image--align "org") +(declare-function org--create-inline-image "org") + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-display-inline-images include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + +(make-obsolete 'org-redisplay-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-redisplay-inline-images () + "Assure display of inline images and refresh them." + (interactive) + (org-toggle-inline-images) + (unless org-link-preview-overlays + (org-toggle-inline-images))) + ;;;; Functions unused in Org core. (defun org-table-recognize-table.el () "If there is a table.el table nearby, recognize it and move into it." diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..1a1c916bd 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -817,19 +817,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 1daedaae8..77cbe5c0f 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,7 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,7 +618,7 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index d5c1dcb35..091a09344 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -5079,7 +5079,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -16651,126 +16651,6 @@ (defun org-normalize-color (value) \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)) @@ -16793,7 +16673,7 @@ (defcustom org-display-remote-inline-images 'skip :safe #'symbolp) (defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. + "How to align images previewed using `org-link-preview-region'. Only stand-alone image links are affected by this setting. These are links without surrounding text. @@ -16850,139 +16730,6 @@ (defun org--create-inline-image (file width) org-image-max-width))) :scale 1)))) -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - (declare-function org-export-read-attribute "ox" (attribute element &optional property)) (defvar visual-fill-column-width) ; Silence compiler warning @@ -17135,32 +16882,6 @@ (defun org-image--align (link) (when (memq org-image-align '(center right)) (symbol-name org-image-align)))))))) - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.44.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v2] Inline image display as part of a new org-link-preview system 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-09-01 13:06 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > First, here's an updated description of the proposed API: > > 1. You can register a previewer with each type of Org link in the > following way: > > (org-link-set-parameters "file" :preview #'org-link-preview-file) > (org-link-set-parameters "https" :preview #'my/org-link-preview-web) > > 2. This previewer is called by Org with three parameters: an overlay, > the link path being previewed and the link element: > > (funcall #'org-link-preview-file ov path link) > > - To preview the link, the previewer must update the overlay ov as > needed, for example by placing an image as its `display' property. > It should not modify the buffer contents. > - The previewer must return a non-nil value to indicate preview > success. If the preview fails, it should return nil. > > That's it. Sounds reasonable. > Subject: [PATCH] org-link: Move inline image display to org-link The patch does a lot more than merely "moving" things around. Please, name it accordingly. Maybe something like New customizable preview API for arbitrary link types > --- a/lisp/ol.el > +++ b/lisp/ol.el > @@ -82,6 +82,13 @@ (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-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)) Some of these declares are now redundant. > +`:preview' > + > + Function to run to generate an in-buffer preview for the link. It > + must accept three arguments: > + - an overlay placed from the start to the end of the link. > + - the link path, as a string. > + - the link element > + This function must return a non-nil value to indicate success. I am wondering whether asynchronicity should be implemented on high level rather than inside individual link preview functions. We may, for example, first create the necessary overlays and indicate that they are "processing..." first, and then call the actual preview functions asynchronously. If we leave asynchronous handling to individual previews, they will have no way to handle queuing large number of link previews, and we may arrive at the common situation with network processes when they schedule and finish around the same time, hanging Emacs while it is handling a bunch of process sentinels. > + ;; C-u argument: clear image at point or in entry > + ((equal arg '(4)) > + (if (get-char-property (point) 'org-image-overlay) > + ;; clear link preview at point > + (when-let ((context (org-element-context)) > + ((org-element-type-p context 'link))) It will be more reliable to clear across overlay boundaries rather than assuming that the overlay covers exactly one link. > +BEG and END define the considered part. They default to the > +buffer boundaries with possible narrowing." > + (interactive "P") > + (when (display-graphic-p) Do we need it here? You check graphics both here and later in the preview function. We may probably drop the check herein. > + (let* ((width (org-display-inline-image--width link)) > + (align (org-image--align link)) > + (image (org--create-inline-image file width))) I am wondering why you left these functions in org.el. Why not moving them here? > + (when image ; Add image to overlay > ... > + (overlay-put ov 'org-image-overlay t) This is redundant, isn't it? > + (when (boundp 'image-map) What if not bound? Why not simply (require 'image)? ... and ORG-NEWS entry. You also need to fixup the manual where it talks about image previews. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-01 13:06 ` Ihor Radchenko @ 2024-09-02 20:13 ` Karthik Chikmagalur 2024-09-08 7:43 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-02 20:13 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 5521 bytes --] New patch version attached. Not all your points were addressed, please check my comments below. >> Subject: [PATCH] org-link: Move inline image display to org-link > > The patch does a lot more than merely "moving" things around. Please, > name it accordingly. Maybe something like > > New customizable preview API for arbitrary link types Renamed commit. >> +(declare-function org-attach-expand "org-attach" (file)) > > Some of these declares are now redundant. Only one declare was redundant (org-attach-expand), fixed. >> + ;; C-u argument: clear image at point or in entry >> + ((equal arg '(4)) >> + (if (get-char-property (point) 'org-image-overlay) >> + ;; clear link preview at point >> + (when-let ((context (org-element-context)) >> + ((org-element-type-p context 'link))) > > It will be more reliable to clear across overlay boundaries rather than > assuming that the overlay covers exactly one link. Fixed. >> +BEG and END define the considered part. They default to the >> +buffer boundaries with possible narrowing." >> + (interactive "P") >> + (when (display-graphic-p) > > Do we need it here? You check graphics both here and later in the > preview function. We may probably drop the check herein. I moved the `refresh' clause out of the display-graphic-p check, but I think it's needed. Otherwise this line will run once per previewed image instead of once per call to `org-link-preview': (when (fboundp 'clear-image-cache) (clear-image-cache)) clearing newly placed previews (within the same run) from the cache. That said, I have a question about `clear-image-cache' here. Why does Org clear the Emacs frame's entire image cache every time `org-link-preview-region' (or the previous `org-display-inline-images') is called? This seems overreaching. There are many situations even within Org mode where we want to avoid the extra file access that clearing the image cache will cause, like in a buffer with hundreds of LaTeX previews. Images in unrelated buffers/major-modes are also affected. >> + (let* ((width (org-display-inline-image--width link)) >> + (align (org-image--align link)) >> + (image (org--create-inline-image file width))) > > I am wondering why you left these functions in org.el. Why not moving > them here? They seemed out of place in ol.el, so my plan was the following: 1. This patch: Implement org-link-preview. 2. Another patch: Move image creation/manipulation/geometry functions from org into an org-image (or org-image-utils) library. Require it in ol. However, I can move them wherever you want, let me know. >> + (when image ; Add image to overlay >> ... >> + (overlay-put ov 'org-image-overlay t) > > This is redundant, isn't it? I don't know how create-image can fail. Suppose a .jpg file exists but is corrupted or does not have image/jpeg data. Then create-image returns nil, as does `org--create-inline-image'. This check guards against trying to preview an "image" that's nil. >> + (when (boundp 'image-map) > > What if not bound? Why not simply (require 'image)? Just unmodified old code. I've require'd image in ol now, let me know if I should do it differently. > ... and ORG-NEWS entry. > > You also need to fixup the manual where it talks about image previews. Yes, I will do both at the end, after the changes are final and before merging. >> +`:preview' >> + >> + Function to run to generate an in-buffer preview for the link. It >> + must accept three arguments: >> + - an overlay placed from the start to the end of the link. >> + - the link path, as a string. >> + - the link element >> + This function must return a non-nil value to indicate success. > > I am wondering whether asynchronicity should be implemented on high > level rather than inside individual link preview functions. > We may, for example, first create the necessary overlays and indicate > that they are "processing..." first, and then call the actual preview > functions asynchronously. > > If we leave asynchronous handling to individual previews, they will have > no way to handle queuing large number of link previews, and we may arrive > at the common situation with network processes when they schedule and > finish around the same time, hanging Emacs while it is handling > a bunch of process sentinels. I'm not sure how to solve this problem. Here's what I'm picturing -- note that this doesn't actually avoid the process sentinel pile-up: The preview provider should return its callback function to Org so Org can run it at its convenience. Org runs (funcall #'preview-func ov path link) preview-func returns either 1. t, if preview is synchronous and successful, 2. nil, if preview is synchronous but failed, 3. A callback-func, if preview is asynchronous. In this case the preview-func starts the network request or async job. Org adds callback-func to the queue of callback-funcs for this run, and runs them spaced out on a timer or something. Preview success depends on whether the callback-func returns t or nil. Implementing something like this should be easy with org-async, included in the LaTeX preview patchset. But the process sentinels will run independent of Org's callback queue. The callbacks only place the resulting image (or other metadata) on the overlays. The bottleneck here is not the display engine, so this doesn't solve the problem. Can you give me some more details of what you have in mind? Karthik [-- 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: 45217 bytes --] From 7c2df91fb6f41e24af8ee5bee452784e896149f8 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-previeworg-link-preview-region, org-link-preview-clear, org-link-preview-file): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. File links and attachments are previewed using inline image previews as before. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images): Use `org-link-preview'. * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el (org-attach-preview-file): Add new `:preview' link parameter for links of type "attachment", set to the new function `org-attach-preview-file'. --- lisp/ol.el | 277 ++++++++++++++++++++++++++++++++- lisp/org-attach.el | 10 +- lisp/org-compat.el | 189 +++++++++++++++++++++++ lisp/org-cycle.el | 10 +- lisp/org-keys.el | 4 +- lisp/org-plot.el | 2 +- lisp/org.el | 283 +--------------------------------- testing/lisp/test-org-fold.el | 4 +- 8 files changed, 486 insertions(+), 293 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..f06fe5a34 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -33,6 +33,7 @@ (org-assert-version) (require 'org-compat) (require 'org-macs) (require 'org-fold) +(require 'image) (defvar clean-buffer-list-kill-buffer-names) (defvar org-agenda-buffer-name) @@ -82,6 +83,12 @@ (declare-function org-src-source-buffer "org-src" ()) (declare-function org-src-source-type "org-src" ()) (declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function outline-next-heading "outline" ()) +(declare-function image-flush "image" (spec &optional frame)) +(declare-function org-entry-end-position "org" ()) +(declare-function org-element-contents-begin "org-element" (node)) +(declare-function org-display-inline-image--width "org" (link)) +(declare-function org-image--align "org" (link)) +(declare-function org--create-inline-image "org" (file width)) \f ;;; Customization @@ -171,6 +178,16 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run to generate an in-buffer preview for the link. It + must accept three arguments: + - an overlay placed from the start to the end of the link. + - the link path, as a string. + - the link element + + This function must return a non-nil value to indicate success. + `:help-echo' String or function used as a value for the `help-echo' text @@ -649,6 +666,13 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + \f ;;; Internal Functions @@ -881,6 +905,30 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delete ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* (((overlay-get ov 'org-image-overlay)) + (disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f ;;; Public API @@ -1573,6 +1621,192 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, preview links with description as +well. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] %d images displayed inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if-let ((ov (cdr (get-char-property-and-overlay + (point) 'org-image-overlay)))) + ;; clear link preview at point + (funcall toggle-previews + (overlay-start ov) (overlay-end ov) + "preview at point" 'remove) + ;; Clear link previews in entry + (funcall toggle-previews + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))) + (org-with-limited-levels (org-entry-end-position)) + "current section" 'remove))) + ;; C-u C-u or C-11 argument :: display images in the whole buffer. + ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove)) + ;; Argument nil or 1, no region selected :: display images in + ;; current section or image link at point. + ((and (member arg '(nil 1)) (null beg) (null end)) + (let ((context (org-element-context))) + ;; toggle display of inline image link at point. + (if (org-element-type-p context 'link) + (let* ((ov (cdr-safe (get-char-property-and-overlay + (point) 'org-image-overlay))) + (remove? (and ov (memq ov org-link-preview-overlays) + 'remove))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "image at point" remove?)) + (let ((beg (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point))))) + (end (org-with-limited-levels (org-entry-end-position)))) + (funcall toggle-previews beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-previews beg end "region"))))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh (org-link-preview-clear beg end)) + (when (display-graphic-p) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t)) + (while (re-search-forward org-link-any-re end t) + (when-let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; TODO: Change this overlay property to `org-link-preview' everywhere. + (overlay-put ov 'org-image-overlay t) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + ;; call preview function for link type + (if (funcall preview-func ov path link) + (when (overlay-buffer ov) + (push ov org-link-preview-overlays)) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov)))))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +1829,48 @@ (defun org-link--open-elisp (path _) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) ;;;; "file" link type -(org-link-set-parameters "file" :complete #'org-link-complete-file) +(org-link-set-parameters "file" + :complete #'org-link-complete-file + :preview #'org-link-preview-file) + +(defun org-link-preview-file (ov path link) + "Display image file PATH in overlay OV for LINK. + +LINK is the Org element being previewed. + +Equip each image with the keymap `image-map'. + +This is intended to be used as the `:preview' link property of +file links, see `org-link-parameters'." + (if (not (display-graphic-p)) + (prog1 nil + (message "Your Emacs does not support displaying images!")) + (when-let* ((file-full (expand-file-name path)) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (when image ; Add image to overlay + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put ov 'keymap image-map) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + t))))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index 7a03d170e..4b0887fcd 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -797,9 +797,17 @@ (defun org-attach-follow (file arg) See `org-open-file' for details about ARG." (org-link-open-as-file (org-attach-expand file) arg)) +(defun org-attach-preview-file (ov path link) + "Preview attachment with PATH in overlay OV. + +LINK is the Org link element being previewed." + (org-link-preview-file + ov (org-attach-expand path) link)) + (org-link-set-parameters "attachment" :follow #'org-attach-follow - :complete #'org-attach-complete-link) + :complete #'org-attach-complete-link + :preview #'org-attach-preview-file) (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." diff --git a/lisp/org-compat.el b/lisp/org-compat.el index d843216f3..242b46a86 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -783,6 +783,195 @@ (defun org-add-link-type (type &optional follow export) (make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." "9.0") +(declare-function org-link-preview--remove-overlay "ol") +(declare-function org-link-preview--get-overlays "ol") +(declare-function org-link-preview-clear "ol") +(declare-function org-link-preview--remove-overlay "ol") + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) +(declare-function org-attach-expand "org-attach") +(declare-function org-display-inline-image--width "org") +(declare-function org-image--align "org") +(declare-function org--create-inline-image "org") + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-display-inline-images include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + +(make-obsolete 'org-redisplay-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-redisplay-inline-images () + "Assure display of inline images and refresh them." + (interactive) + (org-toggle-inline-images) + (unless org-link-preview-overlays + (org-toggle-inline-images))) + ;;;; Functions unused in Org core. (defun org-table-recognize-table.el () "If there is a table.el table nearby, recognize it and move into it." diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..1a1c916bd 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -817,19 +817,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 1daedaae8..77cbe5c0f 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,7 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,7 +618,7 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index d5c1dcb35..091a09344 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -5079,7 +5079,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -16651,126 +16651,6 @@ (defun org-normalize-color (value) \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)) @@ -16793,7 +16673,7 @@ (defcustom org-display-remote-inline-images 'skip :safe #'symbolp) (defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. + "How to align images previewed using `org-link-preview-region'. Only stand-alone image links are affected by this setting. These are links without surrounding text. @@ -16850,139 +16730,6 @@ (defun org--create-inline-image (file width) org-image-max-width))) :scale 1)))) -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - (declare-function org-export-read-attribute "ox" (attribute element &optional property)) (defvar visual-fill-column-width) ; Silence compiler warning @@ -17135,32 +16882,6 @@ (defun org-image--align (link) (when (memq org-image-align '(center right)) (symbol-name org-image-align)))))))) - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.44.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 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-10 19:43 ` Karthik Chikmagalur 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-09-08 7:43 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >>> +BEG and END define the considered part. They default to the >>> +buffer boundaries with possible narrowing." >>> + (interactive "P") >>> + (when (display-graphic-p) >> >> Do we need it here? You check graphics both here and later in the >> preview function. We may probably drop the check herein. > > I moved the `refresh' clause out of the display-graphic-p check, but I > think it's needed. Otherwise this line will run once per previewed > image instead of once per call to `org-link-preview': > > (when (fboundp 'clear-image-cache) (clear-image-cache)) > > clearing newly placed previews (within the same run) from the cache. Sorry, I was not clear. I only referred to `display-graphic-p' check. I did not mean `clear-image-cache'. > That said, I have a question about `clear-image-cache' here. Why does > Org clear the Emacs frame's entire image cache every time > `org-link-preview-region' (or the previous `org-display-inline-images') > is called? This seems overreaching. There are many situations even > within Org mode where we want to avoid the extra file access that > clearing the image cache will cause, like in a buffer with hundreds of > LaTeX previews. Images in unrelated buffers/major-modes are also > affected. Here is the original code: (when refresh (org-remove-inline-images beg end) (when (fboundp 'clear-image-cache) (clear-image-cache))) Image cache is cleared _only_ with REFRESH argument. I think that makes sense, right? >>> + (let* ((width (org-display-inline-image--width link)) >>> + (align (org-image--align link)) >>> + (image (org--create-inline-image file width))) >> >> I am wondering why you left these functions in org.el. Why not moving >> them here? > > They seemed out of place in ol.el, so my plan was the following: > 1. This patch: Implement org-link-preview. > 2. Another patch: Move image creation/manipulation/geometry functions > from org into an org-image (or org-image-utils) library. Require it in > ol. > > However, I can move them wherever you want, let me know. I'd rather see them moved. You can later send another patch moving them into the new utils library, if you want. I am asking because you may or may not have time to create that followup patch. So, better have things sorted out at every step rather than relying upon followups that may or may not happen (e.g. for reasons you cannot control) >>> + (when image ; Add image to overlay >>> ... >>> + (overlay-put ov 'org-image-overlay t) >> >> This is redundant, isn't it? > > I don't know how create-image can fail. Suppose a .jpg file exists but > is corrupted or does not have image/jpeg data. Then create-image > returns nil, as does `org--create-inline-image'. This check guards > against trying to preview an "image" that's nil. Let me elaborate. In `org-link-preview-region', you set org-image-overlay property: + (overlay-put ov 'org-image-overlay t) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + ;; call preview function for link type + (if (funcall preview-func ov path link) + (when (overlay-buffer ov) Then, you do it yet again in `org-link-preview-file', when the property is already there - this part is redundant. I do not see how `org--create-inline-image' affects the above. >>> + (when (boundp 'image-map) >> >> What if not bound? Why not simply (require 'image)? > > Just unmodified old code. I've require'd image in ol now, let me know > if I should do it differently. Let's not require it on top level. Maybe better do it within the preview function. >> I am wondering whether asynchronicity should be implemented on high >> level rather than inside individual link preview functions. >> We may, for example, first create the necessary overlays and indicate >> that they are "processing..." first, and then call the actual preview >> functions asynchronously. >> >> If we leave asynchronous handling to individual previews, they will have >> no way to handle queuing large number of link previews, and we may arrive >> at the common situation with network processes when they schedule and >> finish around the same time, hanging Emacs while it is handling >> a bunch of process sentinels. > > I'm not sure how to solve this problem. Here's what I'm picturing -- > note that this doesn't actually avoid the process sentinel pile-up: > > The preview provider should return its callback function to Org so Org > can run it at its convenience. > > Org runs (funcall #'preview-func ov path link) > > preview-func returns either > 1. t, if preview is synchronous and successful, > 2. nil, if preview is synchronous but failed, > 3. A callback-func, if preview is asynchronous. In this case the > preview-func starts the network request or async job. > > Org adds callback-func to the queue of callback-funcs for this run, and > runs them spaced out on a timer or something. Preview success depends > on whether the callback-func returns t or nil. > > Implementing something like this should be easy with org-async, included > in the LaTeX preview patchset. > > But the process sentinels will run independent of Org's callback queue. > The callbacks only place the resulting image (or other metadata) on the > overlays. The bottleneck here is not the display engine, so this > doesn't solve the problem. > > Can you give me some more details of what you have in mind? I mostly meant calling preview-func asynchronously, while idle, spaced out, spending not longer than a fraction of second to call several preview-funcs. Spacing might then be controlled by the users. We might go further, and let the preview functions return a process. Then, we may explicitly control running sentinels just for that process via `accept-process-output'. But I am not sure if we need to go that far. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-08 7:43 ` Ihor Radchenko @ 2024-09-09 3:21 ` Karthik Chikmagalur 2024-09-09 6:06 ` Ihor Radchenko 2024-09-10 19:43 ` Karthik Chikmagalur 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-09 3:21 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >> I'm not sure how to solve this problem. Here's what I'm picturing -- >> note that this doesn't actually avoid the process sentinel pile-up: >> >> The preview provider should return its callback function to Org so Org >> can run it at its convenience. >> >> Org runs (funcall #'preview-func ov path link) >> >> preview-func returns either >> 1. t, if preview is synchronous and successful, >> 2. nil, if preview is synchronous but failed, >> 3. A callback-func, if preview is asynchronous. In this case the >> preview-func starts the network request or async job. >> >> Org adds callback-func to the queue of callback-funcs for this run, and >> runs them spaced out on a timer or something. Preview success depends >> on whether the callback-func returns t or nil. >> >> Implementing something like this should be easy with org-async, included >> in the LaTeX preview patchset. >> >> But the process sentinels will run independent of Org's callback queue. >> The callbacks only place the resulting image (or other metadata) on the >> overlays. The bottleneck here is not the display engine, so this >> doesn't solve the problem. >> >> Can you give me some more details of what you have in mind? > > I mostly meant calling preview-func asynchronously, while idle, spaced > out, spending not longer than a fraction of second to call several > preview-funcs. > Spacing might then be controlled by the users. > > We might go further, and let the preview functions return a > process. Then, we may explicitly control running sentinels just for that > process via `accept-process-output'. But I am not sure if we need to go > that far. Do you mean something like this? (while (re-search-forward org-link-any-re end t) ;; Make overlay ov here ;; Find path, link and preview-func here (push (list ov preview-func path link) previews-remaining)) (dolist (preview-data-chunk (seq-partition previews-remaining 6)) (run-with-idle-timer 0.10 (lambda (preview-data) (pcase-dolist (`(,ov ,preview-func ,path ,link) preview-data) (when-let ((buf (overlay-buffer ov))) (with-current-buffer buf (if (funcall preview-func ov path link) (push ov org-link-preview-overlays) ;; Preview was unsuccessful, delete overlay (delete-overlay ov)))))) preview-data-chunk)) Where the chunk size (6) and the idle time (0.10 seconds) will be customizable. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 3:21 ` Karthik Chikmagalur @ 2024-09-09 6:06 ` Ihor Radchenko 2024-09-09 6:30 ` Karthik Chikmagalur 2024-09-09 21:45 ` Karthik Chikmagalur 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-09-09 6:06 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> I mostly meant calling preview-func asynchronously, while idle, spaced >> out, spending not longer than a fraction of second to call several >> preview-funcs. >> Spacing might then be controlled by the users. > ... > Do you mean something like this? > > (while (re-search-forward org-link-any-re end t) > ;; Make overlay ov here > ;; Find path, link and preview-func here > > (push (list ov preview-func path link) previews-remaining)) > > (dolist (preview-data-chunk (seq-partition previews-remaining 6)) > (run-with-idle-timer > ... > Where the chunk size (6) and the idle time (0.10 seconds) will be > customizable. Yes. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 6:06 ` Ihor Radchenko @ 2024-09-09 6:30 ` Karthik Chikmagalur 2024-09-09 16:47 ` Ihor Radchenko 2024-09-09 21:45 ` Karthik Chikmagalur 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-09 6:30 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >> Do you mean something like this? >> >> (while (re-search-forward org-link-any-re end t) >> ;; Make overlay ov here >> ;; Find path, link and preview-func here >> >> (push (list ov preview-func path link) previews-remaining)) >> >> (dolist (preview-data-chunk (seq-partition previews-remaining 6)) >> (run-with-idle-timer >> ... >> Where the chunk size (6) and the idle time (0.10 seconds) will be >> customizable. > > Yes. Okay. There's one more issue to resolve. An asynchronous preview will return t but no preview is placed yet. If the preview then fails when the callback runs, we will have an overlay without a preview that hasn't been cleaned up. How do you suggest handling this case? The only thing I can think of is that the async preview-func should call org-link-preview-clear on the failed preview by itself, and we should include this expectation in the org-link-parameters documentation. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 6:30 ` Karthik Chikmagalur @ 2024-09-09 16:47 ` Ihor Radchenko 2024-09-09 19:14 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-09-09 16:47 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >>> Do you mean something like this? >>> >>> (while (re-search-forward org-link-any-re end t) >>> ;; Make overlay ov here >>> ;; Find path, link and preview-func here >>> >>> (push (list ov preview-func path link) previews-remaining)) >>> >>> (dolist (preview-data-chunk (seq-partition previews-remaining 6)) >>> (run-with-idle-timer >>> ... >>> Where the chunk size (6) and the idle time (0.10 seconds) will be >>> customizable. >> >> Yes. > > Okay. There's one more issue to resolve. An asynchronous preview will > return t but no preview is placed yet. If the preview then fails when > the callback runs, we will have an overlay without a preview that hasn't > been cleaned up. How do you suggest handling this case? > > The only thing I can think of is that the async preview-func should call > org-link-preview-clear on the failed preview by itself, and we should > include this expectation in the org-link-parameters documentation. Hmm... Not sure. I feel that we are going too far. Maybe we should use org-pending <https://list.orgmode.org/65df0895.df0a0220.a68c8.0966@mx.google.com/> after it is finalized. For now, my idea is to generalize the asynchronous handling - instead of doing it within individual preview functions, assume that preview functions are synchronous, but run them in chunks. If a preview function is not synchronous, it should take care about managing the passed overlay by itself. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 16:47 ` Ihor Radchenko @ 2024-09-09 19:14 ` Karthik Chikmagalur 2024-09-10 16:57 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-09 19:14 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode > Hmm... Not sure. I feel that we are going too far. > Maybe we should use org-pending > <https://list.orgmode.org/65df0895.df0a0220.a68c8.0966@mx.google.com/> > after it is finalized. Interesting, I didn't know about org-pending. At first I thought this would overlap with the org-async, the process executor used for LaTeX previews, but it looks like this is for locking buffer regions and orthogonal to the source of the async changes. This is an aside, but do you think it makes sense to merge org-async before LaTeX previews (i.e. now), so that we can get feedback on its design? > For now, my idea is to generalize the asynchronous handling - instead of > doing it within individual preview functions, assume that preview > functions are synchronous, but run them in chunks. > > If a preview function is not synchronous, it should take care about > managing the passed overlay by itself. Yes, this is what I suggested. Even chunking and running the preview-funcs on an idle-timer seems like overengineering to me, but I defer to your experience here. I'll update the patch soon. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 19:14 ` Karthik Chikmagalur @ 2024-09-10 16:57 ` Ihor Radchenko 2024-09-10 19:53 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-09-10 16:57 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> Hmm... Not sure. I feel that we are going too far. >> Maybe we should use org-pending >> <https://list.orgmode.org/65df0895.df0a0220.a68c8.0966@mx.google.com/> >> after it is finalized. > > Interesting, I didn't know about org-pending. At first I thought this > would overlap with the org-async, the process executor used for LaTeX > previews, but it looks like this is for locking buffer regions and > orthogonal to the source of the async changes. > > This is an aside, but do you think it makes sense to merge org-async > before LaTeX previews (i.e. now), so that we can get feedback on its > design? The best test for the design is actually using it for things. If you have some feature you want to use org-async for, we can merge that feature + org-async. >> For now, my idea is to generalize the asynchronous handling - instead of >> doing it within individual preview functions, assume that preview >> functions are synchronous, but run them in chunks. >> >> If a preview function is not synchronous, it should take care about >> managing the passed overlay by itself. > > Yes, this is what I suggested. Even chunking and running the > preview-funcs on an idle-timer seems like overengineering to me, but I > defer to your experience here. I'll update the patch soon. Implementing async is not mandatory. Here, I am simply taking into account _your_ experience with LaTeX previews. And I do know that image previews may also take time when there are many images in the buffer. So, I thought that you are probably the best person to design such things :) -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-10 16:57 ` Ihor Radchenko @ 2024-09-10 19:53 ` Karthik Chikmagalur 2024-09-15 7:51 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-10 19:53 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode > The best test for the design is actually using it for things. > If you have some feature you want to use org-async for, we can merge > that feature + org-async. Only LaTeX previews for now. As you may be aware, the patchset also includes asynchronous pdf exports (using org-async), which makes a huge difference in Org's usability when iterating on a document and running frequent exports. >>> If a preview function is not synchronous, it should take care about >>> managing the passed overlay by itself. >> >> Yes, this is what I suggested. Even chunking and running the >> preview-funcs on an idle-timer seems like overengineering to me, but I >> defer to your experience here. I'll update the patch soon. > > Implementing async is not mandatory. > Here, I am simply taking into account _your_ experience with LaTeX > previews. And I do know that image previews may also take time when > there are many images in the buffer. > > So, I thought that you are probably the best person to design such > things :) Unfortunately I don't think I'm good at designing flexible async APIs, at least in Emacs. (The LaTeX preview system is being tuned for speed/lag-free performance, not flexibility.) I think documenting that a failed async preview should call org-link-preview-clear on the link is good enough for now. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-10 19:53 ` Karthik Chikmagalur @ 2024-09-15 7:51 ` Ihor Radchenko 0 siblings, 0 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-09-15 7:51 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> So, I thought that you are probably the best person to design such >> things :) > > Unfortunately I don't think I'm good at designing flexible async APIs, > at least in Emacs. (The LaTeX preview system is being tuned for > speed/lag-free performance, not flexibility.) Nobody that I am aware of is good at it. You, at least, have an experience designing and using it :) -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 6:06 ` Ihor Radchenko 2024-09-09 6:30 ` Karthik Chikmagalur @ 2024-09-09 21:45 ` Karthik Chikmagalur 2024-09-10 16:58 ` Ihor Radchenko 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-09 21:45 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >> ... >> Do you mean something like this? >> >> (while (re-search-forward org-link-any-re end t) >> ;; Make overlay ov here >> ;; Find path, link and preview-func here >> >> (push (list ov preview-func path link) previews-remaining)) >> >> (dolist (preview-data-chunk (seq-partition previews-remaining 6)) >> (run-with-idle-timer >> ... >> Where the chunk size (6) and the idle time (0.10 seconds) will be >> customizable. > > Yes. Another problem: this top level chunk no longer works (when interactive? (let ((new (org-link-preview--get-overlays beg end))) (message (if new (format "[%s] %d images displayed inline %s" scope (length new) (if include-linked "(including images with description)" "")) (format "[%s] No images to display inline" scope))))) since the previews are place asynchronously. It always reports "no images to display inline". Should I remove this messaging, or find a way to run this from the final async chunk? The code gets a little convoluted when I do the latter. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-09 21:45 ` Karthik Chikmagalur @ 2024-09-10 16:58 ` Ihor Radchenko 2024-09-10 17:38 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-09-10 16:58 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > Another problem: this top level chunk no longer works > > (when interactive? > (let ((new (org-link-preview--get-overlays beg end))) > (message > (if new > (format "[%s] %d images displayed inline %s" > scope (length new) > (if include-linked "(including images with description)" > "")) > (format "[%s] No images to display inline" scope))))) > > since the previews are place asynchronously. It always reports "no > images to display inline". Should I remove this messaging, or find a > way to run this from the final async chunk? The code gets a little > convoluted when I do the latter. I am a bit confused. Isn't `org-link-preview--get-overlays' counting overlays? And the overlays should be created before async preview is complete, right? Or do I miss something? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-10 16:58 ` Ihor Radchenko @ 2024-09-10 17:38 ` Karthik Chikmagalur 2024-09-10 18:34 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-10 17:38 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >> Another problem: this top level chunk no longer works >> >> [...] >> >> since the previews are place asynchronously. It always reports "no >> images to display inline". Should I remove this messaging, or find a >> way to run this from the final async chunk? The code gets a little >> convoluted when I do the latter. > > I am a bit confused. Isn't `org-link-preview--get-overlays' counting > overlays? And the overlays should be created before async preview is > complete, right? Or do I miss something? Currently I only add the overlay to `org-link-preview-overlays' if/when the preview is successful. `org-link-preview--get-overlays' counts overlays in this buffer-local list. If I add overlays to `org-link-preview-overlays' when they are created, 1. All previews that (eventually, asynchronously) fail will be reported as successful by the message. 2. `org-link-preview-overlays' will be populated with overlays for failed previews, leading to inconsistent behavior when doing other things, like toggling a link preview at point. These inconsistencies might still be worth it since the code will be much more confusing and harder to maintain if everything (including user messaging) is done via callbacks. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-10 17:38 ` Karthik Chikmagalur @ 2024-09-10 18:34 ` Ihor Radchenko 0 siblings, 0 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-09-10 18:34 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >>> since the previews are place asynchronously. It always reports "no >>> images to display inline". Should I remove this messaging, or find a >>> way to run this from the final async chunk? The code gets a little >>> convoluted when I do the latter. >> >> I am a bit confused. Isn't `org-link-preview--get-overlays' counting >> overlays? And the overlays should be created before async preview is >> complete, right? Or do I miss something? > > Currently I only add the overlay to `org-link-preview-overlays' if/when > the preview is successful. `org-link-preview--get-overlays' counts > overlays in this buffer-local list. > ... Ok. The idea behind that message is mostly to make user aware if there are any/none images to be actually displayed. It does not have to count actual overlays. Instead, we may display something like "X images _will_ be displayed..." -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-08 7:43 ` Ihor Radchenko 2024-09-09 3:21 ` Karthik Chikmagalur @ 2024-09-10 19:43 ` Karthik Chikmagalur 2024-09-15 8:12 ` Ihor Radchenko 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-10 19:43 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 4473 bytes --] Latest iteration of patch attached. >>>> +BEG and END define the considered part. They default to the >>>> +buffer boundaries with possible narrowing." >>>> + (interactive "P") >>>> + (when (display-graphic-p) >>> >>> Do we need it here? You check graphics both here and later in the >>> preview function. We may probably drop the check herein. >> >> I moved the `refresh' clause out of the display-graphic-p check, but I >> think it's needed. Otherwise this line will run once per previewed >> image instead of once per call to `org-link-preview': >> >> (when (fboundp 'clear-image-cache) (clear-image-cache)) >> >> clearing newly placed previews (within the same run) from the cache. > > Sorry, I was not clear. I only referred to `display-graphic-p' check. I > did not mean `clear-image-cache'. Removed the display-graphic-p check. >> That said, I have a question about `clear-image-cache' here. Why does [...] >> LaTeX previews. Images in unrelated buffers/major-modes are also >> affected. > > Here is the original code: > > (when refresh > (org-remove-inline-images beg end) > (when (fboundp 'clear-image-cache) (clear-image-cache))) > > Image cache is cleared _only_ with REFRESH argument. > I think that makes sense, right? Yes. But `org-link-preview-region' is always called with the REFRESH argument set to `t' though. >>> I am wondering why you left these functions in org.el. Why not moving >>> them here? >> >> They seemed out of place in ol.el, so my plan was the following: >> 1. This patch: Implement org-link-preview. >> 2. Another patch: Move image creation/manipulation/geometry functions >> from org into an org-image (or org-image-utils) library. Require it in >> ol. >> >> However, I can move them wherever you want, let me know. > > I'd rather see them moved. > You can later send another patch moving them into the new utils library, > if you want. Okay, I moved them to ol.el. They seem out of place here, but I can send another patch after merging this one to create org-image.el. >> I don't know how create-image can fail. Suppose a .jpg file exists but >> is corrupted or does not have image/jpeg data. Then create-image >> returns nil, as does `org--create-inline-image'. This check guards >> against trying to preview an "image" that's nil. > > Let me elaborate. > > In `org-link-preview-region', you set org-image-overlay property: > > + (overlay-put ov 'org-image-overlay t) > + (overlay-put ov 'modification-hooks > + (list 'org-link-preview--remove-overlay)) > + ;; call preview function for link type > + (if (funcall preview-func ov path link) > + (when (overlay-buffer ov) > > Then, you do it yet again in `org-link-preview-file', when the property > is already there - this part is redundant. Fixed. >>>> + (when (boundp 'image-map) >>> >>> What if not bound? Why not simply (require 'image)? >> >> Just unmodified old code. I've require'd image in ol now, let me know >> if I should do it differently. > > Let's not require it on top level. Maybe better do it within the preview > function. Moved (require 'image) to inside the `org-link-preview-file'. I know it doesn't affect performance in any reasonable way, but I'm usually hesitant to do this in functions that run inside loops. > I mostly meant calling preview-func asynchronously, while idle, spaced > out, spending not longer than a fraction of second to call several > preview-funcs. > Spacing might then be controlled by the users. > > We might go further, and let the preview functions return a > process. Then, we may explicitly control running sentinels just for that > process via `accept-process-output'. But I am not sure if we need to go > that far. Following our other discussion in this thread, I've now implemented batched previews via idle-timers, controlled by `org-link-preview-delay' and `org-link-preview-batch-size'. The default values of these parameters needs to be tuned. I tested a few combinations but couldn't perceive a difference in lag or in the profiler. The only unhandled case is when an asynchronous preview (in the sense that preview-func is itself asynchronous) fails. It is then the responsibility of preview-func's callback to clean up the overlay. This can be done by calling org-link-preview-clear on the bounds of the link. I think this is sufficient for now. [-- 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: 69629 bytes --] From 135735097a4ab9d53f3a7ffef79b34c604cb64b9 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-batch-size, org-link-preview-delay, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-previeworg-link-preview-region, org-link-preview-clear, org-link-preview-file, org-display-remote-inline-images, org-image-align, org--create-inline-image, org-display-inline-image--width, org-image--align): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. This link parameter is a function called asynchronously to place previes. File links and attachments are previewed using inline image previews as before. Move image handling utilities from lisp/org.el to lisp/ol.el. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images): Use `org-link-preview'. * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el (org-attach-preview-file): Add new `:preview' link parameter for links of type "attachment", set to the new function `org-attach-preview-file'. --- lisp/ol.el | 565 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 189 ++++++++++++ lisp/org-cycle.el | 10 +- lisp/org-keys.el | 4 +- lisp/org-plot.el | 2 +- lisp/org.el | 534 +------------------------------- testing/lisp/test-org-fold.el | 4 +- 8 files changed, 774 insertions(+), 545 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..dad84216b 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,11 @@ (declare-function org-src-source-buffer "org-src" ()) (declare-function org-src-source-type "org-src" ()) (declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function outline-next-heading "outline" ()) +(declare-function image-flush "image" (spec &optional frame)) +(declare-function org-entry-end-position "org" ()) +(declare-function org-element-contents-begin "org-element" (node)) +(declare-function org-element-contents-end "org-element" (node)) +(declare-function org-property-or-variable-value "org" (var &optional inherit)) \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,211 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, preview links with description as +well. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] Displaying %d images inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if-let ((ov (cdr (get-char-property-and-overlay + (point) 'org-image-overlay)))) + ;; clear link preview at point + (funcall toggle-previews + (overlay-start ov) (overlay-end ov) + "preview at point" 'remove) + ;; Clear link previews in entry + (funcall toggle-previews + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))) + (org-with-limited-levels (org-entry-end-position)) + "current section" 'remove))) + ;; C-u C-u or C-11 argument :: display images in the whole buffer. + ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove)) + ;; Argument nil or 1, no region selected :: display images in + ;; current section or image link at point. + ((and (member arg '(nil 1)) (null beg) (null end)) + (let ((context (org-element-context))) + ;; toggle display of inline image link at point. + (if (org-element-type-p context 'link) + (let* ((ov (cdr-safe (get-char-property-and-overlay + (point) 'org-image-overlay))) + (remove? (and ov (memq ov org-link-preview-overlays) + 'remove))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "image at point" remove?)) + (let ((beg (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point))))) + (end (org-with-limited-levels (org-entry-end-position)))) + (funcall toggle-previews beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-previews beg end "region"))))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t) + (preview-queue (list nil)) + (preview-queue-size 1)) + (while (re-search-forward org-link-any-re end t) + (when-let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + (overlay-put ov 'org-image-overlay t) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (push ov org-link-preview-overlays) + ;; Collect previews to run asynchronously, in batches + (if (>= (length (car-safe preview-queue)) org-link-preview-batch-size) + (progn (cl-incf preview-queue-size) + (push (list (list preview-func ov path link)) preview-queue)) + (push (list preview-func ov path link) (car preview-queue)))))) + ;; Run preview asynchronously in batches: + ;; preview-queue is a list of preview-batch, which is a list of preview-spec + (when (car preview-queue) + (dolist (preview-batch (nreverse preview-queue)) + (run-with-idle-timer + org-link-preview-delay nil + (lambda (previews) + ;; (message "queue: %d" preview-queue-size) + (cl-decf preview-queue-size) + (dolist (preview-spec (nreverse previews)) ;spec is (preview-func overlay path link) + (when-let* ((ov (cadr preview-spec)) + (buf (overlay-buffer ov))) + (with-current-buffer buf + (unless (apply preview-spec) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays))))))) + preview-batch)))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2117,48 @@ (defun org-link--open-elisp (path _) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) ;;;; "file" link type -(org-link-set-parameters "file" :complete #'org-link-complete-file) +(org-link-set-parameters "file" + :complete #'org-link-complete-file + :preview #'org-link-preview-file) + +(defun org-link-preview-file (ov path link) + "Display image file PATH in overlay OV for LINK. + +LINK is the Org element being previewed. + +Equip each image with the keymap `image-map'. + +This is intended to be used as the `:preview' link property of +file links, see `org-link-parameters'." + (if (not (display-graphic-p)) + (prog1 nil + (message "Your Emacs does not support displaying images!")) + (require 'image) + (when-let* ((file-full (expand-file-name path)) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (when image ; Add image to overlay + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'keymap image-map) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + t))))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index 7a03d170e..3c4e1bfb6 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -797,9 +797,18 @@ (defun org-attach-follow (file arg) See `org-open-file' for details about ARG." (org-link-open-as-file (org-attach-expand file) arg)) +(defun org-attach-preview-file (ov path link) + "Preview attachment with PATH in overlay OV. + +LINK is the Org link element being previewed." + (org-with-point-at (org-element-begin link) + (org-link-preview-file + ov (org-attach-expand path) link))) + (org-link-set-parameters "attachment" :follow #'org-attach-follow - :complete #'org-attach-complete-link) + :complete #'org-attach-complete-link + :preview #'org-attach-preview-file) (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." diff --git a/lisp/org-compat.el b/lisp/org-compat.el index d843216f3..242b46a86 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -783,6 +783,195 @@ (defun org-add-link-type (type &optional follow export) (make-obsolete 'org-add-link-type "use `org-link-set-parameters' instead." "9.0") +(declare-function org-link-preview--remove-overlay "ol") +(declare-function org-link-preview--get-overlays "ol") +(declare-function org-link-preview-clear "ol") +(declare-function org-link-preview--remove-overlay "ol") + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) +(declare-function org-attach-expand "org-attach") +(declare-function org-display-inline-image--width "org") +(declare-function org-image--align "org") +(declare-function org--create-inline-image "org") + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-display-inline-images include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + +(make-obsolete 'org-redisplay-inline-images + 'org-link-preview "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-redisplay-inline-images () + "Assure display of inline images and refresh them." + (interactive) + (org-toggle-inline-images) + (unless org-link-preview-overlays + (org-toggle-inline-images))) + ;;;; Functions unused in Org core. (defun org-table-recognize-table.el () "If there is a table.el table nearby, recognize it and move into it." diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..1a1c916bd 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -817,19 +817,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 1daedaae8..77cbe5c0f 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,7 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,7 +618,7 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index d5c1dcb35..e5cc9308f 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -5079,7 +5079,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -15583,26 +15583,6 @@ (defcustom org-image-actual-width t (list :tag "Use #+ATTR* or a number of pixels" (integer)) (const :tag "Use #+ATTR* or don't resize" nil))) -(defcustom org-image-max-width 'fill-column - "When non-nil, limit the displayed image width. -This setting only takes effect when `org-image-actual-width' is set to -t or when #+ATTR* is set to t. - -Possible values: -- `fill-column' :: limit width to `fill-column' -- `window' :: limit width to window width -- integer :: limit width to number in pixels -- float :: limit width to that fraction of window width -- nil :: do not limit image width" - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Do not limit image width" nil) - (const :tag "Limit to `fill-column'" fill-column) - (const :tag "Limit to window width" window) - (integer :tag "Limit to a number of pixels") - (float :tag "Limit to a fraction of window width"))) - (defcustom org-agenda-inhibit-startup nil "Inhibit startup when preparing agenda buffers. When this variable is t, the initialization of the Org agenda @@ -16649,518 +16629,6 @@ (defun org-normalize-color (value) (format "%g" (/ value 65535.0))) \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 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-10 19:43 ` Karthik Chikmagalur @ 2024-09-15 8:12 ` Ihor Radchenko 2024-09-15 20:50 ` Karthik Chikmagalur 2024-09-15 21:57 ` [PATCH v4] " Karthik Chikmagalur 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-09-15 8:12 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> Image cache is cleared _only_ with REFRESH argument. >> I think that makes sense, right? > > Yes. But `org-link-preview-region' is always called with the REFRESH > argument set to `t' though. Sure. What's a problem with that? In theory, we might not need to clear image cache as long as we call `image-flush' in `org-link-preview-clear'. >>> Just unmodified old code. I've require'd image in ol now, let me know >>> if I should do it differently. >> >> Let's not require it on top level. Maybe better do it within the preview >> function. > > Moved (require 'image) to inside the `org-link-preview-file'. I know it > doesn't affect performance in any reasonable way, but I'm usually > hesitant to do this in functions that run inside loops. There are pros and cons. Ideally, we should have these previews in a separate library, so that loading Org mode does not need to require so much staff. Loading time is not great already. 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. > ... > + ;; Run preview asynchronously in batches: > + ;; preview-queue is a list of preview-batch, which is a list of preview-spec > + (when (car preview-queue) > + (dolist (preview-batch (nreverse preview-queue)) > + (run-with-idle-timer > + org-link-preview-delay nil 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) ... > + (lambda (previews) > + ;; (message "queue: %d" preview-queue-size) > + (cl-decf preview-queue-size) > + (dolist (preview-spec (nreverse previews)) ;spec is (preview-func overlay path link) > + (when-let* ((ov (cadr preview-spec)) > + (buf (overlay-buffer ov))) 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. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v3] Inline image display as part of a new org-link-preview system 2024-09-15 8:12 ` Ihor Radchenko @ 2024-09-15 20:50 ` Karthik Chikmagalur 2024-09-15 21:57 ` [PATCH v4] " Karthik Chikmagalur 1 sibling, 0 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-15 20:50 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >>> Image cache is cleared _only_ with REFRESH argument. >>> I think that makes sense, right? >> >> Yes. But `org-link-preview-region' is always called with the REFRESH >> argument set to `t' though. > > Sure. What's a problem with that? Why flush Emacs' entire image cache to preview images in one Org buffer? This has implications for other Emacs buffers with lots of images, like Org buffers with LaTeX previews. I'm not sure about how much it matters though. > In theory, we might not need to clear image cache as long as we call > `image-flush' in `org-link-preview-clear'. image-flush is called unconditionally in `org-link-preview-file', so it should be fine to not set REFRESH unconditionally. I'll keep the REFRESH argument around but remove the Emacs-wide (clear-image-cache). >> Moved (require 'image) to inside the `org-link-preview-file'. I know it >> doesn't affect performance in any reasonable way, but I'm usually >> hesitant to do this in functions that run inside loops. > > There are pros and cons. > Ideally, we should have these previews in a separate library, so that > loading Org mode does not need to require so much staff. Loading time is > not great already. > > 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. Okay. >> ... >> + ;; Run preview asynchronously in batches: >> + ;; preview-queue is a list of preview-batch, which is a list of preview-spec >> + (when (car preview-queue) >> + (dolist (preview-batch (nreverse preview-queue)) >> + (run-with-idle-timer >> + org-link-preview-delay nil > > 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) ... You're right, I'll redo this. Scheduling many idle timers at once is tricky, the manual even warns against making idle timers setting up the next one with the same idle time: --8<---------------cut here---------------start------------->8--- Similarly, do not write an idle timer function that sets up another idle timer (including the same idle timer) with SECS argument less than or equal to the current idleness time. Such a timer will run almost immediately, and continue running again and again, instead of waiting for the next time Emacs becomes idle. The correct approach is to reschedule with an appropriate increment of the current value of the idleness time, as described below. --8<---------------cut here---------------end--------------->8--- So I'll have to do it carefully. >> + (lambda (previews) >> + ;; (message "queue: %d" preview-queue-size) >> + (cl-decf preview-queue-size) >> + (dolist (preview-spec (nreverse previews)) ;spec is (preview-func overlay path link) >> + (when-let* ((ov (cadr preview-spec)) >> + (buf (overlay-buffer ov))) > > 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. This is handled by the (when-let* ((buf (overlay-buffer ov)))) part. (overlay-buffer ov) is nil if the overlay is gone, or if the buffer is gone. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v4] Inline image display as part of a new org-link-preview system 2024-09-15 8:12 ` Ihor Radchenko 2024-09-15 20:50 ` Karthik Chikmagalur @ 2024-09-15 21:57 ` Karthik Chikmagalur 2024-09-17 18:16 ` Ihor Radchenko 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-15 21:57 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- 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 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v4] Inline image display as part of a new org-link-preview system 2024-09-15 21:57 ` [PATCH v4] " Karthik Chikmagalur @ 2024-09-17 18:16 ` Ihor Radchenko 2024-09-18 1:44 ` [PATCH v5] " Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-09-17 18:16 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> 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. There are still edge cases. Consider a buffer with large number of images (I just tested with my notes.org that has 1000+ images): 1. I run preview on the whole buffer 2. M-x list-timers shows the timer, and it is ugly: * 10.0s - #f(compiled-function () #<bytecode 0x6278e9619bf2d9a> [(#0) ((((org-link-preview-file #<overlay from 3204266 to 3204357 in notes.org> "/home/yantar92/.data/a3/4fcf3a-28c5-42cb-b8cc-eb98dcdb5338/stress-strain-DA1P5.png" (link (:standard... [the line spans thousands of characters further - rendering timer list takes a good 10+ sec] - So, no closures please. Use normal named functions - It may also be a good idea to manage preview queue globally, so that subsequent preview requests can be merged automatically (see below) 3. It will take a long time to preview all those images, so I get impatient and cancel the previews. The timer still remains... 4. I request previews in the whole buffer again. Now, there are two timers. This may be repeated, and the user may end up with many useless idle timers - each of them will take a long time to end, because "preview batches" do not care whether we actually try to preview images or skip them because the underlying overlays were "canceled" by another command - I think that the timers should not group images in batches beforehand, but simply consume images from a single queue until a given number of images are actually submitted for preview generation (overlays are still present). And please fix the compiler warnings. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v5] Inline image display as part of a new org-link-preview system 2024-09-17 18:16 ` Ihor Radchenko @ 2024-09-18 1:44 ` Karthik Chikmagalur 2024-09-21 10:11 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-18 1:44 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 3680 bytes --] Next version of patch attached. > 1. I run preview on the whole buffer > 2. M-x list-timers shows the timer, and it is ugly: > * 10.0s - #f(compiled-function () #<bytecode 0x6278e9619bf2d9a> > > [...] > > 10+ sec] > - So, no closures please. Use normal named functions Done. Only one entry per buffer, `org-link-preview--timer', shows up in `list-timers' now. > - It may also be a good idea to manage preview queue globally, so > that subsequent preview requests can be merged automatically (see > below) > 3. It will take a long time to preview all those images, so I get > impatient and cancel the previews. The timer still remains... > 4. I request previews in the whole buffer again. Now, there are two > timers. This may be repeated, and the user may end up with many > useless idle timers - each of them will take a long time to end, > because "preview batches" do not care whether we actually try to > preview images or skip them because the underlying overlays were > "canceled" by another command - I think that the timers should not > group images in batches beforehand, but simply consume images from a > single queue until a given number of images are actually submitted > for preview generation (overlays are still present). New design: 1. Only one preview queue per buffer, stored in the buffer-local variable `org-link-preview--queue'. This queue is FIFO. 2. Only one (possibly periodically refreshed) preview timer per buffer, stored in the buffer-local variable `org-link-preview--timer'. The function `org-link-preview--process-queue' runs through this queue `org-link-preview-batch-size' items at a time, and sets `org-link-preview--timer'. No explicit batches are created. If you think it's a good idea, I can add pending previews to the queue in a LIFO fashion instead, so that if you call `org-link-preview' in two different sections before the first one is done previewing, the later one is processed first. If `org-link-preview-clear' is run in a region where there are previews pending, these region-specific previews are removed from the queue. Note that this new code in `org-link-preview-clear' is not actually necessary: (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))) This is because the overlay placed over the links for which previews are pending are removed anyway. So when the `--process-queue' function gets to that preview-pending link, it skips it since the overlay is gone. Also, the complexity of the above code is quadratic, so I can remove it if you think it's not required. I included it to be thorough. Also, I think the variable `org-link-preview-overlays' is actually unnecessary. We can just use an overlay property for link-preview overlays and use `overlays-in' and `org-find-overlays', and reduce the amount of global state a little bit this way. Let me know if I should remove it. > And please fix the compiler warnings. As of this patch, I don't see any flymake errors with the `elisp-flymake-byte-compile' backend. Is this not sufficient? Byte-compiling in my active Emacs session doesn't work as the state is "polluted". I don't know of a convenient way to byte-compile code in a sandbox. Please note: I need some help with code style, for example `org-link--preview-queue' vs `org-link-preview--queue', etc. Let's postpone this particular discussion until after the design is final? 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: 70487 bytes --] From e35fdf594f34ca5b433013d0e7cfedc71b5321e7 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--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): 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 | 594 +++++++++++++++++++++++++++++++++- 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, 803 insertions(+), 545 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..b0d7294fe 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,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +993,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 +1906,224 @@ (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)) + ;; 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) + (push (list preview-func ov path link) preview-queue)))) + ;; Collect previews in buffer-local preview queue + (setq org-link-preview--queue + (nconc org-link-preview--queue (nreverse 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." + (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 + (unless (apply spec) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays + (delq ov org-link-preview-overlays))) + else do (cl-incf count) end + finally do + (setq org-link-preview--timer + (and org-link-preview--queue + (run-with-idle-timer + (time-add (or (current-idle-time) 0) + org-link-preview-delay) + nil #'org-link-preview--process-queue org-buffer)))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + ;; Remove pending preview tasks between BEG and END + (when-let ((spec (cl-find ov org-link-preview--queue + :key #'cadr))) + (setq org-link-preview--queue (delq spec org-link-preview--queue))) + ;; Remove placed overlays between BEG and END + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2146,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 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v5] Inline image display as part of a new org-link-preview system 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-09-21 10:11 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > If you think it's a good idea, I can add pending previews to the queue > in a LIFO fashion instead, so that if you call `org-link-preview' in two > different sections before the first one is done previewing, the later > one is processed first. Yes, it would make sense. > If `org-link-preview-clear' is run in a region where there are previews > pending, these region-specific previews are removed from the queue. I tried the following 1. Requests 1000 previews in a buffer 2. Move point to section, request previews on a specific image (not yet displayed) 3. Observe "Preview removed" message, which is confusing as no preview is visible at that point. We need to do something about the logic determining whether any previews are displayed or not. The current check that preview overlays are present in requested region is no longer accurate as they may still be queued and not yet visible to the user, creating impression that there are no previews yet. > Note that this new code in `org-link-preview-clear' is not actually > necessary: > > (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))) > > This is because the overlay placed over the links for which previews are > pending are removed anyway. So when the `--process-queue' function gets > to that preview-pending link, it skips it since the overlay is gone. > Also, the complexity of the above code is quadratic, so I can remove it > if you think it's not required. I included it to be thorough. What you say make sense, but see the above - we make certain (now incorrect) assumptions about `org-link-preview-overlays'. The relevant parts of the code need to be re-checked. > Also, I think the variable `org-link-preview-overlays' is actually > unnecessary. We can just use an overlay property for link-preview > overlays and use `overlays-in' and `org-find-overlays', and reduce the > amount of global state a little bit this way. Let me know if I should > remove it. That might be an option. But we cannot remove it. We can only deprecate it as of now and only remove in following releases. >> And please fix the compiler warnings. > > As of this patch, I don't see any flymake errors with the > `elisp-flymake-byte-compile' backend. Is this not sufficient? > Byte-compiling in my active Emacs session doesn't work as the state is > "polluted". I don't know of a convenient way to byte-compile code in a > sandbox. Just run make in Org git repo. > Please note: I need some help with code style, for example > `org-link--preview-queue' vs `org-link-preview--queue', etc. Let's > postpone this particular discussion until after the design is final? I like org-link-preview-- more, but the most important part is to make things consistent across the function/variable names. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v6] Inline image display as part of a new org-link-preview system 2024-09-21 10:11 ` Ihor Radchenko @ 2024-09-22 22:00 ` Karthik Chikmagalur 2024-10-03 17:03 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-09-22 22:00 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 3455 bytes --] Latest version attached. >> If you think it's a good idea, I can add pending previews to the queue >> in a LIFO fashion instead, so that if you call `org-link-preview' in two >> different sections before the first one is done previewing, the later >> one is processed first. > > Yes, it would make sense. Done. > I tried the following > > 1. Requests 1000 previews in a buffer > 2. Move point to section, request previews on a specific image (not yet displayed) > 3. Observe "Preview removed" message, which is confusing as no preview > is visible at that point. > > We need to do something about the logic determining whether any previews > are displayed or not. The current check that preview overlays are > present in requested region is no longer accurate as they may still be > queued and not yet visible to the user, creating impression that there > are no previews yet. Please try it now, it should be fixed. I now set the `org-image-overlay' when the preview is done/successful, instead of when it is queued. >> Note that this new code in `org-link-preview-clear' is not actually >> necessary: >> >> (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))) >> >> This is because the overlay placed over the links for which previews are >> pending are removed anyway. So when the `--process-queue' function gets >> to that preview-pending link, it skips it since the overlay is gone. >> Also, the complexity of the above code is quadratic, so I can remove it >> if you think it's not required. I included it to be thorough. > > What you say make sense, but see the above - we make certain (now > incorrect) assumptions about `org-link-preview-overlays'. The relevant > parts of the code need to be re-checked. This quadratic check is still present. >> Also, I think the variable `org-link-preview-overlays' is actually >> unnecessary. We can just use an overlay property for link-preview >> overlays and use `overlays-in' and `org-find-overlays', and reduce the >> amount of global state a little bit this way. Let me know if I should >> remove it. > > That might be an option. But we cannot remove it. We can only deprecate > it as of now and only remove in following releases. It turns out `org-link-preview-overlays' is now required again, since the overlay property `org-image-overlay' is only placed after the preview is done, so we can't use it to collect overlays anymore. >>> And please fix the compiler warnings. >> >> As of this patch, I don't see any flymake errors with the >> `elisp-flymake-byte-compile' backend. Is this not sufficient? >> Byte-compiling in my active Emacs session doesn't work as the state is >> "polluted". I don't know of a convenient way to byte-compile code in a >> sandbox. > > Just run make in Org git repo. Thanks. >> Please note: I need some help with code style, for example >> `org-link--preview-queue' vs `org-link-preview--queue', etc. Let's >> postpone this particular discussion until after the design is final? > > I like org-link-preview-- more, but the most important part is to make > things consistent across the function/variable names. I will check this in the final version, along with the manual/NEWS updates. 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: 70430 bytes --] From f20f146e63b03155dc6bf64828e2b1ba11aebf6d 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--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): 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 | 593 +++++++++++++++++++++++++++++++++- 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, 802 insertions(+), 545 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..dc66cb8f6 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,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +993,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1905,224 @@ (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)) + ;; 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 '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." + (with-current-buffer org-buffer + (cl-loop + for spec in org-link-preview--queue + for ov = (cadr spec) ;SPEC is (preview-func ov path link) + for count from org-link-preview-batch-size above 0 + do (pop org-link-preview--queue) + if (overlay-buffer ov) do + (if (apply spec) + (overlay-put ov 'org-image-overlay t) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays + (delq ov org-link-preview-overlays))) + else do (cl-incf count) end + finally do + (setq org-link-preview--timer + (and org-link-preview--queue + (run-with-idle-timer + (time-add (or (current-idle-time) 0) + org-link-preview-delay) + nil #'org-link-preview--process-queue org-buffer)))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + ;; Remove pending preview tasks between BEG and END + (when-let ((spec (cl-find ov org-link-preview--queue + :key #'cadr))) + (setq org-link-preview--queue (delq spec org-link-preview--queue))) + ;; Remove placed overlays between BEG and END + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2145,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 df58b47be..d8c9b59a8 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 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v6] Inline image display as part of a new org-link-preview system 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-03 17:03 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> We need to do something about the logic determining whether any previews >> are displayed or not. The current check that preview overlays are >> present in requested region is no longer accurate as they may still be >> queued and not yet visible to the user, creating impression that there >> are no previews yet. > > Please try it now, it should be fixed. I now set the > `org-image-overlay' when the preview is done/successful, instead of when > it is queued. I tried and things seems to be working fine now. >>> As of this patch, I don't see any flymake errors with the >>> `elisp-flymake-byte-compile' backend. Is this not sufficient? >>> Byte-compiling in my active Emacs session doesn't work as the state is >>> "polluted". I don't know of a convenient way to byte-compile code in a >>> sandbox. >> >> Just run make in Org git repo. > > Thanks. And the warnings are still present :) >>> Please note: I need some help with code style, for example >>> `org-link--preview-queue' vs `org-link-preview--queue', etc. Let's >>> postpone this particular discussion until after the design is final? >> >> I like org-link-preview-- more, but the most important part is to make >> things consistent across the function/variable names. > > I will check this in the final version, along with the manual/NEWS > updates. I have a few minor comments. > + (org-with-point-at (or beg (point-min)) > + (let ((case-fold-search t) > + (preview-queue)) Nitpick: Just preview-queue without parenthesis will be the same. > + ;; 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)) Do we need `save-match-data' here? Nothing inside seems to be making use of it. > + (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)))))) What if `org-buffer' gets killed when the preview timer triggers? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v7] Inline image display as part of a new org-link-preview system 2024-10-03 17:03 ` Ihor Radchenko @ 2024-10-08 0:07 ` Karthik Chikmagalur 2024-10-10 17:15 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-10-08 0:07 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 2937 bytes --] Latest patch attached. >>>> As of this patch, I don't see any flymake errors with the >>>> `elisp-flymake-byte-compile' backend. Is this not sufficient? >>>> Byte-compiling in my active Emacs session doesn't work as the state is >>>> "polluted". I don't know of a convenient way to byte-compile code in a >>>> sandbox. >>> >>> Just run make in Org git repo. >> >> Thanks. > > And the warnings are still present :) (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) Do we need this command any more? I don't understand what it does differently from `org-link-preview'. Calling `org-link-preview' with the right scope argument (e.g. C-u C-u for buffer-wide) should automatically refresh/redisplay images. I also don't know what to do about this warning: (define-obsolete-variable-alias 'org-plain-link-re 'org-link-plain-re "9.3") org-compat.el:1184:4: Warning: Alias for ‘org-link-plain-re’ should be declared before its referent I think this warning is unrelated to this patch? I fixed the other warnings. >>>> Please note: I need some help with code style, for example >>>> `org-link--preview-queue' vs `org-link-preview--queue', etc. Let's >>>> postpone this particular discussion until after the design is final? >>> >>> I like org-link-preview-- more, but the most important part is to make >>> things consistent across the function/variable names. >> >> I will check this in the final version, along with the manual/NEWS >> updates. > > I have a few minor comments. > >> + (org-with-point-at (or beg (point-min)) >> + (let ((case-fold-search t) >> + (preview-queue)) > > Nitpick: Just preview-queue without parenthesis will be the same. I changed it. I've switched to using (foo) instead of foo in let blocks because `elisp-flymake-byte-compile' complains sometimes (not always). I don't know why this happens. > >> + ;; 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)) > > Do we need `save-match-data' here? Nothing inside seems to be making use > of it. I removed it. > >> + (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)))))) > > What if `org-buffer' gets killed when the preview timer triggers? Handled via a `buffer-live-p' check. I don't bother resetting the buffer-local variables `org-link-preview--queue' and `org-link-preview--timer' since I'm assuming the local bindings go away when the buffer is killed. 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: 70468 bytes --] From 88e6c71b9e5822d25fdd3c69e3df6c006a8dbd36 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--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): 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 | 593 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 188 +++++++++++ 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, 801 insertions(+), 545 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..2ef28a2ed 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,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +993,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1905,224 @@ (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) + ;; Collect links to preview + (while (re-search-forward org-link-any-re end t) + (when-let* + ((link (org-element-lineage (org-element-context) 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (push ov org-link-preview-overlays) + (push (list preview-func ov path link) preview-queue)))) + ;; Collect previews in buffer-local LIFO preview queue + (setq org-link-preview--queue + (nconc (nreverse preview-queue) org-link-preview--queue)) + ;; Run preview possibly asynchronously + (when org-link-preview--queue + (org-link-preview--process-queue (current-buffer)))))) + +(defun org-link-preview--process-queue (org-buffer) + "Preview pending Org link previews in ORG-BUFFER. + +Previews are generated from the specs in +`org-link-preview--queue', which see." + (and (buffer-live-p org-buffer) + (with-current-buffer org-buffer + (cl-loop + for spec in org-link-preview--queue + for ov = (cadr spec) ;SPEC is (preview-func ov path link) + for count from org-link-preview-batch-size above 0 + do (pop org-link-preview--queue) + if (overlay-buffer ov) do + (if (apply spec) + (overlay-put ov 'org-image-overlay t) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays + (delq ov org-link-preview-overlays))) + else do (cl-incf count) end + finally do + (setq org-link-preview--timer + (and org-link-preview--queue + (run-with-idle-timer + (time-add (or (current-idle-time) 0) + org-link-preview-delay) + nil #'org-link-preview--process-queue org-buffer))))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + ;; Remove pending preview tasks between BEG and END + (when-let ((spec (cl-find ov org-link-preview--queue + :key #'cadr))) + (setq org-link-preview--queue (delq spec org-link-preview--queue))) + ;; Remove placed overlays between BEG and END + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2145,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..7e13a21e8 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -783,6 +783,194 @@ (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") +(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")))))) + +(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-link-preview-region nil t (point-min) (point-max))) + ;;;; 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 df58b47be..d8c9b59a8 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 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v7] Inline image display as part of a new org-link-preview system 2024-10-08 0:07 ` [PATCH v7] " Karthik Chikmagalur @ 2024-10-10 17:15 ` Ihor Radchenko 2024-10-10 21:23 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-10 17:15 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > Latest patch attached. Thanks! See my (minor) comments below. I think that it is the time to move ahead with updating the ORG-NEWS and the manual. > (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) > > Do we need this command any more? I don't understand what it does > differently from `org-link-preview'. Calling `org-link-preview' with > the right scope argument (e.g. C-u C-u for buffer-wide) should > automatically refresh/redisplay images. We can simply make it call org-link-preview with C-u C-u argument. > I also don't know what to do about this warning: > > (define-obsolete-variable-alias 'org-plain-link-re > 'org-link-plain-re "9.3") > > org-compat.el:1184:4: Warning: Alias for ‘org-link-plain-re’ should be declared before its referent > > I think this warning is unrelated to this patch? It is related. The warning is raised because you put (defvar org-link-plain-re) before the obsolete alias. You need to move things further down, after that obsolete definition. > +(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") > ... Good idea to provide arglist declaration as well. > +(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") same -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v7] Inline image display as part of a new org-link-preview system 2024-10-10 17:15 ` Ihor Radchenko @ 2024-10-10 21:23 ` Karthik Chikmagalur 2024-10-12 8:15 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-10-10 21:23 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode Will address the other issues. I had some questions about the manual. > I think that it is the time to move ahead with updating the ORG-NEWS and > the manual. Suppose a package author is aware that Org now supports previews for custom link types and wants to add a preview. What section of the manual would they expect to find this information? My current plan for the manual is the following: - (info "(org) Adding Hyperlink Types"): Include an example of defining a :preview function for a link type. - (info "(org) Images"): When replacing the description of org-toggle-inline-images with org-link-preview, mention that it can be used to preview all supported link types. Should I rename the node "(org) Images" to something else, like "(org) Images and supported links"? Should I create a new node instead under (info "(org) Markup for Rich Contents"), titled "Links"? Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v7] Inline image display as part of a new org-link-preview system 2024-10-10 21:23 ` Karthik Chikmagalur @ 2024-10-12 8:15 ` Ihor Radchenko 2024-10-28 6:13 ` [PATCH v8] " Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-12 8:15 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > Suppose a package author is aware that Org now supports previews for > custom link types and wants to add a preview. What section of the > manual would they expect to find this information? "Adding hyperlink types", I think. As you guessed. > My current plan for the manual is the following: > > - (info "(org) Adding Hyperlink Types"): Include an example of defining > a :preview function for a link type. > > - (info "(org) Images"): When replacing the description of > org-toggle-inline-images with org-link-preview, mention that it can be > used to preview all supported link types. Sounds good. > Should I rename the node "(org) Images" to something else, like "(org) > Images and supported links"? > Should I create a new node instead under (info "(org) Markup for Rich > Contents"), titled "Links"? I feel that it may be too obscure. I'd prefer an emphasis on images in the node titles - that's what new users can recognize better. Maybe rename "Images" to "Images and previews" and then put two subsections inside - one for images (the existing one) and one for more general link previews. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v8] Inline image display as part of a new org-link-preview system 2024-10-12 8:15 ` Ihor Radchenko @ 2024-10-28 6:13 ` Karthik Chikmagalur 2024-10-28 18:16 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-10-28 6:13 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 963 bytes --] I've attached two patches: 1. Patch 0001 is the code patch from before, with the warnings from make addressed. 2. Patch 0002 is the documentation patch, which adds to ORG-NEWS and the manual. There are two new sections -- one is in the appendix under "Hacking", and shows how to add a preview function for a link type (along with a code example). >> Should I rename the node "(org) Images" to something else, like "(org) >> Images and supported links"? > >> Should I create a new node instead under (info "(org) Markup for Rich >> Contents"), titled "Links"? > > I feel that it may be too obscure. I'd prefer an emphasis on images in > the node titles - that's what new users can recognize better. > > Maybe rename "Images" to "Images and previews" and then put two > subsections inside - one for images (the existing one) and one for more > general link previews. The other is the node that was previously "Images", and does what you suggest here. 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: 70889 bytes --] From 5989fff75d3ee8e0658a32bbd56bbd219d0de936 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH 1/2] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-batch-size, org-link-preview-delay, org-link-preview--timer, org-link-preview--queue, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-preview, org-link-preview-region, org-link-preview--process-queue, org-link-preview-clear, org-link-preview-file, org-display-remote-inline-images, org-image-align, org--create-inline-image, org-display-inline-image--width, org-image--align): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. This link parameter is a function called asynchronously to place previes. File links and attachments are previewed using inline image previews as before. Move image handling utilities from lisp/org.el to lisp/ol.el. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images): 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 | 599 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 185 +++++++++++ lisp/org-cycle.el | 10 +- lisp/org-keys.el | 7 +- lisp/org-plot.el | 2 +- lisp/org.el | 534 +----------------------------- testing/lisp/test-org-fold.el | 4 +- 8 files changed, 806 insertions(+), 546 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..5f9473e99 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,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +993,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1905,230 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, 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"))))) + +;;;###autoload +(defun org-link-preview-refresh () + "Assure display of link previews in buffer and refresh them." + (interactive) + (org-link-preview-region nil t (point-min) (point-max))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh (org-link-preview-clear beg end)) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t) + preview-queue) + ;; Collect links to preview + (while (re-search-forward org-link-any-re end t) + (when-let* + ((link (org-element-lineage (org-element-context) 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (push ov org-link-preview-overlays) + (push (list preview-func ov path link) preview-queue)))) + ;; Collect previews in buffer-local LIFO preview queue + (setq org-link-preview--queue + (nconc (nreverse preview-queue) org-link-preview--queue)) + ;; Run preview possibly asynchronously + (when org-link-preview--queue + (org-link-preview--process-queue (current-buffer)))))) + +(defun org-link-preview--process-queue (org-buffer) + "Preview pending Org link previews in ORG-BUFFER. + +Previews are generated from the specs in +`org-link-preview--queue', which see." + (and (buffer-live-p org-buffer) + (with-current-buffer org-buffer + (cl-loop + for spec in org-link-preview--queue + for ov = (cadr spec) ;SPEC is (preview-func ov path link) + for count from org-link-preview-batch-size above 0 + do (pop org-link-preview--queue) + if (overlay-buffer ov) do + (if (apply spec) + (overlay-put ov 'org-image-overlay t) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays + (delq ov org-link-preview-overlays))) + else do (cl-incf count) end + finally do + (setq org-link-preview--timer + (and org-link-preview--queue + (run-with-idle-timer + (time-add (or (current-idle-time) 0) + org-link-preview-delay) + nil #'org-link-preview--process-queue org-buffer))))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + ;; Remove pending preview tasks between BEG and END + (when-let ((spec (cl-find ov org-link-preview--queue + :key #'cadr))) + (setq org-link-preview--queue (delq spec org-link-preview--queue))) + ;; Remove placed overlays between BEG and END + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2151,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..885a5245d 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -1010,6 +1010,191 @@ (define-obsolete-function-alias 'org-open-link-from-string (define-obsolete-function-alias 'org-add-angle-brackets 'org-link-add-angle-brackets "9.3") +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-link-preview--get-overlays "ol" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-attach-expand "org-attach" (file)) +(declare-function org-display-inline-image--width "org" (link)) +(declare-function org-image--align "org" (link)) +(declare-function org--create-inline-image "org" (file width)) + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-function-alias 'org-redisplay-inline-images + 'org-link-preview-refresh "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +(declare-function org-link-preview-region "ol") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-link-preview-region include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + + ;; The function was made obsolete by commit 65399674d5 of 2013-02-22. ;; This make-obsolete call was added 2016-09-01. (make-obsolete 'org-capture-import-remember-templates diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..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..d2e4a6993 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,8 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) +(declare-function org-link-preview-refresh "ol" ()) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,8 +619,8 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) -(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) +(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-link-preview-refresh) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index df58b47be..d8c9b59a8 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.46.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --] [-- Type: text/x-patch, Size: 17825 bytes --] From 6715b431e6e4f4d419be6f180a6ac2937fa2bd46 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Sun, 27 Oct 2024 20:22:59 -0700 Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types * etc/ORG-NEWS: Mention new commands and options for link preview. * doc/org-manual.org: Add sections on - previewing external links (Images and link previews), and - adding the link preview feature to other link types (Adding Hyperlink preview). --- doc/org-manual.org | 276 ++++++++++++++++++++++++++++++--------------- etc/ORG-NEWS | 74 ++++++++++-- 2 files changed, 250 insertions(+), 100 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 94fabde3b..84e1619a0 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11778,7 +11778,67 @@ ** Literal Examples the end of the current line. Then the label is stored as a link =(label)=, for retrieval with {{{kbd(C-c C-l)}}}. -** Images +** Images and link previews + +Org mode can preview hyperlinks in the buffer as images or with other +decorations. + +*** External link previews +:PROPERTIES: +:DESCRIPTION: Preview links in the buffer. +:END: + +Org mode can preview [[* External links][External links]] as inline +images or with other decorations. This requires support for the +corresponding link types to be available[fn:: To add support for or to +modify how links of a certain type are previewed, see .]. By default +Org mode includes support for previewing file and attachment links for +image files (see [[* Images][Images]]). + +Supported link types can be previewed within the buffer with the +following commands: + +- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) :: + + #+kindex: C-c C-x C-v + #+findex: org-link-preview + Create inline previews for external links in the active region, the + link at point or in the current section. With a prefix argument, + clear link previews at point or in the current entry. With a double + prefix argument, preview all links in the buffer. With triple + prefix argument, hide previews for all links in the buffer. + + By default, only image links without a description are displayed. + You can force displaying previews for all supported links using + numeric argument to toggle all the images in current section (~1~ + argument) or the whole buffer (~11~ argument). + + #+vindex: org-startup-with-inline-images + You can ask for inline link previews to be displayed at startup by + configuring the variable ~org-startup-with-inline-images~[fn:: The + variable ~org-startup-with-inline-images~ can be set within a buffer + with the =STARTUP= options =inlineimages= and =noinlineimages=.]. + +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: + + #+kindex: C-c C-x C-M-v + #+findex: org-link-preview-refresh + Assure inline display of external link previews in the buffer and + refresh them. + +- (~org-link-preview-region~) :: + + #+findex: org-link-preview-region + Create inline previews for external links in the active region, or + the buffer. With a prefix argument, also preview links with a text + description part. + +- (~org-link-preview-clear~) :: + + #+findex: org-link-preview-clear + Clear external link previews in the active region, or the buffer. + +*** Images :PROPERTIES: :DESCRIPTION: Display an image. :END: @@ -11803,97 +11863,76 @@ ** Images [[./img/a.jpg]] #+end_example -Such images can be displayed within the buffer with the following -command: - -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: - - #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images-command - Toggle the inline display of linked images in current section or at - point. With a prefix argument, toggle inline images in the whole - buffer. With double prefix argument, hide all the images in buffer. - - By default, only the image links without description are displayed. - You can force displaying all the images using numeric argument to - toggle all the images in current section (~1~ argument) or the whole - buffer (~11~ argument). - - #+vindex: org-startup-with-inline-images - You can ask for inline images to be displayed at - startup by configuring the variable - ~org-startup-with-inline-images~[fn:: The variable - ~org-startup-with-inline-images~ can be set within a buffer with the - =STARTUP= options =inlineimages= and =noinlineimages=.]. - - - #+vindex: org-image-actual-width - #+vindex: org-image-max-width - #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property - By default, Org mode displays inline images according to their - actual width, but no wider than ~fill-column~ characters. - - You can customize the displayed image width using - ~org-image-actual-width~ variable (globally) or - =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can - be customized in Emacs >= 24.1, built with ImageMagick support.]. - Their value can be the following: - - (default) Non-~nil~, use the actual width of images when inlining - them. If the actual width is too wide, limit it according to - ~org-image-max-width~. - - When set to a number, use ImageMagick (when available) to set the - image's width to this value. - - When set to a number in a list, try to get the width from any - =#+ATTR.*= keyword if it matches a width specification like: - #+begin_example - ,#+ATTR_HTML: :width 300px - #+end_example - and fall back on that number if none is found. - - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword - and fall back on the original width or ~org-image-max-width~ if - none is found. - - ~org-image-max-width~ limits the maximum displayed image width, but - only when the image width is not set explicitly. Possible maximum - width can be set to: - - (default) ~fill-column~, limit width to ~fill-column~ number of - characters. - - ~window~, limit width to current window width. - - integer number, limit width to that specified number of pixels. - - ~nil~, do not limit the width. - - #+vindex: org-image-align - Org mode can left-align, center or right-align the display of inline - images. This setting is controlled (globally) by ~org-image-align~. - Only standalone images are affected, corresponding to links with no - surrounding text in their paragraph except for whitespace. Its - value can be the following: - - (default) The symbol ~left~, which inserts the image where the - link appears in the buffer. - - The symbol ~center~, which will preview links centered in the - Emacs window. - - The symbol ~right~, which will preview links right-aligned in the - Emacs window. - - Inline image alignment can be specified for each link using the - =#+ATTR.*= keyword if it matches an alignment specification like: +Such images can be displayed within the buffer with the +~org-link-preview~ and associated command, see [[*External link previews]]. + +#+vindex: org-image-actual-width +#+vindex: org-image-max-width +#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property +By default, Org mode displays inline images according to their +actual width, but no wider than ~fill-column~ characters. + +You can customize the displayed image width using +~org-image-actual-width~ variable (globally) or +=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can +be customized in Emacs >= 24.1, built with ImageMagick support.]. +Their value can be the following: +- (default) Non-~nil~, use the actual width of images when inlining + them. If the actual width is too wide, limit it according to + ~org-image-max-width~. +- When set to a number, use ImageMagick (when available) to set the + image's width to this value. +- When set to a number in a list, try to get the width from any + =#+ATTR.*= keyword if it matches a width specification like: #+begin_example - ,#+ATTR_HTML: :align center + ,#+ATTR_HTML: :width 300px #+end_example - Org will use the alignment specification from any =#+ATTR.*= - keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= - (if present) will override the others. For instance, this link - #+begin_example - ,#+ATTR_HTML: :align right - ,#+ATTR_ORG: :align center - [[/path/to/image/file.png]] - #+end_example - will be displayed centered in Emacs but exported right-aligned to - HTML. + and fall back on that number if none is found. +- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword + and fall back on the original width or ~org-image-max-width~ if + none is found. + +~org-image-max-width~ limits the maximum displayed image width, but +only when the image width is not set explicitly. Possible maximum +width can be set to: +- (default) ~fill-column~, limit width to ~fill-column~ number of + characters. +- ~window~, limit width to current window width. +- integer number, limit width to that specified number of pixels. +- ~nil~, do not limit the width. + +#+vindex: org-image-align +Org mode can left-align, center or right-align the display of inline +images. This setting is controlled (globally) by ~org-image-align~. +Only standalone images are affected, corresponding to links with no +surrounding text in their paragraph except for whitespace. Its +value can be the following: +- (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. +- The symbol ~center~, which will preview links centered in the + Emacs window. +- The symbol ~right~, which will preview links right-aligned in the + Emacs window. + +Inline image alignment can be specified for each link using the +=#+ATTR.*= keyword if it matches an alignment specification like: +#+begin_example +,#+ATTR_HTML: :align center +#+end_example +Org will use the alignment specification from any =#+ATTR.*= +keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= +(if present) will override the others. For instance, this link +#+begin_example +,#+ATTR_HTML: :align right +,#+ATTR_ORG: :align center +[[/path/to/image/file.png]] +#+end_example +will be displayed centered in Emacs but exported right-aligned to +HTML. - When =#+ATTR_ORG= is not set, inline image alignment is also read - from the =:center= attribute supported by some export backends (like - HTML, LaTeX and Beamer.) +When =#+ATTR_ORG= is not set, inline image alignment is also read from +the =:center= attribute supported by some export backends (like HTML, +LaTeX and Beamer.) #+vindex: org-cycle-inline-images-display Inline images can also be displayed when cycling the folding state. @@ -21794,6 +21833,65 @@ ** Adding Hyperlink Types topic. It also provides a default description. The function ~org-insert-link~ can insert it back into an Org buffer later on. +** Adding Hyperlink preview +:PROPERTIES: +:DESCRIPTION: Preview behavior for new hyperlink types. +:END: +#+cindex: hyperlinks, adding preview behavior + +By default, Org supports previewing external links for links ot type +=file= and =attachment= that point to image files. (See [[*Images]].) + +Support for previewing other link types inline can be added to Org in +the following way: + +1. Add a =:preview= link parameter to the link type using + ~org-link-set-parameters~. As an example, here we add previews for + the =docview= link type. + + #+begin_src emacs-lisp +(org-link-set-parameters + "docview" :preview #'org-link-docview-preview) + #+end_src + +2. The value of the =:preview= parameter must be a function that + accepts three arguments: + - an overlay placed from the start to the end of the link, + - the link path, as a string, and + - the link element. + It must return a non-nil value to indicate preview success. + + In our example, we use the =convert= program (part of the + =imagemagick= suite of tools) to create the thumbnail that is + displayed inline. + + #+begin_src emacs-lisp +(defun org-link-docview-preview (ov path _elem) + "Preview file at PATH in overlay OV. + +_ELEM is the link element." + (when (executable-find "convert") + (let* ((path (expand-file-name (substitute-in-file-name path))) + (output-file (expand-file-name (concat "org-docview-preview-" + (substring (sha1 path) 0 10) + ".png") + temporary-file-directory))) + ;; Create or find preview for path + (when (or (file-readable-p output-file) + (= 0 (call-process + "convert" + nil (get-buffer-create "*Org Docview Preview Output*") nil + "-thumbnail" "x320" "-colorspace" "rgb" + "-background" "white" "-alpha" "remove" "-strip" + (concat path "[0]") output-file))) + ;; If preview image is available, display it via the overlay + (overlay-put ov 'display (create-image output-file)))))) + #+end_src + +3. Now previews of docview links for supported document types (PDF, + djvu) are generated (along with image file previews) when calling + ~org-link-preview~. + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index d8018690f..f03043518 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole buffer (or narrowed part of the buffer). With prefix argument, it also forced displaying image links with description. -Now, =C-c C-x C-v= is bound to a new command -~org-toggle-inline-images-command~, which uses different defaults: +Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which +uses different defaults: -1. By default, it toggles image at point or, if there is no image at - point, images in current entry +1. When the region is active, images in the region are previewed -2. When region is active, it is honored +2. Otherwise, if there is an image at point, it is toggled. If there + is no image at point, images in the current entry are previewed -3. =C-u= argument changed its meaning. Now, it forces toggling images - in the whole buffer +3. With the =C-u= argument, image previews in the active region or at + point are cleared instead -4. =C-u C-u= argument unconditionally hides all the images in buffer +4. The =C-u C-u= argument unconditionally shows all images in the + accessible portion of the buffer -5. Displaying images over links with description can be forced using +5. The =C-u C-u C-u= argument unconditionally clears all images in the + accessible portion of the buffer + +6. Displaying images over links with description can be forced using numeric argument: - ~C-u 1~ for toggling all images at point/current entry - ~C-u 11~ for toggling all images in buffer -The old ~org-toggle-inline-images~ command is still available. You -can bind it back to =C-c C-x C-v= by adding the following to you config: +(The first five of these prefix arg behaviors are the same as that of +the ~org-latex-preview~ command.) + +In addition to images, ~org-link-preview~ can also be used to preview +Org links of all types for which preview behavior is defined, see +[[#link-preview][previews for arbitrary link types]]. + +The old ~org-toggle-inline-images~ command is obsolete but still +available. You can bind it back to =C-c C-x C-v= by adding the +following to your config: #+begin_src emacs-lisp (eval-after-load 'org-keys (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) @@ -76,6 +88,16 @@ now have diary timestamps included as well. # We list the most important features, and the features that may # require user action to be used. +*** All Org link types can be previewed +:PROPERTIES: +:CUSTOM_ID: link-preview +:END: + +Org links support a new parameter =:preview= that can be used to +preview arbitrary link types. The value of this parameter should be a +function that is called to preview links of the corresponding type +(see ~org-link-parameters~). + *** Alignment of image previews can be customized This is not a new feature. It has been added in Org 9.7, but not @@ -97,6 +119,16 @@ or newer. # adding new customizations, or changing the interpretation of the # existing customizations. +*** New option ~org-link-preview-batch-size~ + +Org link previews are generated a few at a time, in batches. This +option controls the number of links that are previewed in each batch. + +*** New option ~org-link-preview-delay~ + +Org link previews are generated asynchronously. This option controls +the minimum idle time in seconds between previews of batches of links. + *** Allow disabling macro replacement during export New custom option ~org-export-replace-macros~ controls whether Org @@ -139,6 +171,26 @@ bibliography format requires them to be written in title-case. # This also includes changes in function behavior from Elisp perspective. +*** New command ~org-link-preview~ to preview Org links + +This command replaces ~org-toggle-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer + +This command replaces ~org-display-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer + +This command replaces ~org-remove-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer + +This command replaces ~org-redisplay-inline-images~, which is now +obsolete. + *** ob-sqlite: Added ability to open a database in readonly mode Added option :readonly to ob-sqlite. -- 2.46.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v8] Inline image display as part of a new org-link-preview system 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-28 18:16 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > 1. Patch 0001 is the code patch from before, with the warnings from make > addressed. > > 2. Patch 0002 is the documentation patch, which adds to ORG-NEWS and the > manual. There are two new sections -- one is in the appendix under > "Hacking", and shows how to add a preview function for a link type > (along with a code example). Thanks! See my comments on the documentation patch below. > -** Images > +** Images and link previews > + > +Org mode can preview hyperlinks in the buffer as images or with other > +decorations. May put a link to [*Hyperlinks] here. Also, this reads awkward. Maybe something like Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By default, only image links[fn::previews are available =file:= and =attachment:= links to image files; Emacs should be able to display the linked images (see ~image-types~ variable)] can be previewed - the images are displayed in place of the link path. You can customize the previews as described in [*Adding Hyperlink preview] section. The custom previews do not have to display images - any kind of [[info:elisp#Overlay Properties][display decoration]] can be used. > +*** External link previews > +:PROPERTIES: > +:DESCRIPTION: Preview links in the buffer. > +:END: > + > +Org mode can preview [[* External links][External links]] as inline > +images or with other decorations. This requires support for the > +corresponding link types to be available[fn:: To add support for or to > +modify how links of a certain type are previewed, see .]. By default ^^^^^ > +Org mode includes support for previewing file and attachment links for > +image files (see [[* Images][Images]]). The first sentence in the parent section and the first sentence here are basically identical. As I suggested above, let's move these things up. (Feel free to modify my version as you see fit.) > +Supported link types can be previewed within the buffer with the > +following commands: If possible, try to use active voice. We are talking to users in the manual: You can preview the supported link types in the whole buffer, in current section, region, or at point. > + By default, only image links without a description are displayed. > + You can force displaying previews for all supported links using > + numeric argument to toggle all the images in current section (~1~ > + argument) or the whole buffer (~11~ argument). This is not accurate. C-1 argument will act just as no argument: display links at point/region/section depending on the context. Looks like the docstring wrt C-1/C-11 args should be improved. > + #+vindex: org-startup-with-inline-images > + You can ask for inline link previews to be displayed at startup by > + configuring the variable ~org-startup-with-inline-images~[fn:: The > + variable ~org-startup-with-inline-images~ can be set within a buffer > + with the =STARTUP= options =inlineimages= and =noinlineimages=.]. Org mode can display link previews automatically when opening buffers. Either customize ~org-startup-with-inline-images~ or use in-buffer =#+STARTUP: inlineimages= keyword to enable the previews. =#+STARTUP: noinlineimages= will disable the previews in specific buffer. Also, since we are generalizing things away from images, maybe we should introduce some aliases to ~org-startup-with-inline-images~ and to the startup option values. Something like ~org-startup-with-link-previews~ and similar to startup option. > +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: > + > + #+kindex: C-c C-x C-M-v > + #+findex: org-link-preview-refresh > + Assure inline display of external link previews in the buffer and > + refresh them. "the whole buffer" maybe. > +Such images can be displayed within the buffer with the > +~org-link-preview~ and associated command, see [[*External link previews]]. This sentence reads awkwardly after the previous section that already talked about previews and ~org-link-preview~ command. We can write When [[*External link previews][link previews]] are displayed as images, the image size and alignment can be further customized. > #+vindex: org-cycle-inline-images-display > Inline images can also be displayed when cycling the folding state. > @@ -21794,6 +21833,65 @@ ** Adding Hyperlink Types > topic. It also provides a default description. The function > ~org-insert-link~ can insert it back into an Org buffer later on. I suspect that it belongs to the previous section - cycling should work for all the link previews, not just images. > +2. The value of the =:preview= parameter must be a function that > + accepts three arguments: > + - an overlay placed from the start to the end of the link, > + - the link path, as a string, and > + - the link element. element -> syntax node > + It must return a non-nil value to indicate preview success. ... and your example can sometimes return nil, which is confusing. We should explain what nil means as a return value as well. > --- a/etc/ORG-NEWS > +++ b/etc/ORG-NEWS > @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole > buffer (or narrowed part of the buffer). With prefix argument, it > also forced displaying image links with description. > ... > +2. Otherwise, if there is an image at point, it is toggled. If there > + is no image at point, images in the current entry are previewed Aside: I am wondering if we should also attach preview toggle to <TAB> when ~org-cycle-inline-images-display~ is non-nil and there is a link at point. > +*** New option ~org-link-preview-batch-size~ > + > +Org link previews are generated a few at a time, in batches. This asynchronously > +option controls the number of links that are previewed in each batch. > + > +*** New option ~org-link-preview-delay~ > + > +Org link previews are generated asynchronously. This option controls > +the minimum idle time in seconds between previews of batches of links. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v9] Inline image display as part of a new org-link-preview system 2024-10-28 18:16 ` Ihor Radchenko @ 2024-10-28 21:53 ` Karthik Chikmagalur 2024-10-29 18:46 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-10-28 21:53 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 6789 bytes --] New patches attached. >> -** Images >> +** Images and link previews >> + >> +Org mode can preview hyperlinks in the buffer as images or with other >> +decorations. > > May put a link to [*Hyperlinks] here. Done. > Also, this reads awkward. Maybe something like > > Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside > Org buffers. By default, only image links[fn::previews are > available =file:= and =attachment:= links to image files; Emacs > should be able to display the linked images (see ~image-types~ > variable)] can be previewed - the images are displayed in place of > the link path. > > You can customize the previews as described in [*Adding Hyperlink > preview] section. The custom previews do not have to display images > - any kind of [[info:elisp#Overlay Properties][display decoration]] > can be used. Done. >> +*** External link previews >> +:PROPERTIES: >> +:DESCRIPTION: Preview links in the buffer. >> +:END: >> + >> +Org mode can preview [[* External links][External links]] as inline >> +images or with other decorations. This requires support for the >> +corresponding link types to be available[fn:: To add support for or to >> +modify how links of a certain type are previewed, see .]. By default > ^^^^^ >> +Org mode includes support for previewing file and attachment links for >> +image files (see [[* Images][Images]]). > > The first sentence in the parent section and the first sentence here are > basically identical. As I suggested above, let's move these things > up. (Feel free to modify my version as you see fit.) This is the new organization: --8<---------------cut here---------------start------------->8--- * Images and link previews [Explanation of link previews and org-link-preview] ** Images [Image-preview-specific options] --8<---------------cut here---------------end--------------->8--- The section "External link previews" is gone, moved up into the parent. >> +Supported link types can be previewed within the buffer with the >> +following commands: > > If possible, try to use active voice. We are talking to users in the > manual: > > You can preview the supported link types in the whole buffer, in > current section, region, or at point. Used active voice. >> + By default, only image links without a description are displayed. >> + You can force displaying previews for all supported links using >> + numeric argument to toggle all the images in current section (~1~ >> + argument) or the whole buffer (~11~ argument). > > This is not accurate. C-1 argument will act just as no argument: display > links at point/region/section depending on the context. > > Looks like the docstring wrt C-1/C-11 args should be improved. Improved both in the manual and in the command documentation in ol.el. > Org mode can display link previews automatically when opening > buffers. Either customize ~org-startup-with-inline-images~ or use > in-buffer =#+STARTUP: inlineimages= keyword to enable the previews. > =#+STARTUP: noinlineimages= will disable the previews in specific > buffer. > > Also, since we are generalizing things away from images, maybe we should > introduce some aliases to ~org-startup-with-inline-images~ and to the > startup option values. Something like ~org-startup-with-link-previews~ > and similar to startup option. Okay. I've added `org-startup-with-link-previews' and the "linkpreview", "nolinkpreview" STARTUP options. `org-startup-with-inline-images' is an alias to the new variable, and the "inlineimages" and "noinlineimages" options are still respected. There are two other changes. In org-cycle, I've renamed `org-cycle-inline-images-display' and `org-cycle-display-inline-images' to `org-cycle-link-previews-display' and `org-cycle-display-link-previews'. The older user option and function are aliased to the new ones. There are new entries in ORG-NEWS documenting these renames. None of the above have been obsoleted. Let me know if I should make them all obsolete aliases instead. >> +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: >> + >> + #+kindex: C-c C-x C-M-v >> + #+findex: org-link-preview-refresh >> + Assure inline display of external link previews in the buffer and >> + refresh them. > > "the whole buffer" maybe. Done. >> +Such images can be displayed within the buffer with the >> +~org-link-preview~ and associated command, see [[*External link previews]]. > > This sentence reads awkwardly after the previous section that already > talked about previews and ~org-link-preview~ command. > > We can write > > When [[*External link previews][link previews]] are displayed as > images, the image size and alignment can be further customized. Done. >> #+vindex: org-cycle-inline-images-display >> Inline images can also be displayed when cycling the folding state. >> @@ -21794,6 +21833,65 @@ ** Adding Hyperlink Types >> topic. It also provides a default description. The function >> ~org-insert-link~ can insert it back into an Org buffer later on. > > I suspect that it belongs to the previous section - cycling should work > for all the link previews, not just images. Done. >> +2. The value of the =:preview= parameter must be a function that >> + accepts three arguments: >> + - an overlay placed from the start to the end of the link, >> + - the link path, as a string, and >> + - the link element. > > element -> syntax node Done. >> + It must return a non-nil value to indicate preview success. > > ... and your example can sometimes return nil, which is confusing. > We should explain what nil means as a return value as well. Done. >> --- a/etc/ORG-NEWS >> +++ b/etc/ORG-NEWS >> @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole >> buffer (or narrowed part of the buffer). With prefix argument, it >> also forced displaying image links with description. >> ... >> +2. Otherwise, if there is an image at point, it is toggled. If there >> + is no image at point, images in the current entry are previewed > > Aside: I am wondering if we should also attach preview toggle to <TAB> > when ~org-cycle-inline-images-display~ is non-nil and there is a link at > point. I haven't added this. I think it's better not to do this, since cycling is expected to only involve hiding/showing buffer contents. Generating a preview might cause new external processes to be started. I can add it if you think we should. >> +*** New option ~org-link-preview-batch-size~ >> + >> +Org link previews are generated a few at a time, in batches. This > > asynchronously Done, both in the manual and in the variable docstring. Karthik [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-link-Customizable-preview-API-for-arbitrary-link.patch --] [-- Type: text/x-patch, Size: 75484 bytes --] From 9809676310228a927ddea84aced6d26bb9f9dc8e Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH 1/2] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-batch-size, org-link-preview-delay, org-link-preview--timer, org-link-preview--queue, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-preview, org-link-preview-region, org-link-preview--process-queue, org-link-preview-clear, org-link-preview-file, org-display-remote-inline-images, org-image-align, org--create-inline-image, org-display-inline-image--width, org-image--align): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. This link parameter is a function called asynchronously to place previes. File links and attachments are previewed using inline image previews as before. Move image handling utilities from lisp/org.el to lisp/ol.el. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images, org-startup-with-inline-images, org-startup-with-link-previews, org-startup-options): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. Rename `org-startup-with-inline-images' to `org-startup-with-link-previews'. Add new STARTUP options for link previews to `org-startup-options': "linkpreviews" and "nolinkpreviews". * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images, org-cycle-display-link-previews, org-cycle-inline-images-display, org-cycle-inline-link-previews): Use `org-link-preview' and `org-link-preview-region'. Rename inline-images functions and user options to their link-previews equivalents: - `org-cycle-display-inline-images' to `org-cycle-display-link-previews' - `org-cycle-inline-images-display' to `org-cycle-inline-link-previews' * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el (org-attach-preview-file): Add new `:preview' link parameter for links of type "attachment", set to the new function `org-attach-preview-file'. --- lisp/ol.el | 602 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 185 +++++++++++ lisp/org-cycle.el | 31 +- lisp/org-keys.el | 7 +- lisp/org-plot.el | 2 +- lisp/org.el | 554 +------------------------------ testing/lisp/test-org-fold.el | 4 +- 8 files changed, 838 insertions(+), 558 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..2c0b80dfe 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,11 @@ (declare-function org-src-source-buffer "org-src" ()) (declare-function org-src-source-type "org-src" ()) (declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function outline-next-heading "outline" ()) +(declare-function image-flush "image" (spec &optional frame)) +(declare-function org-entry-end-position "org" ()) +(declare-function org-element-contents-begin "org-element" (node)) +(declare-function org-element-contents-end "org-element" (node)) +(declare-function org-property-or-variable-value "org" (var &optional inherit)) \f ;;; Customization @@ -171,6 +176,18 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run to generate an in-buffer preview for the link. It + must accept three arguments: + - an overlay placed from the start to the end of the link + - the link path, as a string + - the syntax node for the link + + This function must return a non-nil value to indicate success. + A return value of nil implies that the preview failed, and the + overlay placed on the link will be removed. + `:help-echo' String or function used as a value for the `help-echo' text @@ -521,6 +538,81 @@ (defcustom org-link-keep-stored-after-insertion nil :type 'boolean :safe #'booleanp) +(defcustom org-link-preview-delay 0.05 + "Idle delay in seconds between link previews when using +`org-link-preview'. Links are previewed in batches (see +`org-link-preview-batch-size') spaced out by this delay. Set +this to a small number for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'number) + +(defcustom org-link-preview-batch-size 6 + "Number of links that are previewed at once with +`org-link-preview'. Links are previewed asynchronously, in +batches spaced out in time (see `org-link-preview-delay'). Set +this to a large integer for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'natnum) + +(defcustom org-display-remote-inline-images 'skip + "How to display remote inline images. +Possible values of this option are: + +skip Don't display remote images. +download Always download and display remote images. +t +cache Display remote images, and open them in separate buffers + for caching. Silently update the image buffer when a file + change is detected." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Ignore remote images" skip) + (const :tag "Always display remote images" download) + (const :tag "Display and silently update remote images" cache)) + :safe #'symbolp) + +(defcustom org-image-max-width 'fill-column + "When non-nil, limit the displayed image width. +This setting only takes effect when `org-image-actual-width' is set to +t or when #+ATTR* is set to t. + +Possible values: +- `fill-column' :: limit width to `fill-column' +- `window' :: limit width to window width +- integer :: limit width to number in pixels +- float :: limit width to that fraction of window width +- nil :: do not limit image width" + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Do not limit image width" nil) + (const :tag "Limit to `fill-column'" fill-column) + (const :tag "Limit to window width" window) + (integer :tag "Limit to a number of pixels") + (float :tag "Limit to a fraction of window width"))) + +(defcustom org-image-align 'left + "How to align images previewed using `org-link-preview-region'. + +Only stand-alone image links are affected by this setting. These +are links without surrounding text. + +Possible values of this option are: + +left Insert image at specified position. +center Center image previews. +right Right-align image previews." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Left align (or don\\='t align) image previews" left) + (const :tag "Center image previews" center) + (const :tag "Right align image previews" right)) + :safe #'symbolp) + ;;; Public variables (defconst org-target-regexp (let ((border "[^<>\n\r \t]")) @@ -649,6 +741,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +996,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1908,230 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, also preview links with description in +the active region, at point or in the current section. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] Displaying %d images inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if-let ((ov (cdr (get-char-property-and-overlay + (point) 'org-image-overlay)))) + ;; clear link preview at point + (funcall toggle-previews + (overlay-start ov) (overlay-end ov) + "preview at point" 'remove) + ;; Clear link previews in entry + (funcall toggle-previews + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))) + (org-with-limited-levels (org-entry-end-position)) + "current section" 'remove))) + ;; C-u C-u or C-11 argument :: display images in the whole buffer. + ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove)) + ;; Argument nil or 1, no region selected :: display images in + ;; current section or image link at point. + ((and (member arg '(nil 1)) (null beg) (null end)) + (let ((context (org-element-context))) + ;; toggle display of inline image link at point. + (if (org-element-type-p context 'link) + (let* ((ov (cdr-safe (get-char-property-and-overlay + (point) 'org-image-overlay))) + (remove? (and ov (memq ov org-link-preview-overlays) + 'remove))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "image at point" remove?)) + (let ((beg (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point))))) + (end (org-with-limited-levels (org-entry-end-position)))) + (funcall toggle-previews beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-previews beg end "region"))))) + +;;;###autoload +(defun org-link-preview-refresh () + "Assure display of link previews in buffer and refresh them." + (interactive) + (org-link-preview-region nil t (point-min) (point-max))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh (org-link-preview-clear beg end)) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t) + preview-queue) + ;; Collect links to preview + (while (re-search-forward org-link-any-re end t) + (when-let* + ((link (org-element-lineage (org-element-context) 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (push ov org-link-preview-overlays) + (push (list preview-func ov path link) preview-queue)))) + ;; Collect previews in buffer-local LIFO preview queue + (setq org-link-preview--queue + (nconc (nreverse preview-queue) org-link-preview--queue)) + ;; Run preview possibly asynchronously + (when org-link-preview--queue + (org-link-preview--process-queue (current-buffer)))))) + +(defun org-link-preview--process-queue (org-buffer) + "Preview pending Org link previews in ORG-BUFFER. + +Previews are generated from the specs in +`org-link-preview--queue', which see." + (and (buffer-live-p org-buffer) + (with-current-buffer org-buffer + (cl-loop + for spec in org-link-preview--queue + for ov = (cadr spec) ;SPEC is (preview-func ov path link) + for count from org-link-preview-batch-size above 0 + do (pop org-link-preview--queue) + if (overlay-buffer ov) do + (if (apply spec) + (overlay-put ov 'org-image-overlay t) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays + (delq ov org-link-preview-overlays))) + else do (cl-incf count) end + finally do + (setq org-link-preview--timer + (and org-link-preview--queue + (run-with-idle-timer + (time-add (or (current-idle-time) 0) + org-link-preview-delay) + nil #'org-link-preview--process-queue org-buffer))))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + ;; Remove pending preview tasks between BEG and END + (when-let ((spec (cl-find ov org-link-preview--queue + :key #'cadr))) + (setq org-link-preview--queue (delq spec org-link-preview--queue))) + ;; Remove placed overlays between BEG and END + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2154,48 @@ (defun org-link--open-elisp (path _) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) ;;;; "file" link type -(org-link-set-parameters "file" :complete #'org-link-complete-file) +(org-link-set-parameters "file" + :complete #'org-link-complete-file + :preview #'org-link-preview-file) + +(defun org-link-preview-file (ov path link) + "Display image file PATH in overlay OV for LINK. + +LINK is the Org element being previewed. + +Equip each image with the keymap `image-map'. + +This is intended to be used as the `:preview' link property of +file links, see `org-link-parameters'." + (if (not (display-graphic-p)) + (prog1 nil + (message "Your Emacs does not support displaying images!")) + (require 'image) + (when-let* ((file-full (expand-file-name path)) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (when image ; Add image to overlay + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'keymap image-map) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + t))))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index 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..885a5245d 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -1010,6 +1010,191 @@ (define-obsolete-function-alias 'org-open-link-from-string (define-obsolete-function-alias 'org-add-angle-brackets 'org-link-add-angle-brackets "9.3") +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-link-preview--get-overlays "ol" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-attach-expand "org-attach" (file)) +(declare-function org-display-inline-image--width "org" (link)) +(declare-function org-image--align "org" (link)) +(declare-function org--create-inline-image "org" (file width)) + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-function-alias 'org-redisplay-inline-images + 'org-link-preview-refresh "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +(declare-function org-link-preview-region "ol") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-link-preview-region include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + + ;; The function was made obsolete by commit 65399674d5 of 2013-02-22. ;; This make-obsolete call was added 2016-09-01. (make-obsolete 'org-capture-import-remember-templates diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..97a545393 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -217,7 +217,7 @@ (defcustom org-cycle-pre-hook nil (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees org-cycle-show-empty-lines org-cycle-optimize-window-after-visibility-change - org-cycle-display-inline-images) + org-cycle-display-link-previews) "Hook that is run after `org-cycle' has changed the buffer visibility. The function(s) in this hook must accept a single argument which indicates the new state that was set by the most recent `org-cycle' command. The @@ -237,11 +237,15 @@ (defcustom org-cycle-open-archived-trees nil :group 'org-cycle :type 'boolean) -(defcustom org-cycle-inline-images-display nil - "Non-nil means auto display inline images under subtree when cycling." +(defvaralias 'org-cycle-inline-images-display + 'org-cycle-link-previews-display + "Non-nil means auto display inline images under subtree when cycling.") + +(defcustom org-cycle-link-previews-display nil + "Non-nil means auto display link previews under subtree when cycling." :group 'org-startup :group 'org-cycle - :package-version '(Org . "9.6") + :package-version '(Org . "9.8") :type 'boolean) (defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook) @@ -804,12 +808,15 @@ (defun org-cycle-hide-archived-subtrees (state) "Subtree is archived and stays closed. Use \ `\\[org-cycle-force-archived]' to cycle it anyway.")))))) -(defun org-cycle-display-inline-images (state) +(defalias 'org-cycle-inline-images-display + 'org-cycle-display-link-previews) + +(defun org-cycle-display-link-previews (state) "Auto display inline images under subtree when cycling. -It works when `org-cycle-inline-images-display' is non-nil. +It works when `org-cycle-link-previews-display' is non-nil. STATE is the current outline visibility state. It should be one of symbols `content', `all', `folded', `children', or `subtree'." - (when org-cycle-inline-images-display + (when org-cycle-link-previews-display (pcase state ('children (org-with-wide-buffer @@ -817,19 +824,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 1daedaae8..d2e4a6993 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,8 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) +(declare-function org-link-preview-refresh "ol" ()) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,8 +619,8 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) -(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) +(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-link-preview-refresh) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index df58b47be..c2a125000 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -1160,14 +1160,22 @@ (defcustom org-startup-shrink-all-tables nil :package-version '(Org . "9.2") :safe #'booleanp) -(defcustom org-startup-with-inline-images nil +(defvaralias 'org-startup-with-inline-images + 'org-startup-with-link-previews "Non-nil means show inline images when loading a new Org file. This can also be configured on a per-file basis by adding one of the following lines anywhere in the buffer: #+STARTUP: inlineimages - #+STARTUP: noinlineimages" + #+STARTUP: noinlineimages") + +(defcustom org-startup-with-link-previews nil + "Non-nil means show link previews when loading a new Org file. +This can also be configured on a per-file basis by adding one of +the following lines anywhere in the buffer: + #+STARTUP: linkpreviews + #+STARTUP: nolinkpreviews" :group 'org-startup - :version "24.1" + :version "29.4" :type 'boolean) (defcustom org-startup-with-latex-preview nil @@ -4153,8 +4161,10 @@ (defconst org-startup-options ("shrink" org-startup-shrink-all-tables t) ("descriptivelinks" org-link-descriptive t) ("literallinks" org-link-descriptive nil) - ("inlineimages" org-startup-with-inline-images t) - ("noinlineimages" org-startup-with-inline-images nil) + ("inlineimages" org-startup-with-link-previews t) + ("noinlineimages" org-startup-with-link-previews nil) + ("linkpreviews" org-startup-with-link-previews t) + ("nolinkpreviews" org-startup-with-link-previews nil) ("latexpreview" org-startup-with-latex-preview t) ("nolatexpreview" org-startup-with-latex-preview nil) ("customtime" org-display-custom-times t) @@ -5079,7 +5089,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -15583,26 +15593,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 +16639,6 @@ (defun org-normalize-color (value) (format "%g" (/ value 65535.0))) \f -;; Image display - -(defvar-local org-inline-image-overlays nil) -;; Preserve when switching modes or when restarting Org. -;; If we clear the overlay list and later enable Or mode, the existing -;; image overlays will never be cleared by `org-toggle-inline-images' -;; and `org-toggle-inline-images-command'. -(put 'org-inline-image-overlays 'permanent-local t) - -(defun org--inline-image-overlays (&optional beg end) - "Return image overlays between BEG and END." - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end)) - result) - (dolist (ov overlays result) - (when (memq ov org-inline-image-overlays) - (push ov result))))) - -(defun org-toggle-inline-images-command (&optional arg beg end) - "Toggle display of inline images without description at point. - -When point is at an image link, toggle displaying that image. -Otherwise, toggle displaying images in current entry. - -When region BEG..END is active, toggle displaying images in the -region. - -With numeric prefix ARG 1, display images with description as well. - -With prefix ARG `\\[universal-argument]', toggle displaying images in -the accessible portion of the buffer. With numeric prefix ARG 11, do -the same, but include images with description. - -With prefix ARG `\\[universal-argument] \\[universal-argument]', hide -all the images in accessible portion of the buffer. - -This command is designed for interactive use. From Elisp, you can -also use `org-toggle-inline-images'." - (interactive (cons current-prefix-arg - (when (use-region-p) - (list (region-beginning) (region-end))))) - (let* ((include-linked - (cond - ((member arg '(nil (4) (16)) ) nil) - ((member arg '(1 11)) 'include-linked) - (t 'include-linked))) - (interactive? (called-interactively-p 'any)) - (toggle-images - (lambda (&optional beg end scope force-remove) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (old (org--inline-image-overlays beg end)) - (scope (or scope (format "%d:%d" beg end)))) - (if (or old force-remove) - (progn - (org-remove-inline-images beg end) - (when interactive? - (message - "[%s] Inline image display turned off (removed %d images)" - scope (length old)))) - (org-display-inline-images include-linked t beg end) - (when interactive? - (let ((new (org--inline-image-overlays beg end))) - (message - (if new - (format "[%s] %d images displayed inline %s" - scope (length new) - (if include-linked "(including images with description)" - "")) - (format "[%s] No images to display inline" scope)))))))))) - (cond - ((not (display-graphic-p)) - (message "Your Emacs does not support displaying images!")) - ;; Region selected :: toggle images in region. - ((and beg end) (funcall toggle-images beg end "region")) - ;; C-u or C-11 argument :: toggle images in the whole buffer. - ((member arg '(11 (4))) (funcall toggle-images nil nil "buffer")) - ;; C-u C-u argument :: unconditionally hide images in the buffer. - ((equal arg '(16)) (funcall toggle-images nil nil "buffer" 'remove)) - ;; Argument nil or 1, no region selected :: toggle (display or hide - ;; dwim) images in current section or image link at point. - ((and (member arg '(nil 1)) (null beg) (null end)) - (let ((context (org-element-context))) - ;; toggle display of inline image link at point. - (if (org-element-type-p context 'link) - (funcall toggle-images - (org-element-begin context) - (org-element-end context) - "image at point") - (let ((beg (if (org-before-first-heading-p) (point-min) - (save-excursion - (org-with-limited-levels (org-back-to-heading t) (point))))) - (end (org-with-limited-levels (org-entry-end-position)))) - (funcall toggle-images beg end "current section"))))) - ;; Any other non-nil argument. - ((not (null arg)) (funcall toggle-images beg end "region"))))) - -(defun org-toggle-inline-images (&optional include-linked beg end) - "Toggle the display of inline images. -INCLUDE-LINKED is passed to `org-display-inline-images'." - (interactive "P") - (if (org--inline-image-overlays beg end) - (progn - (org-remove-inline-images beg end) - (when (called-interactively-p 'interactive) - (message "Inline image display turned off"))) - (org-display-inline-images include-linked nil beg end) - (when (called-interactively-p 'interactive) - (let ((new (org--inline-image-overlays beg end))) - (message (if new - (format "%d images displayed inline" - (length new)) - "No images to display inline")))))) - -(defun org-redisplay-inline-images () - "Assure display of inline images and refresh them." - (interactive) - (org-toggle-inline-images) - (unless org-inline-image-overlays - (org-toggle-inline-images))) - -;; For without-x builds. -(declare-function image-flush "image" (spec &optional frame)) - -(defcustom org-display-remote-inline-images 'skip - "How to display remote inline images. -Possible values of this option are: - -skip Don't display remote images. -download Always download and display remote images. -t -cache Display remote images, and open them in separate buffers - for caching. Silently update the image buffer when a file - change is detected." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Ignore remote images" skip) - (const :tag "Always display remote images" download) - (const :tag "Display and silently update remote images" cache)) - :safe #'symbolp) - -(defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. - -Only stand-alone image links are affected by this setting. These -are links without surrounding text. - -Possible values of this option are: - -left Insert image at specified position. -center Center image previews. -right Right-align image previews." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Left align (or don\\='t align) image previews" left) - (const :tag "Center image previews" center) - (const :tag "Right align image previews" right)) - :safe #'symbolp) - -(defun org--create-inline-image (file width) - "Create image located at FILE, or return nil. -WIDTH is the width of the image. The image may not be created -according to the value of `org-display-remote-inline-images'." - (let* ((remote? (file-remote-p file)) - (file-or-data - (pcase org-display-remote-inline-images - ((guard (not remote?)) file) - (`download (with-temp-buffer - (set-buffer-multibyte nil) - (insert-file-contents-literally file) - (buffer-string))) - ((or `cache `t) - (let ((revert-without-query '("."))) - (with-current-buffer (find-file-noselect file) - (buffer-string)))) - (`skip nil) - (other - (message "Invalid value of `org-display-remote-inline-images': %S" - other) - nil)))) - (when file-or-data - (create-image file-or-data - (and (image-type-available-p 'imagemagick) - width - 'imagemagick) - remote? - :width width - :max-width - (pcase org-image-max-width - (`fill-column (* fill-column (frame-char-width (selected-frame)))) - (`window (window-width nil t)) - ((pred integerp) org-image-max-width) - ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) - (`nil nil) - (_ (error "Unsupported value of `org-image-max-width': %S" - org-image-max-width))) - :scale 1)))) - -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - -(declare-function org-export-read-attribute "ox" - (attribute element &optional property)) -(defvar visual-fill-column-width) ; Silence compiler warning -(defun org-display-inline-image--width (link) - "Determine the display width of the image LINK, in pixels. -- When `org-image-actual-width' is t, the image's pixel width is used. -- When `org-image-actual-width' is a number, that value will is used. -- When `org-image-actual-width' is nil or a list, :width attribute of - #+attr_org or the first #+attr_... (if it exists) is used to set the - image width. A width of X% is divided by 100. If the value is a - float between 0 and 2, it interpreted as that proportion of the text - width in the buffer. - - If no :width attribute is given and `org-image-actual-width' is a - list with a number as the car, then that number is used as the - default value." - ;; Apply `org-image-actual-width' specifications. - ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified - ;; width. - (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) - (cond - ((eq org-image-actual-width t) nil) - ((listp org-image-actual-width) - (require 'ox) - (let* ((par (org-element-lineage link 'paragraph)) - ;; Try to find an attribute providing a :width. - ;; #+ATTR_ORG: :width ... - (attr-width (org-export-read-attribute :attr_org par :width)) - (width-unreadable? - (lambda (value) - (or (not (stringp value)) - (unless (string= value "t") - (or (not (string-match - (rx bos (opt "+") - (or - ;; Number of pixels - ;; must be a lone number, not - ;; things like 4in - (seq (1+ (in "0-9")) eos) - ;; Numbers ending with % - (seq (1+ (in "0-9.")) (group-n 1 "%")) - ;; Fractions - (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) - value)) - (let ((number (string-to-number value))) - (and (floatp number) - (not (match-string 1 value)) ; X% - (not (<= 0.0 number 2.0))))))))) - ;; #+ATTR_BACKEND: :width ... - (attr-other - (catch :found - (org-element-properties-map - (lambda (prop _) - (when (and - (not (eq prop :attr_org)) - (string-match-p "^:attr_" (symbol-name prop)) - (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) - (throw :found prop))) - par))) - (attr-width - (if (not (funcall width-unreadable? attr-width)) - attr-width - ;; When #+attr_org: does not have readable :width - (and attr-other - (org-export-read-attribute attr-other par :width)))) - (width - (cond - ;; Treat :width t as if `org-image-actual-width' were t. - ((string= attr-width "t") nil) - ;; Fallback to `org-image-actual-width' if no interprable width is given. - ((funcall width-unreadable? attr-width) - (car org-image-actual-width)) - ;; Convert numeric widths to numbers, converting percentages. - ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) - (/ (string-to-number attr-width) 100.0)) - (t (string-to-number attr-width))))) - (if (and (floatp width) (<= 0.0 width 2.0)) - ;; A float in [0,2] should be interpereted as this portion of - ;; the text width in the window. This works well with cases like - ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, - ;; as the "0.X" is pulled out as a float. We use 2 as the upper - ;; bound as cases such as 1.2\linewidth are feasible. - (round (* width - (window-pixel-width) - (/ (or (and (bound-and-true-p visual-fill-column-mode) - (or visual-fill-column-width auto-fill-function)) - (when auto-fill-function fill-column) - (- (window-text-width) (line-number-display-width))) - (float (window-total-width))))) - width))) - ((numberp org-image-actual-width) - org-image-actual-width) - (t nil)))) - -(defun org-image--align (link) - "Determine the alignment of the image LINK. -LINK is a link object. - -In decreasing order of priority, this is controlled: -- Per image by the value of `:center' or `:align' in the -affiliated keyword `#+attr_org'. -- By the `#+attr_html' or `#+attr_latex` keywords with valid - `:center' or `:align' values. -- Globally by the user option `org-image-align'. - -The result is either nil or one of the strings \"left\", -\"center\" or \"right\". - -\"center\" will cause the image preview to be centered, \"right\" -will cause it to be right-aligned. A value of \"left\" or nil -implies no special alignment." - (let ((par (org-element-lineage link 'paragraph))) - ;; Only align when image is not surrounded by paragraph text: - (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align - (= (org-element-begin link) - (save-excursion - (goto-char (org-element-contents-begin par)) - (skip-chars-forward "\t ") - (point))) ;account for leading space - ;before link - (<= (- (org-element-contents-end par) - (org-element-end link)) - 1)) ;account for trailing newline - ;at end of paragraph - (save-match-data - ;; Look for a valid ":center t" or ":align left|center|right" - ;; attribute. - ;; - ;; An attr_org keyword has the highest priority, with - ;; any attr.* next. Choosing between these is - ;; unspecified. - (let ((center-re ":\\(center\\)[[:space:]]+t\\b") - (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") - attr-align) - (catch 'exit - (org-element-properties-mapc - (lambda (propname propval) - (when (and propval - (string-match-p ":attr.*" (symbol-name propname))) - (setq propval (car-safe propval)) - (when (or (string-match center-re propval) - (string-match align-re propval)) - (setq attr-align (match-string 1 propval)) - (when (eq propname :attr_org) - (throw 'exit t))))) - par)) - (if attr-align - (when (member attr-align '("center" "right")) attr-align) - ;; No image-specific keyword, check global alignment property - (when (memq org-image-align '(center right)) - (symbol-name org-image-align)))))))) - - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.46.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --] [-- Type: text/x-patch, Size: 19709 bytes --] From 7dc0be9790a6abcba37554b8a26bcd751e7ad31d Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Sun, 27 Oct 2024 20:22:59 -0700 Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types * etc/ORG-NEWS: Mention new commands and options for link preview. * doc/org-manual.org: Add sections on - previewing external links (Images and link previews), and - adding the link preview feature to other link types (Adding Hyperlink preview). --- doc/org-manual.org | 290 ++++++++++++++++++++++++++++++--------------- etc/ORG-NEWS | 93 +++++++++++++-- 2 files changed, 278 insertions(+), 105 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 94fabde3b..6ed5fac93 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11778,7 +11778,74 @@ ** Literal Examples the end of the current line. Then the label is stored as a link =(label)=, for retrieval with {{{kbd(C-c C-l)}}}. -** Images +** Images and link previews +:PROPERTIES: +:DESCRIPTION: Preview links in the buffer. +:END: + +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By +default, only image links[fn::Image links are =file:= and +=attachment:= links to existing image files; Emacs should be able to +display the linked images (see ~image-types~ variable)] can be +previewed inline, where the images are displayed in place of the link +path. + +You can customize the previews as described in the [[*Adding Hyperlink +preview]] section. Link previews do not have to display images -- any +kind of [[info:elisp#Overlay Properties][display decoration]] can be used. + +You can preview the supported link types in the whole buffer, in the +current section, active region or at point with the following commands: + +- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) :: + + #+kindex: C-c C-x C-v + #+findex: org-link-preview + Create inline previews for external links in the active region, the + link at point or in the current section. With a prefix argument, + clear link previews at point or in the current entry. With a double + prefix argument, preview all links in the buffer. With triple + prefix argument, hide previews for all links in the buffer. + + By default, only image links without a description are displayed. + You can force displaying previews for all supported links using a + numeric argument of ~1~ to toggle all previews in the active + region, the link at point or the current section. A numeric prefix + argument of ~11~ will toggle previews in the whole buffer. + + #+vindex: org-startup-with-link-previews + Org mode can display link previews automatically when opening + buffers. Either customize ~org-startup-with-link-previews~ or use + the =#+STARTUP: linkpreviews= keyword to enable previews for that + specific buffer when opening it. =#+STARTUP: nolinkpreviews= will + disable previews on startup in the buffer. + +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: + + #+kindex: C-c C-x C-M-v + #+findex: org-link-preview-refresh + Assure inline display of external link previews in the whole buffer + and refresh them. + +- (~org-link-preview-region~) :: + + #+findex: org-link-preview-region + Create inline previews for external links in the active region, or + the buffer. With a prefix argument, also preview links with a text + description part. + +- (~org-link-preview-clear~) :: + + #+findex: org-link-preview-clear + Clear external link previews in the active region, or the buffer. + +#+vindex: org-cycle-inline-images-display +Link previews can also be displayed when cycling the folding state. +When the custom option ~org-cycle-link-previews-display~ is set, +supported link types under the subtree (including images) will be +displayed automatically. + +*** Images :PROPERTIES: :DESCRIPTION: Display an image. :END: @@ -11803,102 +11870,76 @@ ** Images [[./img/a.jpg]] #+end_example -Such images can be displayed within the buffer with the following -command: - -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: - - #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images-command - Toggle the inline display of linked images in current section or at - point. With a prefix argument, toggle inline images in the whole - buffer. With double prefix argument, hide all the images in buffer. - - By default, only the image links without description are displayed. - You can force displaying all the images using numeric argument to - toggle all the images in current section (~1~ argument) or the whole - buffer (~11~ argument). - - #+vindex: org-startup-with-inline-images - You can ask for inline images to be displayed at - startup by configuring the variable - ~org-startup-with-inline-images~[fn:: The variable - ~org-startup-with-inline-images~ can be set within a buffer with the - =STARTUP= options =inlineimages= and =noinlineimages=.]. - - - #+vindex: org-image-actual-width - #+vindex: org-image-max-width - #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property - By default, Org mode displays inline images according to their - actual width, but no wider than ~fill-column~ characters. - - You can customize the displayed image width using - ~org-image-actual-width~ variable (globally) or - =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can - be customized in Emacs >= 24.1, built with ImageMagick support.]. - Their value can be the following: - - (default) Non-~nil~, use the actual width of images when inlining - them. If the actual width is too wide, limit it according to - ~org-image-max-width~. - - When set to a number, use ImageMagick (when available) to set the - image's width to this value. - - When set to a number in a list, try to get the width from any - =#+ATTR.*= keyword if it matches a width specification like: - #+begin_example - ,#+ATTR_HTML: :width 300px - #+end_example - and fall back on that number if none is found. - - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword - and fall back on the original width or ~org-image-max-width~ if - none is found. - - ~org-image-max-width~ limits the maximum displayed image width, but - only when the image width is not set explicitly. Possible maximum - width can be set to: - - (default) ~fill-column~, limit width to ~fill-column~ number of - characters. - - ~window~, limit width to current window width. - - integer number, limit width to that specified number of pixels. - - ~nil~, do not limit the width. - - #+vindex: org-image-align - Org mode can left-align, center or right-align the display of inline - images. This setting is controlled (globally) by ~org-image-align~. - Only standalone images are affected, corresponding to links with no - surrounding text in their paragraph except for whitespace. Its - value can be the following: - - (default) The symbol ~left~, which inserts the image where the - link appears in the buffer. - - The symbol ~center~, which will preview links centered in the - Emacs window. - - The symbol ~right~, which will preview links right-aligned in the - Emacs window. - - Inline image alignment can be specified for each link using the - =#+ATTR.*= keyword if it matches an alignment specification like: - #+begin_example - ,#+ATTR_HTML: :align center - #+end_example - Org will use the alignment specification from any =#+ATTR.*= - keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= - (if present) will override the others. For instance, this link +When [[*Images and link previews][link previews]] are displayed as images, the image size and +alignment can be further customized. + +#+vindex: org-image-actual-width +#+vindex: org-image-max-width +#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property +By default, Org mode displays inline images according to their +actual width, but no wider than ~fill-column~ characters. + +You can customize the displayed image width using +~org-image-actual-width~ variable (globally) or +=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can +be customized in Emacs >= 24.1, built with ImageMagick support.]. +Their value can be the following: +- (default) Non-~nil~, use the actual width of images when inlining + them. If the actual width is too wide, limit it according to + ~org-image-max-width~. +- When set to a number, use ImageMagick (when available) to set the + image's width to this value. +- When set to a number in a list, try to get the width from any + =#+ATTR.*= keyword if it matches a width specification like: #+begin_example - ,#+ATTR_HTML: :align right - ,#+ATTR_ORG: :align center - [[/path/to/image/file.png]] + ,#+ATTR_HTML: :width 300px #+end_example - will be displayed centered in Emacs but exported right-aligned to - HTML. - - When =#+ATTR_ORG= is not set, inline image alignment is also read - from the =:center= attribute supported by some export backends (like - HTML, LaTeX and Beamer.) + and fall back on that number if none is found. +- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword + and fall back on the original width or ~org-image-max-width~ if + none is found. + +~org-image-max-width~ limits the maximum displayed image width, but +only when the image width is not set explicitly. Possible maximum +width can be set to: +- (default) ~fill-column~, limit width to ~fill-column~ number of + characters. +- ~window~, limit width to current window width. +- integer number, limit width to that specified number of pixels. +- ~nil~, do not limit the width. + +#+vindex: org-image-align +Org mode can left-align, center or right-align the display of inline +images. This setting is controlled (globally) by ~org-image-align~. +Only standalone images are affected, corresponding to links with no +surrounding text in their paragraph except for whitespace. Its +value can be the following: +- (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. +- The symbol ~center~, which will preview links centered in the + Emacs window. +- The symbol ~right~, which will preview links right-aligned in the + Emacs window. + +Inline image alignment can be specified for each link using the +=#+ATTR.*= keyword if it matches an alignment specification like: +#+begin_example +,#+ATTR_HTML: :align center +#+end_example +Org will use the alignment specification from any =#+ATTR.*= +keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= +(if present) will override the others. For instance, this link +#+begin_example +,#+ATTR_HTML: :align right +,#+ATTR_ORG: :align center +[[/path/to/image/file.png]] +#+end_example +will be displayed centered in Emacs but exported right-aligned to +HTML. -#+vindex: org-cycle-inline-images-display -Inline images can also be displayed when cycling the folding state. -When custom option ~org-cycle-inline-images-display~ is set, the -visible inline images under subtree will be displayed automatically. +When =#+ATTR_ORG= is not set, inline image alignment is also read from +the =:center= attribute supported by some export backends (like HTML, +LaTeX and Beamer.) ** Captions :PROPERTIES: @@ -21794,6 +21835,67 @@ ** Adding Hyperlink Types topic. It also provides a default description. The function ~org-insert-link~ can insert it back into an Org buffer later on. +** Adding Hyperlink preview +:PROPERTIES: +:DESCRIPTION: Preview behavior for new hyperlink types. +:END: +#+cindex: hyperlinks, adding preview behavior + +By default, Org supports previewing external links for links ot type +=file= and =attachment= that point to image files. (See [[*Images]].) + +Support for previewing other link types inline can be added to Org in +the following way: + +1. Add a =:preview= link parameter to the link type using + ~org-link-set-parameters~. As an example, here we add previews for + the =docview= link type. + + #+begin_src emacs-lisp +(org-link-set-parameters + "docview" :preview #'org-link-docview-preview) + #+end_src + +2. The value of the =:preview= parameter must be a function that + accepts three arguments: + - an overlay placed from the start to the end of the link, + - the link path, as a string, and + - the syntax node for the link. + It must return a non-nil value to indicate preview success. A + value of =nil= implies that the preview failed, and the overlay + placed on the link will be removed. + + In our example, we use the =convert= program (part of the + =imagemagick= suite of tools) to create the thumbnail that is + displayed inline. + + #+begin_src emacs-lisp +(defun org-link-docview-preview (ov path _elem) + "Preview file at PATH in overlay OV. + +_ELEM is the syntax node of the link element." + (when (executable-find "convert") + (let* ((path (expand-file-name (substitute-in-file-name path))) + (output-file (expand-file-name (concat "org-docview-preview-" + (substring (sha1 path) 0 10) + ".png") + temporary-file-directory))) + ;; Create or find preview for path + (when (or (file-readable-p output-file) + (= 0 (call-process + "convert" + nil (get-buffer-create "*Org Docview Preview Output*") nil + "-thumbnail" "x320" "-colorspace" "rgb" + "-background" "white" "-alpha" "remove" "-strip" + (concat path "[0]") output-file))) + ;; If preview image is available, display it via the overlay + (overlay-put ov 'display (create-image output-file)))))) + #+end_src + +3. Now previews of docview links for supported document types (PDF, + djvu) are generated (along with image file previews) when calling + ~org-link-preview~. + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index d8018690f..a033fafcd 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole buffer (or narrowed part of the buffer). With prefix argument, it also forced displaying image links with description. -Now, =C-c C-x C-v= is bound to a new command -~org-toggle-inline-images-command~, which uses different defaults: +Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which +uses different defaults: -1. By default, it toggles image at point or, if there is no image at - point, images in current entry +1. When the region is active, images in the region are previewed -2. When region is active, it is honored +2. Otherwise, if there is an image at point, it is toggled. If there + is no image at point, images in the current entry are previewed -3. =C-u= argument changed its meaning. Now, it forces toggling images - in the whole buffer +3. With the =C-u= argument, image previews in the active region or at + point are cleared instead -4. =C-u C-u= argument unconditionally hides all the images in buffer +4. The =C-u C-u= argument unconditionally shows all images in the + accessible portion of the buffer -5. Displaying images over links with description can be forced using +5. The =C-u C-u C-u= argument unconditionally clears all images in the + accessible portion of the buffer + +6. Displaying images over links with description can be forced using numeric argument: - ~C-u 1~ for toggling all images at point/current entry - ~C-u 11~ for toggling all images in buffer -The old ~org-toggle-inline-images~ command is still available. You -can bind it back to =C-c C-x C-v= by adding the following to you config: +(The first five of these prefix arg behaviors are the same as that of +the ~org-latex-preview~ command.) + +In addition to images, ~org-link-preview~ can also be used to preview +Org links of all types for which preview behavior is defined, see +[[#link-preview][previews for arbitrary link types]]. + +The old ~org-toggle-inline-images~ command is obsolete but still +available. You can bind it back to =C-c C-x C-v= by adding the +following to your config: #+begin_src emacs-lisp (eval-after-load 'org-keys (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) @@ -76,6 +88,16 @@ now have diary timestamps included as well. # We list the most important features, and the features that may # require user action to be used. +*** All Org link types can be previewed +:PROPERTIES: +:CUSTOM_ID: link-preview +:END: + +Org links support a new parameter =:preview= that can be used to +preview arbitrary link types. The value of this parameter should be a +function that is called to preview links of the corresponding type +(see ~org-link-parameters~). + *** Alignment of image previews can be customized This is not a new feature. It has been added in Org 9.7, but not @@ -97,6 +119,17 @@ or newer. # adding new customizations, or changing the interpretation of the # existing customizations. +*** New option ~org-link-preview-batch-size~ + +Org link previews are generated asynchronously and a few at a time, in +batches. This option controls the number of links that are previewed +in each batch. + +*** New option ~org-link-preview-delay~ + +Org link previews are generated asynchronously. This option controls +the minimum idle time in seconds between previews of batches of links. + *** Allow disabling macro replacement during export New custom option ~org-export-replace-macros~ controls whether Org @@ -139,6 +172,26 @@ bibliography format requires them to be written in title-case. # This also includes changes in function behavior from Elisp perspective. +*** New command ~org-link-preview~ to preview Org links + +This command replaces ~org-toggle-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer + +This command replaces ~org-display-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer + +This command replaces ~org-remove-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer + +This command replaces ~org-redisplay-inline-images~, which is now +obsolete. + *** ob-sqlite: Added ability to open a database in readonly mode Added option :readonly to ob-sqlite. @@ -163,6 +216,24 @@ accept the INFO channel and return a string. This makes it possible to dynamically generate the content of the resulting ~<head>~ tag in the resulting HTML document. +** Removed or renamed functions and variables + +*** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~ + +Inline image previews in Org mode are now provided by the more general +link previews feature. The behavior with regards to image links is +unchanged. + +*** ~org-cycle-inline-images-display~ is renamed to ~org-cycle-link-previews-display~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + +*** ~org-startup-with-inline-images~ is renamed to ~org-startup-with-link-previews~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + ** Miscellaneous *** Org mode no longer prevents =flyspell= from spell-checking inside =LOGBOOK= drawers -- 2.46.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v9] Inline image display as part of a new org-link-preview system 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 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-29 18:46 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > None of the above have been obsoleted. Let me know if I should make > them all obsolete aliases instead. I think that an alias is fine. At least, it is the approach I am leaning to recently. >> Aside: I am wondering if we should also attach preview toggle to <TAB> >> when ~org-cycle-inline-images-display~ is non-nil and there is a link at >> point. > > I haven't added this. I think it's better not to do this, since cycling > is expected to only involve hiding/showing buffer contents. Generating > a preview might cause new external processes to be started. I can add > it if you think we should. ~org-cycle-inline-images-display~ already invokes link previews. What I had in mind is extending it a tiny bit - I myself have this feature locally and find it useful. You do not have to implement it though. It is slightly out of scope of this particular patchset. > Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types > ... > ... > + By default, only image links without a description are displayed. > + You can force displaying previews for all supported links using a > + numeric argument of ~1~ to toggle all previews in the active > + region, the link at point or the current section. A numeric prefix > + argument of ~11~ will toggle previews in the whole buffer. It is slightly inaccurate I think. Once new :preview handlers are added to non-image link types, the same rule about links with/without description will apply to non-image links. So, we may better explain C-1 and C-11 arguments in more generic terms: "image links without description" -> "previews for links without description" -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-10-29 18:46 ` Ihor Radchenko @ 2024-10-29 20:45 ` Karthik Chikmagalur 2024-10-30 17:32 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-10-29 20:45 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 2580 bytes --] New version of Patch 0002 (documentation patch) attached. Patch 0001 (code patch) is unchanged. >> None of the above have been obsoleted. Let me know if I should make >> them all obsolete aliases instead. > > I think that an alias is fine. > At least, it is the approach I am leaning to recently. Okay, we can obsolete it later if deemed necessary. >> I haven't added this. I think it's better not to do this, since cycling >> is expected to only involve hiding/showing buffer contents. Generating >> a preview might cause new external processes to be started. I can add >> it if you think we should. > > ~org-cycle-inline-images-display~ already invokes link previews. While that's true, link previews don't start new OS processes right now. They can for custom :preview types. > You do not have to implement it though. It is slightly out of scope of > this particular patchset. Okay, let's leave it out. This patchset has become reasonably large already. >> Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types >> ... >> ... >> + By default, only image links without a description are displayed. >> + You can force displaying previews for all supported links using a >> + numeric argument of ~1~ to toggle all previews in the active >> + region, the link at point or the current section. A numeric prefix >> + argument of ~11~ will toggle previews in the whole buffer. > > It is slightly inaccurate I think. Once new :preview handlers are added > to non-image link types, the same rule about links with/without > description will apply to non-image links. So, we may better explain C-1 > and C-11 arguments in more generic terms: "image links without > description" -> "previews for links without description" Fixed in the the documentation patch. I have one more question for you: The overlay property that is used to check if it corresponds to a link preview is inconsistent with the rest of Org right now. We are checking for the non-nil overlay property `org-image-overlay'. 1. Should we rename this property to `org-link-preview-overlay', or `org-link-preview'? 2. Org's LaTeX previews (both the existing and WIP versions) use a different system. They set two overlay properties: `category' set to `org', and `org-overlay-type', set to `org-latex-preview'. Should we use a consistent set of properties to identify Org-related overlays? This is an implementation detail and none of it really matters, but I can unify them if required. If this is not an issue and there's nothing else, I think we are good to merge. Karthik [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --] [-- Type: text/x-patch, Size: 19759 bytes --] From 7d4656c3718e303330c02e0025817851d8761b5b Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Sun, 27 Oct 2024 20:22:59 -0700 Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types * etc/ORG-NEWS: Mention new commands and options for link preview. * doc/org-manual.org: Add sections on - previewing external links (Images and link previews), and - adding the link preview feature to other link types (Adding Hyperlink preview). --- doc/org-manual.org | 291 ++++++++++++++++++++++++++++++--------------- etc/ORG-NEWS | 93 +++++++++++++-- 2 files changed, 279 insertions(+), 105 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 94fabde3b..c94358779 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11778,7 +11778,75 @@ ** Literal Examples the end of the current line. Then the label is stored as a link =(label)=, for retrieval with {{{kbd(C-c C-l)}}}. -** Images +** Images and link previews +:PROPERTIES: +:DESCRIPTION: Preview links in the buffer. +:END: + +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By +default, only image links[fn::Image links are =file:= and +=attachment:= links to existing image files; Emacs should be able to +display the linked images (see ~image-types~ variable)] can be +previewed inline, where the images are displayed in place of the link +path. + +You can customize the previews as described in the [[*Adding Hyperlink +preview]] section. Link previews do not have to display images -- any +kind of [[info:elisp#Overlay Properties][display decoration]] can be used. + +You can preview the supported link types in the whole buffer, in the +current section, active region or at point with the following commands: + +- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) :: + + #+kindex: C-c C-x C-v + #+findex: org-link-preview + Create inline previews for external links in the active region, the + link at point or in the current section. With a prefix argument, + clear link previews at point or in the current entry. With a double + prefix argument, preview all links in the buffer. With triple + prefix argument, hide previews for all links in the buffer. + + By default, only links without descriptions are previewed. You + can force displaying previews for all supported links (including + links with descriptions) using a numeric argument of ~1~. This + will toggle all previews in the active region, the link at point + or the current section. A numeric prefix argument of ~11~ will + toggle previews in the whole buffer instead. + + #+vindex: org-startup-with-link-previews + Org mode can display link previews automatically when opening + buffers. Either customize ~org-startup-with-link-previews~ or use + the =#+STARTUP: linkpreviews= keyword to enable previews for that + specific buffer when opening it. =#+STARTUP: nolinkpreviews= will + disable previews on startup in the buffer. + +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: + + #+kindex: C-c C-x C-M-v + #+findex: org-link-preview-refresh + Assure inline display of external link previews in the whole buffer + and refresh them. + +- (~org-link-preview-region~) :: + + #+findex: org-link-preview-region + Create inline previews for external links in the active region, or + the buffer. With a prefix argument, also preview links with a text + description part. + +- (~org-link-preview-clear~) :: + + #+findex: org-link-preview-clear + Clear external link previews in the active region, or the buffer. + +#+vindex: org-cycle-inline-images-display +Link previews can also be displayed when cycling the folding state. +When the custom option ~org-cycle-link-previews-display~ is set, +supported link types under the subtree (including images) will be +displayed automatically. + +*** Images :PROPERTIES: :DESCRIPTION: Display an image. :END: @@ -11803,102 +11871,76 @@ ** Images [[./img/a.jpg]] #+end_example -Such images can be displayed within the buffer with the following -command: - -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: - - #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images-command - Toggle the inline display of linked images in current section or at - point. With a prefix argument, toggle inline images in the whole - buffer. With double prefix argument, hide all the images in buffer. - - By default, only the image links without description are displayed. - You can force displaying all the images using numeric argument to - toggle all the images in current section (~1~ argument) or the whole - buffer (~11~ argument). - - #+vindex: org-startup-with-inline-images - You can ask for inline images to be displayed at - startup by configuring the variable - ~org-startup-with-inline-images~[fn:: The variable - ~org-startup-with-inline-images~ can be set within a buffer with the - =STARTUP= options =inlineimages= and =noinlineimages=.]. - - - #+vindex: org-image-actual-width - #+vindex: org-image-max-width - #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property - By default, Org mode displays inline images according to their - actual width, but no wider than ~fill-column~ characters. - - You can customize the displayed image width using - ~org-image-actual-width~ variable (globally) or - =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can - be customized in Emacs >= 24.1, built with ImageMagick support.]. - Their value can be the following: - - (default) Non-~nil~, use the actual width of images when inlining - them. If the actual width is too wide, limit it according to - ~org-image-max-width~. - - When set to a number, use ImageMagick (when available) to set the - image's width to this value. - - When set to a number in a list, try to get the width from any - =#+ATTR.*= keyword if it matches a width specification like: - #+begin_example - ,#+ATTR_HTML: :width 300px - #+end_example - and fall back on that number if none is found. - - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword - and fall back on the original width or ~org-image-max-width~ if - none is found. - - ~org-image-max-width~ limits the maximum displayed image width, but - only when the image width is not set explicitly. Possible maximum - width can be set to: - - (default) ~fill-column~, limit width to ~fill-column~ number of - characters. - - ~window~, limit width to current window width. - - integer number, limit width to that specified number of pixels. - - ~nil~, do not limit the width. - - #+vindex: org-image-align - Org mode can left-align, center or right-align the display of inline - images. This setting is controlled (globally) by ~org-image-align~. - Only standalone images are affected, corresponding to links with no - surrounding text in their paragraph except for whitespace. Its - value can be the following: - - (default) The symbol ~left~, which inserts the image where the - link appears in the buffer. - - The symbol ~center~, which will preview links centered in the - Emacs window. - - The symbol ~right~, which will preview links right-aligned in the - Emacs window. - - Inline image alignment can be specified for each link using the - =#+ATTR.*= keyword if it matches an alignment specification like: - #+begin_example - ,#+ATTR_HTML: :align center - #+end_example - Org will use the alignment specification from any =#+ATTR.*= - keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= - (if present) will override the others. For instance, this link +When [[*Images and link previews][link previews]] are displayed as images, the image size and +alignment can be further customized. + +#+vindex: org-image-actual-width +#+vindex: org-image-max-width +#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property +By default, Org mode displays inline images according to their +actual width, but no wider than ~fill-column~ characters. + +You can customize the displayed image width using +~org-image-actual-width~ variable (globally) or +=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can +be customized in Emacs >= 24.1, built with ImageMagick support.]. +Their value can be the following: +- (default) Non-~nil~, use the actual width of images when inlining + them. If the actual width is too wide, limit it according to + ~org-image-max-width~. +- When set to a number, use ImageMagick (when available) to set the + image's width to this value. +- When set to a number in a list, try to get the width from any + =#+ATTR.*= keyword if it matches a width specification like: #+begin_example - ,#+ATTR_HTML: :align right - ,#+ATTR_ORG: :align center - [[/path/to/image/file.png]] + ,#+ATTR_HTML: :width 300px #+end_example - will be displayed centered in Emacs but exported right-aligned to - HTML. + and fall back on that number if none is found. +- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword + and fall back on the original width or ~org-image-max-width~ if + none is found. + +~org-image-max-width~ limits the maximum displayed image width, but +only when the image width is not set explicitly. Possible maximum +width can be set to: +- (default) ~fill-column~, limit width to ~fill-column~ number of + characters. +- ~window~, limit width to current window width. +- integer number, limit width to that specified number of pixels. +- ~nil~, do not limit the width. + +#+vindex: org-image-align +Org mode can left-align, center or right-align the display of inline +images. This setting is controlled (globally) by ~org-image-align~. +Only standalone images are affected, corresponding to links with no +surrounding text in their paragraph except for whitespace. Its +value can be the following: +- (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. +- The symbol ~center~, which will preview links centered in the + Emacs window. +- The symbol ~right~, which will preview links right-aligned in the + Emacs window. + +Inline image alignment can be specified for each link using the +=#+ATTR.*= keyword if it matches an alignment specification like: +#+begin_example +,#+ATTR_HTML: :align center +#+end_example +Org will use the alignment specification from any =#+ATTR.*= +keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= +(if present) will override the others. For instance, this link +#+begin_example +,#+ATTR_HTML: :align right +,#+ATTR_ORG: :align center +[[/path/to/image/file.png]] +#+end_example +will be displayed centered in Emacs but exported right-aligned to +HTML. - When =#+ATTR_ORG= is not set, inline image alignment is also read - from the =:center= attribute supported by some export backends (like - HTML, LaTeX and Beamer.) - -#+vindex: org-cycle-inline-images-display -Inline images can also be displayed when cycling the folding state. -When custom option ~org-cycle-inline-images-display~ is set, the -visible inline images under subtree will be displayed automatically. +When =#+ATTR_ORG= is not set, inline image alignment is also read from +the =:center= attribute supported by some export backends (like HTML, +LaTeX and Beamer.) ** Captions :PROPERTIES: @@ -21794,6 +21836,67 @@ ** Adding Hyperlink Types topic. It also provides a default description. The function ~org-insert-link~ can insert it back into an Org buffer later on. +** Adding Hyperlink preview +:PROPERTIES: +:DESCRIPTION: Preview behavior for new hyperlink types. +:END: +#+cindex: hyperlinks, adding preview behavior + +By default, Org supports previewing external links for links ot type +=file= and =attachment= that point to image files. (See [[*Images]].) + +Support for previewing other link types inline can be added to Org in +the following way: + +1. Add a =:preview= link parameter to the link type using + ~org-link-set-parameters~. As an example, here we add previews for + the =docview= link type. + + #+begin_src emacs-lisp +(org-link-set-parameters + "docview" :preview #'org-link-docview-preview) + #+end_src + +2. The value of the =:preview= parameter must be a function that + accepts three arguments: + - an overlay placed from the start to the end of the link, + - the link path, as a string, and + - the syntax node for the link. + It must return a non-nil value to indicate preview success. A + value of =nil= implies that the preview failed, and the overlay + placed on the link will be removed. + + In our example, we use the =convert= program (part of the + =imagemagick= suite of tools) to create the thumbnail that is + displayed inline. + + #+begin_src emacs-lisp +(defun org-link-docview-preview (ov path _elem) + "Preview file at PATH in overlay OV. + +_ELEM is the syntax node of the link element." + (when (executable-find "convert") + (let* ((path (expand-file-name (substitute-in-file-name path))) + (output-file (expand-file-name (concat "org-docview-preview-" + (substring (sha1 path) 0 10) + ".png") + temporary-file-directory))) + ;; Create or find preview for path + (when (or (file-readable-p output-file) + (= 0 (call-process + "convert" + nil (get-buffer-create "*Org Docview Preview Output*") nil + "-thumbnail" "x320" "-colorspace" "rgb" + "-background" "white" "-alpha" "remove" "-strip" + (concat path "[0]") output-file))) + ;; If preview image is available, display it via the overlay + (overlay-put ov 'display (create-image output-file)))))) + #+end_src + +3. Now previews of docview links for supported document types (PDF, + djvu) are generated (along with image file previews) when calling + ~org-link-preview~. + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index d8018690f..a033fafcd 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole buffer (or narrowed part of the buffer). With prefix argument, it also forced displaying image links with description. -Now, =C-c C-x C-v= is bound to a new command -~org-toggle-inline-images-command~, which uses different defaults: +Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which +uses different defaults: -1. By default, it toggles image at point or, if there is no image at - point, images in current entry +1. When the region is active, images in the region are previewed -2. When region is active, it is honored +2. Otherwise, if there is an image at point, it is toggled. If there + is no image at point, images in the current entry are previewed -3. =C-u= argument changed its meaning. Now, it forces toggling images - in the whole buffer +3. With the =C-u= argument, image previews in the active region or at + point are cleared instead -4. =C-u C-u= argument unconditionally hides all the images in buffer +4. The =C-u C-u= argument unconditionally shows all images in the + accessible portion of the buffer -5. Displaying images over links with description can be forced using +5. The =C-u C-u C-u= argument unconditionally clears all images in the + accessible portion of the buffer + +6. Displaying images over links with description can be forced using numeric argument: - ~C-u 1~ for toggling all images at point/current entry - ~C-u 11~ for toggling all images in buffer -The old ~org-toggle-inline-images~ command is still available. You -can bind it back to =C-c C-x C-v= by adding the following to you config: +(The first five of these prefix arg behaviors are the same as that of +the ~org-latex-preview~ command.) + +In addition to images, ~org-link-preview~ can also be used to preview +Org links of all types for which preview behavior is defined, see +[[#link-preview][previews for arbitrary link types]]. + +The old ~org-toggle-inline-images~ command is obsolete but still +available. You can bind it back to =C-c C-x C-v= by adding the +following to your config: #+begin_src emacs-lisp (eval-after-load 'org-keys (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) @@ -76,6 +88,16 @@ now have diary timestamps included as well. # We list the most important features, and the features that may # require user action to be used. +*** All Org link types can be previewed +:PROPERTIES: +:CUSTOM_ID: link-preview +:END: + +Org links support a new parameter =:preview= that can be used to +preview arbitrary link types. The value of this parameter should be a +function that is called to preview links of the corresponding type +(see ~org-link-parameters~). + *** Alignment of image previews can be customized This is not a new feature. It has been added in Org 9.7, but not @@ -97,6 +119,17 @@ or newer. # adding new customizations, or changing the interpretation of the # existing customizations. +*** New option ~org-link-preview-batch-size~ + +Org link previews are generated asynchronously and a few at a time, in +batches. This option controls the number of links that are previewed +in each batch. + +*** New option ~org-link-preview-delay~ + +Org link previews are generated asynchronously. This option controls +the minimum idle time in seconds between previews of batches of links. + *** Allow disabling macro replacement during export New custom option ~org-export-replace-macros~ controls whether Org @@ -139,6 +172,26 @@ bibliography format requires them to be written in title-case. # This also includes changes in function behavior from Elisp perspective. +*** New command ~org-link-preview~ to preview Org links + +This command replaces ~org-toggle-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer + +This command replaces ~org-display-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer + +This command replaces ~org-remove-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer + +This command replaces ~org-redisplay-inline-images~, which is now +obsolete. + *** ob-sqlite: Added ability to open a database in readonly mode Added option :readonly to ob-sqlite. @@ -163,6 +216,24 @@ accept the INFO channel and return a string. This makes it possible to dynamically generate the content of the resulting ~<head>~ tag in the resulting HTML document. +** Removed or renamed functions and variables + +*** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~ + +Inline image previews in Org mode are now provided by the more general +link previews feature. The behavior with regards to image links is +unchanged. + +*** ~org-cycle-inline-images-display~ is renamed to ~org-cycle-link-previews-display~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + +*** ~org-startup-with-inline-images~ is renamed to ~org-startup-with-link-previews~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + ** Miscellaneous *** Org mode no longer prevents =flyspell= from spell-checking inside =LOGBOOK= drawers -- 2.46.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-10-29 20:45 ` [PATCH v10] " Karthik Chikmagalur @ 2024-10-30 17:32 ` Ihor Radchenko 2024-10-30 19:50 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-30 17:32 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > I have one more question for you: > > The overlay property that is used to check if it corresponds to a link > preview is inconsistent with the rest of Org right now. We are checking > for the non-nil overlay property `org-image-overlay'. > > 1. Should we rename this property to `org-link-preview-overlay', or > `org-link-preview'? > > 2. Org's LaTeX previews (both the existing and WIP versions) use a > different system. They set two overlay properties: `category' set to > `org', and `org-overlay-type', set to `org-latex-preview'. Should we > use a consistent set of properties to identify Org-related overlays? May you elaborate on why you went with category = 'org? category property has a special meaning in overlays. > This is an implementation detail and none of it really matters, but I > can unify them if required. What I am certain about is that we do need to keep `org-image-overlay' property as it might be used by third-party code. We may add something extra to make things more consistent across Org mode, but `org-image-overlay' must be kept for backwards compatibility reasons. > +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By I just started doing a final pass of the proofreading and figured that we should not use links to other parts of the manual or other manuals that have description. This is because texinfo formats them unexpectedly, as Section X.X [description], page YYY. May you please run make docs with your patch and look into the results org.pdf file? We need to make sure that things the new section is readable there. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-10-30 17:32 ` Ihor Radchenko @ 2024-10-30 19:50 ` Karthik Chikmagalur 2024-10-31 18:16 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-10-30 19:50 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 1900 bytes --] >> 2. Org's LaTeX previews (both the existing and WIP versions) use a >> different system. They set two overlay properties: `category' set to >> `org', and `org-overlay-type', set to `org-latex-preview'. Should we >> use a consistent set of properties to identify Org-related overlays? > > May you elaborate on why you went with category = 'org? category > property has a special meaning in overlays. I stand corrected -- the `category' is not being set either in the new or old LaTeX preview code. The only overlay property used for identification is (overlay-put ov 'org-overlay-type 'org-latex-overlay) in `org--make-preview-overlay'. We have retained this behavior, we did not add to or change it in the LaTeX preview rewrite patchset. >> This is an implementation detail and none of it really matters, but I >> can unify them if required. > > What I am certain about is that we do need to keep `org-image-overlay' > property as it might be used by third-party code. > > We may add something extra to make things more consistent across Org > mode, but `org-image-overlay' must be kept for backwards compatibility > reasons. Noted. >> +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By > > I just started doing a final pass of the proofreading and figured that > we should not use links to other parts of the manual or other manuals > that have description. This is because texinfo formats them > unexpectedly, as Section X.X [description], page YYY. May you please run > make docs with your patch and look into the results org.pdf file? > > We need to make sure that things the new section is readable there. make docs failed with the following output (also included as attachment for completeness): https://paste.karthinks.com/4ca52197-org-make-docs-errors.html It seems unrelated to what we want to check. Any ideas how to fix it? Karthik [-- Attachment #2: Compilation errors when running 'make docs' for Org --] [-- Type: text/plain, Size: 4780 bytes --] -*- mode: compilation; default-directory: "~/.local/share/git/elpaca/repos/org/" -*- Compilation started at Wed Oct 30 12:41:09 make docs ====================================================== = Invoke "make help" for a synopsis of make targets. = = Created a default local.mk template. = = Setting "oldorg" as the default target. = = Please adapt local.mk to your local setup! = ====================================================== Loading /home/karthik/.local/share/git/elpaca/repos/org/lisp/org-compat.el (source)... Loading /home/karthik/.local/share/git/elpaca/repos/org/mk/org-fixup.el (source)... make -C doc info make[1]: Entering directory '/home/karthik/.local/share/git/elpaca/repos/org/doc' org-version: 9.8-pre (release_9.7.12-377-g806f01) makeinfo --no-split org.texi -o org.info org.texi:16204: warning: @anchor should not appear on @item line org.texi:16217: warning: @anchor should not appear on @item line emacs -Q -batch --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \ --eval '(add-to-list `load-path "../lisp")' \ --eval '(load "../mk/org-fixup.el")' \ --eval '(setq gc-cons-threshold (* 50 1000 1000))' \ --eval '(org-make-guide)' Loading /home/karthik/.local/share/git/elpaca/repos/org/mk/org-fixup.el (source)... makeinfo --no-split orgguide.texi -o orgguide.info orgguide.texi:1431: warning: @node name should not contain `,': Capture, Refile, Archive make[1]: Leaving directory '/home/karthik/.local/share/git/elpaca/repos/org/doc' make -C doc html make[1]: Entering directory '/home/karthik/.local/share/git/elpaca/repos/org/doc' makeinfo --html --number-sections --css-ref "https://www.gnu.org/software/emacs/manual.css" --no-split -o org.html org.texi org.texi:16204: warning: @anchor should not appear on @item line org.texi:16217: warning: @anchor should not appear on @item line makeinfo --html --number-sections --css-ref "https://www.gnu.org/software/emacs/manual.css" --no-split -o orgguide.html orgguide.texi make[1]: Leaving directory '/home/karthik/.local/share/git/elpaca/repos/org/doc' make -C doc pdf make[1]: Entering directory '/home/karthik/.local/share/git/elpaca/repos/org/doc' texi2pdf --batch --clean --expand org.texi org.texi:16204: warning: @anchor should not appear on @item line org.texi:16217: warning: @anchor should not appear on @item line This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/nixos.org) (preloaded format=pdftex) restricted \write18 enabled. entering extended mode (../../../org.t2d/pdf/src/org.texi (/home/karthik/.local/share/git/elpaca/repos/org/doc/texinfo.tex Loading texinfo [version 2013-09-11.11]: pdf, fonts, markup, glyphs, page headings, tables, conditionals, indexing, sectioning, toc, environments, defuns, macros, cross references, insertions, localization, formatting, and turning on texinfo input format.) Runaway argument? {en_\finish }\else \globaldefs = 1 \input txi-en.tex \fi \closein 1 \endgroup \ ETC. ../../../org.t2d/pdf/src/org.texi:14: Paragraph ended before \documentlanguaget rywithoutunderscore was complete. <to be read again> \par l.14 ? ../../../org.t2d/pdf/src/org.texi:14: Emergency stop. <to be read again> \par l.14 ../../../org.t2d/pdf/src/org.texi:14: ==> Fatal error occurred, no output PDF file produced! Transcript written on org.log. This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/nixos.org) (preloaded format=pdftex) restricted \write18 enabled. entering extended mode (../../../org.t2d/pdf/src/org.texi (/home/karthik/.local/share/git/elpaca/repos/org/doc/texinfo.tex Loading texinfo [version 2013-09-11.11]: pdf, fonts, markup, glyphs, page headings, tables, conditionals, indexing, sectioning, toc, environments, defuns, macros, cross references, insertions, localization, formatting, and turning on texinfo input format.) (./org.aux) Runaway argument? {en_\finish }\else \globaldefs = 1 \input txi-en.tex \fi \closein 1 \endgroup \ ETC. ../../../org.t2d/pdf/src/org.texi:14: Paragraph ended before \documentlanguaget rywithoutunderscore was complete. <to be read again> \par l.14 ? ../../../org.t2d/pdf/src/org.texi:14: Emergency stop. <to be read again> \par l.14 ../../../org.t2d/pdf/src/org.texi:14: ==> Fatal error occurred, no output PDF file produced! Transcript written on org.log. /run/current-system/sw/bin/texi2dvi: pdftex exited with bad status, quitting. make[1]: *** [Makefile:92: org.pdf] Error 1 make[1]: Leaving directory '/home/karthik/.local/share/git/elpaca/repos/org/doc' make: *** [mk/targets.mk:122: pdf] Error 2 Compilation exited abnormally with code 2 at Wed Oct 30 12:41:21 ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-10-30 19:50 ` Karthik Chikmagalur @ 2024-10-31 18:16 ` Ihor Radchenko 2024-11-14 5:43 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-10-31 18:16 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > make docs failed with the following output (also included as attachment > for completeness): > ... > makeinfo --no-split org.texi -o org.info > org.texi:16204: warning: @anchor should not appear on @item line > org.texi:16217: warning: @anchor should not appear on @item line > ... makeinfo does not report errors, so you are probably experiencing a bug in texinfo itself. Try to upgrade it. Also, these warnings are probably coming from a specific version of texinfo - they have been removed in the latest version after my report. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-10-31 18:16 ` Ihor Radchenko @ 2024-11-14 5:43 ` Karthik Chikmagalur 2024-11-14 17:14 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-14 5:43 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >> make docs failed with the following output (also included as attachment >> for completeness): >> ... >> makeinfo --no-split org.texi -o org.info >> org.texi:16204: warning: @anchor should not appear on @item line >> org.texi:16217: warning: @anchor should not appear on @item line >> ... > > makeinfo does not report errors, so you are probably experiencing a bug > in texinfo itself. Try to upgrade it. > > Also, these warnings are probably coming from a specific version of > texinfo - they have been removed in the latest version after my report. I'm using texinfo 7.1 now, the pdf build is still failing when I try "make docs", with the same errors. Not sure what to do now. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-11-14 5:43 ` Karthik Chikmagalur @ 2024-11-14 17:14 ` Ihor Radchenko 2024-11-14 22:10 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-11-14 17:14 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > I'm using texinfo 7.1 now, the pdf build is still failing when I try > "make docs", with the same errors. Not sure what to do now. May you please try to create a reproducer then? Starting from a freshly cloned Org repo. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 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:11 ` [PATCH v10] " Ihor Radchenko 0 siblings, 2 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-14 22:10 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 366 bytes --] >> I'm using texinfo 7.1 now, the pdf build is still failing when I try >> "make docs", with the same errors. Not sure what to do now. > > May you please try to create a reproducer then? > Starting from a freshly cloned Org repo. Here's what I get when I run `make docs' after pulling from main. Should I create a bug report? I'm not sure yet if PEBKAC. Karthik [-- Attachment #2: Type: text/plain, Size: 5617 bytes --] -*- mode: compilation; default-directory: "~/.local/share/git/elpaca/repos/org-origin-main/" -*- Compilation started at Thu Nov 14 14:05:02 make docs ====================================================== = Invoke "make help" for a synopsis of make targets. = = Created a default local.mk template. = = Setting "oldorg" as the default target. = = Please adapt local.mk to your local setup! = ====================================================== Loading /home/karthik/.local/share/git/elpaca/repos/org-origin-main/lisp/org-compat.el (source)... Loading /home/karthik/.local/share/git/elpaca/repos/org-origin-main/mk/org-fixup.el (source)... make -C doc info make[1]: Entering directory '/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc' emacs -Q -batch --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \ --eval '(add-to-list `load-path "../lisp")' \ --eval '(load "../mk/org-fixup.el")' \ --eval '(setq gc-cons-threshold (* 50 1000 1000))' \ --eval '(org-make-manual)' Loading /home/karthik/.local/share/git/elpaca/repos/org-origin-main/mk/org-fixup.el (source)... WARNING: No org-loaddefs.el file could be found from where org.el is loaded. You need to run "make" or "make autoloads" from Org lisp directory org-version: 9.8-pre (release_9.7.12-179-g3ff21c) makeinfo --no-split org.texi -o org.info org.texi:16168: warning: @anchor should not appear on @item line org.texi:16181: warning: @anchor should not appear on @item line emacs -Q -batch --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \ --eval '(add-to-list `load-path "../lisp")' \ --eval '(load "../mk/org-fixup.el")' \ --eval '(setq gc-cons-threshold (* 50 1000 1000))' \ --eval '(org-make-guide)' Loading /home/karthik/.local/share/git/elpaca/repos/org-origin-main/mk/org-fixup.el (source)... WARNING: No org-loaddefs.el file could be found from where org.el is loaded. You need to run "make" or "make autoloads" from Org lisp directory makeinfo --no-split orgguide.texi -o orgguide.info orgguide.texi:1431: warning: @node name should not contain `,': Capture, Refile, Archive make[1]: Leaving directory '/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc' make -C doc html make[1]: Entering directory '/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc' makeinfo --html --number-sections --css-ref "https://www.gnu.org/software/emacs/manual.css" --no-split -o org.html org.texi org.texi:16168: warning: @anchor should not appear on @item line org.texi:16181: warning: @anchor should not appear on @item line makeinfo --html --number-sections --css-ref "https://www.gnu.org/software/emacs/manual.css" --no-split -o orgguide.html orgguide.texi make[1]: Leaving directory '/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc' make -C doc pdf make[1]: Entering directory '/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc' texi2pdf --batch --clean --expand org.texi org.texi:16168: warning: @anchor should not appear on @item line org.texi:16181: warning: @anchor should not appear on @item line This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/nixos.org) (preloaded format=pdftex) restricted \write18 enabled. entering extended mode (../../../org.t2d/pdf/src/org.texi (/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc/texinfo.tex Loading texinfo [version 2013-09-11.11]: pdf, fonts, markup, glyphs, page headings, tables, conditionals, indexing, sectioning, toc, environments, defuns, macros, cross references, insertions, localization, formatting, and turning on texinfo input format.) Runaway argument? {en_\finish }\else \globaldefs = 1 \input txi-en.tex \fi \closein 1 \endgroup \ ETC. ../../../org.t2d/pdf/src/org.texi:14: Paragraph ended before \documentlanguaget rywithoutunderscore was complete. <to be read again> \par l.14 ? ../../../org.t2d/pdf/src/org.texi:14: Emergency stop. <to be read again> \par l.14 ../../../org.t2d/pdf/src/org.texi:14: ==> Fatal error occurred, no output PDF file produced! Transcript written on org.log. This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/nixos.org) (preloaded format=pdftex) restricted \write18 enabled. entering extended mode (../../../org.t2d/pdf/src/org.texi (/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc/texinfo.tex Loading texinfo [version 2013-09-11.11]: pdf, fonts, markup, glyphs, page headings, tables, conditionals, indexing, sectioning, toc, environments, defuns, macros, cross references, insertions, localization, formatting, and turning on texinfo input format.) (./org.aux) Runaway argument? {en_\finish }\else \globaldefs = 1 \input txi-en.tex \fi \closein 1 \endgroup \ ETC. ../../../org.t2d/pdf/src/org.texi:14: Paragraph ended before \documentlanguaget rywithoutunderscore was complete. <to be read again> \par l.14 ? ../../../org.t2d/pdf/src/org.texi:14: Emergency stop. <to be read again> \par l.14 ../../../org.t2d/pdf/src/org.texi:14: ==> Fatal error occurred, no output PDF file produced! Transcript written on org.log. /home/karthik/.nix-profile/bin/texi2dvi: pdftex exited with bad status, quitting. make[1]: *** [Makefile:92: org.pdf] Error 1 make[1]: Leaving directory '/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc' make: *** [mk/targets.mk:122: pdf] Error 2 Compilation exited abnormally with code 2 at Thu Nov 14 14:05:52 ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-11-14 22:10 ` Karthik Chikmagalur @ 2024-11-21 3:46 ` Karthik Chikmagalur 2024-11-23 16:04 ` Ihor Radchenko 2024-11-23 16:11 ` [PATCH v10] " Ihor Radchenko 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-21 3:46 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >>> I'm using texinfo 7.1 now, the pdf build is still failing when I try >>> "make docs", with the same errors. Not sure what to do now. >> >> May you please try to create a reproducer then? >> Starting from a freshly cloned Org repo. > > Here's what I get when I run `make docs' after pulling from main. > Should I create a bug report? I'm not sure yet if PEBKAC. Ihor, do you need anything else from me, or want me to try something else here? This patch is held up because I'm not able to get "make docs" to run. (make output is in my previous email.) Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-11-21 3:46 ` Karthik Chikmagalur @ 2024-11-23 16:04 ` Ihor Radchenko 2024-11-25 3:17 ` [PATCH v11] " Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-11-23 16:04 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 908 bytes --] Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >>>> I'm using texinfo 7.1 now, the pdf build is still failing when I try >>>> "make docs", with the same errors. Not sure what to do now. >>> >>> May you please try to create a reproducer then? >>> Starting from a freshly cloned Org repo. >> >> Here's what I get when I run `make docs' after pulling from main. >> Should I create a bug report? I'm not sure yet if PEBKAC. > > Ihor, do you need anything else from me, or want me to try something > else here? This patch is held up because I'm not able to get "make > docs" to run. (make output is in my previous email.) For the purposes of the inline image display patch, see the attached pdf generated using your latest org-manual patch. We need to get rid of the description parts of the info link or rewrite the surrounding text a bit to make things readable in PDF version of the manual. [-- Attachment #2: org.pdf --] [-- Type: application/pdf, Size: 1359936 bytes --] [-- Attachment #3: Type: text/plain, Size: 224 bytes --] -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v11] Inline image display as part of a new org-link-preview system 2024-11-23 16:04 ` Ihor Radchenko @ 2024-11-25 3:17 ` Karthik Chikmagalur 2024-11-25 18:11 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-25 3:17 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 716 bytes --] >> Ihor, do you need anything else from me, or want me to try something >> else here? This patch is held up because I'm not able to get "make >> docs" to run. (make output is in my previous email.) > > For the purposes of the inline image display patch, see the attached pdf > generated using your latest org-manual patch. > We need to get rid of the description parts of the info link or rewrite > the surrounding text a bit to make things readable in PDF version of the > manual. I've attached the latest version of the patchset, with the link descriptions in the manual fixed for PDF readability. I can't test it here so please let me know if it looks good. I've also rebased the commits onto main. Karthik [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-link-Customizable-preview-API-for-arbitrary-link.patch --] [-- Type: text/x-patch, Size: 75484 bytes --] From e02a53dfaa40113bc3a3df241a542dcbe7bb6f62 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH 1/2] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-batch-size, org-link-preview-delay, org-link-preview--timer, org-link-preview--queue, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-preview, org-link-preview-region, org-link-preview--process-queue, org-link-preview-clear, org-link-preview-file, org-display-remote-inline-images, org-image-align, org--create-inline-image, org-display-inline-image--width, org-image--align): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. This link parameter is a function called asynchronously to place previes. File links and attachments are previewed using inline image previews as before. Move image handling utilities from lisp/org.el to lisp/ol.el. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images, org-startup-with-inline-images, org-startup-with-link-previews, org-startup-options): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. Rename `org-startup-with-inline-images' to `org-startup-with-link-previews'. Add new STARTUP options for link previews to `org-startup-options': "linkpreviews" and "nolinkpreviews". * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images, org-cycle-display-link-previews, org-cycle-inline-images-display, org-cycle-inline-link-previews): Use `org-link-preview' and `org-link-preview-region'. Rename inline-images functions and user options to their link-previews equivalents: - `org-cycle-display-inline-images' to `org-cycle-display-link-previews' - `org-cycle-inline-images-display' to `org-cycle-inline-link-previews' * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el (org-attach-preview-file): Add new `:preview' link parameter for links of type "attachment", set to the new function `org-attach-preview-file'. --- lisp/ol.el | 602 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 185 +++++++++++ lisp/org-cycle.el | 31 +- lisp/org-keys.el | 7 +- lisp/org-plot.el | 2 +- lisp/org.el | 554 +------------------------------ testing/lisp/test-org-fold.el | 4 +- 8 files changed, 838 insertions(+), 558 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index ea01e2254..1480c4719 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,11 @@ (declare-function org-src-source-buffer "org-src" ()) (declare-function org-src-source-type "org-src" ()) (declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function outline-next-heading "outline" ()) +(declare-function image-flush "image" (spec &optional frame)) +(declare-function org-entry-end-position "org" ()) +(declare-function org-element-contents-begin "org-element" (node)) +(declare-function org-element-contents-end "org-element" (node)) +(declare-function org-property-or-variable-value "org" (var &optional inherit)) \f ;;; Customization @@ -171,6 +176,18 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run to generate an in-buffer preview for the link. It + must accept three arguments: + - an overlay placed from the start to the end of the link + - the link path, as a string + - the syntax node for the link + + This function must return a non-nil value to indicate success. + A return value of nil implies that the preview failed, and the + overlay placed on the link will be removed. + `:help-echo' String or function used as a value for the `help-echo' text @@ -521,6 +538,81 @@ (defcustom org-link-keep-stored-after-insertion nil :type 'boolean :safe #'booleanp) +(defcustom org-link-preview-delay 0.05 + "Idle delay in seconds between link previews when using +`org-link-preview'. Links are previewed in batches (see +`org-link-preview-batch-size') spaced out by this delay. Set +this to a small number for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'number) + +(defcustom org-link-preview-batch-size 6 + "Number of links that are previewed at once with +`org-link-preview'. Links are previewed asynchronously, in +batches spaced out in time (see `org-link-preview-delay'). Set +this to a large integer for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'natnum) + +(defcustom org-display-remote-inline-images 'skip + "How to display remote inline images. +Possible values of this option are: + +skip Don't display remote images. +download Always download and display remote images. +t +cache Display remote images, and open them in separate buffers + for caching. Silently update the image buffer when a file + change is detected." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Ignore remote images" skip) + (const :tag "Always display remote images" download) + (const :tag "Display and silently update remote images" cache)) + :safe #'symbolp) + +(defcustom org-image-max-width 'fill-column + "When non-nil, limit the displayed image width. +This setting only takes effect when `org-image-actual-width' is set to +t or when #+ATTR* is set to t. + +Possible values: +- `fill-column' :: limit width to `fill-column' +- `window' :: limit width to window width +- integer :: limit width to number in pixels +- float :: limit width to that fraction of window width +- nil :: do not limit image width" + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Do not limit image width" nil) + (const :tag "Limit to `fill-column'" fill-column) + (const :tag "Limit to window width" window) + (integer :tag "Limit to a number of pixels") + (float :tag "Limit to a fraction of window width"))) + +(defcustom org-image-align 'left + "How to align images previewed using `org-link-preview-region'. + +Only stand-alone image links are affected by this setting. These +are links without surrounding text. + +Possible values of this option are: + +left Insert image at specified position. +center Center image previews. +right Right-align image previews." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Left align (or don\\='t align) image previews" left) + (const :tag "Center image previews" center) + (const :tag "Right align image previews" right)) + :safe #'symbolp) + ;;; Public variables (defconst org-target-regexp (let ((border "[^<>\n\r \t]")) @@ -649,6 +741,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +996,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1908,230 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, also preview links with description in +the active region, at point or in the current section. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] Displaying %d images inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if-let ((ov (cdr (get-char-property-and-overlay + (point) 'org-image-overlay)))) + ;; clear link preview at point + (funcall toggle-previews + (overlay-start ov) (overlay-end ov) + "preview at point" 'remove) + ;; Clear link previews in entry + (funcall toggle-previews + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))) + (org-with-limited-levels (org-entry-end-position)) + "current section" 'remove))) + ;; C-u C-u or C-11 argument :: display images in the whole buffer. + ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove)) + ;; Argument nil or 1, no region selected :: display images in + ;; current section or image link at point. + ((and (member arg '(nil 1)) (null beg) (null end)) + (let ((context (org-element-context))) + ;; toggle display of inline image link at point. + (if (org-element-type-p context 'link) + (let* ((ov (cdr-safe (get-char-property-and-overlay + (point) 'org-image-overlay))) + (remove? (and ov (memq ov org-link-preview-overlays) + 'remove))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "image at point" remove?)) + (let ((beg (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point))))) + (end (org-with-limited-levels (org-entry-end-position)))) + (funcall toggle-previews beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-previews beg end "region"))))) + +;;;###autoload +(defun org-link-preview-refresh () + "Assure display of link previews in buffer and refresh them." + (interactive) + (org-link-preview-region nil t (point-min) (point-max))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh (org-link-preview-clear beg end)) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t) + preview-queue) + ;; Collect links to preview + (while (re-search-forward org-link-any-re end t) + (when-let* + ((link (org-element-lineage (org-element-context) 'link t)) + (linktype (org-element-property :type link)) + (preview-func (org-link-get-parameter linktype :preview)) + (path (and (or include-linked + (not (org-element-contents-begin link))) + (org-element-property :path link)))) + ;; Create an overlay to hold the preview + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + (overlay-put ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (push ov org-link-preview-overlays) + (push (list preview-func ov path link) preview-queue)))) + ;; Collect previews in buffer-local LIFO preview queue + (setq org-link-preview--queue + (nconc (nreverse preview-queue) org-link-preview--queue)) + ;; Run preview possibly asynchronously + (when org-link-preview--queue + (org-link-preview--process-queue (current-buffer)))))) + +(defun org-link-preview--process-queue (org-buffer) + "Preview pending Org link previews in ORG-BUFFER. + +Previews are generated from the specs in +`org-link-preview--queue', which see." + (and (buffer-live-p org-buffer) + (with-current-buffer org-buffer + (cl-loop + for spec in org-link-preview--queue + for ov = (cadr spec) ;SPEC is (preview-func ov path link) + for count from org-link-preview-batch-size above 0 + do (pop org-link-preview--queue) + if (overlay-buffer ov) do + (if (apply spec) + (overlay-put ov 'org-image-overlay t) + ;; Preview was unsuccessful, delete overlay + (delete-overlay ov) + (setq org-link-preview-overlays + (delq ov org-link-preview-overlays))) + else do (cl-incf count) end + finally do + (setq org-link-preview--timer + (and org-link-preview--queue + (run-with-idle-timer + (time-add (or (current-idle-time) 0) + org-link-preview-delay) + nil #'org-link-preview--process-queue org-buffer))))))) + +(defun org-link-preview-clear (&optional beg end) + "Clear link previews in region BEG to END." + (interactive (and (use-region-p) (list (region-beginning) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end))) + (dolist (ov overlays) + (when (memq ov org-link-preview-overlays) + ;; Remove pending preview tasks between BEG and END + (when-let ((spec (cl-find ov org-link-preview--queue + :key #'cadr))) + (setq org-link-preview--queue (delq spec org-link-preview--queue))) + ;; Remove placed overlays between BEG and END + (when-let ((image (overlay-get ov 'display)) + ((imagep image))) + (image-flush image)) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + (delete-overlay ov))) + ;; Clear removed overlays. + (dolist (ov org-link-preview-overlays) + (unless (overlay-buffer ov) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)))))) + \f ;;; Built-in link types @@ -1595,7 +2154,48 @@ (defun org-link--open-elisp (path _) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) ;;;; "file" link type -(org-link-set-parameters "file" :complete #'org-link-complete-file) +(org-link-set-parameters "file" + :complete #'org-link-complete-file + :preview #'org-link-preview-file) + +(defun org-link-preview-file (ov path link) + "Display image file PATH in overlay OV for LINK. + +LINK is the Org element being previewed. + +Equip each image with the keymap `image-map'. + +This is intended to be used as the `:preview' link property of +file links, see `org-link-parameters'." + (if (not (display-graphic-p)) + (prog1 nil + (message "Your Emacs does not support displaying images!")) + (require 'image) + (when-let* ((file-full (expand-file-name path)) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (when image ; Add image to overlay + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'keymap image-map) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + t))))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index d4393324f..4fe06cbf4 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -797,9 +797,18 @@ (defun org-attach-follow (file arg) See `org-open-file' for details about ARG." (org-link-open-as-file (org-attach-expand file) arg)) +(defun org-attach-preview-file (ov path link) + "Preview attachment with PATH in overlay OV. + +LINK is the Org link element being previewed." + (org-with-point-at (org-element-begin link) + (org-link-preview-file + ov (org-attach-expand path) link))) + (org-link-set-parameters "attachment" :follow #'org-attach-follow - :complete #'org-attach-complete-link) + :complete #'org-attach-complete-link + :preview #'org-attach-preview-file) (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." diff --git a/lisp/org-compat.el b/lisp/org-compat.el index 05fe13457..20caafb2b 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -1010,6 +1010,191 @@ (define-obsolete-function-alias 'org-open-link-from-string (define-obsolete-function-alias 'org-add-angle-brackets 'org-link-add-angle-brackets "9.3") +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-link-preview--get-overlays "ol" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-attach-expand "org-attach" (file)) +(declare-function org-display-inline-image--width "org" (link)) +(declare-function org-image--align "org" (link)) +(declare-function org--create-inline-image "org" (file width)) + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-function-alias 'org-redisplay-inline-images + 'org-link-preview-refresh "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +(declare-function org-link-preview-region "ol") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-link-preview-region include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + + ;; The function was made obsolete by commit 65399674d5 of 2013-02-22. ;; This make-obsolete call was added 2016-09-01. (make-obsolete 'org-capture-import-remember-templates diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..97a545393 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -217,7 +217,7 @@ (defcustom org-cycle-pre-hook nil (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees org-cycle-show-empty-lines org-cycle-optimize-window-after-visibility-change - org-cycle-display-inline-images) + org-cycle-display-link-previews) "Hook that is run after `org-cycle' has changed the buffer visibility. The function(s) in this hook must accept a single argument which indicates the new state that was set by the most recent `org-cycle' command. The @@ -237,11 +237,15 @@ (defcustom org-cycle-open-archived-trees nil :group 'org-cycle :type 'boolean) -(defcustom org-cycle-inline-images-display nil - "Non-nil means auto display inline images under subtree when cycling." +(defvaralias 'org-cycle-inline-images-display + 'org-cycle-link-previews-display + "Non-nil means auto display inline images under subtree when cycling.") + +(defcustom org-cycle-link-previews-display nil + "Non-nil means auto display link previews under subtree when cycling." :group 'org-startup :group 'org-cycle - :package-version '(Org . "9.6") + :package-version '(Org . "9.8") :type 'boolean) (defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook) @@ -804,12 +808,15 @@ (defun org-cycle-hide-archived-subtrees (state) "Subtree is archived and stays closed. Use \ `\\[org-cycle-force-archived]' to cycle it anyway.")))))) -(defun org-cycle-display-inline-images (state) +(defalias 'org-cycle-inline-images-display + 'org-cycle-display-link-previews) + +(defun org-cycle-display-link-previews (state) "Auto display inline images under subtree when cycling. -It works when `org-cycle-inline-images-display' is non-nil. +It works when `org-cycle-link-previews-display' is non-nil. STATE is the current outline visibility state. It should be one of symbols `content', `all', `folded', `children', or `subtree'." - (when org-cycle-inline-images-display + (when org-cycle-link-previews-display (pcase state ('children (org-with-wide-buffer @@ -817,19 +824,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index f6de753af..3f07a8818 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,8 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) +(declare-function org-link-preview-refresh "ol" ()) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -652,8 +653,8 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) -(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) +(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-link-preview-refresh) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index 1e9057935..def094a9a 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -1160,14 +1160,22 @@ (defcustom org-startup-shrink-all-tables nil :package-version '(Org . "9.2") :safe #'booleanp) -(defcustom org-startup-with-inline-images nil +(defvaralias 'org-startup-with-inline-images + 'org-startup-with-link-previews "Non-nil means show inline images when loading a new Org file. This can also be configured on a per-file basis by adding one of the following lines anywhere in the buffer: #+STARTUP: inlineimages - #+STARTUP: noinlineimages" + #+STARTUP: noinlineimages") + +(defcustom org-startup-with-link-previews nil + "Non-nil means show link previews when loading a new Org file. +This can also be configured on a per-file basis by adding one of +the following lines anywhere in the buffer: + #+STARTUP: linkpreviews + #+STARTUP: nolinkpreviews" :group 'org-startup - :version "24.1" + :version "29.4" :type 'boolean) (defcustom org-startup-with-latex-preview nil @@ -4153,8 +4161,10 @@ (defconst org-startup-options ("shrink" org-startup-shrink-all-tables t) ("descriptivelinks" org-link-descriptive t) ("literallinks" org-link-descriptive nil) - ("inlineimages" org-startup-with-inline-images t) - ("noinlineimages" org-startup-with-inline-images nil) + ("inlineimages" org-startup-with-link-previews t) + ("noinlineimages" org-startup-with-link-previews nil) + ("linkpreviews" org-startup-with-link-previews t) + ("nolinkpreviews" org-startup-with-link-previews nil) ("latexpreview" org-startup-with-latex-preview t) ("nolatexpreview" org-startup-with-latex-preview nil) ("customtime" org-display-custom-times t) @@ -5079,7 +5089,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -15601,26 +15611,6 @@ (defcustom org-image-actual-width t (list :tag "Use #+ATTR* or a number of pixels" (integer)) (const :tag "Use #+ATTR* or don't resize" nil))) -(defcustom org-image-max-width 'fill-column - "When non-nil, limit the displayed image width. -This setting only takes effect when `org-image-actual-width' is set to -t or when #+ATTR* is set to t. - -Possible values: -- `fill-column' :: limit width to `fill-column' -- `window' :: limit width to window width -- integer :: limit width to number in pixels -- float :: limit width to that fraction of window width -- nil :: do not limit image width" - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Do not limit image width" nil) - (const :tag "Limit to `fill-column'" fill-column) - (const :tag "Limit to window width" window) - (integer :tag "Limit to a number of pixels") - (float :tag "Limit to a fraction of window width"))) - (defcustom org-agenda-inhibit-startup nil "Inhibit startup when preparing agenda buffers. When this variable is t, the initialization of the Org agenda @@ -16667,518 +16657,6 @@ (defun org-normalize-color (value) (format "%g" (/ value 65535.0))) \f -;; Image display - -(defvar-local org-inline-image-overlays nil) -;; Preserve when switching modes or when restarting Org. -;; If we clear the overlay list and later enable Or mode, the existing -;; image overlays will never be cleared by `org-toggle-inline-images' -;; and `org-toggle-inline-images-command'. -(put 'org-inline-image-overlays 'permanent-local t) - -(defun org--inline-image-overlays (&optional beg end) - "Return image overlays between BEG and END." - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end)) - result) - (dolist (ov overlays result) - (when (memq ov org-inline-image-overlays) - (push ov result))))) - -(defun org-toggle-inline-images-command (&optional arg beg end) - "Toggle display of inline images without description at point. - -When point is at an image link, toggle displaying that image. -Otherwise, toggle displaying images in current entry. - -When region BEG..END is active, toggle displaying images in the -region. - -With numeric prefix ARG 1, display images with description as well. - -With prefix ARG `\\[universal-argument]', toggle displaying images in -the accessible portion of the buffer. With numeric prefix ARG 11, do -the same, but include images with description. - -With prefix ARG `\\[universal-argument] \\[universal-argument]', hide -all the images in accessible portion of the buffer. - -This command is designed for interactive use. From Elisp, you can -also use `org-toggle-inline-images'." - (interactive (cons current-prefix-arg - (when (use-region-p) - (list (region-beginning) (region-end))))) - (let* ((include-linked - (cond - ((member arg '(nil (4) (16)) ) nil) - ((member arg '(1 11)) 'include-linked) - (t 'include-linked))) - (interactive? (called-interactively-p 'any)) - (toggle-images - (lambda (&optional beg end scope force-remove) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (old (org--inline-image-overlays beg end)) - (scope (or scope (format "%d:%d" beg end)))) - (if (or old force-remove) - (progn - (org-remove-inline-images beg end) - (when interactive? - (message - "[%s] Inline image display turned off (removed %d images)" - scope (length old)))) - (org-display-inline-images include-linked t beg end) - (when interactive? - (let ((new (org--inline-image-overlays beg end))) - (message - (if new - (format "[%s] %d images displayed inline %s" - scope (length new) - (if include-linked "(including images with description)" - "")) - (format "[%s] No images to display inline" scope)))))))))) - (cond - ((not (display-graphic-p)) - (message "Your Emacs does not support displaying images!")) - ;; Region selected :: toggle images in region. - ((and beg end) (funcall toggle-images beg end "region")) - ;; C-u or C-11 argument :: toggle images in the whole buffer. - ((member arg '(11 (4))) (funcall toggle-images nil nil "buffer")) - ;; C-u C-u argument :: unconditionally hide images in the buffer. - ((equal arg '(16)) (funcall toggle-images nil nil "buffer" 'remove)) - ;; Argument nil or 1, no region selected :: toggle (display or hide - ;; dwim) images in current section or image link at point. - ((and (member arg '(nil 1)) (null beg) (null end)) - (let ((context (org-element-context))) - ;; toggle display of inline image link at point. - (if (org-element-type-p context 'link) - (funcall toggle-images - (org-element-begin context) - (org-element-end context) - "image at point") - (let ((beg (if (org-before-first-heading-p) (point-min) - (save-excursion - (org-with-limited-levels (org-back-to-heading t) (point))))) - (end (org-with-limited-levels (org-entry-end-position)))) - (funcall toggle-images beg end "current section"))))) - ;; Any other non-nil argument. - ((not (null arg)) (funcall toggle-images beg end "region"))))) - -(defun org-toggle-inline-images (&optional include-linked beg end) - "Toggle the display of inline images. -INCLUDE-LINKED is passed to `org-display-inline-images'." - (interactive "P") - (if (org--inline-image-overlays beg end) - (progn - (org-remove-inline-images beg end) - (when (called-interactively-p 'interactive) - (message "Inline image display turned off"))) - (org-display-inline-images include-linked nil beg end) - (when (called-interactively-p 'interactive) - (let ((new (org--inline-image-overlays beg end))) - (message (if new - (format "%d images displayed inline" - (length new)) - "No images to display inline")))))) - -(defun org-redisplay-inline-images () - "Assure display of inline images and refresh them." - (interactive) - (org-toggle-inline-images) - (unless org-inline-image-overlays - (org-toggle-inline-images))) - -;; For without-x builds. -(declare-function image-flush "image" (spec &optional frame)) - -(defcustom org-display-remote-inline-images 'skip - "How to display remote inline images. -Possible values of this option are: - -skip Don't display remote images. -download Always download and display remote images. -t -cache Display remote images, and open them in separate buffers - for caching. Silently update the image buffer when a file - change is detected." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Ignore remote images" skip) - (const :tag "Always display remote images" download) - (const :tag "Display and silently update remote images" cache)) - :safe #'symbolp) - -(defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. - -Only stand-alone image links are affected by this setting. These -are links without surrounding text. - -Possible values of this option are: - -left Insert image at specified position. -center Center image previews. -right Right-align image previews." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Left align (or don\\='t align) image previews" left) - (const :tag "Center image previews" center) - (const :tag "Right align image previews" right)) - :safe #'symbolp) - -(defun org--create-inline-image (file width) - "Create image located at FILE, or return nil. -WIDTH is the width of the image. The image may not be created -according to the value of `org-display-remote-inline-images'." - (let* ((remote? (file-remote-p file)) - (file-or-data - (pcase org-display-remote-inline-images - ((guard (not remote?)) file) - (`download (with-temp-buffer - (set-buffer-multibyte nil) - (insert-file-contents-literally file) - (buffer-string))) - ((or `cache `t) - (let ((revert-without-query '("."))) - (with-current-buffer (find-file-noselect file) - (buffer-string)))) - (`skip nil) - (other - (message "Invalid value of `org-display-remote-inline-images': %S" - other) - nil)))) - (when file-or-data - (create-image file-or-data - (and (image-type-available-p 'imagemagick) - width - 'imagemagick) - remote? - :width width - :max-width - (pcase org-image-max-width - (`fill-column (* fill-column (frame-char-width (selected-frame)))) - (`window (window-width nil t)) - ((pred integerp) org-image-max-width) - ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) - (`nil nil) - (_ (error "Unsupported value of `org-image-max-width': %S" - org-image-max-width))) - :scale 1)))) - -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - -(declare-function org-export-read-attribute "ox" - (attribute element &optional property)) -(defvar visual-fill-column-width) ; Silence compiler warning -(defun org-display-inline-image--width (link) - "Determine the display width of the image LINK, in pixels. -- When `org-image-actual-width' is t, the image's pixel width is used. -- When `org-image-actual-width' is a number, that value will is used. -- When `org-image-actual-width' is nil or a list, :width attribute of - #+attr_org or the first #+attr_... (if it exists) is used to set the - image width. A width of X% is divided by 100. If the value is a - float between 0 and 2, it interpreted as that proportion of the text - width in the buffer. - - If no :width attribute is given and `org-image-actual-width' is a - list with a number as the car, then that number is used as the - default value." - ;; Apply `org-image-actual-width' specifications. - ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified - ;; width. - (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) - (cond - ((eq org-image-actual-width t) nil) - ((listp org-image-actual-width) - (require 'ox) - (let* ((par (org-element-lineage link 'paragraph)) - ;; Try to find an attribute providing a :width. - ;; #+ATTR_ORG: :width ... - (attr-width (org-export-read-attribute :attr_org par :width)) - (width-unreadable? - (lambda (value) - (or (not (stringp value)) - (unless (string= value "t") - (or (not (string-match - (rx bos (opt "+") - (or - ;; Number of pixels - ;; must be a lone number, not - ;; things like 4in - (seq (1+ (in "0-9")) eos) - ;; Numbers ending with % - (seq (1+ (in "0-9.")) (group-n 1 "%")) - ;; Fractions - (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) - value)) - (let ((number (string-to-number value))) - (and (floatp number) - (not (match-string 1 value)) ; X% - (not (<= 0.0 number 2.0))))))))) - ;; #+ATTR_BACKEND: :width ... - (attr-other - (catch :found - (org-element-properties-map - (lambda (prop _) - (when (and - (not (eq prop :attr_org)) - (string-match-p "^:attr_" (symbol-name prop)) - (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) - (throw :found prop))) - par))) - (attr-width - (if (not (funcall width-unreadable? attr-width)) - attr-width - ;; When #+attr_org: does not have readable :width - (and attr-other - (org-export-read-attribute attr-other par :width)))) - (width - (cond - ;; Treat :width t as if `org-image-actual-width' were t. - ((string= attr-width "t") nil) - ;; Fallback to `org-image-actual-width' if no interprable width is given. - ((funcall width-unreadable? attr-width) - (car org-image-actual-width)) - ;; Convert numeric widths to numbers, converting percentages. - ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) - (/ (string-to-number attr-width) 100.0)) - (t (string-to-number attr-width))))) - (if (and (floatp width) (<= 0.0 width 2.0)) - ;; A float in [0,2] should be interpereted as this portion of - ;; the text width in the window. This works well with cases like - ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, - ;; as the "0.X" is pulled out as a float. We use 2 as the upper - ;; bound as cases such as 1.2\linewidth are feasible. - (round (* width - (window-pixel-width) - (/ (or (and (bound-and-true-p visual-fill-column-mode) - (or visual-fill-column-width auto-fill-function)) - (when auto-fill-function fill-column) - (- (window-text-width) (line-number-display-width))) - (float (window-total-width))))) - width))) - ((numberp org-image-actual-width) - org-image-actual-width) - (t nil)))) - -(defun org-image--align (link) - "Determine the alignment of the image LINK. -LINK is a link object. - -In decreasing order of priority, this is controlled: -- Per image by the value of `:center' or `:align' in the -affiliated keyword `#+attr_org'. -- By the `#+attr_html' or `#+attr_latex` keywords with valid - `:center' or `:align' values. -- Globally by the user option `org-image-align'. - -The result is either nil or one of the strings \"left\", -\"center\" or \"right\". - -\"center\" will cause the image preview to be centered, \"right\" -will cause it to be right-aligned. A value of \"left\" or nil -implies no special alignment." - (let ((par (org-element-lineage link 'paragraph))) - ;; Only align when image is not surrounded by paragraph text: - (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align - (= (org-element-begin link) - (save-excursion - (goto-char (org-element-contents-begin par)) - (skip-chars-forward "\t ") - (point))) ;account for leading space - ;before link - (<= (- (org-element-contents-end par) - (org-element-end link)) - 1)) ;account for trailing newline - ;at end of paragraph - (save-match-data - ;; Look for a valid ":center t" or ":align left|center|right" - ;; attribute. - ;; - ;; An attr_org keyword has the highest priority, with - ;; any attr.* next. Choosing between these is - ;; unspecified. - (let ((center-re ":\\(center\\)[[:space:]]+t\\b") - (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") - attr-align) - (catch 'exit - (org-element-properties-mapc - (lambda (propname propval) - (when (and propval - (string-match-p ":attr.*" (symbol-name propname))) - (setq propval (car-safe propval)) - (when (or (string-match center-re propval) - (string-match align-re propval)) - (setq attr-align (match-string 1 propval)) - (when (eq propname :attr_org) - (throw 'exit t))))) - par)) - (if attr-align - (when (member attr-align '("center" "right")) attr-align) - ;; No image-specific keyword, check global alignment property - (when (memq org-image-align '(center right)) - (symbol-name org-image-align)))))))) - - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.46.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --] [-- Type: text/x-patch, Size: 19818 bytes --] From 3fb7dcff71e0786770230c69cea8550270412ecc Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Sun, 24 Nov 2024 19:14:28 -0800 Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types * etc/ORG-NEWS: Mention new commands and options for link preview. * doc/org-manual.org: Add sections on - previewing external links (Images and link previews), and - adding the link preview feature to other link types (Adding Hyperlink preview). --- doc/org-manual.org | 291 ++++++++++++++++++++++++++++++--------------- etc/ORG-NEWS | 93 +++++++++++++-- 2 files changed, 279 insertions(+), 105 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 451fd72cd..e904b27db 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11778,7 +11778,75 @@ ** Literal Examples the end of the current line. Then the label is stored as a link =(label)=, for retrieval with {{{kbd(C-c C-l)}}}. -** Images +** Images and link previews +:PROPERTIES: +:DESCRIPTION: Preview links in the buffer. +:END: + +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By +default, only image links[fn::Image links are =file:= and +=attachment:= links to existing image files; Emacs should be able to +display the linked images (see ~image-types~ variable)] can be +previewed inline, where the images are displayed in place of the link +path. + +You can customize the previews as described in the [[*Adding Hyperlink +preview]] section. Link previews do not have to display images -- any +kind of [[info:elisp#Overlay Properties][display decoration]] can be used. + +You can preview the supported link types in the whole buffer, in the +current section, active region or at point with the following commands: + +- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) :: + + #+kindex: C-c C-x C-v + #+findex: org-link-preview + Create inline previews for external links in the active region, the + link at point or in the current section. With a prefix argument, + clear link previews at point or in the current entry. With a double + prefix argument, preview all links in the buffer. With triple + prefix argument, hide previews for all links in the buffer. + + By default, only links without descriptions are previewed. You + can force displaying previews for all supported links (including + links with descriptions) using a numeric argument of ~1~. This + will toggle all previews in the active region, the link at point + or the current section. A numeric prefix argument of ~11~ will + toggle previews in the whole buffer instead. + + #+vindex: org-startup-with-link-previews + Org mode can display link previews automatically when opening + buffers. Either customize ~org-startup-with-link-previews~ or use + the =#+STARTUP: linkpreviews= keyword to enable previews for that + specific buffer when opening it. =#+STARTUP: nolinkpreviews= will + disable previews on startup in the buffer. + +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: + + #+kindex: C-c C-x C-M-v + #+findex: org-link-preview-refresh + Assure inline display of external link previews in the whole buffer + and refresh them. + +- (~org-link-preview-region~) :: + + #+findex: org-link-preview-region + Create inline previews for external links in the active region, or + the buffer. With a prefix argument, also preview links with a text + description part. + +- (~org-link-preview-clear~) :: + + #+findex: org-link-preview-clear + Clear external link previews in the active region, or the buffer. + +#+vindex: org-cycle-inline-images-display +Link previews can also be displayed when cycling the folding state. +When the custom option ~org-cycle-link-previews-display~ is set, +supported link types under the subtree (including images) will be +displayed automatically. + +*** Images :PROPERTIES: :DESCRIPTION: Display an image. :END: @@ -11803,102 +11871,76 @@ ** Images [[./img/a.jpg]] #+end_example -Such images can be displayed within the buffer with the following -command: - -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: - - #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images-command - Toggle the inline display of linked images in current section or at - point. With a prefix argument, toggle inline images in the whole - buffer. With double prefix argument, hide all the images in buffer. - - By default, only the image links without description are displayed. - You can force displaying all the images using numeric argument to - toggle all the images in current section (~1~ argument) or the whole - buffer (~11~ argument). - - #+vindex: org-startup-with-inline-images - You can ask for inline images to be displayed at - startup by configuring the variable - ~org-startup-with-inline-images~[fn:: The variable - ~org-startup-with-inline-images~ can be set within a buffer with the - =STARTUP= options =inlineimages= and =noinlineimages=.]. - - - #+vindex: org-image-actual-width - #+vindex: org-image-max-width - #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property - By default, Org mode displays inline images according to their - actual width, but no wider than ~fill-column~ characters. - - You can customize the displayed image width using - ~org-image-actual-width~ variable (globally) or - =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can - be customized in Emacs >= 24.1, built with ImageMagick support.]. - Their value can be the following: - - (default) Non-~nil~, use the actual width of images when inlining - them. If the actual width is too wide, limit it according to - ~org-image-max-width~. - - When set to a number, use ImageMagick (when available) to set the - image's width to this value. - - When set to a number in a list, try to get the width from any - =#+ATTR.*= keyword if it matches a width specification like: - #+begin_example - ,#+ATTR_HTML: :width 300px - #+end_example - and fall back on that number if none is found. - - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword - and fall back on the original width or ~org-image-max-width~ if - none is found. - - ~org-image-max-width~ limits the maximum displayed image width, but - only when the image width is not set explicitly. Possible maximum - width can be set to: - - (default) ~fill-column~, limit width to ~fill-column~ number of - characters. - - ~window~, limit width to current window width. - - integer number, limit width to that specified number of pixels. - - ~nil~, do not limit the width. - - #+vindex: org-image-align - Org mode can left-align, center or right-align the display of inline - images. This setting is controlled (globally) by ~org-image-align~. - Only standalone images are affected, corresponding to links with no - surrounding text in their paragraph except for whitespace. Its - value can be the following: - - (default) The symbol ~left~, which inserts the image where the - link appears in the buffer. - - The symbol ~center~, which will preview links centered in the - Emacs window. - - The symbol ~right~, which will preview links right-aligned in the - Emacs window. - - Inline image alignment can be specified for each link using the - =#+ATTR.*= keyword if it matches an alignment specification like: +When [[*Images and link previews][link previews]] are displayed as images, the image size and +alignment can be further customized. + +#+vindex: org-image-actual-width +#+vindex: org-image-max-width +#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property +By default, Org mode displays inline images according to their +actual width, but no wider than ~fill-column~ characters. + +You can customize the displayed image width using +~org-image-actual-width~ variable (globally) or +=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can +be customized in Emacs >= 24.1, built with ImageMagick support.]. +Their value can be the following: +- (default) Non-~nil~, use the actual width of images when inlining + them. If the actual width is too wide, limit it according to + ~org-image-max-width~. +- When set to a number, use ImageMagick (when available) to set the + image's width to this value. +- When set to a number in a list, try to get the width from any + =#+ATTR.*= keyword if it matches a width specification like: #+begin_example - ,#+ATTR_HTML: :align center + ,#+ATTR_HTML: :width 300px #+end_example - Org will use the alignment specification from any =#+ATTR.*= - keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= - (if present) will override the others. For instance, this link - #+begin_example - ,#+ATTR_HTML: :align right - ,#+ATTR_ORG: :align center - [[/path/to/image/file.png]] - #+end_example - will be displayed centered in Emacs but exported right-aligned to - HTML. - - When =#+ATTR_ORG= is not set, inline image alignment is also read - from the =:center= attribute supported by some export backends (like - HTML, LaTeX and Beamer.) + and fall back on that number if none is found. +- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword + and fall back on the original width or ~org-image-max-width~ if + none is found. + +~org-image-max-width~ limits the maximum displayed image width, but +only when the image width is not set explicitly. Possible maximum +width can be set to: +- (default) ~fill-column~, limit width to ~fill-column~ number of + characters. +- ~window~, limit width to current window width. +- integer number, limit width to that specified number of pixels. +- ~nil~, do not limit the width. + +#+vindex: org-image-align +Org mode can left-align, center or right-align the display of inline +images. This setting is controlled (globally) by ~org-image-align~. +Only standalone images are affected, corresponding to links with no +surrounding text in their paragraph except for whitespace. Its +value can be the following: +- (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. +- The symbol ~center~, which will preview links centered in the + Emacs window. +- The symbol ~right~, which will preview links right-aligned in the + Emacs window. + +Inline image alignment can be specified for each link using the +=#+ATTR.*= keyword if it matches an alignment specification like: +#+begin_example +,#+ATTR_HTML: :align center +#+end_example +Org will use the alignment specification from any =#+ATTR.*= +keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= +(if present) will override the others. For instance, this link +#+begin_example +,#+ATTR_HTML: :align right +,#+ATTR_ORG: :align center +[[/path/to/image/file.png]] +#+end_example +will be displayed centered in Emacs but exported right-aligned to +HTML. -#+vindex: org-cycle-inline-images-display -Inline images can also be displayed when cycling the folding state. -When custom option ~org-cycle-inline-images-display~ is set, the -visible inline images under subtree will be displayed automatically. +When =#+ATTR_ORG= is not set, inline image alignment is also read from +the =:center= attribute supported by some export backends (like HTML, +LaTeX and Beamer.) ** Captions :PROPERTIES: @@ -21837,6 +21879,67 @@ ** Adding Hyperlink Types topic. It also provides a default description. The function ~org-insert-link~ can insert it back into an Org buffer later on. +** Adding Hyperlink preview +:PROPERTIES: +:DESCRIPTION: Preview behavior for new hyperlink types. +:END: +#+cindex: hyperlinks, adding preview behavior + +By default, Org supports previewing external links for links ot type +=file= and =attachment= that point to image files. (See [[*Images]].) + +Support for previewing other link types inline can be added to Org in +the following way: + +1. Add a =:preview= link parameter to the link type using + ~org-link-set-parameters~. As an example, here we add previews for + the =docview= link type. + + #+begin_src emacs-lisp +(org-link-set-parameters + "docview" :preview #'org-link-docview-preview) + #+end_src + +2. The value of the =:preview= parameter must be a function that + accepts three arguments: + - an overlay placed from the start to the end of the link, + - the link path, as a string, and + - the syntax node for the link. + It must return a non-nil value to indicate preview success. A + value of =nil= implies that the preview failed, and the overlay + placed on the link will be removed. + + In our example, we use the =convert= program (part of the + =imagemagick= suite of tools) to create the thumbnail that is + displayed inline. + + #+begin_src emacs-lisp +(defun org-link-docview-preview (ov path _elem) + "Preview file at PATH in overlay OV. + +_ELEM is the syntax node of the link element." + (when (executable-find "convert") + (let* ((path (expand-file-name (substitute-in-file-name path))) + (output-file (expand-file-name (concat "org-docview-preview-" + (substring (sha1 path) 0 10) + ".png") + temporary-file-directory))) + ;; Create or find preview for path + (when (or (file-readable-p output-file) + (= 0 (call-process + "convert" + nil (get-buffer-create "*Org Docview Preview Output*") nil + "-thumbnail" "x320" "-colorspace" "rgb" + "-background" "white" "-alpha" "remove" "-strip" + (concat path "[0]") output-file))) + ;; If preview image is available, display it via the overlay + (overlay-put ov 'display (create-image output-file)))))) + #+end_src + +3. Now previews of docview links for supported document types (PDF, + djvu) are generated (along with image file previews) when calling + ~org-link-preview~. + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 92bfe3501..08857962b 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole buffer (or narrowed part of the buffer). With prefix argument, it also forced displaying image links with description. -Now, =C-c C-x C-v= is bound to a new command -~org-toggle-inline-images-command~, which uses different defaults: +Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which +uses different defaults: -1. By default, it toggles image at point or, if there is no image at - point, images in current entry +1. When the region is active, images in the region are previewed -2. When region is active, it is honored +2. Otherwise, if there is an image at point, it is toggled. If there + is no image at point, images in the current entry are previewed -3. =C-u= argument changed its meaning. Now, it forces toggling images - in the whole buffer +3. With the =C-u= argument, image previews in the active region or at + point are cleared instead -4. =C-u C-u= argument unconditionally hides all the images in buffer +4. The =C-u C-u= argument unconditionally shows all images in the + accessible portion of the buffer -5. Displaying images over links with description can be forced using +5. The =C-u C-u C-u= argument unconditionally clears all images in the + accessible portion of the buffer + +6. Displaying images over links with description can be forced using numeric argument: - ~C-u 1~ for toggling all images at point/current entry - ~C-u 11~ for toggling all images in buffer -The old ~org-toggle-inline-images~ command is still available. You -can bind it back to =C-c C-x C-v= by adding the following to you config: +(The first five of these prefix arg behaviors are the same as that of +the ~org-latex-preview~ command.) + +In addition to images, ~org-link-preview~ can also be used to preview +Org links of all types for which preview behavior is defined, see +[[#link-preview][previews for arbitrary link types]]. + +The old ~org-toggle-inline-images~ command is obsolete but still +available. You can bind it back to =C-c C-x C-v= by adding the +following to your config: #+begin_src emacs-lisp (eval-after-load 'org-keys (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) @@ -76,6 +88,16 @@ now have diary timestamps included as well. # We list the most important features, and the features that may # require user action to be used. +*** All Org link types can be previewed +:PROPERTIES: +:CUSTOM_ID: link-preview +:END: + +Org links support a new parameter =:preview= that can be used to +preview arbitrary link types. The value of this parameter should be a +function that is called to preview links of the corresponding type +(see ~org-link-parameters~). + *** Alignment of image previews can be customized This is not a new feature. It has been added in Org 9.7, but not @@ -120,6 +142,17 @@ See the new [[info:org#Repeating commands]["Repeating commands"]] section in Org # adding new customizations, or changing the interpretation of the # existing customizations. +*** New option ~org-link-preview-batch-size~ + +Org link previews are generated asynchronously and a few at a time, in +batches. This option controls the number of links that are previewed +in each batch. + +*** New option ~org-link-preview-delay~ + +Org link previews are generated asynchronously. This option controls +the minimum idle time in seconds between previews of batches of links. + *** Allow disabling macro replacement during export New custom option ~org-export-replace-macros~ controls whether Org @@ -162,6 +195,26 @@ bibliography format requires them to be written in title-case. # This also includes changes in function behavior from Elisp perspective. +*** New command ~org-link-preview~ to preview Org links + +This command replaces ~org-toggle-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer + +This command replaces ~org-display-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer + +This command replaces ~org-remove-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer + +This command replaces ~org-redisplay-inline-images~, which is now +obsolete. + *** ob-sqlite: Added ability to open a database in readonly mode Added option :readonly to ob-sqlite. @@ -204,6 +257,24 @@ leave extra prompts after evaluation, and skipping the prompt filtering can be more robust for such languages (as this avoids removing false positive prompts). +** Removed or renamed functions and variables + +*** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~ + +Inline image previews in Org mode are now provided by the more general +link previews feature. The behavior with regards to image links is +unchanged. + +*** ~org-cycle-inline-images-display~ is renamed to ~org-cycle-link-previews-display~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + +*** ~org-startup-with-inline-images~ is renamed to ~org-startup-with-link-previews~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + ** Miscellaneous *** Org mode no longer prevents =flyspell= from spell-checking inside =LOGBOOK= drawers -- 2.46.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v11] Inline image display as part of a new org-link-preview system 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 2:08 ` Karthik Chikmagalur 0 siblings, 2 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-11-25 18:11 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 1034 bytes --] Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > I've attached the latest version of the patchset, with the link > descriptions in the manual fixed for PDF readability. I can't test it > here so please let me know if it looks good. > > I've also rebased the commits onto main. Thanks! See the attached further adjustments to the manual. Also, I found a bug in the new preview system. Consider the following Org file: * This is test :ATTACH: :PROPERTIES: :ID: c4cd9f0c-b1fc-4906-9e34-967564123e64 :END: [[attachment:2024-11-25_19-03.png]][[attachment:2024-11-25_19-03.png]][[attachment:2024-11-25_19-03.png]] If you try to preview images in section, not all the images will be previewed. Furthermore, trying to toggle image previews with point at the links will yield "wrong side of point": 1. Open the file. Make sure that you adjust the links to point to the real files 2. C-c C-x C-v 3. Observe only one link being previewed 4. Move point to link that is not previewed 5. C-c C-x C-v 6. Observe error [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: cleanup.diff --] [-- Type: text/x-patch, Size: 3508 bytes --] diff --git a/doc/org-manual.org b/doc/org-manual.org index 95846b4758..e904b27db2 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11780,20 +11780,19 @@ ** Literal Examples ** Images and link previews :PROPERTIES: -:DESCRIPTION: Preview links in the buffer +:DESCRIPTION: Preview links in the buffer. :END: -Org mode can display previews of hyperlinks (see [[*Hyperlinks]]) -inside Org buffers. By default, only image links[fn::Image links are -=file:= and =attachment:= links to existing image files; Emacs can -only display the linked images listed in ~image-types~ variable] can -be previewed inline, where the images are displayed in place of the -link path. +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By +default, only image links[fn::Image links are =file:= and +=attachment:= links to existing image files; Emacs should be able to +display the linked images (see ~image-types~ variable)] can be +previewed inline, where the images are displayed in place of the link +path. -You can customize the previews as described in [[*Adding Hyperlink -preview]]. Link previews do not have to display images -- any kind of -display decoration (see [[info:elisp#Overlay Properties]]) can be -used. +You can customize the previews as described in the [[*Adding Hyperlink +preview]] section. Link previews do not have to display images -- any +kind of [[info:elisp#Overlay Properties][display decoration]] can be used. You can preview the supported link types in the whole buffer, in the current section, active region or at point with the following commands: @@ -11802,12 +11801,11 @@ ** Images and link previews #+kindex: C-c C-x C-v #+findex: org-link-preview - Create inline previews for external links in the active region, if - any; the link at point, if there is such; or in the current section - otherwise. With a prefix argument, clear link previews at point or - in the current entry. With a double prefix argument, preview all - links in the buffer. With triple prefix argument, hide previews for - all links in the buffer. + Create inline previews for external links in the active region, the + link at point or in the current section. With a prefix argument, + clear link previews at point or in the current entry. With a double + prefix argument, preview all links in the buffer. With triple + prefix argument, hide previews for all links in the buffer. By default, only links without descriptions are previewed. You can force displaying previews for all supported links (including @@ -11830,14 +11828,14 @@ ** Images and link previews Assure inline display of external link previews in the whole buffer and refresh them. -- {{{kbd(M-x org-link-preview-region)}}} :: +- (~org-link-preview-region~) :: #+findex: org-link-preview-region Create inline previews for external links in the active region, or the buffer. With a prefix argument, also preview links with a text description part. -- {{{kbd(M-x org-link-preview-clear)}}} :: +- (~org-link-preview-clear~) :: #+findex: org-link-preview-clear Clear external link previews in the active region, or the buffer. @@ -11873,7 +11871,7 @@ *** Images [[./img/a.jpg]] #+end_example -When link previews are displayed as images, the image size and +When [[*Images and link previews][link previews]] are displayed as images, the image size and alignment can be further customized. #+vindex: org-image-actual-width [-- Attachment #3: Type: text/plain, Size: 225 bytes --] -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v11] Inline image display as part of a new org-link-preview system 2024-11-25 18:11 ` Ihor Radchenko @ 2024-11-26 1:08 ` Karthik Chikmagalur 2024-11-26 17:18 ` Ihor Radchenko 2024-11-26 2:08 ` Karthik Chikmagalur 1 sibling, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-26 1:08 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode > See the attached further adjustments to the manual. Ihor, many of the adjustments you made are how it was previously written -- links with descriptions are exported awkwardly to LaTeX in the document you sent me. I changed links with descriptions to links without descriptions and added a "(see [[foo]])" comment, but in your patch you've changed them back. Example: -Org mode can display previews of hyperlinks (see [[*Hyperlinks]]) -inside Org buffers. By default, only image links[fn::Image links are +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org buffers. By +default, only image links[fn::Image links are =file:= and Also, the word "section" is added by LaTeX as part of the reference, so this change will cause it to be exported as "Adding Hyperlink preview section section": -You can customize the previews as described in [[*Adding Hyperlink -preview]]. Link previews do not have to display images -- any kind of -display decoration (see [[info:elisp#Overlay Properties]]) can be -used. +You can customize the previews as described in the [[*Adding Hyperlink +preview]] section. Link previews do not have to display images -- any +kind of [[info:elisp#Overlay Properties][display decoration]] can be used. > Also, I found a bug in the new preview system. > Consider the following Org file: I'll work on fixing the bug. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v11] Inline image display as part of a new org-link-preview system 2024-11-26 1:08 ` Karthik Chikmagalur @ 2024-11-26 17:18 ` Ihor Radchenko 2024-11-26 17:46 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-11-26 17:18 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> See the attached further adjustments to the manual. > > Ihor, many of the adjustments you made are how it was previously > written -- links with descriptions are exported awkwardly to LaTeX in > the document you sent me. I changed links with descriptions to links without > descriptions and added a "(see [[foo]])" comment, but in your patch > you've changed them back. Oops. My patch happens to be exactly the opposite of my changes. (I chose wrong direction of the diff range) -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v11] Inline image display as part of a new org-link-preview system 2024-11-26 17:18 ` Ihor Radchenko @ 2024-11-26 17:46 ` Ihor Radchenko 2024-11-26 18:45 ` [PATCH v12] " Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-11-26 17:46 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 633 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: > >>> See the attached further adjustments to the manual. >> >> Ihor, many of the adjustments you made are how it was previously >> written -- links with descriptions are exported awkwardly to LaTeX in >> the document you sent me. I changed links with descriptions to links without >> descriptions and added a "(see [[foo]])" comment, but in your patch >> you've changed them back. > > Oops. My patch happens to be exactly the opposite of my changes. (I > chose wrong direction of the diff range) Here is the correct diff. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Adjustments-to-the-manual.patch --] [-- Type: text/x-patch, Size: 4631 bytes --] From ef65b9f4c2e46216f862169ecaee0d9cea09e290 Mon Sep 17 00:00:00 2001 Message-ID: <ef65b9f4c2e46216f862169ecaee0d9cea09e290.1732643142.git.yantar92@posteo.net> From: Ihor Radchenko <yantar92@posteo.net> Date: Tue, 26 Nov 2024 18:45:32 +0100 Subject: [PATCH] Adjustments to the manual --- doc/org-manual.org | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 3a4840d56f..95846b4758 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11780,18 +11780,20 @@ ** Literal Examples ** Images and link previews :PROPERTIES: -:DESCRIPTION: Preview links in the buffer. +:DESCRIPTION: Preview links in the buffer :END: -Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org -buffers. By default, only image links[fn::Image links are =file:= and -=attachment:= links to existing image files; Emacs should be able to -display the linked images (see ~image-types~ variable)] can be previewed -inline, where the images are displayed in place of the link path. +Org mode can display previews of hyperlinks (see [[*Hyperlinks]]) +inside Org buffers. By default, only image links[fn::Image links are +=file:= and =attachment:= links to existing image files; Emacs can +only display the linked images listed in ~image-types~ variable] can +be previewed inline, where the images are displayed in place of the +link path. -You can customize the previews as described in the [[*Adding Hyperlink -preview]] section. Link previews do not have to display images -- any -overlay property (see [[info:elisp#Overlay Properties]]) can be used. +You can customize the previews as described in [[*Adding Hyperlink +preview]]. Link previews do not have to display images -- any kind of +display decoration (see [[info:elisp#Overlay Properties]]) can be +used. You can preview the supported link types in the whole buffer, in the current section, active region or at point with the following commands: @@ -11800,11 +11802,12 @@ ** Images and link previews #+kindex: C-c C-x C-v #+findex: org-link-preview - Create inline previews for external links in the active region, the - link at point or in the current section. With a prefix argument, - clear link previews at point or in the current entry. With a double - prefix argument, preview all links in the buffer. With triple - prefix argument, hide previews for all links in the buffer. + Create inline previews for external links in the active region, if + any; the link at point, if there is such; or in the current section + otherwise. With a prefix argument, clear link previews at point or + in the current entry. With a double prefix argument, preview all + links in the buffer. With triple prefix argument, hide previews for + all links in the buffer. By default, only links without descriptions are previewed. You can force displaying previews for all supported links (including @@ -11827,14 +11830,14 @@ ** Images and link previews Assure inline display of external link previews in the whole buffer and refresh them. -- (~org-link-preview-region~) :: +- {{{kbd(M-x org-link-preview-region)}}} :: #+findex: org-link-preview-region Create inline previews for external links in the active region, or the buffer. With a prefix argument, also preview links with a text description part. -- (~org-link-preview-clear~) :: +- {{{kbd(M-x org-link-preview-clear)}}} :: #+findex: org-link-preview-clear Clear external link previews in the active region, or the buffer. @@ -11870,8 +11873,8 @@ *** Images [[./img/a.jpg]] #+end_example -When [[*Images and link previews][link previews]] are displayed as -images, the image size and alignment can be further customized. +When link previews are displayed as images, the image size and +alignment can be further customized. #+vindex: org-image-actual-width #+vindex: org-image-max-width @@ -21911,7 +21914,7 @@ ** Adding Hyperlink preview In our example, we use the =convert= program (part of the =imagemagick= suite of tools) to create the thumbnail that is displayed inline. - + #+begin_src emacs-lisp (defun org-link-docview-preview (ov path _elem) "Preview file at PATH in overlay OV. @@ -21938,7 +21941,7 @@ ** Adding Hyperlink preview 3. Now previews of docview links for supported document types (PDF, djvu) are generated (along with image file previews) when calling ~org-link-preview~. - + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. -- 2.47.0 [-- Attachment #3: Type: text/plain, Size: 224 bytes --] -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v12] Inline image display as part of a new org-link-preview system 2024-11-26 17:46 ` Ihor Radchenko @ 2024-11-26 18:45 ` Karthik Chikmagalur 2024-11-27 18:47 ` Ihor Radchenko 0 siblings, 1 reply; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-26 18:45 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 191 bytes --] >> Oops. My patch happens to be exactly the opposite of my changes. (I >> chose wrong direction of the diff range) > > Here is the correct diff. Applied diff and rebased onto main. Karthik [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-link-Customizable-preview-API-for-arbitrary-link.patch --] [-- Type: text/x-patch, Size: 75552 bytes --] From eedcba143b72abdaa93e34d1356616012141c7f4 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH 1/2] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-batch-size, org-link-preview-delay, org-link-preview--timer, org-link-preview--queue, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-preview, org-link-preview-region, org-link-preview--process-queue, org-link-preview-clear, org-link-preview-file, org-display-remote-inline-images, org-image-align, org--create-inline-image, org-display-inline-image--width, org-image--align): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. This link parameter is a function called asynchronously to place previes. File links and attachments are previewed using inline image previews as before. Move image handling utilities from lisp/org.el to lisp/ol.el. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images, org-startup-with-inline-images, org-startup-with-link-previews, org-startup-options): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. Rename `org-startup-with-inline-images' to `org-startup-with-link-previews'. Add new STARTUP options for link previews to `org-startup-options': "linkpreviews" and "nolinkpreviews". * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images, org-cycle-display-link-previews, org-cycle-inline-images-display, org-cycle-inline-link-previews): Use `org-link-preview' and `org-link-preview-region'. Rename inline-images functions and user options to their link-previews equivalents: - `org-cycle-display-inline-images' to `org-cycle-display-link-previews' - `org-cycle-inline-images-display' to `org-cycle-inline-link-previews' * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el (org-attach-preview-file): Add new `:preview' link parameter for links of type "attachment", set to the new function `org-attach-preview-file'. --- lisp/ol.el | 603 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 185 +++++++++++ lisp/org-cycle.el | 31 +- lisp/org-keys.el | 7 +- lisp/org-plot.el | 2 +- lisp/org.el | 554 +------------------------------ testing/lisp/test-org-fold.el | 4 +- 8 files changed, 839 insertions(+), 558 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index ea01e2254..032610bad 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,11 @@ (declare-function org-src-source-buffer "org-src" ()) (declare-function org-src-source-type "org-src" ()) (declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function outline-next-heading "outline" ()) +(declare-function image-flush "image" (spec &optional frame)) +(declare-function org-entry-end-position "org" ()) +(declare-function org-element-contents-begin "org-element" (node)) +(declare-function org-element-contents-end "org-element" (node)) +(declare-function org-property-or-variable-value "org" (var &optional inherit)) \f ;;; Customization @@ -171,6 +176,18 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run to generate an in-buffer preview for the link. It + must accept three arguments: + - an overlay placed from the start to the end of the link + - the link path, as a string + - the syntax node for the link + + This function must return a non-nil value to indicate success. + A return value of nil implies that the preview failed, and the + overlay placed on the link will be removed. + `:help-echo' String or function used as a value for the `help-echo' text @@ -521,6 +538,81 @@ (defcustom org-link-keep-stored-after-insertion nil :type 'boolean :safe #'booleanp) +(defcustom org-link-preview-delay 0.05 + "Idle delay in seconds between link previews when using +`org-link-preview'. Links are previewed in batches (see +`org-link-preview-batch-size') spaced out by this delay. Set +this to a small number for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'number) + +(defcustom org-link-preview-batch-size 6 + "Number of links that are previewed at once with +`org-link-preview'. Links are previewed asynchronously, in +batches spaced out in time (see `org-link-preview-delay'). Set +this to a large integer for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'natnum) + +(defcustom org-display-remote-inline-images 'skip + "How to display remote inline images. +Possible values of this option are: + +skip Don't display remote images. +download Always download and display remote images. +t +cache Display remote images, and open them in separate buffers + for caching. Silently update the image buffer when a file + change is detected." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Ignore remote images" skip) + (const :tag "Always display remote images" download) + (const :tag "Display and silently update remote images" cache)) + :safe #'symbolp) + +(defcustom org-image-max-width 'fill-column + "When non-nil, limit the displayed image width. +This setting only takes effect when `org-image-actual-width' is set to +t or when #+ATTR* is set to t. + +Possible values: +- `fill-column' :: limit width to `fill-column' +- `window' :: limit width to window width +- integer :: limit width to number in pixels +- float :: limit width to that fraction of window width +- nil :: do not limit image width" + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Do not limit image width" nil) + (const :tag "Limit to `fill-column'" fill-column) + (const :tag "Limit to window width" window) + (integer :tag "Limit to a number of pixels") + (float :tag "Limit to a fraction of window width"))) + +(defcustom org-image-align 'left + "How to align images previewed using `org-link-preview-region'. + +Only stand-alone image links are affected by this setting. These +are links without surrounding text. + +Possible values of this option are: + +left Insert image at specified position. +center Center image previews. +right Right-align image previews." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Left align (or don\\='t align) image previews" left) + (const :tag "Center image previews" center) + (const :tag "Right align image previews" right)) + :safe #'symbolp) + ;;; Public variables (defconst org-target-regexp (let ((border "[^<>\n\r \t]")) @@ -649,6 +741,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +996,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1908,231 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, also preview links with description in +the active region, at point or in the current section. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] Displaying %d images inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if-let ((ov (cdr (get-char-property-and-overlay + (point) 'org-image-overlay)))) + ;; clear link preview at point + (funcall toggle-previews + (overlay-start ov) (overlay-end ov) + "preview at point" 'remove) + ;; Clear link previews in entry + (funcall toggle-previews + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))) + (org-with-limited-levels (org-entry-end-position)) + "current section" 'remove))) + ;; C-u C-u or C-11 argument :: display images in the whole buffer. + ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove)) + ;; Argument nil or 1, no region selected :: display images in + ;; current section or image link at point. + ((and (member arg '(nil 1)) (null beg) (null end)) + (let ((context (org-element-context))) + ;; toggle display of inline image link at point. + (if (org-element-type-p context 'link) + (let* ((ov (cdr-safe (get-char-property-and-overlay + (point) 'org-image-overlay))) + (remove? (and ov (memq ov org-link-preview-overlays) + 'remove))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "image at point" remove?)) + (let ((beg (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point))))) + (end (org-with-limited-levels (org-entry-end-position)))) + (funcall toggle-previews beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-previews beg end "region"))))) + +;;;###autoload +(defun org-link-preview-refresh () + "Assure display of link previews in buffer and refresh them." + (interactive) + (org-link-preview-region nil t (point-min) (point-max))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh (org-link-preview-clear beg end)) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t) + preview-queue) + ;; Collect links to preview + (while (re-search-forward org-link-any-re end t) + (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)))))) + \f ;;; Built-in link types @@ -1595,7 +2155,48 @@ (defun org-link--open-elisp (path _) (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) ;;;; "file" link type -(org-link-set-parameters "file" :complete #'org-link-complete-file) +(org-link-set-parameters "file" + :complete #'org-link-complete-file + :preview #'org-link-preview-file) + +(defun org-link-preview-file (ov path link) + "Display image file PATH in overlay OV for LINK. + +LINK is the Org element being previewed. + +Equip each image with the keymap `image-map'. + +This is intended to be used as the `:preview' link property of +file links, see `org-link-parameters'." + (if (not (display-graphic-p)) + (prog1 nil + (message "Your Emacs does not support displaying images!")) + (require 'image) + (when-let* ((file-full (expand-file-name path)) + (file (substitute-in-file-name file-full)) + ((string-match-p (image-file-name-regexp) file)) + ((file-exists-p file))) + (let* ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (image (org--create-inline-image file width))) + (when image ; Add image to overlay + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'keymap image-map) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + t))))) ;;;; "help" link type (defun org-link--open-help (path _) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index d4393324f..4fe06cbf4 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -797,9 +797,18 @@ (defun org-attach-follow (file arg) See `org-open-file' for details about ARG." (org-link-open-as-file (org-attach-expand file) arg)) +(defun org-attach-preview-file (ov path link) + "Preview attachment with PATH in overlay OV. + +LINK is the Org link element being previewed." + (org-with-point-at (org-element-begin link) + (org-link-preview-file + ov (org-attach-expand path) link))) + (org-link-set-parameters "attachment" :follow #'org-attach-follow - :complete #'org-attach-complete-link) + :complete #'org-attach-complete-link + :preview #'org-attach-preview-file) (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." diff --git a/lisp/org-compat.el b/lisp/org-compat.el index 05fe13457..20caafb2b 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -1010,6 +1010,191 @@ (define-obsolete-function-alias 'org-open-link-from-string (define-obsolete-function-alias 'org-add-angle-brackets 'org-link-add-angle-brackets "9.3") +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-link-preview--get-overlays "ol" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-attach-expand "org-attach" (file)) +(declare-function org-display-inline-image--width "org" (link)) +(declare-function org-image--align "org" (link)) +(declare-function org--create-inline-image "org" (file width)) + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-function-alias 'org-redisplay-inline-images + 'org-link-preview-refresh "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +(declare-function org-link-preview-region "ol") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-link-preview-region include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + + ;; The function was made obsolete by commit 65399674d5 of 2013-02-22. ;; This make-obsolete call was added 2016-09-01. (make-obsolete 'org-capture-import-remember-templates diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..97a545393 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -217,7 +217,7 @@ (defcustom org-cycle-pre-hook nil (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees org-cycle-show-empty-lines org-cycle-optimize-window-after-visibility-change - org-cycle-display-inline-images) + org-cycle-display-link-previews) "Hook that is run after `org-cycle' has changed the buffer visibility. The function(s) in this hook must accept a single argument which indicates the new state that was set by the most recent `org-cycle' command. The @@ -237,11 +237,15 @@ (defcustom org-cycle-open-archived-trees nil :group 'org-cycle :type 'boolean) -(defcustom org-cycle-inline-images-display nil - "Non-nil means auto display inline images under subtree when cycling." +(defvaralias 'org-cycle-inline-images-display + 'org-cycle-link-previews-display + "Non-nil means auto display inline images under subtree when cycling.") + +(defcustom org-cycle-link-previews-display nil + "Non-nil means auto display link previews under subtree when cycling." :group 'org-startup :group 'org-cycle - :package-version '(Org . "9.6") + :package-version '(Org . "9.8") :type 'boolean) (defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook) @@ -804,12 +808,15 @@ (defun org-cycle-hide-archived-subtrees (state) "Subtree is archived and stays closed. Use \ `\\[org-cycle-force-archived]' to cycle it anyway.")))))) -(defun org-cycle-display-inline-images (state) +(defalias 'org-cycle-inline-images-display + 'org-cycle-display-link-previews) + +(defun org-cycle-display-link-previews (state) "Auto display inline images under subtree when cycling. -It works when `org-cycle-inline-images-display' is non-nil. +It works when `org-cycle-link-previews-display' is non-nil. STATE is the current outline visibility state. It should be one of symbols `content', `all', `folded', `children', or `subtree'." - (when org-cycle-inline-images-display + (when org-cycle-link-previews-display (pcase state ('children (org-with-wide-buffer @@ -817,19 +824,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index f6de753af..3f07a8818 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,8 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) +(declare-function org-link-preview-refresh "ol" ()) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -652,8 +653,8 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) -(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) +(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-link-preview-refresh) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index 1e9057935..def094a9a 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -1160,14 +1160,22 @@ (defcustom org-startup-shrink-all-tables nil :package-version '(Org . "9.2") :safe #'booleanp) -(defcustom org-startup-with-inline-images nil +(defvaralias 'org-startup-with-inline-images + 'org-startup-with-link-previews "Non-nil means show inline images when loading a new Org file. This can also be configured on a per-file basis by adding one of the following lines anywhere in the buffer: #+STARTUP: inlineimages - #+STARTUP: noinlineimages" + #+STARTUP: noinlineimages") + +(defcustom org-startup-with-link-previews nil + "Non-nil means show link previews when loading a new Org file. +This can also be configured on a per-file basis by adding one of +the following lines anywhere in the buffer: + #+STARTUP: linkpreviews + #+STARTUP: nolinkpreviews" :group 'org-startup - :version "24.1" + :version "29.4" :type 'boolean) (defcustom org-startup-with-latex-preview nil @@ -4153,8 +4161,10 @@ (defconst org-startup-options ("shrink" org-startup-shrink-all-tables t) ("descriptivelinks" org-link-descriptive t) ("literallinks" org-link-descriptive nil) - ("inlineimages" org-startup-with-inline-images t) - ("noinlineimages" org-startup-with-inline-images nil) + ("inlineimages" org-startup-with-link-previews t) + ("noinlineimages" org-startup-with-link-previews nil) + ("linkpreviews" org-startup-with-link-previews t) + ("nolinkpreviews" org-startup-with-link-previews nil) ("latexpreview" org-startup-with-latex-preview t) ("nolatexpreview" org-startup-with-latex-preview nil) ("customtime" org-display-custom-times t) @@ -5079,7 +5089,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -15601,26 +15611,6 @@ (defcustom org-image-actual-width t (list :tag "Use #+ATTR* or a number of pixels" (integer)) (const :tag "Use #+ATTR* or don't resize" nil))) -(defcustom org-image-max-width 'fill-column - "When non-nil, limit the displayed image width. -This setting only takes effect when `org-image-actual-width' is set to -t or when #+ATTR* is set to t. - -Possible values: -- `fill-column' :: limit width to `fill-column' -- `window' :: limit width to window width -- integer :: limit width to number in pixels -- float :: limit width to that fraction of window width -- nil :: do not limit image width" - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Do not limit image width" nil) - (const :tag "Limit to `fill-column'" fill-column) - (const :tag "Limit to window width" window) - (integer :tag "Limit to a number of pixels") - (float :tag "Limit to a fraction of window width"))) - (defcustom org-agenda-inhibit-startup nil "Inhibit startup when preparing agenda buffers. When this variable is t, the initialization of the Org agenda @@ -16667,518 +16657,6 @@ (defun org-normalize-color (value) (format "%g" (/ value 65535.0))) \f -;; Image display - -(defvar-local org-inline-image-overlays nil) -;; Preserve when switching modes or when restarting Org. -;; If we clear the overlay list and later enable Or mode, the existing -;; image overlays will never be cleared by `org-toggle-inline-images' -;; and `org-toggle-inline-images-command'. -(put 'org-inline-image-overlays 'permanent-local t) - -(defun org--inline-image-overlays (&optional beg end) - "Return image overlays between BEG and END." - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end)) - result) - (dolist (ov overlays result) - (when (memq ov org-inline-image-overlays) - (push ov result))))) - -(defun org-toggle-inline-images-command (&optional arg beg end) - "Toggle display of inline images without description at point. - -When point is at an image link, toggle displaying that image. -Otherwise, toggle displaying images in current entry. - -When region BEG..END is active, toggle displaying images in the -region. - -With numeric prefix ARG 1, display images with description as well. - -With prefix ARG `\\[universal-argument]', toggle displaying images in -the accessible portion of the buffer. With numeric prefix ARG 11, do -the same, but include images with description. - -With prefix ARG `\\[universal-argument] \\[universal-argument]', hide -all the images in accessible portion of the buffer. - -This command is designed for interactive use. From Elisp, you can -also use `org-toggle-inline-images'." - (interactive (cons current-prefix-arg - (when (use-region-p) - (list (region-beginning) (region-end))))) - (let* ((include-linked - (cond - ((member arg '(nil (4) (16)) ) nil) - ((member arg '(1 11)) 'include-linked) - (t 'include-linked))) - (interactive? (called-interactively-p 'any)) - (toggle-images - (lambda (&optional beg end scope force-remove) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (old (org--inline-image-overlays beg end)) - (scope (or scope (format "%d:%d" beg end)))) - (if (or old force-remove) - (progn - (org-remove-inline-images beg end) - (when interactive? - (message - "[%s] Inline image display turned off (removed %d images)" - scope (length old)))) - (org-display-inline-images include-linked t beg end) - (when interactive? - (let ((new (org--inline-image-overlays beg end))) - (message - (if new - (format "[%s] %d images displayed inline %s" - scope (length new) - (if include-linked "(including images with description)" - "")) - (format "[%s] No images to display inline" scope)))))))))) - (cond - ((not (display-graphic-p)) - (message "Your Emacs does not support displaying images!")) - ;; Region selected :: toggle images in region. - ((and beg end) (funcall toggle-images beg end "region")) - ;; C-u or C-11 argument :: toggle images in the whole buffer. - ((member arg '(11 (4))) (funcall toggle-images nil nil "buffer")) - ;; C-u C-u argument :: unconditionally hide images in the buffer. - ((equal arg '(16)) (funcall toggle-images nil nil "buffer" 'remove)) - ;; Argument nil or 1, no region selected :: toggle (display or hide - ;; dwim) images in current section or image link at point. - ((and (member arg '(nil 1)) (null beg) (null end)) - (let ((context (org-element-context))) - ;; toggle display of inline image link at point. - (if (org-element-type-p context 'link) - (funcall toggle-images - (org-element-begin context) - (org-element-end context) - "image at point") - (let ((beg (if (org-before-first-heading-p) (point-min) - (save-excursion - (org-with-limited-levels (org-back-to-heading t) (point))))) - (end (org-with-limited-levels (org-entry-end-position)))) - (funcall toggle-images beg end "current section"))))) - ;; Any other non-nil argument. - ((not (null arg)) (funcall toggle-images beg end "region"))))) - -(defun org-toggle-inline-images (&optional include-linked beg end) - "Toggle the display of inline images. -INCLUDE-LINKED is passed to `org-display-inline-images'." - (interactive "P") - (if (org--inline-image-overlays beg end) - (progn - (org-remove-inline-images beg end) - (when (called-interactively-p 'interactive) - (message "Inline image display turned off"))) - (org-display-inline-images include-linked nil beg end) - (when (called-interactively-p 'interactive) - (let ((new (org--inline-image-overlays beg end))) - (message (if new - (format "%d images displayed inline" - (length new)) - "No images to display inline")))))) - -(defun org-redisplay-inline-images () - "Assure display of inline images and refresh them." - (interactive) - (org-toggle-inline-images) - (unless org-inline-image-overlays - (org-toggle-inline-images))) - -;; For without-x builds. -(declare-function image-flush "image" (spec &optional frame)) - -(defcustom org-display-remote-inline-images 'skip - "How to display remote inline images. -Possible values of this option are: - -skip Don't display remote images. -download Always download and display remote images. -t -cache Display remote images, and open them in separate buffers - for caching. Silently update the image buffer when a file - change is detected." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Ignore remote images" skip) - (const :tag "Always display remote images" download) - (const :tag "Display and silently update remote images" cache)) - :safe #'symbolp) - -(defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. - -Only stand-alone image links are affected by this setting. These -are links without surrounding text. - -Possible values of this option are: - -left Insert image at specified position. -center Center image previews. -right Right-align image previews." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Left align (or don\\='t align) image previews" left) - (const :tag "Center image previews" center) - (const :tag "Right align image previews" right)) - :safe #'symbolp) - -(defun org--create-inline-image (file width) - "Create image located at FILE, or return nil. -WIDTH is the width of the image. The image may not be created -according to the value of `org-display-remote-inline-images'." - (let* ((remote? (file-remote-p file)) - (file-or-data - (pcase org-display-remote-inline-images - ((guard (not remote?)) file) - (`download (with-temp-buffer - (set-buffer-multibyte nil) - (insert-file-contents-literally file) - (buffer-string))) - ((or `cache `t) - (let ((revert-without-query '("."))) - (with-current-buffer (find-file-noselect file) - (buffer-string)))) - (`skip nil) - (other - (message "Invalid value of `org-display-remote-inline-images': %S" - other) - nil)))) - (when file-or-data - (create-image file-or-data - (and (image-type-available-p 'imagemagick) - width - 'imagemagick) - remote? - :width width - :max-width - (pcase org-image-max-width - (`fill-column (* fill-column (frame-char-width (selected-frame)))) - (`window (window-width nil t)) - ((pred integerp) org-image-max-width) - ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) - (`nil nil) - (_ (error "Unsupported value of `org-image-max-width': %S" - org-image-max-width))) - :scale 1)))) - -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - -(declare-function org-export-read-attribute "ox" - (attribute element &optional property)) -(defvar visual-fill-column-width) ; Silence compiler warning -(defun org-display-inline-image--width (link) - "Determine the display width of the image LINK, in pixels. -- When `org-image-actual-width' is t, the image's pixel width is used. -- When `org-image-actual-width' is a number, that value will is used. -- When `org-image-actual-width' is nil or a list, :width attribute of - #+attr_org or the first #+attr_... (if it exists) is used to set the - image width. A width of X% is divided by 100. If the value is a - float between 0 and 2, it interpreted as that proportion of the text - width in the buffer. - - If no :width attribute is given and `org-image-actual-width' is a - list with a number as the car, then that number is used as the - default value." - ;; Apply `org-image-actual-width' specifications. - ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified - ;; width. - (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) - (cond - ((eq org-image-actual-width t) nil) - ((listp org-image-actual-width) - (require 'ox) - (let* ((par (org-element-lineage link 'paragraph)) - ;; Try to find an attribute providing a :width. - ;; #+ATTR_ORG: :width ... - (attr-width (org-export-read-attribute :attr_org par :width)) - (width-unreadable? - (lambda (value) - (or (not (stringp value)) - (unless (string= value "t") - (or (not (string-match - (rx bos (opt "+") - (or - ;; Number of pixels - ;; must be a lone number, not - ;; things like 4in - (seq (1+ (in "0-9")) eos) - ;; Numbers ending with % - (seq (1+ (in "0-9.")) (group-n 1 "%")) - ;; Fractions - (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) - value)) - (let ((number (string-to-number value))) - (and (floatp number) - (not (match-string 1 value)) ; X% - (not (<= 0.0 number 2.0))))))))) - ;; #+ATTR_BACKEND: :width ... - (attr-other - (catch :found - (org-element-properties-map - (lambda (prop _) - (when (and - (not (eq prop :attr_org)) - (string-match-p "^:attr_" (symbol-name prop)) - (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) - (throw :found prop))) - par))) - (attr-width - (if (not (funcall width-unreadable? attr-width)) - attr-width - ;; When #+attr_org: does not have readable :width - (and attr-other - (org-export-read-attribute attr-other par :width)))) - (width - (cond - ;; Treat :width t as if `org-image-actual-width' were t. - ((string= attr-width "t") nil) - ;; Fallback to `org-image-actual-width' if no interprable width is given. - ((funcall width-unreadable? attr-width) - (car org-image-actual-width)) - ;; Convert numeric widths to numbers, converting percentages. - ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) - (/ (string-to-number attr-width) 100.0)) - (t (string-to-number attr-width))))) - (if (and (floatp width) (<= 0.0 width 2.0)) - ;; A float in [0,2] should be interpereted as this portion of - ;; the text width in the window. This works well with cases like - ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, - ;; as the "0.X" is pulled out as a float. We use 2 as the upper - ;; bound as cases such as 1.2\linewidth are feasible. - (round (* width - (window-pixel-width) - (/ (or (and (bound-and-true-p visual-fill-column-mode) - (or visual-fill-column-width auto-fill-function)) - (when auto-fill-function fill-column) - (- (window-text-width) (line-number-display-width))) - (float (window-total-width))))) - width))) - ((numberp org-image-actual-width) - org-image-actual-width) - (t nil)))) - -(defun org-image--align (link) - "Determine the alignment of the image LINK. -LINK is a link object. - -In decreasing order of priority, this is controlled: -- Per image by the value of `:center' or `:align' in the -affiliated keyword `#+attr_org'. -- By the `#+attr_html' or `#+attr_latex` keywords with valid - `:center' or `:align' values. -- Globally by the user option `org-image-align'. - -The result is either nil or one of the strings \"left\", -\"center\" or \"right\". - -\"center\" will cause the image preview to be centered, \"right\" -will cause it to be right-aligned. A value of \"left\" or nil -implies no special alignment." - (let ((par (org-element-lineage link 'paragraph))) - ;; Only align when image is not surrounded by paragraph text: - (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align - (= (org-element-begin link) - (save-excursion - (goto-char (org-element-contents-begin par)) - (skip-chars-forward "\t ") - (point))) ;account for leading space - ;before link - (<= (- (org-element-contents-end par) - (org-element-end link)) - 1)) ;account for trailing newline - ;at end of paragraph - (save-match-data - ;; Look for a valid ":center t" or ":align left|center|right" - ;; attribute. - ;; - ;; An attr_org keyword has the highest priority, with - ;; any attr.* next. Choosing between these is - ;; unspecified. - (let ((center-re ":\\(center\\)[[:space:]]+t\\b") - (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") - attr-align) - (catch 'exit - (org-element-properties-mapc - (lambda (propname propval) - (when (and propval - (string-match-p ":attr.*" (symbol-name propname))) - (setq propval (car-safe propval)) - (when (or (string-match center-re propval) - (string-match align-re propval)) - (setq attr-align (match-string 1 propval)) - (when (eq propname :attr_org) - (throw 'exit t))))) - par)) - (if attr-align - (when (member attr-align '("center" "right")) attr-align) - ;; No image-specific keyword, check global alignment property - (when (memq org-image-align '(center right)) - (symbol-name org-image-align)))))))) - - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.46.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --] [-- Type: text/x-patch, Size: 19841 bytes --] From 43df7292f834644aff1cdc8e0c28eaedacba6fe7 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Sun, 27 Oct 2024 20:22:59 -0700 Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types * etc/ORG-NEWS: Mention new commands and options for link preview. * doc/org-manual.org: Add sections on - previewing external links (Images and link previews), and - adding the link preview feature to other link types (Adding Hyperlink preview). --- doc/org-manual.org | 293 ++++++++++++++++++++++++++++++--------------- etc/ORG-NEWS | 93 ++++++++++++-- 2 files changed, 281 insertions(+), 105 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 451fd72cd..95846b475 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11778,7 +11778,77 @@ ** Literal Examples the end of the current line. Then the label is stored as a link =(label)=, for retrieval with {{{kbd(C-c C-l)}}}. -** Images +** Images and link previews +:PROPERTIES: +:DESCRIPTION: Preview links in the buffer +:END: + +Org mode can display previews of hyperlinks (see [[*Hyperlinks]]) +inside Org buffers. By default, only image links[fn::Image links are +=file:= and =attachment:= links to existing image files; Emacs can +only display the linked images listed in ~image-types~ variable] can +be previewed inline, where the images are displayed in place of the +link path. + +You can customize the previews as described in [[*Adding Hyperlink +preview]]. Link previews do not have to display images -- any kind of +display decoration (see [[info:elisp#Overlay Properties]]) can be +used. + +You can preview the supported link types in the whole buffer, in the +current section, active region or at point with the following commands: + +- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) :: + + #+kindex: C-c C-x C-v + #+findex: org-link-preview + Create inline previews for external links in the active region, if + any; the link at point, if there is such; or in the current section + otherwise. With a prefix argument, clear link previews at point or + in the current entry. With a double prefix argument, preview all + links in the buffer. With triple prefix argument, hide previews for + all links in the buffer. + + By default, only links without descriptions are previewed. You + can force displaying previews for all supported links (including + links with descriptions) using a numeric argument of ~1~. This + will toggle all previews in the active region, the link at point + or the current section. A numeric prefix argument of ~11~ will + toggle previews in the whole buffer instead. + + #+vindex: org-startup-with-link-previews + Org mode can display link previews automatically when opening + buffers. Either customize ~org-startup-with-link-previews~ or use + the =#+STARTUP: linkpreviews= keyword to enable previews for that + specific buffer when opening it. =#+STARTUP: nolinkpreviews= will + disable previews on startup in the buffer. + +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: + + #+kindex: C-c C-x C-M-v + #+findex: org-link-preview-refresh + Assure inline display of external link previews in the whole buffer + and refresh them. + +- {{{kbd(M-x org-link-preview-region)}}} :: + + #+findex: org-link-preview-region + Create inline previews for external links in the active region, or + the buffer. With a prefix argument, also preview links with a text + description part. + +- {{{kbd(M-x org-link-preview-clear)}}} :: + + #+findex: org-link-preview-clear + Clear external link previews in the active region, or the buffer. + +#+vindex: org-cycle-inline-images-display +Link previews can also be displayed when cycling the folding state. +When the custom option ~org-cycle-link-previews-display~ is set, +supported link types under the subtree (including images) will be +displayed automatically. + +*** Images :PROPERTIES: :DESCRIPTION: Display an image. :END: @@ -11803,102 +11873,76 @@ ** Images [[./img/a.jpg]] #+end_example -Such images can be displayed within the buffer with the following -command: - -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: - - #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images-command - Toggle the inline display of linked images in current section or at - point. With a prefix argument, toggle inline images in the whole - buffer. With double prefix argument, hide all the images in buffer. - - By default, only the image links without description are displayed. - You can force displaying all the images using numeric argument to - toggle all the images in current section (~1~ argument) or the whole - buffer (~11~ argument). - - #+vindex: org-startup-with-inline-images - You can ask for inline images to be displayed at - startup by configuring the variable - ~org-startup-with-inline-images~[fn:: The variable - ~org-startup-with-inline-images~ can be set within a buffer with the - =STARTUP= options =inlineimages= and =noinlineimages=.]. - - - #+vindex: org-image-actual-width - #+vindex: org-image-max-width - #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property - By default, Org mode displays inline images according to their - actual width, but no wider than ~fill-column~ characters. - - You can customize the displayed image width using - ~org-image-actual-width~ variable (globally) or - =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can - be customized in Emacs >= 24.1, built with ImageMagick support.]. - Their value can be the following: - - (default) Non-~nil~, use the actual width of images when inlining - them. If the actual width is too wide, limit it according to - ~org-image-max-width~. - - When set to a number, use ImageMagick (when available) to set the - image's width to this value. - - When set to a number in a list, try to get the width from any - =#+ATTR.*= keyword if it matches a width specification like: - #+begin_example - ,#+ATTR_HTML: :width 300px - #+end_example - and fall back on that number if none is found. - - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword - and fall back on the original width or ~org-image-max-width~ if - none is found. - - ~org-image-max-width~ limits the maximum displayed image width, but - only when the image width is not set explicitly. Possible maximum - width can be set to: - - (default) ~fill-column~, limit width to ~fill-column~ number of - characters. - - ~window~, limit width to current window width. - - integer number, limit width to that specified number of pixels. - - ~nil~, do not limit the width. - - #+vindex: org-image-align - Org mode can left-align, center or right-align the display of inline - images. This setting is controlled (globally) by ~org-image-align~. - Only standalone images are affected, corresponding to links with no - surrounding text in their paragraph except for whitespace. Its - value can be the following: - - (default) The symbol ~left~, which inserts the image where the - link appears in the buffer. - - The symbol ~center~, which will preview links centered in the - Emacs window. - - The symbol ~right~, which will preview links right-aligned in the - Emacs window. - - Inline image alignment can be specified for each link using the - =#+ATTR.*= keyword if it matches an alignment specification like: +When link previews are displayed as images, the image size and +alignment can be further customized. + +#+vindex: org-image-actual-width +#+vindex: org-image-max-width +#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property +By default, Org mode displays inline images according to their +actual width, but no wider than ~fill-column~ characters. + +You can customize the displayed image width using +~org-image-actual-width~ variable (globally) or +=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can +be customized in Emacs >= 24.1, built with ImageMagick support.]. +Their value can be the following: +- (default) Non-~nil~, use the actual width of images when inlining + them. If the actual width is too wide, limit it according to + ~org-image-max-width~. +- When set to a number, use ImageMagick (when available) to set the + image's width to this value. +- When set to a number in a list, try to get the width from any + =#+ATTR.*= keyword if it matches a width specification like: #+begin_example - ,#+ATTR_HTML: :align center + ,#+ATTR_HTML: :width 300px #+end_example - Org will use the alignment specification from any =#+ATTR.*= - keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= - (if present) will override the others. For instance, this link - #+begin_example - ,#+ATTR_HTML: :align right - ,#+ATTR_ORG: :align center - [[/path/to/image/file.png]] - #+end_example - will be displayed centered in Emacs but exported right-aligned to - HTML. - - When =#+ATTR_ORG= is not set, inline image alignment is also read - from the =:center= attribute supported by some export backends (like - HTML, LaTeX and Beamer.) + and fall back on that number if none is found. +- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword + and fall back on the original width or ~org-image-max-width~ if + none is found. + +~org-image-max-width~ limits the maximum displayed image width, but +only when the image width is not set explicitly. Possible maximum +width can be set to: +- (default) ~fill-column~, limit width to ~fill-column~ number of + characters. +- ~window~, limit width to current window width. +- integer number, limit width to that specified number of pixels. +- ~nil~, do not limit the width. + +#+vindex: org-image-align +Org mode can left-align, center or right-align the display of inline +images. This setting is controlled (globally) by ~org-image-align~. +Only standalone images are affected, corresponding to links with no +surrounding text in their paragraph except for whitespace. Its +value can be the following: +- (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. +- The symbol ~center~, which will preview links centered in the + Emacs window. +- The symbol ~right~, which will preview links right-aligned in the + Emacs window. + +Inline image alignment can be specified for each link using the +=#+ATTR.*= keyword if it matches an alignment specification like: +#+begin_example +,#+ATTR_HTML: :align center +#+end_example +Org will use the alignment specification from any =#+ATTR.*= +keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= +(if present) will override the others. For instance, this link +#+begin_example +,#+ATTR_HTML: :align right +,#+ATTR_ORG: :align center +[[/path/to/image/file.png]] +#+end_example +will be displayed centered in Emacs but exported right-aligned to +HTML. -#+vindex: org-cycle-inline-images-display -Inline images can also be displayed when cycling the folding state. -When custom option ~org-cycle-inline-images-display~ is set, the -visible inline images under subtree will be displayed automatically. +When =#+ATTR_ORG= is not set, inline image alignment is also read from +the =:center= attribute supported by some export backends (like HTML, +LaTeX and Beamer.) ** Captions :PROPERTIES: @@ -21837,6 +21881,67 @@ ** Adding Hyperlink Types topic. It also provides a default description. The function ~org-insert-link~ can insert it back into an Org buffer later on. +** Adding Hyperlink preview +:PROPERTIES: +:DESCRIPTION: Preview behavior for new hyperlink types. +:END: +#+cindex: hyperlinks, adding preview behavior + +By default, Org supports previewing external links for links ot type +=file= and =attachment= that point to image files. (See [[*Images]].) + +Support for previewing other link types inline can be added to Org in +the following way: + +1. Add a =:preview= link parameter to the link type using + ~org-link-set-parameters~. As an example, here we add previews for + the =docview= link type. + + #+begin_src emacs-lisp +(org-link-set-parameters + "docview" :preview #'org-link-docview-preview) + #+end_src + +2. The value of the =:preview= parameter must be a function that + accepts three arguments: + - an overlay placed from the start to the end of the link, + - the link path, as a string, and + - the syntax node for the link. + It must return a non-nil value to indicate preview success. A + value of =nil= implies that the preview failed, and the overlay + placed on the link will be removed. + + In our example, we use the =convert= program (part of the + =imagemagick= suite of tools) to create the thumbnail that is + displayed inline. + + #+begin_src emacs-lisp +(defun org-link-docview-preview (ov path _elem) + "Preview file at PATH in overlay OV. + +_ELEM is the syntax node of the link element." + (when (executable-find "convert") + (let* ((path (expand-file-name (substitute-in-file-name path))) + (output-file (expand-file-name (concat "org-docview-preview-" + (substring (sha1 path) 0 10) + ".png") + temporary-file-directory))) + ;; Create or find preview for path + (when (or (file-readable-p output-file) + (= 0 (call-process + "convert" + nil (get-buffer-create "*Org Docview Preview Output*") nil + "-thumbnail" "x320" "-colorspace" "rgb" + "-background" "white" "-alpha" "remove" "-strip" + (concat path "[0]") output-file))) + ;; If preview image is available, display it via the overlay + (overlay-put ov 'display (create-image output-file)))))) + #+end_src + +3. Now previews of docview links for supported document types (PDF, + djvu) are generated (along with image file previews) when calling + ~org-link-preview~. + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 92bfe3501..08857962b 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole buffer (or narrowed part of the buffer). With prefix argument, it also forced displaying image links with description. -Now, =C-c C-x C-v= is bound to a new command -~org-toggle-inline-images-command~, which uses different defaults: +Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which +uses different defaults: -1. By default, it toggles image at point or, if there is no image at - point, images in current entry +1. When the region is active, images in the region are previewed -2. When region is active, it is honored +2. Otherwise, if there is an image at point, it is toggled. If there + is no image at point, images in the current entry are previewed -3. =C-u= argument changed its meaning. Now, it forces toggling images - in the whole buffer +3. With the =C-u= argument, image previews in the active region or at + point are cleared instead -4. =C-u C-u= argument unconditionally hides all the images in buffer +4. The =C-u C-u= argument unconditionally shows all images in the + accessible portion of the buffer -5. Displaying images over links with description can be forced using +5. The =C-u C-u C-u= argument unconditionally clears all images in the + accessible portion of the buffer + +6. Displaying images over links with description can be forced using numeric argument: - ~C-u 1~ for toggling all images at point/current entry - ~C-u 11~ for toggling all images in buffer -The old ~org-toggle-inline-images~ command is still available. You -can bind it back to =C-c C-x C-v= by adding the following to you config: +(The first five of these prefix arg behaviors are the same as that of +the ~org-latex-preview~ command.) + +In addition to images, ~org-link-preview~ can also be used to preview +Org links of all types for which preview behavior is defined, see +[[#link-preview][previews for arbitrary link types]]. + +The old ~org-toggle-inline-images~ command is obsolete but still +available. You can bind it back to =C-c C-x C-v= by adding the +following to your config: #+begin_src emacs-lisp (eval-after-load 'org-keys (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) @@ -76,6 +88,16 @@ now have diary timestamps included as well. # We list the most important features, and the features that may # require user action to be used. +*** All Org link types can be previewed +:PROPERTIES: +:CUSTOM_ID: link-preview +:END: + +Org links support a new parameter =:preview= that can be used to +preview arbitrary link types. The value of this parameter should be a +function that is called to preview links of the corresponding type +(see ~org-link-parameters~). + *** Alignment of image previews can be customized This is not a new feature. It has been added in Org 9.7, but not @@ -120,6 +142,17 @@ See the new [[info:org#Repeating commands]["Repeating commands"]] section in Org # adding new customizations, or changing the interpretation of the # existing customizations. +*** New option ~org-link-preview-batch-size~ + +Org link previews are generated asynchronously and a few at a time, in +batches. This option controls the number of links that are previewed +in each batch. + +*** New option ~org-link-preview-delay~ + +Org link previews are generated asynchronously. This option controls +the minimum idle time in seconds between previews of batches of links. + *** Allow disabling macro replacement during export New custom option ~org-export-replace-macros~ controls whether Org @@ -162,6 +195,26 @@ bibliography format requires them to be written in title-case. # This also includes changes in function behavior from Elisp perspective. +*** New command ~org-link-preview~ to preview Org links + +This command replaces ~org-toggle-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer + +This command replaces ~org-display-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer + +This command replaces ~org-remove-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer + +This command replaces ~org-redisplay-inline-images~, which is now +obsolete. + *** ob-sqlite: Added ability to open a database in readonly mode Added option :readonly to ob-sqlite. @@ -204,6 +257,24 @@ leave extra prompts after evaluation, and skipping the prompt filtering can be more robust for such languages (as this avoids removing false positive prompts). +** Removed or renamed functions and variables + +*** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~ + +Inline image previews in Org mode are now provided by the more general +link previews feature. The behavior with regards to image links is +unchanged. + +*** ~org-cycle-inline-images-display~ is renamed to ~org-cycle-link-previews-display~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + +*** ~org-startup-with-inline-images~ is renamed to ~org-startup-with-link-previews~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + ** Miscellaneous *** Org mode no longer prevents =flyspell= from spell-checking inside =LOGBOOK= drawers -- 2.46.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v12] Inline image display as part of a new org-link-preview system 2024-11-26 18:45 ` [PATCH v12] " Karthik Chikmagalur @ 2024-11-27 18:47 ` Ihor Radchenko 2024-11-27 21:53 ` Karthik Chikmagalur 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-11-27 18:47 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >> Here is the correct diff. > > Applied diff and rebased onto main. Applied, onto main. https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=9cce40c94f https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=e87ecf88be May you also share the problem with an overlay being placed after invisible text to emacs-devel? I recall you had a reproducer that does not require Org. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v12] Inline image display as part of a new org-link-preview system 2024-11-27 18:47 ` Ihor Radchenko @ 2024-11-27 21:53 ` Karthik Chikmagalur 0 siblings, 0 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-27 21:53 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode >>> Here is the correct diff. >> >> Applied diff and rebased onto main. > > Applied, onto main. > https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=9cce40c94f > https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=e87ecf88be That's great! I'm on the org-latex-preview branch but I'm looking forward to the rebase now. > May you also share the problem with an overlay being placed after > invisible text to emacs-devel? I recall you had a reproducer that does > not require Org. I don't have a reproducer without Org, but I can create one. I'm not familiar with setting the 'invisible text-property but I'll look into it. Karthik ^ permalink raw reply [flat|nested] 90+ messages in thread
* Re: [PATCH v12] Inline image display as part of a new org-link-preview system 2024-11-25 18:11 ` Ihor Radchenko 2024-11-26 1:08 ` Karthik Chikmagalur @ 2024-11-26 2:08 ` Karthik Chikmagalur 1 sibling, 0 replies; 90+ messages in thread From: Karthik Chikmagalur @ 2024-11-26 2:08 UTC (permalink / raw) To: Ihor Radchenko; +Cc: stardiviner, Org mode [-- Attachment #1: Type: text/plain, Size: 485 bytes --] Patchset attached. > Thanks! > See the attached further adjustments to the manual. I've kept in your changes to the links, but please check the LaTeX output. I'm unable to compile it here. > Also, I found a bug in the new preview system. Fixed. I'm using (forward-char -1) to make sure that we are on the right link when executing (org-element-context) after running the regexp-search. It's a bit of a hack, but I'm assuming there are no zero-length org links possible. 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: 75552 bytes --] From 83933366d0bad7bb997ada2d454e06494403290a Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Fri, 23 Aug 2024 15:46:53 -0700 Subject: [PATCH 1/2] org-link: Customizable preview API for arbitrary link types Add a customizable preview API for arbitrary link types. Make inline image previews a part of the more universal org-link preview feature. Each link type can now be previewed differently based on a new link parameter. * lisp/ol.el (org-link-parameters, org-link-preview-batch-size, org-link-preview-delay, org-link-preview--timer, org-link-preview--queue, org-link-preview-overlays, org-link-preview--get-overlays, org-link-preview--remove-overlay, org-link-preview, org-link-preview-region, org-link-preview--process-queue, org-link-preview-clear, org-link-preview-file, org-display-remote-inline-images, org-image-align, org--create-inline-image, org-display-inline-image--width, org-image--align): Add new commands `org-link-preview', `org-link-preview-region' and `org-link-preview-clear' for creating link previews for any kind of link. Add new org-link parameter `:preview' for specifying how a link type should be previewed. This link parameter is a function called asynchronously to place previes. File links and attachments are previewed using inline image previews as before. Move image handling utilities from lisp/org.el to lisp/ol.el. * testing/lisp/test-org-fold.el: Use `org-link-preview'. * lisp/org.el (org-toggle-inline-images, org-toggle-inline-images-command, org-display-inline-images, org--inline-image-overlays, org-inline-image-overlays, org-redisplay-inline-images, org-image-align, org-display-inline-remove-overlay, org-remove-inline-images, org-startup-with-inline-images, org-startup-with-link-previews, org-startup-options): Obsolete and move `org-toggle-inline-images', `org-display-inline-images' and `org-redisplay-inline-images' to org-compat. These are obsoleted by `org-link-preview' and `org-link-preview-region'. Remove `org-toggle-inline-images-command'. Move the other internal functions to org-link. Rename `org-startup-with-inline-images' to `org-startup-with-link-previews'. Add new STARTUP options for link previews to `org-startup-options': "linkpreviews" and "nolinkpreviews". * lisp/org-plot.el (org-plot/redisplay-img-in-buffer): Modify to use `org-link-preview'. * lisp/org-keys.el: Bind `C-c C-x C-v' to new command `org-link-preview', which has the same prefix arg behaviors as `org-latex-preview'. In addition to these, it supports numeric prefix args 1 and 11 to preview links with descriptions at point/region (with 1) and across the buffer (with 11). * lisp/org-cycle.el (org-cycle-display-inline-images, org-cycle-display-link-previews, org-cycle-inline-images-display, org-cycle-inline-link-previews): Use `org-link-preview' and `org-link-preview-region'. Rename inline-images functions and user options to their link-previews equivalents: - `org-cycle-display-inline-images' to `org-cycle-display-link-previews' - `org-cycle-inline-images-display' to `org-cycle-inline-link-previews' * lisp/org-compat.el (org-display-inline-remove-overlay, org--inline-image-overlays, org-remove-inline-images, org-inline-image-overlays, org-display-inline-images, org-toggle-inline-images): * lisp/org-attach.el (org-attach-preview-file): Add new `:preview' link parameter for links of type "attachment", set to the new function `org-attach-preview-file'. --- lisp/ol.el | 603 +++++++++++++++++++++++++++++++++- lisp/org-attach.el | 11 +- lisp/org-compat.el | 185 +++++++++++ lisp/org-cycle.el | 31 +- lisp/org-keys.el | 7 +- lisp/org-plot.el | 2 +- lisp/org.el | 554 +------------------------------ testing/lisp/test-org-fold.el | 4 +- 8 files changed, 839 insertions(+), 558 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 52ea62d69..3574a25da 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -82,6 +82,11 @@ (declare-function org-src-source-buffer "org-src" ()) (declare-function org-src-source-type "org-src" ()) (declare-function org-time-stamp-format "org" (&optional long inactive)) (declare-function outline-next-heading "outline" ()) +(declare-function image-flush "image" (spec &optional frame)) +(declare-function org-entry-end-position "org" ()) +(declare-function org-element-contents-begin "org-element" (node)) +(declare-function org-element-contents-end "org-element" (node)) +(declare-function org-property-or-variable-value "org" (var &optional inherit)) \f ;;; Customization @@ -171,6 +176,18 @@ (defcustom org-link-parameters nil The default face is `org-link'. +`:preview' + + Function to run to generate an in-buffer preview for the link. It + must accept three arguments: + - an overlay placed from the start to the end of the link + - the link path, as a string + - the syntax node for the link + + This function must return a non-nil value to indicate success. + A return value of nil implies that the preview failed, and the + overlay placed on the link will be removed. + `:help-echo' String or function used as a value for the `help-echo' text @@ -521,6 +538,81 @@ (defcustom org-link-keep-stored-after-insertion nil :type 'boolean :safe #'booleanp) +(defcustom org-link-preview-delay 0.05 + "Idle delay in seconds between link previews when using +`org-link-preview'. Links are previewed in batches (see +`org-link-preview-batch-size') spaced out by this delay. Set +this to a small number for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'number) + +(defcustom org-link-preview-batch-size 6 + "Number of links that are previewed at once with +`org-link-preview'. Links are previewed asynchronously, in +batches spaced out in time (see `org-link-preview-delay'). Set +this to a large integer for more immediate previews, but at the +expense of higher lag." + :group 'org-link + :type 'natnum) + +(defcustom org-display-remote-inline-images 'skip + "How to display remote inline images. +Possible values of this option are: + +skip Don't display remote images. +download Always download and display remote images. +t +cache Display remote images, and open them in separate buffers + for caching. Silently update the image buffer when a file + change is detected." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Ignore remote images" skip) + (const :tag "Always display remote images" download) + (const :tag "Display and silently update remote images" cache)) + :safe #'symbolp) + +(defcustom org-image-max-width 'fill-column + "When non-nil, limit the displayed image width. +This setting only takes effect when `org-image-actual-width' is set to +t or when #+ATTR* is set to t. + +Possible values: +- `fill-column' :: limit width to `fill-column' +- `window' :: limit width to window width +- integer :: limit width to number in pixels +- float :: limit width to that fraction of window width +- nil :: do not limit image width" + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Do not limit image width" nil) + (const :tag "Limit to `fill-column'" fill-column) + (const :tag "Limit to window width" window) + (integer :tag "Limit to a number of pixels") + (float :tag "Limit to a fraction of window width"))) + +(defcustom org-image-align 'left + "How to align images previewed using `org-link-preview-region'. + +Only stand-alone image links are affected by this setting. These +are links without surrounding text. + +Possible values of this option are: + +left Insert image at specified position. +center Center image previews. +right Right-align image previews." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Left align (or don\\='t align) image previews" left) + (const :tag "Center image previews" center) + (const :tag "Right align image previews" right)) + :safe #'symbolp) + ;;; Public variables (defconst org-target-regexp (let ((border "[^<>\n\r \t]")) @@ -649,6 +741,29 @@ (defvar org-link--insert-history nil (defvar org-link--search-failed nil "Non-nil when last link search failed.") +(defvar-local org-link-preview-overlays nil) +;; Preserve when switching modes or when restarting Org. +;; If we clear the overlay list and later enable Or mode, the existing +;; image overlays will never be cleared by `org-link-preview' +;; and `org-link-preview-clear'. +(put 'org-link-preview-overlays 'permanent-local t) + +(defvar-local org-link-preview--timer nil + "Timer for previewing Org links in buffer. + +This timer creates previews for specs in +`org-link-preview--queue'.") + +(defvar-local org-link-preview--queue nil + "Queue of pending previews for Org links in buffer. + +Each element of this queue is a list of the form + +(PREVIEW-FUNC OVERLAY PATH LINK) + +where PREVIEW-FUNC places a preview of PATH using OVERLAY. LINK +is the Org element being previewed.") + \f ;;; Internal Functions @@ -881,7 +996,227 @@ (defun org-link--file-link-to-here () (setq desc search-desc)))) (cons link desc))) +(defun org-link-preview--get-overlays (&optional beg end) + "Return link preview overlays between BEG and END." + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (overlays (overlays-in beg end)) + result) + (dolist (ov overlays result) + (when (memq ov org-link-preview-overlays) + (push ov result))))) + +(defun org-link-preview--remove-overlay (ov after _beg _end &optional _len) + "Remove link-preview overlay OV if a corresponding region is modified. + +AFTER is true when this function is called post-change." + (when (and ov after) + (setq org-link-preview-overlays (delq ov org-link-preview-overlays)) + ;; Clear image from cache to avoid image not updating upon + ;; changing on disk. See Emacs bug#59902. + (when-let* ((disp (overlay-get ov 'display)) + ((imagep disp))) + (image-flush disp)) + (delete-overlay ov))) + \f + +;;;; Utilities for image preview display + +;; For without-x builds. +(declare-function image-flush "image" (spec &optional frame)) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + ((or `cache `t) + (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width + :max-width + (pcase org-image-max-width + (`fill-column (* fill-column (frame-char-width (selected-frame)))) + (`window (window-width nil t)) + ((pred integerp) org-image-max-width) + ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) + (`nil nil) + (_ (error "Unsupported value of `org-image-max-width': %S" + org-image-max-width))) + :scale 1)))) + +(declare-function org-export-read-attribute "ox" + (attribute element &optional property)) +(defvar visual-fill-column-width) ; Silence compiler warning +(defun org-display-inline-image--width (link) + "Determine the display width of the image LINK, in pixels. +- When `org-image-actual-width' is t, the image's pixel width is used. +- When `org-image-actual-width' is a number, that value will is used. +- When `org-image-actual-width' is nil or a list, :width attribute of + #+attr_org or the first #+attr_... (if it exists) is used to set the + image width. A width of X% is divided by 100. If the value is a + float between 0 and 2, it interpreted as that proportion of the text + width in the buffer. + + If no :width attribute is given and `org-image-actual-width' is a + list with a number as the car, then that number is used as the + default value." + ;; Apply `org-image-actual-width' specifications. + ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified + ;; width. + (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) + (cond + ((eq org-image-actual-width t) nil) + ((listp org-image-actual-width) + (require 'ox) + (let* ((par (org-element-lineage link 'paragraph)) + ;; Try to find an attribute providing a :width. + ;; #+ATTR_ORG: :width ... + (attr-width (org-export-read-attribute :attr_org par :width)) + (width-unreadable? + (lambda (value) + (or (not (stringp value)) + (unless (string= value "t") + (or (not (string-match + (rx bos (opt "+") + (or + ;; Number of pixels + ;; must be a lone number, not + ;; things like 4in + (seq (1+ (in "0-9")) eos) + ;; Numbers ending with % + (seq (1+ (in "0-9.")) (group-n 1 "%")) + ;; Fractions + (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) + value)) + (let ((number (string-to-number value))) + (and (floatp number) + (not (match-string 1 value)) ; X% + (not (<= 0.0 number 2.0))))))))) + ;; #+ATTR_BACKEND: :width ... + (attr-other + (catch :found + (org-element-properties-map + (lambda (prop _) + (when (and + (not (eq prop :attr_org)) + (string-match-p "^:attr_" (symbol-name prop)) + (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) + (throw :found prop))) + par))) + (attr-width + (if (not (funcall width-unreadable? attr-width)) + attr-width + ;; When #+attr_org: does not have readable :width + (and attr-other + (org-export-read-attribute attr-other par :width)))) + (width + (cond + ;; Treat :width t as if `org-image-actual-width' were t. + ((string= attr-width "t") nil) + ;; Fallback to `org-image-actual-width' if no interprable width is given. + ((funcall width-unreadable? attr-width) + (car org-image-actual-width)) + ;; Convert numeric widths to numbers, converting percentages. + ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) + (/ (string-to-number attr-width) 100.0)) + (t (string-to-number attr-width))))) + (if (and (floatp width) (<= 0.0 width 2.0)) + ;; A float in [0,2] should be interpereted as this portion of + ;; the text width in the window. This works well with cases like + ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, + ;; as the "0.X" is pulled out as a float. We use 2 as the upper + ;; bound as cases such as 1.2\linewidth are feasible. + (round (* width + (window-pixel-width) + (/ (or (and (bound-and-true-p visual-fill-column-mode) + (or visual-fill-column-width auto-fill-function)) + (when auto-fill-function fill-column) + (- (window-text-width) (line-number-display-width))) + (float (window-total-width))))) + width))) + ((numberp org-image-actual-width) + org-image-actual-width) + (t nil)))) + +(defun org-image--align (link) + "Determine the alignment of the image LINK. +LINK is a link object. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or `:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align + (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + ;;; Public API (defun org-link-types () @@ -1573,6 +1908,231 @@ (defun org-link-add-angle-brackets (s) (unless (equal (substring s -1) ">") (setq s (concat s ">"))) s) +;;;###autoload +(defun org-link-preview (&optional arg beg end) + "Toggle display of link previews in the buffer. + +When region BEG..END is active, preview links in the +region. + +When point is at a link, display a preview for that link only. +Otherwise, display previews for links in current entry. + +With numeric prefix ARG 1, also preview links with description in +the active region, at point or in the current section. + +With prefix ARG `\\[universal-argument]', clear link previews at +point or in the current entry. + +With prefix ARG `\\[universal-argument] \\[universal-argument]', + display link previews in the accessible portion of the + buffer. With numeric prefix ARG 11, do the same, but include + links with descriptions. + +With prefix ARG `\\[universal-argument] \\[universal-argument] \\[universal-argument]', +hide all link previews in the accessible portion of the buffer. + +This command is designed for interactive use. From Elisp, you can +also use `org-link-preview-region'." + (interactive (cons current-prefix-arg + (when (use-region-p) + (list (region-beginning) (region-end))))) + (let* ((include-linked + (cond + ((member arg '(nil (4) (16)) ) nil) + ((member arg '(1 11)) 'include-linked) + (t 'include-linked))) + (interactive? (called-interactively-p 'any)) + (toggle-previews + (lambda (&optional beg end scope remove) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (old (org-link-preview--get-overlays beg end)) + (scope (or scope (format "%d:%d" beg end)))) + (if remove + (progn + (org-link-preview-clear beg end) + (when interactive? + (message + "[%s] Inline link previews turned off (removed %d images)" + scope (length old)))) + (org-link-preview-region include-linked t beg end) + (when interactive? + (let ((new (org-link-preview--get-overlays beg end))) + (message + (if new + (format "[%s] Displaying %d images inline %s" + scope (length new) + (if include-linked "(including images with description)" + "")) + (format "[%s] No images to display inline" scope)))))))))) + (cond + ;; Region selected :: display previews in region. + ((and beg end) + (funcall toggle-previews beg end "region" + (and (equal arg '(4)) 'remove))) + ;; C-u argument: clear image at point or in entry + ((equal arg '(4)) + (if-let ((ov (cdr (get-char-property-and-overlay + (point) 'org-image-overlay)))) + ;; clear link preview at point + (funcall toggle-previews + (overlay-start ov) (overlay-end ov) + "preview at point" 'remove) + ;; Clear link previews in entry + (funcall toggle-previews + (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point)))) + (org-with-limited-levels (org-entry-end-position)) + "current section" 'remove))) + ;; C-u C-u or C-11 argument :: display images in the whole buffer. + ((member arg '(11 (16))) (funcall toggle-previews nil nil "buffer")) + ;; C-u C-u C-u argument :: unconditionally hide images in the buffer. + ((equal arg '(64)) (funcall toggle-previews nil nil "buffer" 'remove)) + ;; Argument nil or 1, no region selected :: display images in + ;; current section or image link at point. + ((and (member arg '(nil 1)) (null beg) (null end)) + (let ((context (org-element-context))) + ;; toggle display of inline image link at point. + (if (org-element-type-p context 'link) + (let* ((ov (cdr-safe (get-char-property-and-overlay + (point) 'org-image-overlay))) + (remove? (and ov (memq ov org-link-preview-overlays) + 'remove))) + (funcall toggle-previews + (org-element-begin context) + (org-element-end context) + "image at point" remove?)) + (let ((beg (if (org-before-first-heading-p) (point-min) + (save-excursion + (org-with-limited-levels (org-back-to-heading t) (point))))) + (end (org-with-limited-levels (org-entry-end-position)))) + (funcall toggle-previews beg end "current section"))))) + ;; Any other non-nil argument. + ((not (null arg)) (funcall toggle-previews beg end "region"))))) + +;;;###autoload +(defun org-link-preview-refresh () + "Assure display of link previews in buffer and refresh them." + (interactive) + (org-link-preview-region nil t (point-min) (point-max))) + +(defun org-link-preview-region (&optional include-linked refresh beg end) + "Display link previews. + +A previewable link type is one that has a `:preview' link +parameter, see `org-link-parameters'. + +By default, a file link or attachment is previewable if it +follows either of these conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +File links are equipped with the keymap `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, links with a +text description part will also be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when refresh (org-link-preview-clear beg end)) + (org-with-point-at (or beg (point-min)) + (let ((case-fold-search t) + preview-queue) + ;; Collect links to preview + (while (re-search-forward org-link-any-re end t) + (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)))))) + \f ;;; Built-in link types @@ -1595,7 +2155,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..885a5245d 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -1010,6 +1010,191 @@ (define-obsolete-function-alias 'org-open-link-from-string (define-obsolete-function-alias 'org-add-angle-brackets 'org-link-add-angle-brackets "9.3") +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-link-preview--get-overlays "ol" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) +(declare-function org-link-preview--remove-overlay "ol" + (ov after beg end &optional len)) +(declare-function org-attach-expand "org-attach" (file)) +(declare-function org-display-inline-image--width "org" (link)) +(declare-function org-image--align "org" (link)) +(declare-function org--create-inline-image "org" (file width)) + +(define-obsolete-function-alias 'org-display-inline-remove-overlay + 'org-link-preview--remove-overlay "9.8") +(define-obsolete-function-alias 'org--inline-image-overlays + 'org-link-preview--get-overlays "9.8") +(define-obsolete-function-alias 'org-remove-inline-images + 'org-link-preview-clear "9.8") +(define-obsolete-function-alias 'org-redisplay-inline-images + 'org-link-preview-refresh "9.8") +(define-obsolete-variable-alias 'org-inline-image-overlays + 'org-link-preview-overlays "9.8") +(defvar org-link-preview-overlays) +(defvar org-link-abbrev-alist-local) +(defvar org-link-abbrev-alist) +(defvar org-link-angle-re) +(defvar org-link-plain-re) + +(make-obsolete 'org-display-inline-images + 'org-link-preview-region "9.8") +;; FIXME: Unused; obsoleted; to be removed +(defun org-display-inline-images (&optional include-linked refresh beg end) + "Display inline images. + +An inline image is a link which follows either of these +conventions: + + 1. Its path is a file with an extension matching return value + from `image-file-name-regexp' and it has no contents. + + 2. Its description consists in a single link of the previous + type. In this case, that link must be a well-formed plain + or angle link, i.e., it must have an explicit \"file\" or + \"attachment\" type. + +Equip each image with the key-map `image-map'. + +When optional argument INCLUDE-LINKED is non-nil, also links with +a text description part will be inlined. This can be nice for +a quick look at those images, but it does not reflect what +exported files will look like. + +When optional argument REFRESH is non-nil, refresh existing +images between BEG and END. This will create new image displays +only if necessary. + +BEG and END define the considered part. They default to the +buffer boundaries with possible narrowing." + (interactive "P") + (when (display-graphic-p) + (when refresh + (org-link-preview-clear beg end) + (when (fboundp 'clear-image-cache) (clear-image-cache))) + (let ((end (or end (point-max)))) + (org-with-point-at (or beg (point-min)) + (let* ((case-fold-search t) + (file-extension-re (image-file-name-regexp)) + (link-abbrevs (mapcar #'car + (append org-link-abbrev-alist-local + org-link-abbrev-alist))) + ;; Check absolute, relative file names and explicit + ;; "file:" links. Also check link abbreviations since + ;; some might expand to "file" links. + (file-types-re + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" + (if (not link-abbrevs) "" + (concat "\\|" (regexp-opt link-abbrevs)))))) + (while (re-search-forward file-types-re end t) + (let* ((link (org-element-lineage + (save-match-data (org-element-context)) + 'link t)) + (linktype (org-element-property :type link)) + (inner-start (match-beginning 1)) + (path + (cond + ;; No link at point; no inline image. + ((not link) nil) + ;; File link without a description. Also handle + ;; INCLUDE-LINKED here since it should have + ;; precedence over the next case. I.e., if link + ;; contains filenames in both the path and the + ;; description, prioritize the path only when + ;; INCLUDE-LINKED is non-nil. + ((or (not (org-element-contents-begin link)) + include-linked) + (and (or (equal "file" linktype) + (equal "attachment" linktype)) + (org-element-property :path link))) + ;; Link with a description. Check if description + ;; is a filename. Even if Org doesn't have syntax + ;; for those -- clickable image -- constructs, fake + ;; them, as in `org-export-insert-image-links'. + ((not inner-start) nil) + (t + (org-with-point-at inner-start + (and (looking-at + (if (char-equal ?< (char-after inner-start)) + org-link-angle-re + org-link-plain-re)) + ;; File name must fill the whole + ;; description. + (= (org-element-contents-end link) + (match-end 0)) + (progn + (setq linktype (match-string 1)) + (match-string 2)))))))) + (when (and path (string-match-p file-extension-re path)) + (let ((file (if (equal "attachment" linktype) + (progn + (require 'org-attach) + (ignore-errors (org-attach-expand path))) + (expand-file-name path)))) + ;; Expand environment variables. + (when file (setq file (substitute-in-file-name file))) + (when (and file (file-exists-p file)) + (let ((width (org-display-inline-image--width link)) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) + 'org-image-overlay))) + (if (and (car-safe old) refresh) + (image-flush (overlay-get (cdr old) 'display)) + (let ((image (org--create-inline-image file width))) + (when image + (let ((ov (make-overlay + (org-element-begin link) + (progn + (goto-char + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) + (point))))) + ;; See bug#59902. We cannot rely + ;; on Emacs to update image if the file + ;; has changed. + (image-flush image) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (overlay-put ov 'org-image-overlay t) + (overlay-put + ov 'modification-hooks + (list 'org-link-preview--remove-overlay)) + (when (boundp 'image-map) + (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) + (push ov org-inline-image-overlays)))))))))))))))) + +(make-obsolete 'org-toggle-inline-images + 'org-link-preview "9.8") +(declare-function org-link-preview-region "ol") +;; FIXME: Unused; obsoleted; to be removed +(defun org-toggle-inline-images (&optional include-linked beg end) + "Toggle the display of inline images. +INCLUDE-LINKED is passed to `org-display-inline-images'." + (interactive "P") + (if (org-link-preview--get-overlays beg end) + (progn + (org-link-preview-clear beg end) + (when (called-interactively-p 'interactive) + (message "Inline image display turned off"))) + (org-link-preview-region include-linked nil beg end) + (when (called-interactively-p 'interactive) + (let ((new (org-link-preview--get-overlays beg end))) + (message (if new + (format "%d images displayed inline" + (length new)) + "No images to display inline")))))) + + ;; The function was made obsolete by commit 65399674d5 of 2013-02-22. ;; This make-obsolete call was added 2016-09-01. (make-obsolete 'org-capture-import-remember-templates diff --git a/lisp/org-cycle.el b/lisp/org-cycle.el index 8a39bdb8c..97a545393 100644 --- a/lisp/org-cycle.el +++ b/lisp/org-cycle.el @@ -40,14 +40,14 @@ (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-post-affiliated "org-element" (node)) (declare-function org-element-lineage "org-element-ast" (datum &optional types with-self)) (declare-function org-element-at-point "org-element" (&optional pom cached-only)) -(declare-function org-display-inline-images "org" (&optional include-linked refresh beg end)) +(declare-function org-link-preview-region "ol" (&optional include-linked refresh beg end)) (declare-function org-get-tags "org" (&optional pos local fontify)) (declare-function org-subtree-end-visible-p "org" ()) (declare-function org-narrow-to-subtree "org" (&optional element)) (declare-function org-next-visible-heading "org" (arg)) (declare-function org-at-property-p "org" ()) (declare-function org-re-property "org" (property &optional literal allow-null value)) -(declare-function org-remove-inline-images "org" (&optional beg end)) +(declare-function org-link-preview-clear "ol" (&optional beg end)) (declare-function org-item-beginning-re "org" ()) (declare-function org-at-heading-p "org" (&optional invisible-not-ok)) (declare-function org-at-item-p "org" ()) @@ -217,7 +217,7 @@ (defcustom org-cycle-pre-hook nil (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees org-cycle-show-empty-lines org-cycle-optimize-window-after-visibility-change - org-cycle-display-inline-images) + org-cycle-display-link-previews) "Hook that is run after `org-cycle' has changed the buffer visibility. The function(s) in this hook must accept a single argument which indicates the new state that was set by the most recent `org-cycle' command. The @@ -237,11 +237,15 @@ (defcustom org-cycle-open-archived-trees nil :group 'org-cycle :type 'boolean) -(defcustom org-cycle-inline-images-display nil - "Non-nil means auto display inline images under subtree when cycling." +(defvaralias 'org-cycle-inline-images-display + 'org-cycle-link-previews-display + "Non-nil means auto display inline images under subtree when cycling.") + +(defcustom org-cycle-link-previews-display nil + "Non-nil means auto display link previews under subtree when cycling." :group 'org-startup :group 'org-cycle - :package-version '(Org . "9.6") + :package-version '(Org . "9.8") :type 'boolean) (defvaralias 'org-tab-first-hook 'org-cycle-tab-first-hook) @@ -804,12 +808,15 @@ (defun org-cycle-hide-archived-subtrees (state) "Subtree is archived and stays closed. Use \ `\\[org-cycle-force-archived]' to cycle it anyway.")))))) -(defun org-cycle-display-inline-images (state) +(defalias 'org-cycle-inline-images-display + 'org-cycle-display-link-previews) + +(defun org-cycle-display-link-previews (state) "Auto display inline images under subtree when cycling. -It works when `org-cycle-inline-images-display' is non-nil. +It works when `org-cycle-link-previews-display' is non-nil. STATE is the current outline visibility state. It should be one of symbols `content', `all', `folded', `children', or `subtree'." - (when org-cycle-inline-images-display + (when org-cycle-link-previews-display (pcase state ('children (org-with-wide-buffer @@ -817,19 +824,19 @@ (defun org-cycle-display-inline-images (state) ;; If has nested headlines, beg,end only from parent headline ;; to first child headline which reference to upper ;; let-binding `org-next-visible-heading'. - (org-display-inline-images + (org-link-preview-region nil nil (point-min) (progn (org-next-visible-heading 1) (point))))) ('subtree (org-with-wide-buffer (org-narrow-to-subtree) ;; If has nested headlines, also inline display images under all sub-headlines. - (org-display-inline-images nil nil (point-min) (point-max)))) + (org-link-preview-region nil nil (point-min) (point-max)))) ('folded (org-with-wide-buffer (org-narrow-to-subtree) (if (numberp (point-max)) - (org-remove-inline-images (point-min) (point-max)) + (org-link-preview-clear (point-min) (point-max)) (ignore))))))) (provide 'org-cycle) diff --git a/lisp/org-keys.el b/lisp/org-keys.el index 1daedaae8..d2e4a6993 100644 --- a/lisp/org-keys.el +++ b/lisp/org-keys.el @@ -218,7 +218,8 @@ (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) (declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) -(declare-function org-toggle-inline-images-command "org" (&optional arg beg end)) +(declare-function org-link-preview "ol" (&optional arg beg end)) +(declare-function org-link-preview-refresh "ol" ()) (declare-function org-latex-preview "org" (&optional arg)) (declare-function org-toggle-narrow-to-subtree "org" ()) (declare-function org-toggle-ordered-property "org" ()) @@ -618,8 +619,8 @@ (org-defkey org-mode-map (kbd "C-c C-x C-d") #'org-clock-display) (org-defkey org-mode-map (kbd "C-c C-x x") #'org-dynamic-block-insert-dblock) (org-defkey org-mode-map (kbd "C-c C-x C-u") #'org-dblock-update) (org-defkey org-mode-map (kbd "C-c C-x C-l") #'org-latex-preview) -(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images-command) -(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) +(org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-link-preview) +(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-link-preview-refresh) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) (org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index b045344f0..836cfaffc 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -633,7 +633,7 @@ (defun org-plot/gnuplot-script (table data-file num-cols params &optional prefac (defun org-plot/redisplay-img-in-buffer (img-file) "Find any overlays for IMG-FILE in the current Org buffer, and refresh them." - (dolist (img-overlay org-inline-image-overlays) + (dolist (img-overlay org-link-preview-overlays) (when (string= img-file (plist-get (cdr (overlay-get img-overlay 'display)) :file)) (when (and (file-exists-p img-file) (fboundp 'image-flush)) diff --git a/lisp/org.el b/lisp/org.el index df58b47be..c2a125000 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -1160,14 +1160,22 @@ (defcustom org-startup-shrink-all-tables nil :package-version '(Org . "9.2") :safe #'booleanp) -(defcustom org-startup-with-inline-images nil +(defvaralias 'org-startup-with-inline-images + 'org-startup-with-link-previews "Non-nil means show inline images when loading a new Org file. This can also be configured on a per-file basis by adding one of the following lines anywhere in the buffer: #+STARTUP: inlineimages - #+STARTUP: noinlineimages" + #+STARTUP: noinlineimages") + +(defcustom org-startup-with-link-previews nil + "Non-nil means show link previews when loading a new Org file. +This can also be configured on a per-file basis by adding one of +the following lines anywhere in the buffer: + #+STARTUP: linkpreviews + #+STARTUP: nolinkpreviews" :group 'org-startup - :version "24.1" + :version "29.4" :type 'boolean) (defcustom org-startup-with-latex-preview nil @@ -4153,8 +4161,10 @@ (defconst org-startup-options ("shrink" org-startup-shrink-all-tables t) ("descriptivelinks" org-link-descriptive t) ("literallinks" org-link-descriptive nil) - ("inlineimages" org-startup-with-inline-images t) - ("noinlineimages" org-startup-with-inline-images nil) + ("inlineimages" org-startup-with-link-previews t) + ("noinlineimages" org-startup-with-link-previews nil) + ("linkpreviews" org-startup-with-link-previews t) + ("nolinkpreviews" org-startup-with-link-previews nil) ("latexpreview" org-startup-with-latex-preview t) ("nolatexpreview" org-startup-with-latex-preview nil) ("customtime" org-display-custom-times t) @@ -5079,7 +5089,7 @@ (define-derived-mode org-mode outline-mode "Org" ;; modifications to make cache updates work reliably. (org-unmodified (when org-startup-with-beamer-mode (org-beamer-mode)) - (when org-startup-with-inline-images (org-display-inline-images)) + (when org-startup-with-inline-images (org-link-preview '(16))) (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-cycle-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) @@ -15583,26 +15593,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 +16639,6 @@ (defun org-normalize-color (value) (format "%g" (/ value 65535.0))) \f -;; Image display - -(defvar-local org-inline-image-overlays nil) -;; Preserve when switching modes or when restarting Org. -;; If we clear the overlay list and later enable Or mode, the existing -;; image overlays will never be cleared by `org-toggle-inline-images' -;; and `org-toggle-inline-images-command'. -(put 'org-inline-image-overlays 'permanent-local t) - -(defun org--inline-image-overlays (&optional beg end) - "Return image overlays between BEG and END." - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end)) - result) - (dolist (ov overlays result) - (when (memq ov org-inline-image-overlays) - (push ov result))))) - -(defun org-toggle-inline-images-command (&optional arg beg end) - "Toggle display of inline images without description at point. - -When point is at an image link, toggle displaying that image. -Otherwise, toggle displaying images in current entry. - -When region BEG..END is active, toggle displaying images in the -region. - -With numeric prefix ARG 1, display images with description as well. - -With prefix ARG `\\[universal-argument]', toggle displaying images in -the accessible portion of the buffer. With numeric prefix ARG 11, do -the same, but include images with description. - -With prefix ARG `\\[universal-argument] \\[universal-argument]', hide -all the images in accessible portion of the buffer. - -This command is designed for interactive use. From Elisp, you can -also use `org-toggle-inline-images'." - (interactive (cons current-prefix-arg - (when (use-region-p) - (list (region-beginning) (region-end))))) - (let* ((include-linked - (cond - ((member arg '(nil (4) (16)) ) nil) - ((member arg '(1 11)) 'include-linked) - (t 'include-linked))) - (interactive? (called-interactively-p 'any)) - (toggle-images - (lambda (&optional beg end scope force-remove) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (old (org--inline-image-overlays beg end)) - (scope (or scope (format "%d:%d" beg end)))) - (if (or old force-remove) - (progn - (org-remove-inline-images beg end) - (when interactive? - (message - "[%s] Inline image display turned off (removed %d images)" - scope (length old)))) - (org-display-inline-images include-linked t beg end) - (when interactive? - (let ((new (org--inline-image-overlays beg end))) - (message - (if new - (format "[%s] %d images displayed inline %s" - scope (length new) - (if include-linked "(including images with description)" - "")) - (format "[%s] No images to display inline" scope)))))))))) - (cond - ((not (display-graphic-p)) - (message "Your Emacs does not support displaying images!")) - ;; Region selected :: toggle images in region. - ((and beg end) (funcall toggle-images beg end "region")) - ;; C-u or C-11 argument :: toggle images in the whole buffer. - ((member arg '(11 (4))) (funcall toggle-images nil nil "buffer")) - ;; C-u C-u argument :: unconditionally hide images in the buffer. - ((equal arg '(16)) (funcall toggle-images nil nil "buffer" 'remove)) - ;; Argument nil or 1, no region selected :: toggle (display or hide - ;; dwim) images in current section or image link at point. - ((and (member arg '(nil 1)) (null beg) (null end)) - (let ((context (org-element-context))) - ;; toggle display of inline image link at point. - (if (org-element-type-p context 'link) - (funcall toggle-images - (org-element-begin context) - (org-element-end context) - "image at point") - (let ((beg (if (org-before-first-heading-p) (point-min) - (save-excursion - (org-with-limited-levels (org-back-to-heading t) (point))))) - (end (org-with-limited-levels (org-entry-end-position)))) - (funcall toggle-images beg end "current section"))))) - ;; Any other non-nil argument. - ((not (null arg)) (funcall toggle-images beg end "region"))))) - -(defun org-toggle-inline-images (&optional include-linked beg end) - "Toggle the display of inline images. -INCLUDE-LINKED is passed to `org-display-inline-images'." - (interactive "P") - (if (org--inline-image-overlays beg end) - (progn - (org-remove-inline-images beg end) - (when (called-interactively-p 'interactive) - (message "Inline image display turned off"))) - (org-display-inline-images include-linked nil beg end) - (when (called-interactively-p 'interactive) - (let ((new (org--inline-image-overlays beg end))) - (message (if new - (format "%d images displayed inline" - (length new)) - "No images to display inline")))))) - -(defun org-redisplay-inline-images () - "Assure display of inline images and refresh them." - (interactive) - (org-toggle-inline-images) - (unless org-inline-image-overlays - (org-toggle-inline-images))) - -;; For without-x builds. -(declare-function image-flush "image" (spec &optional frame)) - -(defcustom org-display-remote-inline-images 'skip - "How to display remote inline images. -Possible values of this option are: - -skip Don't display remote images. -download Always download and display remote images. -t -cache Display remote images, and open them in separate buffers - for caching. Silently update the image buffer when a file - change is detected." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Ignore remote images" skip) - (const :tag "Always display remote images" download) - (const :tag "Display and silently update remote images" cache)) - :safe #'symbolp) - -(defcustom org-image-align 'left - "How to align images previewed using `org-display-inline-images'. - -Only stand-alone image links are affected by this setting. These -are links without surrounding text. - -Possible values of this option are: - -left Insert image at specified position. -center Center image previews. -right Right-align image previews." - :group 'org-appearance - :package-version '(Org . "9.7") - :type '(choice - (const :tag "Left align (or don\\='t align) image previews" left) - (const :tag "Center image previews" center) - (const :tag "Right align image previews" right)) - :safe #'symbolp) - -(defun org--create-inline-image (file width) - "Create image located at FILE, or return nil. -WIDTH is the width of the image. The image may not be created -according to the value of `org-display-remote-inline-images'." - (let* ((remote? (file-remote-p file)) - (file-or-data - (pcase org-display-remote-inline-images - ((guard (not remote?)) file) - (`download (with-temp-buffer - (set-buffer-multibyte nil) - (insert-file-contents-literally file) - (buffer-string))) - ((or `cache `t) - (let ((revert-without-query '("."))) - (with-current-buffer (find-file-noselect file) - (buffer-string)))) - (`skip nil) - (other - (message "Invalid value of `org-display-remote-inline-images': %S" - other) - nil)))) - (when file-or-data - (create-image file-or-data - (and (image-type-available-p 'imagemagick) - width - 'imagemagick) - remote? - :width width - :max-width - (pcase org-image-max-width - (`fill-column (* fill-column (frame-char-width (selected-frame)))) - (`window (window-width nil t)) - ((pred integerp) org-image-max-width) - ((pred floatp) (floor (* org-image-max-width (window-width nil t)))) - (`nil nil) - (_ (error "Unsupported value of `org-image-max-width': %S" - org-image-max-width))) - :scale 1)))) - -(defun org-display-inline-images (&optional include-linked refresh beg end) - "Display inline images. - -An inline image is a link which follows either of these -conventions: - - 1. Its path is a file with an extension matching return value - from `image-file-name-regexp' and it has no contents. - - 2. Its description consists in a single link of the previous - type. In this case, that link must be a well-formed plain - or angle link, i.e., it must have an explicit \"file\" or - \"attachment\" type. - -Equip each image with the key-map `image-map'. - -When optional argument INCLUDE-LINKED is non-nil, also links with -a text description part will be inlined. This can be nice for -a quick look at those images, but it does not reflect what -exported files will look like. - -When optional argument REFRESH is non-nil, refresh existing -images between BEG and END. This will create new image displays -only if necessary. - -BEG and END define the considered part. They default to the -buffer boundaries with possible narrowing." - (interactive "P") - (when (display-graphic-p) - (when refresh - (org-remove-inline-images beg end) - (when (fboundp 'clear-image-cache) (clear-image-cache))) - (let ((end (or end (point-max)))) - (org-with-point-at (or beg (point-min)) - (let* ((case-fold-search t) - (file-extension-re (image-file-name-regexp)) - (link-abbrevs (mapcar #'car - (append org-link-abbrev-alist-local - org-link-abbrev-alist))) - ;; Check absolute, relative file names and explicit - ;; "file:" links. Also check link abbreviations since - ;; some might expand to "file" links. - (file-types-re - (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)" - (if (not link-abbrevs) "" - (concat "\\|" (regexp-opt link-abbrevs)))))) - (while (re-search-forward file-types-re end t) - (let* ((link (org-element-lineage - (save-match-data (org-element-context)) - 'link t)) - (linktype (org-element-property :type link)) - (inner-start (match-beginning 1)) - (path - (cond - ;; No link at point; no inline image. - ((not link) nil) - ;; File link without a description. Also handle - ;; INCLUDE-LINKED here since it should have - ;; precedence over the next case. I.e., if link - ;; contains filenames in both the path and the - ;; description, prioritize the path only when - ;; INCLUDE-LINKED is non-nil. - ((or (not (org-element-contents-begin link)) - include-linked) - (and (or (equal "file" linktype) - (equal "attachment" linktype)) - (org-element-property :path link))) - ;; Link with a description. Check if description - ;; is a filename. Even if Org doesn't have syntax - ;; for those -- clickable image -- constructs, fake - ;; them, as in `org-export-insert-image-links'. - ((not inner-start) nil) - (t - (org-with-point-at inner-start - (and (looking-at - (if (char-equal ?< (char-after inner-start)) - org-link-angle-re - org-link-plain-re)) - ;; File name must fill the whole - ;; description. - (= (org-element-contents-end link) - (match-end 0)) - (progn - (setq linktype (match-string 1)) - (match-string 2)))))))) - (when (and path (string-match-p file-extension-re path)) - (let ((file (if (equal "attachment" linktype) - (progn - (require 'org-attach) - (ignore-errors (org-attach-expand path))) - (expand-file-name path)))) - ;; Expand environment variables. - (when file (setq file (substitute-in-file-name file))) - (when (and file (file-exists-p file)) - (let ((width (org-display-inline-image--width link)) - (align (org-image--align link)) - (old (get-char-property-and-overlay - (org-element-begin link) - 'org-image-overlay))) - (if (and (car-safe old) refresh) - (image-flush (overlay-get (cdr old) 'display)) - (let ((image (org--create-inline-image file width))) - (when image - (let ((ov (make-overlay - (org-element-begin link) - (progn - (goto-char - (org-element-end link)) - (unless (eolp) (skip-chars-backward " \t")) - (point))))) - ;; See bug#59902. We cannot rely - ;; on Emacs to update image if the file - ;; has changed. - (image-flush image) - (overlay-put ov 'display image) - (overlay-put ov 'face 'default) - (overlay-put ov 'org-image-overlay t) - (overlay-put - ov 'modification-hooks - (list 'org-display-inline-remove-overlay)) - (when (boundp 'image-map) - (overlay-put ov 'keymap image-map)) - (when align - (overlay-put - ov 'before-string - (propertize - " " 'face 'default - 'display - (pcase align - ("center" `(space :align-to (- center (0.5 . ,image)))) - ("right" `(space :align-to (- right ,image))))))) - (push ov org-inline-image-overlays)))))))))))))))) - -(declare-function org-export-read-attribute "ox" - (attribute element &optional property)) -(defvar visual-fill-column-width) ; Silence compiler warning -(defun org-display-inline-image--width (link) - "Determine the display width of the image LINK, in pixels. -- When `org-image-actual-width' is t, the image's pixel width is used. -- When `org-image-actual-width' is a number, that value will is used. -- When `org-image-actual-width' is nil or a list, :width attribute of - #+attr_org or the first #+attr_... (if it exists) is used to set the - image width. A width of X% is divided by 100. If the value is a - float between 0 and 2, it interpreted as that proportion of the text - width in the buffer. - - If no :width attribute is given and `org-image-actual-width' is a - list with a number as the car, then that number is used as the - default value." - ;; Apply `org-image-actual-width' specifications. - ;; Support subtree-level property "ORG-IMAGE-ACTUAL-WIDTH" specified - ;; width. - (let ((org-image-actual-width (org-property-or-variable-value 'org-image-actual-width))) - (cond - ((eq org-image-actual-width t) nil) - ((listp org-image-actual-width) - (require 'ox) - (let* ((par (org-element-lineage link 'paragraph)) - ;; Try to find an attribute providing a :width. - ;; #+ATTR_ORG: :width ... - (attr-width (org-export-read-attribute :attr_org par :width)) - (width-unreadable? - (lambda (value) - (or (not (stringp value)) - (unless (string= value "t") - (or (not (string-match - (rx bos (opt "+") - (or - ;; Number of pixels - ;; must be a lone number, not - ;; things like 4in - (seq (1+ (in "0-9")) eos) - ;; Numbers ending with % - (seq (1+ (in "0-9.")) (group-n 1 "%")) - ;; Fractions - (seq (0+ (in "0-9")) "." (1+ (in "0-9"))))) - value)) - (let ((number (string-to-number value))) - (and (floatp number) - (not (match-string 1 value)) ; X% - (not (<= 0.0 number 2.0))))))))) - ;; #+ATTR_BACKEND: :width ... - (attr-other - (catch :found - (org-element-properties-map - (lambda (prop _) - (when (and - (not (eq prop :attr_org)) - (string-match-p "^:attr_" (symbol-name prop)) - (not (funcall width-unreadable? (org-export-read-attribute prop par :width)))) - (throw :found prop))) - par))) - (attr-width - (if (not (funcall width-unreadable? attr-width)) - attr-width - ;; When #+attr_org: does not have readable :width - (and attr-other - (org-export-read-attribute attr-other par :width)))) - (width - (cond - ;; Treat :width t as if `org-image-actual-width' were t. - ((string= attr-width "t") nil) - ;; Fallback to `org-image-actual-width' if no interprable width is given. - ((funcall width-unreadable? attr-width) - (car org-image-actual-width)) - ;; Convert numeric widths to numbers, converting percentages. - ((string-match-p "\\`[[+]?[0-9.]+%" attr-width) - (/ (string-to-number attr-width) 100.0)) - (t (string-to-number attr-width))))) - (if (and (floatp width) (<= 0.0 width 2.0)) - ;; A float in [0,2] should be interpereted as this portion of - ;; the text width in the window. This works well with cases like - ;; #+attr_latex: :width 0.X\{line,page,column,etc.}width, - ;; as the "0.X" is pulled out as a float. We use 2 as the upper - ;; bound as cases such as 1.2\linewidth are feasible. - (round (* width - (window-pixel-width) - (/ (or (and (bound-and-true-p visual-fill-column-mode) - (or visual-fill-column-width auto-fill-function)) - (when auto-fill-function fill-column) - (- (window-text-width) (line-number-display-width))) - (float (window-total-width))))) - width))) - ((numberp org-image-actual-width) - org-image-actual-width) - (t nil)))) - -(defun org-image--align (link) - "Determine the alignment of the image LINK. -LINK is a link object. - -In decreasing order of priority, this is controlled: -- Per image by the value of `:center' or `:align' in the -affiliated keyword `#+attr_org'. -- By the `#+attr_html' or `#+attr_latex` keywords with valid - `:center' or `:align' values. -- Globally by the user option `org-image-align'. - -The result is either nil or one of the strings \"left\", -\"center\" or \"right\". - -\"center\" will cause the image preview to be centered, \"right\" -will cause it to be right-aligned. A value of \"left\" or nil -implies no special alignment." - (let ((par (org-element-lineage link 'paragraph))) - ;; Only align when image is not surrounded by paragraph text: - (when (and par ; when image is not in paragraph, but in table/headline/etc, do not align - (= (org-element-begin link) - (save-excursion - (goto-char (org-element-contents-begin par)) - (skip-chars-forward "\t ") - (point))) ;account for leading space - ;before link - (<= (- (org-element-contents-end par) - (org-element-end link)) - 1)) ;account for trailing newline - ;at end of paragraph - (save-match-data - ;; Look for a valid ":center t" or ":align left|center|right" - ;; attribute. - ;; - ;; An attr_org keyword has the highest priority, with - ;; any attr.* next. Choosing between these is - ;; unspecified. - (let ((center-re ":\\(center\\)[[:space:]]+t\\b") - (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") - attr-align) - (catch 'exit - (org-element-properties-mapc - (lambda (propname propval) - (when (and propval - (string-match-p ":attr.*" (symbol-name propname))) - (setq propval (car-safe propval)) - (when (or (string-match center-re propval) - (string-match align-re propval)) - (setq attr-align (match-string 1 propval)) - (when (eq propname :attr_org) - (throw 'exit t))))) - par)) - (if attr-align - (when (member attr-align '("center" "right")) attr-align) - ;; No image-specific keyword, check global alignment property - (when (memq org-image-align '(center right)) - (symbol-name org-image-align)))))))) - - -(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) - "Remove inline-display overlay if a corresponding region is modified." - (when (and ov after) - (setq org-inline-image-overlays (delete ov org-inline-image-overlays)) - ;; Clear image from cache to avoid image not updating upon - ;; changing on disk. See Emacs bug#59902. - (when (overlay-get ov 'org-image-overlay) - (image-flush (overlay-get ov 'display))) - (delete-overlay ov))) - -(defun org-remove-inline-images (&optional beg end) - "Remove inline display of images." - (interactive) - (let* ((beg (or beg (point-min))) - (end (or end (point-max))) - (overlays (overlays-in beg end))) - (dolist (ov overlays) - (when (memq ov org-inline-image-overlays) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)) - (delete-overlay ov))) - ;; Clear removed overlays. - (dolist (ov org-inline-image-overlays) - (unless (overlay-buffer ov) - (setq org-inline-image-overlays (delq ov org-inline-image-overlays)))))) - (defvar org-self-insert-command-undo-counter 0) (defvar org-speed-command nil) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index f58642be6..809738f6c 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -716,14 +716,14 @@ (ert-deftest test-org-fold/org-fold-display-inline-images () (org-show-subtree) (org-fold-subtree t) (run-hook-with-args 'org-cycle-hook 'folded) - (should-not org-inline-image-overlays) + (should-not org-link-preview-overlays) (should-not (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) (overlays-in (point-min) (point-max)))) (org-show-subtree) (run-hook-with-args 'org-cycle-hook 'subtree) - (should org-inline-image-overlays) + (should org-link-preview-overlays) (should (cl-every (lambda (ov) (overlay-get ov 'org-image-overlay)) -- 2.46.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Org-Document-preview-API-for-arbitrary-link-types.patch --] [-- Type: text/x-patch, Size: 19753 bytes --] From bb0bbce61b2d719a649a08645411e3b8a1c620f8 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur <karthikchikmagalur@gmail.com> Date: Sun, 27 Oct 2024 20:22:59 -0700 Subject: [PATCH 2/2] Org: Document preview API for arbitrary link types * etc/ORG-NEWS: Mention new commands and options for link preview. * doc/org-manual.org: Add sections on - previewing external links (Images and link previews), and - adding the link preview feature to other link types (Adding Hyperlink preview). --- doc/org-manual.org | 290 ++++++++++++++++++++++++++++++--------------- etc/ORG-NEWS | 93 +++++++++++++-- 2 files changed, 278 insertions(+), 105 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 94fabde3b..8254f992f 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11778,7 +11778,74 @@ ** Literal Examples the end of the current line. Then the label is stored as a link =(label)=, for retrieval with {{{kbd(C-c C-l)}}}. -** Images +** Images and link previews +:PROPERTIES: +:DESCRIPTION: Preview links in the buffer. +:END: + +Org mode can display previews of [[*Hyperlinks][hyperlinks]] inside Org +buffers. By default, only image links[fn::Image links are =file:= and +=attachment:= links to existing image files; Emacs should be able to +display the linked images (see ~image-types~ variable)] can be previewed +inline, where the images are displayed in place of the link path. + +You can customize the previews as described in the [[*Adding Hyperlink +preview]] section. Link previews do not have to display images -- any +overlay property (see [[info:elisp#Overlay Properties]]) can be used. + +You can preview the supported link types in the whole buffer, in the +current section, active region or at point with the following commands: + +- {{{kbd(C-c C-x C-v)}}} (~org-link-preview~) :: + + #+kindex: C-c C-x C-v + #+findex: org-link-preview + Create inline previews for external links in the active region, the + link at point or in the current section. With a prefix argument, + clear link previews at point or in the current entry. With a double + prefix argument, preview all links in the buffer. With triple + prefix argument, hide previews for all links in the buffer. + + By default, only links without descriptions are previewed. You + can force displaying previews for all supported links (including + links with descriptions) using a numeric argument of ~1~. This + will toggle all previews in the active region, the link at point + or the current section. A numeric prefix argument of ~11~ will + toggle previews in the whole buffer instead. + + #+vindex: org-startup-with-link-previews + Org mode can display link previews automatically when opening + buffers. Either customize ~org-startup-with-link-previews~ or use + the =#+STARTUP: linkpreviews= keyword to enable previews for that + specific buffer when opening it. =#+STARTUP: nolinkpreviews= will + disable previews on startup in the buffer. + +- {{{kbd(C-c C-x C-M-v)}}} (~org-link-preview-refresh~) :: + + #+kindex: C-c C-x C-M-v + #+findex: org-link-preview-refresh + Assure inline display of external link previews in the whole buffer + and refresh them. + +- (~org-link-preview-region~) :: + + #+findex: org-link-preview-region + Create inline previews for external links in the active region, or + the buffer. With a prefix argument, also preview links with a text + description part. + +- (~org-link-preview-clear~) :: + + #+findex: org-link-preview-clear + Clear external link previews in the active region, or the buffer. + +#+vindex: org-cycle-inline-images-display +Link previews can also be displayed when cycling the folding state. +When the custom option ~org-cycle-link-previews-display~ is set, +supported link types under the subtree (including images) will be +displayed automatically. + +*** Images :PROPERTIES: :DESCRIPTION: Display an image. :END: @@ -11803,102 +11870,76 @@ ** Images [[./img/a.jpg]] #+end_example -Such images can be displayed within the buffer with the following -command: - -- {{{kbd(C-c C-x C-v)}}} (~org-toggle-inline-images-command~) :: - - #+kindex: C-c C-x C-v - #+findex: org-toggle-inline-images-command - Toggle the inline display of linked images in current section or at - point. With a prefix argument, toggle inline images in the whole - buffer. With double prefix argument, hide all the images in buffer. - - By default, only the image links without description are displayed. - You can force displaying all the images using numeric argument to - toggle all the images in current section (~1~ argument) or the whole - buffer (~11~ argument). - - #+vindex: org-startup-with-inline-images - You can ask for inline images to be displayed at - startup by configuring the variable - ~org-startup-with-inline-images~[fn:: The variable - ~org-startup-with-inline-images~ can be set within a buffer with the - =STARTUP= options =inlineimages= and =noinlineimages=.]. - - - #+vindex: org-image-actual-width - #+vindex: org-image-max-width - #+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property - By default, Org mode displays inline images according to their - actual width, but no wider than ~fill-column~ characters. - - You can customize the displayed image width using - ~org-image-actual-width~ variable (globally) or - =ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can - be customized in Emacs >= 24.1, built with ImageMagick support.]. - Their value can be the following: - - (default) Non-~nil~, use the actual width of images when inlining - them. If the actual width is too wide, limit it according to - ~org-image-max-width~. - - When set to a number, use ImageMagick (when available) to set the - image's width to this value. - - When set to a number in a list, try to get the width from any - =#+ATTR.*= keyword if it matches a width specification like: - #+begin_example - ,#+ATTR_HTML: :width 300px - #+end_example - and fall back on that number if none is found. - - When set to ~nil~, try to get the width from an =#+ATTR.*= keyword - and fall back on the original width or ~org-image-max-width~ if - none is found. - - ~org-image-max-width~ limits the maximum displayed image width, but - only when the image width is not set explicitly. Possible maximum - width can be set to: - - (default) ~fill-column~, limit width to ~fill-column~ number of - characters. - - ~window~, limit width to current window width. - - integer number, limit width to that specified number of pixels. - - ~nil~, do not limit the width. - - #+vindex: org-image-align - Org mode can left-align, center or right-align the display of inline - images. This setting is controlled (globally) by ~org-image-align~. - Only standalone images are affected, corresponding to links with no - surrounding text in their paragraph except for whitespace. Its - value can be the following: - - (default) The symbol ~left~, which inserts the image where the - link appears in the buffer. - - The symbol ~center~, which will preview links centered in the - Emacs window. - - The symbol ~right~, which will preview links right-aligned in the - Emacs window. - - Inline image alignment can be specified for each link using the - =#+ATTR.*= keyword if it matches an alignment specification like: - #+begin_example - ,#+ATTR_HTML: :align center - #+end_example - Org will use the alignment specification from any =#+ATTR.*= - keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= - (if present) will override the others. For instance, this link +When [[*Images and link previews][link previews]] are displayed as +images, the image size and alignment can be further customized. + +#+vindex: org-image-actual-width +#+vindex: org-image-max-width +#+cindex: @samp{ORG-IMAGE-ACTUAL-WIDTH}, property +By default, Org mode displays inline images according to their +actual width, but no wider than ~fill-column~ characters. + +You can customize the displayed image width using +~org-image-actual-width~ variable (globally) or +=ORG-IMAGE-ACTUAL-WIDTH= property (subtree-level)[fn:: The width can +be customized in Emacs >= 24.1, built with ImageMagick support.]. +Their value can be the following: +- (default) Non-~nil~, use the actual width of images when inlining + them. If the actual width is too wide, limit it according to + ~org-image-max-width~. +- When set to a number, use ImageMagick (when available) to set the + image's width to this value. +- When set to a number in a list, try to get the width from any + =#+ATTR.*= keyword if it matches a width specification like: #+begin_example - ,#+ATTR_HTML: :align right - ,#+ATTR_ORG: :align center - [[/path/to/image/file.png]] + ,#+ATTR_HTML: :width 300px #+end_example - will be displayed centered in Emacs but exported right-aligned to - HTML. + and fall back on that number if none is found. +- When set to ~nil~, try to get the width from an =#+ATTR.*= keyword + and fall back on the original width or ~org-image-max-width~ if + none is found. + +~org-image-max-width~ limits the maximum displayed image width, but +only when the image width is not set explicitly. Possible maximum +width can be set to: +- (default) ~fill-column~, limit width to ~fill-column~ number of + characters. +- ~window~, limit width to current window width. +- integer number, limit width to that specified number of pixels. +- ~nil~, do not limit the width. + +#+vindex: org-image-align +Org mode can left-align, center or right-align the display of inline +images. This setting is controlled (globally) by ~org-image-align~. +Only standalone images are affected, corresponding to links with no +surrounding text in their paragraph except for whitespace. Its +value can be the following: +- (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. +- The symbol ~center~, which will preview links centered in the + Emacs window. +- The symbol ~right~, which will preview links right-aligned in the + Emacs window. + +Inline image alignment can be specified for each link using the +=#+ATTR.*= keyword if it matches an alignment specification like: +#+begin_example +,#+ATTR_HTML: :align center +#+end_example +Org will use the alignment specification from any =#+ATTR.*= +keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= +(if present) will override the others. For instance, this link +#+begin_example +,#+ATTR_HTML: :align right +,#+ATTR_ORG: :align center +[[/path/to/image/file.png]] +#+end_example +will be displayed centered in Emacs but exported right-aligned to +HTML. - When =#+ATTR_ORG= is not set, inline image alignment is also read - from the =:center= attribute supported by some export backends (like - HTML, LaTeX and Beamer.) - -#+vindex: org-cycle-inline-images-display -Inline images can also be displayed when cycling the folding state. -When custom option ~org-cycle-inline-images-display~ is set, the -visible inline images under subtree will be displayed automatically. +When =#+ATTR_ORG= is not set, inline image alignment is also read from +the =:center= attribute supported by some export backends (like HTML, +LaTeX and Beamer.) ** Captions :PROPERTIES: @@ -21794,6 +21835,67 @@ ** Adding Hyperlink Types topic. It also provides a default description. The function ~org-insert-link~ can insert it back into an Org buffer later on. +** Adding Hyperlink preview +:PROPERTIES: +:DESCRIPTION: Preview behavior for new hyperlink types. +:END: +#+cindex: hyperlinks, adding preview behavior + +By default, Org supports previewing external links for links ot type +=file= and =attachment= that point to image files. (See [[*Images]].) + +Support for previewing other link types inline can be added to Org in +the following way: + +1. Add a =:preview= link parameter to the link type using + ~org-link-set-parameters~. As an example, here we add previews for + the =docview= link type. + + #+begin_src emacs-lisp +(org-link-set-parameters + "docview" :preview #'org-link-docview-preview) + #+end_src + +2. The value of the =:preview= parameter must be a function that + accepts three arguments: + - an overlay placed from the start to the end of the link, + - the link path, as a string, and + - the syntax node for the link. + It must return a non-nil value to indicate preview success. A + value of =nil= implies that the preview failed, and the overlay + placed on the link will be removed. + + In our example, we use the =convert= program (part of the + =imagemagick= suite of tools) to create the thumbnail that is + displayed inline. + + #+begin_src emacs-lisp +(defun org-link-docview-preview (ov path _elem) + "Preview file at PATH in overlay OV. + +_ELEM is the syntax node of the link element." + (when (executable-find "convert") + (let* ((path (expand-file-name (substitute-in-file-name path))) + (output-file (expand-file-name (concat "org-docview-preview-" + (substring (sha1 path) 0 10) + ".png") + temporary-file-directory))) + ;; Create or find preview for path + (when (or (file-readable-p output-file) + (= 0 (call-process + "convert" + nil (get-buffer-create "*Org Docview Preview Output*") nil + "-thumbnail" "x320" "-colorspace" "rgb" + "-background" "white" "-alpha" "remove" "-strip" + (concat path "[0]") output-file))) + ;; If preview image is available, display it via the overlay + (overlay-put ov 'display (create-image output-file)))))) + #+end_src + +3. Now previews of docview links for supported document types (PDF, + djvu) are generated (along with image file previews) when calling + ~org-link-preview~. + ** Adding Export Backends :PROPERTIES: :DESCRIPTION: How to write new export backends. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index d8018690f..a033fafcd 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -24,26 +24,38 @@ Previously, =C-c C-x C-v= always toggled image display in the whole buffer (or narrowed part of the buffer). With prefix argument, it also forced displaying image links with description. -Now, =C-c C-x C-v= is bound to a new command -~org-toggle-inline-images-command~, which uses different defaults: +Now, =C-c C-x C-v= is bound to a new command ~org-link-preview~, which +uses different defaults: -1. By default, it toggles image at point or, if there is no image at - point, images in current entry +1. When the region is active, images in the region are previewed -2. When region is active, it is honored +2. Otherwise, if there is an image at point, it is toggled. If there + is no image at point, images in the current entry are previewed -3. =C-u= argument changed its meaning. Now, it forces toggling images - in the whole buffer +3. With the =C-u= argument, image previews in the active region or at + point are cleared instead -4. =C-u C-u= argument unconditionally hides all the images in buffer +4. The =C-u C-u= argument unconditionally shows all images in the + accessible portion of the buffer -5. Displaying images over links with description can be forced using +5. The =C-u C-u C-u= argument unconditionally clears all images in the + accessible portion of the buffer + +6. Displaying images over links with description can be forced using numeric argument: - ~C-u 1~ for toggling all images at point/current entry - ~C-u 11~ for toggling all images in buffer -The old ~org-toggle-inline-images~ command is still available. You -can bind it back to =C-c C-x C-v= by adding the following to you config: +(The first five of these prefix arg behaviors are the same as that of +the ~org-latex-preview~ command.) + +In addition to images, ~org-link-preview~ can also be used to preview +Org links of all types for which preview behavior is defined, see +[[#link-preview][previews for arbitrary link types]]. + +The old ~org-toggle-inline-images~ command is obsolete but still +available. You can bind it back to =C-c C-x C-v= by adding the +following to your config: #+begin_src emacs-lisp (eval-after-load 'org-keys (org-defkey org-mode-map (kbd "C-c C-x C-v") #'org-toggle-inline-images)) @@ -76,6 +88,16 @@ now have diary timestamps included as well. # We list the most important features, and the features that may # require user action to be used. +*** All Org link types can be previewed +:PROPERTIES: +:CUSTOM_ID: link-preview +:END: + +Org links support a new parameter =:preview= that can be used to +preview arbitrary link types. The value of this parameter should be a +function that is called to preview links of the corresponding type +(see ~org-link-parameters~). + *** Alignment of image previews can be customized This is not a new feature. It has been added in Org 9.7, but not @@ -97,6 +119,17 @@ or newer. # adding new customizations, or changing the interpretation of the # existing customizations. +*** New option ~org-link-preview-batch-size~ + +Org link previews are generated asynchronously and a few at a time, in +batches. This option controls the number of links that are previewed +in each batch. + +*** New option ~org-link-preview-delay~ + +Org link previews are generated asynchronously. This option controls +the minimum idle time in seconds between previews of batches of links. + *** Allow disabling macro replacement during export New custom option ~org-export-replace-macros~ controls whether Org @@ -139,6 +172,26 @@ bibliography format requires them to be written in title-case. # This also includes changes in function behavior from Elisp perspective. +*** New command ~org-link-preview~ to preview Org links + +This command replaces ~org-toggle-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-region~ to preview Org links in a region or the buffer + +This command replaces ~org-display-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-clear~ to clear Org link previews in a region or the buffer + +This command replaces ~org-remove-inline-images~, which is now +obsolete. + +*** New command ~org-link-preview-refresh~ to refresh Org link previews in the buffer + +This command replaces ~org-redisplay-inline-images~, which is now +obsolete. + *** ob-sqlite: Added ability to open a database in readonly mode Added option :readonly to ob-sqlite. @@ -163,6 +216,24 @@ accept the INFO channel and return a string. This makes it possible to dynamically generate the content of the resulting ~<head>~ tag in the resulting HTML document. +** Removed or renamed functions and variables + +*** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~ + +Inline image previews in Org mode are now provided by the more general +link previews feature. The behavior with regards to image links is +unchanged. + +*** ~org-cycle-inline-images-display~ is renamed to ~org-cycle-link-previews-display~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + +*** ~org-startup-with-inline-images~ is renamed to ~org-startup-with-link-previews~ + +The behavior is unchanged, except in that the new variable now affects +previews of supported link types besides image links. + ** Miscellaneous *** Org mode no longer prevents =flyspell= from spell-checking inside =LOGBOOK= drawers -- 2.46.1 ^ permalink raw reply related [flat|nested] 90+ messages in thread
* Re: [PATCH v10] Inline image display as part of a new org-link-preview system 2024-11-14 22:10 ` Karthik Chikmagalur 2024-11-21 3:46 ` Karthik Chikmagalur @ 2024-11-23 16:11 ` Ihor Radchenko 1 sibling, 0 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-11-23 16:11 UTC (permalink / raw) To: Karthik Chikmagalur; +Cc: stardiviner, Org mode Karthik Chikmagalur <karthikchikmagalur@gmail.com> writes: >>> I'm using texinfo 7.1 now, the pdf build is still failing when I try >>> "make docs", with the same errors. Not sure what to do now. >> >> May you please try to create a reproducer then? >> Starting from a freshly cloned Org repo. > > Here's what I get when I run `make docs' after pulling from main. > Should I create a bug report? I'm not sure yet if PEBKAC. > texi2pdf --batch --clean --expand org.texi > org.texi:16168: warning: @anchor should not appear on @item line > org.texi:16181: warning: @anchor should not appear on @item line At least this one has been fixed in Texinfo 7.1.1 - bugfix away from your version. > This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/nixos.org) (preloaded format=pdftex) > restricted \write18 enabled. > entering extended mode > (../../../org.t2d/pdf/src/org.texi > (/home/karthik/.local/share/git/elpaca/repos/org-origin-main/doc/texinfo.tex > Loading texinfo [version 2013-09-11.11]: pdf, fonts, markup, glyphs, > page headings, tables, conditionals, indexing, sectioning, toc, environments, > defuns, macros, cross references, insertions, localization, formatting, > and turning on texinfo input format.) > Runaway argument? What I am getting here is different. Very strange. This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023 Gentoo Linux) (preloaded format=pdftex) restricted \write18 enabled. entering extended mode (../../../org.t2d/pdf/src/org.texi (/home/yantar92/Git/org-mode/doc/texinfo.tex Loading texinfo [version 2013-09-11.11]: pdf, fonts, markup, glyphs, page headings, tables, conditionals, indexing, sectioning, toc, environments, defuns, macros, cross references, insertions, (/usr/share/texmf-dist/tex/generic/epsf/epsf.tex This is `epsf.tex' v2.7.4 <14 February 2011> ) localization, formatting, and turning on texinfo input format.) (/usr/share/texmf-dist/tex/texinfo/txi-en.tex) [1{/var/lib/texmf/fonts/map/pdft ex/updmap/pdftex.map}] [2] [-1] Chapter 1 [1] Overfull \hbox (290.87877pt too wide) in paragraph at lines 646--646 []@texttt emacs -Q -batch -eval "(progn (require ^^Mpackage) (package-initiali ze) (package-refresh-contents) (package-upgrade ^^Morg))"[] ... -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
* [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) 2024-08-14 2:04 ` stardiviner 2024-08-18 10:27 ` Ihor Radchenko @ 2024-08-18 10:34 ` Ihor Radchenko [not found] ` <66c54dfc.a70a0220.3c823a.2899@mx.google.com> 1 sibling, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-18 10:34 UTC (permalink / raw) To: stardiviner; +Cc: Org mode stardiviner <numbchild@gmail.com> writes: > I have an idea, would you like to add an function in bellowing: > > #+begin_src emacs-lisp > (defun org-toggle-inline-images-in-results () > "Toggle inline images in babel source block results." > (save-excursion > ;; [C-c C-v C-o] `org-babel-open-src-block-result' > (let ((begin (org-babel-where-is-src-block-result)) > (end (progn > (end-of-line) > (skip-chars-forward " \r\t\n") > (looking-at org-link-bracket-re) > ;; (org-babel-read-result) > (point)))) > (org-toggle-inline-images-command nil begin end)))) > > (add-hook 'org-babel-after-execute-hook > 'org-toggle-inline-images-in-results) > #+end_src > > WDYT? after all I originally propose this patch for this purpose to > improve the babel result inline image toggle displaying performance. I do not mind this kind of feature, but `org-babel-after-execute-hook' is probably not the best place - it may be triggered noninteractively, including in Emacs running in batch mode, where displaying images is impossible. In particular, `org-babel-tangle' does it. Also, you do not need `org-toggle-inline-images-command' for this. You do not need "toggle" in general - what if an image was previously displayed and you evaluate an src block? A simple `org-display-inline-images' would do (with REFRESH argument). I suggest to put this (1) under a new custom option; (2) only when Emacs can actually display images. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
[parent not found: <66c54dfc.a70a0220.3c823a.2899@mx.google.com>]
* Re: [FR] Automatically display images in resutls of evaluation [not found] ` <66c54dfc.a70a0220.3c823a.2899@mx.google.com> @ 2024-08-22 13:06 ` Ihor Radchenko [not found] ` <66c89411.170a0220.3255c1.0cd5@mx.google.com> 0 siblings, 1 reply; 90+ messages in thread From: Ihor Radchenko @ 2024-08-22 13:06 UTC (permalink / raw) To: Christopher M. Miles; +Cc: emacs-orgmode [ Adding Org ML back to CC ] "Christopher M. Miles" <numbchild@gmail.com> writes: >> A simple `org-display-inline-images' would do (with REFRESH argument). > > I will adjust my upper code snippet in my Emacs config. I myself have something similar in my config: (add-hook 'org-babel-after-execute-hook (lambda () (unless (eq this-command 'org-babel-tangle) (org-display-inline-images nil nil (save-excursion (org-back-to-heading-or-point-min t) (point)) (save-excursion (or (outline-next-heading) (point-max))))))) And unless (eq this-command 'org-babel-tangle) was there for a reason (although I do not remember what was that reason). Still, need to be careful. >> I suggest to put this (1) under a new custom option; (2) only when >> Emacs can actually display images. > > I suggest use an custom option for this behavior. About (2), can add a condition: (display-graphic-p) > > (when (display-graphic-p) Looks reasonable. > (save-excursion > ;; [C-c C-v C-o] `org-babel-open-src-block-result' > (let ((begin (org-babel-where-is-src-block-result)) > (end (progn > (end-of-line) > (skip-chars-forward " \r\t\n") > (looking-at org-link-bracket-re) > ;; (org-babel-read-result) > (point)))) What is more than one image? What if it is wrapped into, say, a drawer? I'd rather be a bit less selective and refresh the whole result element. Looking forward to your updated patch. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
[parent not found: <66c89411.170a0220.3255c1.0cd5@mx.google.com>]
[parent not found: <87zfor7b04.fsf@localhost>]
[parent not found: <CAL1eYuLOsaS43ahueN4uWiCn+Ykp=p_-t9dzAypKdy1en_53BQ@mail.gmail.com>]
* Re: [PATCH v3] Re: [FR] Automatically display images in resutls of evaluation [not found] ` <CAL1eYuLOsaS43ahueN4uWiCn+Ykp=p_-t9dzAypKdy1en_53BQ@mail.gmail.com> @ 2024-09-15 13:33 ` Ihor Radchenko 0 siblings, 0 replies; 90+ messages in thread From: Ihor Radchenko @ 2024-09-15 13:33 UTC (permalink / raw) To: stardiviner; +Cc: emacs-orgmode stardiviner <numbchild@gmail.com> writes: > I updated the patch. Thanks! > + > +(add-hook 'org-babel-after-execute-hook 'org-toggle-inline-image-in-results) Please note that `org-babel-after-execute-hook' is not necessarily evaluated with point at the src block being executed. > +(defun org-toggle-inline-image-in-results () > + "Toggle inline image in babel source block results. > +It support displaying multiple continous inline images in results." > + (unless (eq this-command 'org-babel-tangle) ... so this check is not really sufficient. The very reason why `org-babel-tangle' fails with this code is because `org-babel-tangle' with noweb references may evaluate code blocks and point position will be at the tangled code block rather than at the code blocks being evaluated. `org-babel-tangle' is not the only function that may trigger block evaluation in such a way. Any kind of reference resolution might. We need to come up with same safe way to only display inline images in the results when those images are actually updated interactively. We might even need a new kind of hook that is given more contextual info about code evaluation. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 90+ messages in thread
end of thread, other threads:[~2024-11-27 21:54 UTC | newest] Thread overview: 90+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-05-15 3:28 [PATCH] add a function to only refresh inline images under current headline instead of global buffer Christopher M. Miles 2023-05-15 11:08 ` Ihor Radchenko 2023-05-15 13:01 ` Christopher M. Miles 2023-05-15 14:00 ` [PATCH v2] " Christopher M. Miles 2023-05-16 9:17 ` Ihor Radchenko 2023-05-16 12:18 ` Christopher M. Miles 2023-07-24 11:25 ` Ihor Radchenko 2023-08-01 4:40 ` [PATCH v3] " Christopher M. Miles 2023-08-01 8:04 ` Ihor Radchenko 2023-08-01 12:17 ` [PATCH v3.1] " Christopher M. Miles 2023-08-01 14:09 ` Ihor Radchenko 2023-08-01 15:22 ` Christopher M. Miles 2023-08-01 15:46 ` Christopher M. Miles 2023-08-02 16:08 ` Ihor Radchenko 2023-08-04 6:30 ` Christopher M. Miles 2023-08-02 7:26 ` Ihor Radchenko 2023-08-02 15:44 ` Christopher M. Miles 2023-08-04 8:20 ` Ihor Radchenko 2023-08-05 5:28 ` [PATCH v3.2] " Christopher M. Miles 2024-07-22 10:46 ` Ihor Radchenko 2024-08-01 22:58 ` [PATCH v4.0] " stardiviner [not found] ` <66a8b73b.170a0220.383476.996e@mx.google.com> 2024-08-12 10:18 ` Ihor Radchenko 2024-08-14 2:04 ` stardiviner 2024-08-18 10:27 ` Ihor Radchenko 2024-08-20 2:02 ` Karthik Chikmagalur 2024-08-20 15:43 ` Karthik Chikmagalur 2024-08-20 18:19 ` Ihor Radchenko 2024-08-20 19:46 ` Karthik Chikmagalur 2024-08-22 13:19 ` Ihor Radchenko 2024-08-23 6:04 ` Karthik Chikmagalur 2024-08-23 23:36 ` [PATCH v1] Inline image display as part of a new org-link-preview system Karthik Chikmagalur 2024-08-24 1:00 ` Karthik Chikmagalur 2024-08-31 14:22 ` Ihor Radchenko 2024-08-31 16:41 ` Karthik Chikmagalur 2024-08-31 16:53 ` Ihor Radchenko 2024-08-31 22:37 ` [PATCH v2] " Karthik Chikmagalur 2024-09-01 13:06 ` Ihor Radchenko 2024-09-02 20:13 ` [PATCH v3] " Karthik Chikmagalur 2024-09-08 7:43 ` Ihor Radchenko 2024-09-09 3:21 ` Karthik Chikmagalur 2024-09-09 6:06 ` Ihor Radchenko 2024-09-09 6:30 ` Karthik Chikmagalur 2024-09-09 16:47 ` Ihor Radchenko 2024-09-09 19:14 ` Karthik Chikmagalur 2024-09-10 16:57 ` Ihor Radchenko 2024-09-10 19:53 ` Karthik Chikmagalur 2024-09-15 7:51 ` Ihor Radchenko 2024-09-09 21:45 ` Karthik Chikmagalur 2024-09-10 16:58 ` Ihor Radchenko 2024-09-10 17:38 ` Karthik Chikmagalur 2024-09-10 18:34 ` Ihor Radchenko 2024-09-10 19:43 ` Karthik Chikmagalur 2024-09-15 8:12 ` Ihor Radchenko 2024-09-15 20:50 ` Karthik Chikmagalur 2024-09-15 21:57 ` [PATCH v4] " Karthik Chikmagalur 2024-09-17 18:16 ` Ihor Radchenko 2024-09-18 1:44 ` [PATCH v5] " Karthik Chikmagalur 2024-09-21 10:11 ` Ihor Radchenko 2024-09-22 22:00 ` [PATCH v6] " Karthik Chikmagalur 2024-10-03 17:03 ` Ihor Radchenko 2024-10-08 0:07 ` [PATCH v7] " Karthik Chikmagalur 2024-10-10 17:15 ` Ihor Radchenko 2024-10-10 21:23 ` Karthik Chikmagalur 2024-10-12 8:15 ` Ihor Radchenko 2024-10-28 6:13 ` [PATCH v8] " Karthik Chikmagalur 2024-10-28 18:16 ` Ihor Radchenko 2024-10-28 21:53 ` [PATCH v9] " Karthik Chikmagalur 2024-10-29 18:46 ` Ihor Radchenko 2024-10-29 20:45 ` [PATCH v10] " Karthik Chikmagalur 2024-10-30 17:32 ` Ihor Radchenko 2024-10-30 19:50 ` Karthik Chikmagalur 2024-10-31 18:16 ` Ihor Radchenko 2024-11-14 5:43 ` Karthik Chikmagalur 2024-11-14 17:14 ` Ihor Radchenko 2024-11-14 22:10 ` Karthik Chikmagalur 2024-11-21 3:46 ` Karthik Chikmagalur 2024-11-23 16:04 ` Ihor Radchenko 2024-11-25 3:17 ` [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
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).