From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2.migadu.com ([2001:41d0:403:4876::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms13.migadu.com with LMTPS id cAilGasc1mb/QAAAe85BDQ:P1 (envelope-from ) for ; Mon, 02 Sep 2024 20:14:35 +0000 Received: from aspmx1.migadu.com ([2001:41d0:403:4876::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2.migadu.com with LMTPS id cAilGasc1mb/QAAAe85BDQ (envelope-from ) for ; Mon, 02 Sep 2024 22:14:35 +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="hWxFwA/W"; 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"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none) ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1725308075; 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=DaZ544ocGXtkWSaPt5FB1PDH2wPWfYcsjKnMQDEwJBE=; b=qqP0/LorpIdhT2pUYKvZ6Q13Ao8GkUQ3fdlSi8vOzW+AasW35MMi2vDZeYEaBmP0uGQjSH yN61YmQ4VFhhq0n3lvT2q5a4uAVS+Yo1yA31TMrOundhKZxovq71mBPSvUDrXbik+oZA4v ZuPdSFJhjHbeYjz8lCnm05e05gYpU+sZjMuys3A+3UhHeG/fqSvMHC3QVUx/IcTH/iKBxx DhSjA0p63rR8wCOphLllvFlTifj+ofq57Vav3mBbcaGRIhJtFfwE91O6qSCfoNjCAQCQYG +E81O4bkMKoleLd94SKMPBq5v2oisIfIMY4A2y24YCCDxgTKl0fZaZdrJfvoaA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b="hWxFwA/W"; 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"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none) ARC-Seal: i=1; s=key1; d=yhetil.org; t=1725308075; a=rsa-sha256; cv=none; b=TNyA865J1teZZJmgISEoScddZpykY9d7Roa5lrinx1joRt9OsoGhPx2PDh/um9DtJR09Dv q3edQh0D1E+DtK1+fMFEHi9DbNhEZuvEj1yWH/JW4M/R+be1hGdnQsyHZ67Z00JYjgTJNi ekULG9u3G1r4FFQhkuHhmQDFH43kjldhfL3qZUEPjYtecv6zlU48mS1HH7C+pG1SsQNtdV HEsIDl2YuV35H4QaEqa/14cNAEo2LYrue52M4L+iIImPd7Q9izEGlzHRoeZtoWINxK9Xl6 V4mtmd5IaEsujSOX8CKvnT8lC5k/pGdJdM3MjaywENCwPULD5HPWANTW/1TOYA== 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 E741F7604E for ; Mon, 2 Sep 2024 22:14:34 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1slDQa-0004G3-NB; Mon, 02 Sep 2024 16:13:32 -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 1slDQU-0003rz-HS for emacs-orgmode@gnu.org; Mon, 02 Sep 2024 16:13:27 -0400 Received: from mail-pg1-x529.google.com ([2607:f8b0:4864:20::529]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1slDQQ-0008GK-5b for emacs-orgmode@gnu.org; Mon, 02 Sep 2024 16:13:26 -0400 Received: by mail-pg1-x529.google.com with SMTP id 41be03b00d2f7-7c3ebba7fbbso3150505a12.1 for ; Mon, 02 Sep 2024 13:13:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725308000; x=1725912800; 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=toj1vpTsrTHDAaOIDqLefksLhnQJjJe3qpN7yQnYO58=; b=hWxFwA/WeOs0xJ/s7U4mhoOKR9EbYDXRbMnx7FRSaPAH6oAqNEqrDm8p+a6ordNqFn 94nTAXaJxoMJ67VCQV8oRhwPOWBOxXx5vOJl+Zg4NhUU5o5XzpNTO/GSZn5apIF8z+1I +3MD08KSTXysH2qJptZavBHrLBdsOjKTBRD4CPv3cFcuKZ1OtMs88GgFUgqGtfwDxDae 9Q2JA1e+boVKbZHicE82w6XP6eTLq2SWgkowYN2vhTQIldp4U6PeLFd0QUGtRL6Y/2C4 V9ZSlurbcNeX9jcgNOK9jJq6kz/J3papZ0s1tPlNplF99GWI1yDObyHakFjuBTX3gQ26 5Bjw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725308000; x=1725912800; 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=toj1vpTsrTHDAaOIDqLefksLhnQJjJe3qpN7yQnYO58=; b=diLlDB1GXgUQZTjoEezoJoKgeRkS3QpeV0X5hr4G9Xt7ILizmqqdwWUOCe83VcmQpG THC9zaQuJYInBHKtT1JVQWokbQro+k9BCPo+i8MQzPvqk0s5H5FNWEjux0RcxpvXZyCB uQju4SoxkI/1oJ19Ny7AK0ZeovApTFpl79f1WNO/1kP5zKhzi22Zpvi7+0kxFeuoxFU2 lE3z9Rc2gKz9aRcoXJLYU7UFkbpVEN4wa3934eERsAwixLbWdZEadcykf4dLX3ppphi1 +tEwo/OvthRouxqKMP2N7eKnRIoV/+yw5Z/ZsU95KTBH6YX9tEd0OTuJHJpu/V68IAtY 2oNA== X-Forwarded-Encrypted: i=1; AJvYcCU8B74npgDpkoKlfrvDNyJB11IKtB7y1kdLr0WHLMP+b+Bt/gZsxM3DOdHRoHjFGpf0wJa4t6/aErd6P4cM@gnu.org X-Gm-Message-State: AOJu0YwvqDlTRtQlUgExegCubEeZQIGv1VSppNS8OHVa3T7V+jPGpGt8 btcLVC+vR7+gOzlQSUhYHpQjs6wFOd7OFSGwCwHIO+o+lSKZzW/M X-Google-Smtp-Source: AGHT+IEV0E4LWiURhOggBA2fL3cikFsN1+Sc5jBBmewkyv2n2UKWfBNUlmatkCq6ww2wTHJtn31FpQ== X-Received: by 2002:a17:903:41cb:b0:202:60e:7700 with SMTP id d9443c01a7336-20545e4438emr92538695ad.7.1725307999484; Mon, 02 Sep 2024 13:13:19 -0700 (PDT) Received: from localhost ([2600:8802:5726:2500:d1c0:8ad1:b212:9651]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-205152cd653sm69607555ad.74.2024.09.02.13.13.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Sep 2024 13:13:18 -0700 (PDT) From: Karthik Chikmagalur To: Ihor Radchenko Cc: stardiviner , Org mode Subject: Re: [PATCH v3] Inline image display as part of a new org-link-preview system In-Reply-To: <878qwb8qw1.fsf@localhost> References: <6461a84b.a70a0220.b6d36.5d00@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> <87h6b09v4o.fsf@gmail.com> <878qwb8qw1.fsf@localhost> Date: Mon, 02 Sep 2024 13:13:17 -0700 Message-ID: <878qw9ak6a.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::529; envelope-from=karthikchikmagalur@gmail.com; helo=mail-pg1-x529.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-Country: US X-Migadu-Flow: FLOW_IN X-Spam-Score: 2.52 X-Migadu-Queue-Id: E741F7604E X-Migadu-Scanner: mx10.migadu.com X-Migadu-Spam-Score: 2.52 X-TUID: y0ghJlSoc9+j --=-=-= Content-Type: text/plain 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 --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-org-link-Customizable-preview-API-for-arbitrary-link.patch >From 7c2df91fb6f41e24af8ee5bee452784e896149f8 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur 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)) ;;; 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,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)))))) + ;;; 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:\\|[./~]\\)\\|\\]\\[\\(