From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1.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 uF0aKjIdyWZlhgAA62LTzQ:P1 (envelope-from ) for ; Fri, 23 Aug 2024 23:37:22 +0000 Received: from aspmx1.migadu.com ([2001:41d0:303:e224::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1.migadu.com with LMTPS id uF0aKjIdyWZlhgAA62LTzQ (envelope-from ) for ; Sat, 24 Aug 2024 01:37:22 +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=TRwATFEj; 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=1724456242; 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=aZEET8+dABE2sTzUZ+84bind6t03Fppp1CJMTDMHO0o=; b=PHeJQow/Q3Ch2bu8l7mtJKSrnwTspSCShFDOqiWmEnouIctb7MYw77U3eNi5l+T1k5N/Wm AclBLq1Br+VHuOXkCmb3DhlNFi8SQtrgHMOpk7rZH3A1d8PUC6JJ3ShnyixweJDiO4aW3T grXvFo1FsR9wKLTkiDF9mTPXIx1Ojrkb1qOVNFyBX7ns7duSOrPfzw6pjbHjIAg+7FccKx lDhzwt5U2tqeWHPW4U9wXovCrgXuQyyHKWGBg5Hk0n5n8ujlej+0flCwYKXB9wMmmiCvkW IoEp2gsdVzgyT07AAXnwK9ruPvVCmEuKqggPp/giej2Y7OFtvSpFWiNF3ZXERQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1724456242; a=rsa-sha256; cv=none; b=ROdwfCc3ZRhvA9tN4QP8Ra8Vnw97+GCXeTgVTTJUJGzryjWoRnipuaNnBEu+T7CJ/BRvkl +tKLZaz1CDmNZYjojcGJtb+zay2l8qJ63rPZBOumb7jr6DVONvvb+KOfh18oMp2Pc7DFYQ Ai6Lx0Y60a7hcBVAEyzPBeoAmgGo7Elj42blrUtB3Bpw3o2he+GNqp4/krJ7PcR7g8KzdC IpSFxYQmI0WWZUkgOXqgh7usJdiG0gX04JJaKm0heCEP+VCxvkCS5St5YLC+d0NCva9tD+ DEHfI/+UfWk85u9uqPMDM9PUxpHCCxYEiFzmeDJMFeku3JfQzdQhMT4Q66Q7ZQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=TRwATFEj; 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 107B976814 for ; Sat, 24 Aug 2024 01:37:21 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1shdpH-0007dG-5E; Fri, 23 Aug 2024 19:36:15 -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 1shdpF-0007cm-9J for emacs-orgmode@gnu.org; Fri, 23 Aug 2024 19:36:13 -0400 Received: from mail-pf1-x42c.google.com ([2607:f8b0:4864:20::42c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1shdpA-0003Ma-H5 for emacs-orgmode@gnu.org; Fri, 23 Aug 2024 19:36:13 -0400 Received: by mail-pf1-x42c.google.com with SMTP id d2e1a72fcca58-714226888dfso2243883b3a.1 for ; Fri, 23 Aug 2024 16:36:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724456166; x=1725060966; 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=9Wc0hMJL5mQtGBAQK0sOWGTaQsfWQaqwO1BecwmGsK0=; b=TRwATFEjSbH72g+8hCS9miWcLe7GWu/9REZSiwcBdnwaubr0tijTsWxwAVppPHWDno +GLUl9nMaSU/kgkQS52R/h9DfRgItF4WKaEvRXCNbaWUIZC7bifDAC4Kfk9Lh7RACpXe b9Fb0lAq91LAKq8V+HLvttWL2bzovK10ybItFHrYHAjFs97EAa/aTRgSjDBHWXEVSsl6 oJXT7BueH7NzPF47A4R1cNL+vMh+syIhP9SN/dUTDnkaDCjTtTkC5dMDfNgLBe/njJZq +vPif48ZwGbfcB4nvlKiqp7dO/mfu92O5MLZ3tro509rFOCGOBpDkIZtpuJN1I9oDhSS PYxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724456166; x=1725060966; 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=9Wc0hMJL5mQtGBAQK0sOWGTaQsfWQaqwO1BecwmGsK0=; b=tJwcLHsh7Hcp1naK93gv24WZA9v/VXqcBh+y7Or05njJ+/X1/PEO2dbB6Fd8rnoXwm YpSClEcN4iG8VivGvrxhufdl47qzEYDP7biwam7ndjV+fKiskCDKLSyCI2rHe90l3tLA jO+zf+Ezusv0Ea6BzhgYDtO/4djmsoSQ9UCIHY229SwcwgVuvd9ZoxiGfz4mhMDRxQ3T 1I9qGoMjayvcpwrzWobPxzPUU/y2rdqMVG7AhlzDFxbs8WrVeV+dGbUZl6gwWufSMqq6 veKGqIvKtmIuMaEAuzir2D22S+sCqVh1n3ocX2ZUAJlwc1mfC5yXMdHzOOpBEtvbRJzB yBDw== X-Forwarded-Encrypted: i=1; AJvYcCWl/JlKmjRt/owh4zRzVfTMn45wgPpSyVXLcbmBztBj+c1qEvvHzy6zjdSXzCkGPD8RdZSmrd6AO8qLYiPo@gnu.org X-Gm-Message-State: AOJu0YznscLQaLVYcbi7KnU7a+w57XjmazqZNrca9GQdou0D4A5roTek Zw0I/n+Pf04y8aJ+rM3jX9SG4SSrzIEczc4b7dJ3/vFL2XnUWkGC X-Google-Smtp-Source: AGHT+IF8IHjZw8uf3AgjySUTRt6AbEE1SwhAUu1qVImeGCD1bOPCY8oOCbXmPwedRAeYQ6BS/Z/sQQ== X-Received: by 2002:a05:6a00:3e11:b0:70b:5394:8cae with SMTP id d2e1a72fcca58-714458c4101mr5167436b3a.28.1724456165943; Fri, 23 Aug 2024 16:36:05 -0700 (PDT) Received: from localhost ([2600:8802:5726:2500:9a3e:926b:7d04:b4f6]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-7143679cd7asm3401941b3a.112.2024.08.23.16.36.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Aug 2024 16:36:05 -0700 (PDT) From: Karthik Chikmagalur To: Ihor Radchenko Cc: stardiviner , Org mode Subject: [PATCH v1] Inline image display as part of a new org-link-preview system In-Reply-To: <87msl4wv8d.fsf@localhost> References: <6461a84b.a70a0220.b6d36.5d00@mx.google.com> <87y1lo4ou1.fsf@localhost> <646379fc.620a0220.c0ae4.9fb2@mx.google.com> <87zg3l1rgb.fsf@localhost> <64c8a313.a70a0220.93ee0.14fb@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> Date: Fri, 23 Aug 2024 16:36:04 -0700 Message-ID: <875xrqg6cb.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::42c; envelope-from=karthikchikmagalur@gmail.com; helo=mail-pf1-x42c.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: 107B976814 X-Migadu-Scanner: mx12.migadu.com X-Migadu-Spam-Score: -5.38 X-Spam-Score: -5.38 X-TUID: v5FNywCn3yh5 --=-=-= Content-Type: text/plain >> 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 --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-org-link-Move-inline-image-display-to-org-link.patch >From 93b7e50a50d7c6f72439169e5647a840c6e04bcc Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur 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)) ;;; 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) + ;;; 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))) + ;;; 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)))))) + ;;; 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:\\|[./~]\\)\\|\\]\\[\\(