From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2.migadu.com ([2001:41d0:303:e224::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms13.migadu.com with LMTPS id EHdnOW+b02bdSwEAe85BDQ:P1 (envelope-from ) for ; Sat, 31 Aug 2024 22:38:55 +0000 Received: from aspmx1.migadu.com ([2001:41d0:303:e224::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2.migadu.com with LMTPS id EHdnOW+b02bdSwEAe85BDQ (envelope-from ) for ; Sun, 01 Sep 2024 00:38:40 +0200 X-Envelope-To: larch@yhetil.org Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=H6Ysau7q; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1725143919; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=kQ68T79ofsfF7pdt5bhwazG3IpHeotK+pzjnb2//ltg=; b=d7ooFO4Xmm9iqtYJ2fHFPXVyH0oPKuAr4W0XJ9628+wILqTycrK6hsiuJWWMxWBTLS8mSn J8O6etpGbuVvej1TMM8GLcALVnyNeSf+axh7f0rAQPDRCBeCL/cVFrOiv4IaKhwV3NnigF FraCrHG0omEnlUa1Fp6wn8SiZ2Y8SjYFxVWfpCGen5zlyBDHVCGLDzFWZ68VJcEf+MR+l9 ViJnq8LqvxyBaO8LUW3JVf4Ciocs5VAAdr85HnlKMhLNWbHMTB43jEnV8TOdQB7RzgxX12 7G72E0m7vbj1jX/eCh9xZ2XD4UbpR30rGg5TKxzjlaz4ph9w/qCxBAQorpwSlQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1725143919; a=rsa-sha256; cv=none; b=d85OngEA7xABg8tz8T9Y6wXfAUQfrSMFGR7CYUHx/Qie//0XL/nGuMX35qzX7eILXISLw4 d4jtR47X4sC3eWGGnc+BQdUw8UKVuCoedsWTnDzKjA1k+vv5uQaBFWIPbygKCTnnL1iaxj ymflkiYePQCyBRhWMkYScJdtFpCPdEZZz5QHukrdjo3T9sVlG53uXAt90CcoXD36gFZ0VR 8rcsAypqPEScv5kduGzUAnpRQkuigz/KwgdYAf1JgouyDScASYBFdKwrP4gAFuk6icEDKX xoZJa42TsF/gnKHjUP2Q+6DzoGZEcd+5aJfr6rDbzbL/RvhSlLbrLWr4zIerQA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=H6Ysau7q; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 5778571067 for ; Sun, 1 Sep 2024 00:38:39 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1skWiw-0007rU-Ld; Sat, 31 Aug 2024 18:37:38 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1skWiu-0007r7-6u for emacs-orgmode@gnu.org; Sat, 31 Aug 2024 18:37:36 -0400 Received: from mail-pf1-x432.google.com ([2607:f8b0:4864:20::432]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1skWip-0007D4-TX for emacs-orgmode@gnu.org; Sat, 31 Aug 2024 18:37:35 -0400 Received: by mail-pf1-x432.google.com with SMTP id d2e1a72fcca58-7141b04e7a3so2541582b3a.3 for ; Sat, 31 Aug 2024 15:37:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725143850; x=1725748650; darn=gnu.org; h=mime-version:message-id:date:references:in-reply-to:subject:cc:to :from:from:to:cc:subject:date:message-id:reply-to; bh=QpLbxnSbyey4spO0ftB7ZDyPDUbbbvo/R9xolz/8070=; b=H6Ysau7qJP4WZWwlC6rc1+uqH2jZNeM7bLAo2e2alxM7NzdgcVUIC45Wm0TZ3iJ3yN 6lVxebCqZ4dzwTr0Q8UDbwQ/fG2v0aXvIx2CAMVemJbO7XSAaWUxQzYhonl3W2aoh/pO wMkicslJDPAuJitii4rUgA+i/nIPFSKIx6+rPoMdobWA8r6eZnNx8Ri1auVrSOlZ/iqW wlhfGbAZncXPRVRnF/BkglUD4bzcKlJWuZWtOG5GQszMdvTfdzAW2eZnGo8XncnGPck5 sPCwWci6Wz9ybpWwkGmL3om4j6t9ggt7WgR7e/QJR0JatoH7X3vnZByfFVDPpfwe942s eBtQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725143850; x=1725748650; h=mime-version:message-id:date:references:in-reply-to:subject:cc:to :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=QpLbxnSbyey4spO0ftB7ZDyPDUbbbvo/R9xolz/8070=; b=q0WOcN6mKWtNbh2x0V6382ZbSqX2fjn4kCBj6sGYE53OUhPoB2UWzOqEUZElqrKrV3 L0T9ALnBSIJPXuu0Voixzb02cz8lQbDW+epuQr9IVkGzSW8cbpaplDiDqFcGpzDQBpqD ND3n+Vjf4p3UJ7/7PhX0Ac7ixvKIOS5IwNeBAWcJJBofVOdIP+MqlKx7C4wmnwNZ/aEI 0zDm6lOJBwAJvjcSQ7k1UNZe5GtxOBLE01OzvtpP7hiBysNSL2+n4y3BeejIN6P8BqBx cr1eYjrM1lnxILaT8xQW/k2LEpTK9e+H1BfhClSuP/hYioK68v0uGMxt1dZ34cYqDxGi i/vw== X-Forwarded-Encrypted: i=1; AJvYcCWX2XM4/kKMy1dveuif6oV7776eJ/M4fQOWlCssFDUKLSEoODEHI5syVUeslsUHH5MCOp+arEqfZQo6I2sy@gnu.org X-Gm-Message-State: AOJu0YxMgeRut8QCUc0xsWFoSovq7CnWL2oaKpMxbRkUt6rXDvJmr/lM oG6rJ7tIw26/qXDM3qY0N//XpsTPcPhmkIn7HyqyrU0HcKJaMEomjcQzXw== X-Google-Smtp-Source: AGHT+IFeMY1tnWRRsTfsgHTtAbPcZu7TnJxafXvYyaoLWEKTcwb/RH4+2afweUrfOhvjgVkWRVacDw== X-Received: by 2002:a05:6a21:710a:b0:1cc:eaa4:5e49 with SMTP id adf61e73a8af0-1cceaa4608fmr9042052637.26.1725143849801; Sat, 31 Aug 2024 15:37:29 -0700 (PDT) Received: from localhost ([2600:8802:5726:2500:25e5:c7c1:4443:abba]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-715e569ec97sm4650131b3a.112.2024.08.31.15.37.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 31 Aug 2024 15:37:28 -0700 (PDT) From: Karthik Chikmagalur To: Ihor Radchenko Cc: stardiviner , Org mode Subject: Re: [PATCH v2] Inline image display as part of a new org-link-preview system In-Reply-To: <87jzfwljkq.fsf@localhost> References: <6461a84b.a70a0220.b6d36.5d00@mx.google.com> <87il9zgpdp.fsf@localhost> <64c905d7.170a0220.f434a.fddb@mx.google.com> <87o7jpoqfl.fsf@localhost> <64cc9b8a.170a0220.dfa99.2e18@mx.google.com> <87msz7kym0.fsf@localhost> <669882e5.050a0220.8ff6d.33c6@mx.google.com> <871q3logb9.fsf@localhost> <66a8b73b.170a0220.383476.996e@mx.google.com> <87o75yhwnu.fsf@localhost> <87v7zyyvm3.fsf@localhost> <87frr07xz8.fsf@gmail.com> <87cym38aj8.fsf@gmail.com> <87r0ajawgj.fsf@localhost> <87a5h77zb1.fsf@gmail.com> <87msl4wv8d.fsf@localhost> <875xrqg6cb.fsf@gmail.com> <874j70n559.fsf@localhost> <87msksabld.fsf@gmail.com> <87jzfwljkq.fsf@localhost> Date: Sat, 31 Aug 2024 15:37:27 -0700 Message-ID: <87h6b09v4o.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::432; envelope-from=karthikchikmagalur@gmail.com; helo=mail-pf1-x432.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: emacs-orgmode-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US X-Migadu-Queue-Id: 5778571067 X-Migadu-Scanner: mx12.migadu.com X-Migadu-Spam-Score: -5.38 X-Spam-Score: -5.38 X-TUID: aNRkdPi/HiyX --=-=-= Content-Type: text/plain 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 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-org-link-Move-inline-image-display-to-org-link.patch >From d60d5bef78841d548cfbcdca3a461cb9c52e4314 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur 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)) ;;; 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) + ;;; 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))) + ;;; 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)))))) + ;;; 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:\\|[./~]\\)\\|\\]\\[\\(