diff --git a/NEWS.org b/NEWS.org index be15a9f5..4286e2fa 100644 --- a/NEWS.org +++ b/NEWS.org @@ -41,6 +41,10 @@ - ~mu4e-compose-crypto-policy~ now also recognizes ~sign-signed-replies~, to automatically sign when replying to signed messages (1.14.1). + - For Org-integration, there is a new command ~mu4e-org-agenda-links~ to pop up + a buffer with a tabulated list of the mu4e links in your ~org-agenda-files~ + (1.14.2). + *** scm - added new ~--eval~ command-line option, so you can do e.g. diff --git a/mu4e/mu4e-mime-parts.el b/mu4e/mu4e-mime-parts.el index 351d2f38..114d8cb7 100644 --- a/mu4e/mu4e-mime-parts.el +++ b/mu4e/mu4e-mime-parts.el @@ -450,9 +450,10 @@ buffer is killed." (gnus-map-function mm-file-name-rewrite-functions (file-name-nondirectory fname)))) (fname (if fname - (mu4e-join-paths - mu4e--temp-dir - (replace-regexp-in-string "/" "-" fname)) + (mu4e--uniquify-file-name + (mu4e-join-paths + mu4e--temp-dir + (replace-regexp-in-string "/" "-" fname))) (let ((temporary-file-directory mu4e--temp-dir)) (make-temp-file "mime-part-"))))) (mm-save-part-to-file handle fname) diff --git a/mu4e/mu4e-org.el b/mu4e/mu4e-org.el index 47dbc9b0..2ee6ab8e 100644 --- a/mu4e/mu4e-org.el +++ b/mu4e/mu4e-org.el @@ -1,6 +1,6 @@ ;;; mu4e-org --- Org-links to mu4e messages/queries -*- lexical-binding: t -*- -;; Copyright (C) 2012-2024 Dirk-Jan C. Binnema +;; Copyright (C) 2012-2026 Dirk-Jan C. Binnema ;; Author: Dirk-Jan C. Binnema ;; Maintainer: Dirk-Jan C. Binnema @@ -28,6 +28,7 @@ ;;; Code: (require 'org) +(require 'tabulated-list) (require 'mu4e-view) (require 'mu4e-contacts) @@ -147,5 +148,146 @@ it with org)." (org-link-set-parameters "mu4e" :follow #'mu4e-org-open :store #'mu4e-org-store-link) + + +(defun mu4e-org-links-in-buffer (&optional file) + "List all mu4e:msgid links in the current `org-mode' buffer. + +The list elements are plists with: + :link the org-link + :msgid the message-id it refers to + :context a string describing the org item + :file the FILE in which this link was found + +If FILE is non-nil: + :start the start position of the link +plists." + (save-excursion + (org-with-wide-buffer + (goto-char (point-min)) + (org-element-map (org-element-parse-buffer) 'link + (lambda (link) + (let* ((type (org-element-property :type link)) + (raw-link (org-element-property :raw-link link)) + (start (org-element-property :contents-begin link)) + (path (org-element-property :path link)) + (msgid "msgid:")) + ;; a little hacky; seems org-mode doesn't have something built-in for + ;; this. + (when (string= type "mu4e") + (when-let* ((msgid (when (string-prefix-p msgid path) + (substring path (length msgid))))) + (let* ((begin (org-element-property :begin link)) + (context-line + (save-excursion + (goto-char begin) + (org-link-display-format + (string-trim + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)))))) + (info (list :link raw-link + :msgid msgid + :context context-line + :start start)) + (info + (if file + (plist-put info :file file) + info))) + info))))) + nil nil nil nil t)))) + +(defun mu4e-org-links-in-agenda-files () + "List all mu4e:msgid: links in `org-agenda-files'. + +The list elements are plists as per `mu4e-org-links-in-buffer'.." + (let ((msgid-links '())) + (dolist (file org-agenda-files) + (when (file-exists-p file) + (with-current-buffer (find-file-noselect file) + (when-let* ((buflinks (mu4e-org-links-in-buffer file))) + (setq msgid-links (append msgid-links buflinks)))))) + msgid-links)) + +(defcustom mu4e-org-agenda-links-context-length 60 + "Maximum length of the Context column in `mu4e-org-agenda-links'. +Longer context strings are ellipsized to fit." + :type 'integer + :group 'mu4e-org) + +(defvar-keymap mu4e-org-agenda-links-mode-map + :doc "Keymap for `mu4e-org-agenda-links-mode'." + :parent tabulated-list-mode-map + "RET" #'mu4e-org-agenda-links-follow + "o" #'mu4e-org-agenda-links-visit-file) + +(define-derived-mode mu4e-org-agenda-links-mode tabulated-list-mode + "mu4e-org-agenda-links" + "Major mode for listing mu4e:msgid links found in `org-agenda-files'." + (setq tabulated-list-format + (vector + (list "Context" mu4e-org-agenda-links-context-length nil) + (list "File" 25 t))) + (setq tabulated-list-padding 2) + (tabulated-list-init-header)) + +(defun mu4e-org-agenda-links--entry-at-point () + "Return the link entry at point, or signal an error." + (or (tabulated-list-get-id) + (mu4e-error "No entry at point"))) + +(defun mu4e-org-agenda-links-follow (&optional other-window) + "Follow the mu4e link at point. +With prefix arg OTHER-WINDOW, display the result in another window." + (interactive "P") + (let ((link (plist-get (mu4e-org-agenda-links--entry-at-point) :link))) + (if other-window + (let ((display-buffer-overriding-action + '((display-buffer-reuse-window + display-buffer-pop-up-window) + (inhibit-same-window . t)))) + (org-link-open-from-string link))1 + (org-link-open-from-string link)))) + +(defun mu4e-org-agenda-links-visit-file (&optional other-window) + "Visit the org file for the link at point, at its source location. +With prefix arg OTHER-WINDOW, visit in another window." + (interactive "P") + (let* ((entry (mu4e-org-agenda-links--entry-at-point)) + (file (plist-get entry :file)) + (start (plist-get entry :start))) + (unless (and file (file-exists-p file)) + (mu4e-error "File not found: %s" file)) + (if other-window + (find-file-other-window file) + (find-file file)) + (when start (goto-char start)))) + +;;;###autoload +(defun mu4e-org-agenda-links () + "Show mu4e:msgid links found in `org-agenda-files'. +Display a tabulated-list buffer with Context and File columns." + (interactive) + (let ((buf (get-buffer-create "*mu4e-org-agenda-links*"))) + (with-current-buffer buf + (mu4e-org-agenda-links-mode) + (setq tabulated-list-entries + (mapcar + (lambda (entry) + (let* ((file (or (plist-get entry :file) "")) + (context (or (plist-get entry :context) "")) + (short (truncate-string-to-width + context + mu4e-org-agenda-links-context-length + nil nil t))) + (list entry + (vector + (propertize short 'face 'mu4e-link-face) + (propertize (file-name-nondirectory file) + 'face 'mu4e-title-face))))) + (mu4e-org-links-in-agenda-files))) + (tabulated-list-print)) + (pop-to-buffer buf))) + (provide 'mu4e-org) ;;; mu4e-org.el ends here diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index 018efab7..5c7e35cf 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -3826,6 +3826,15 @@ key-bindings for that in headers and view mode: (define-key mu4e-view-mode-map (kbd "C-c c") 'mu4e-org-store-and-capture) @end lisp +@subsection Listing linked messages + +@t{mu4e} offers a command @kbd{mu4e-org-agenda-links}. This provides a buffer +that shows a tabulated list of all the mu4e message links found in your +@code{org-agenda-files}. + +Pressing @kbd{RET} opens the link, while @kbd{o} visits the Org-file where the +links was found. When prefixed by @kbd{C-u} these use the 'other window'. + @subsection Tracking sent mail To build on the above, it can be useful to automatically track outgoing e-mail