2019-09-16 19:54:59 +02:00
|
|
|
|
;;; mu4e-actions.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
2020-02-11 12:23:40 +01:00
|
|
|
|
|
2022-01-23 09:29:45 +01:00
|
|
|
|
;; Copyright (C) 2011-2022 Dirk-Jan C. Binnema
|
2012-04-24 17:11:56 +02:00
|
|
|
|
|
|
|
|
|
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
|
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
|
|
|
|
|
|
;; This file is not part of GNU Emacs.
|
2020-02-11 12:23:40 +01:00
|
|
|
|
|
2021-05-29 23:36:13 +02:00
|
|
|
|
;; mu4e is free software: you can redistribute it and/or modify
|
2012-04-24 17:11:56 +02:00
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
2021-05-29 23:36:13 +02:00
|
|
|
|
;; mu4e is distributed in the hope that it will be useful,
|
2012-04-24 17:11:56 +02:00
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
2021-05-29 23:36:13 +02:00
|
|
|
|
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
|
2012-04-24 17:11:56 +02:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; Example actions for messages, attachments (see chapter 'Actions' in the
|
|
|
|
|
;; manual)
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
2020-02-11 15:10:35 +01:00
|
|
|
|
|
2013-10-08 05:52:29 +02:00
|
|
|
|
(require 'ido)
|
2012-06-10 15:14:21 +02:00
|
|
|
|
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(require 'mu4e-helpers)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(require 'mu4e-message)
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(require 'mu4e-search)
|
2022-07-28 06:29:31 +02:00
|
|
|
|
(require 'mu4e-contacts)
|
2012-05-12 16:10:57 +02:00
|
|
|
|
|
2020-02-18 22:39:30 +01:00
|
|
|
|
;;; Count lines
|
2020-02-11 15:10:35 +01:00
|
|
|
|
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(defun mu4e-action-count-lines (msg)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Count the number of lines in the e-mail MSG.
|
2012-11-10 14:01:17 +01:00
|
|
|
|
Works for headers view and message-view."
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(message "Number of lines: %s"
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(shell-command-to-string
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(concat "wc -l < "
|
|
|
|
|
(shell-quote-argument (mu4e-message-field msg :path))))))
|
2012-04-24 17:11:56 +02:00
|
|
|
|
|
2020-02-18 22:39:30 +01:00
|
|
|
|
;;; Org Helpers
|
2012-04-24 17:48:07 +02:00
|
|
|
|
|
|
|
|
|
(defvar mu4e-captured-message nil
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"The most recently captured message.")
|
2012-04-24 17:48:07 +02:00
|
|
|
|
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(defun mu4e-action-capture-message (msg)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Remember MSG.
|
|
|
|
|
Later, we can create an attachment based on this message with
|
|
|
|
|
`mu4e-compose-attach-captured-message'."
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(setq mu4e-captured-message msg)
|
|
|
|
|
(message "Message has been captured"))
|
|
|
|
|
|
|
|
|
|
|
2017-08-27 16:35:23 +02:00
|
|
|
|
(defun mu4e-action-copy-message-file-path (msg)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Save the full path for the current MSG to the kill ring."
|
2017-08-27 16:35:23 +02:00
|
|
|
|
(kill-new (mu4e-message-field msg :path)))
|
|
|
|
|
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(defvar mu4e-org-contacts-file nil
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"File to store contact information for org-contacts.
|
|
|
|
|
Needed by `mu4e-action-add-org-contact'.")
|
2012-04-24 17:11:56 +02:00
|
|
|
|
|
|
|
|
|
(eval-when-compile ;; silence compiler warning about free variable
|
|
|
|
|
(unless (require 'org-capture nil 'noerror)
|
|
|
|
|
(defvar org-capture-templates nil)))
|
|
|
|
|
|
|
|
|
|
(defun mu4e-action-add-org-contact (msg)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Add an org-contact based on the sender ddress of the current MSG.
|
|
|
|
|
You need to set `mu4e-org-contacts-file' to the full path to the
|
|
|
|
|
file where you store your org-contacts."
|
2012-09-03 10:15:27 +02:00
|
|
|
|
(unless (require 'org-capture nil 'noerror)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
(mu4e-error "Feature org-capture is not available"))
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(unless mu4e-org-contacts-file
|
2019-04-15 06:38:58 +02:00
|
|
|
|
(mu4e-error "Variable `mu4e-org-contacts-file' is nil"))
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(let* ((sender (car-safe (mu4e-message-field msg :from)))
|
2022-07-28 06:29:31 +02:00
|
|
|
|
(name (mu4e-contact-name sender))
|
|
|
|
|
(email (mu4e-contact-email sender))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(blurb
|
|
|
|
|
(format
|
|
|
|
|
(concat
|
|
|
|
|
"* %%?%s\n"
|
|
|
|
|
":PROPERTIES:\n"
|
|
|
|
|
":EMAIL: %s\n"
|
|
|
|
|
":NICK:\n"
|
|
|
|
|
":BIRTHDAY:\n"
|
|
|
|
|
":END:\n\n")
|
|
|
|
|
(or name email "")
|
|
|
|
|
(or email "")))
|
|
|
|
|
(key "mu4e-add-org-contact-key")
|
|
|
|
|
(org-capture-templates
|
|
|
|
|
(append org-capture-templates
|
|
|
|
|
(list (list key "contacts" 'entry
|
|
|
|
|
(list 'file mu4e-org-contacts-file) blurb)))))
|
2012-06-10 11:21:41 +02:00
|
|
|
|
(when (fboundp 'org-capture)
|
|
|
|
|
(org-capture nil key))))
|
2012-05-12 16:10:57 +02:00
|
|
|
|
|
2020-02-18 22:39:30 +01:00
|
|
|
|
;;; Patches
|
2018-05-21 19:53:42 +02:00
|
|
|
|
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(defvar mu4e--patch-directory-history nil
|
2018-05-21 19:53:42 +02:00
|
|
|
|
"History of directories we have applied patches to.")
|
|
|
|
|
|
|
|
|
|
;; This essentially works around the fact that read-directory-name
|
|
|
|
|
;; can't have custom history.
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(defun mu4e--read-patch-directory (&optional prompt)
|
2018-05-21 19:53:42 +02:00
|
|
|
|
"Read a `PROMPT'ed directory name via `completing-read' with history."
|
|
|
|
|
(unless prompt
|
|
|
|
|
(setq prompt "Target directory:"))
|
|
|
|
|
(file-truename
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(completing-read prompt 'read-file-name-internal #'file-directory-p
|
2021-08-30 21:02:49 +02:00
|
|
|
|
nil nil 'mu4e--patch-directory-history)))
|
2018-05-21 19:53:42 +02:00
|
|
|
|
|
2012-10-29 11:26:17 +01:00
|
|
|
|
(defun mu4e-action-git-apply-patch (msg)
|
2018-05-21 19:53:42 +02:00
|
|
|
|
"Apply `MSG' as a git patch."
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(let ((path (mu4e--read-patch-directory "Target directory: ")))
|
2018-05-21 19:53:42 +02:00
|
|
|
|
(let ((default-directory path))
|
|
|
|
|
(shell-command
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(format "git apply %s"
|
|
|
|
|
(shell-quote-argument (mu4e-message-field msg :path)))))))
|
2013-10-25 13:09:26 +02:00
|
|
|
|
|
2017-06-26 11:08:35 +02:00
|
|
|
|
(defun mu4e-action-git-apply-mbox (msg &optional signoff)
|
2018-05-21 19:53:42 +02:00
|
|
|
|
"Apply `MSG' a git patch with optional `SIGNOFF'.
|
2015-08-31 16:07:10 +02:00
|
|
|
|
|
|
|
|
|
If the `default-directory' matches the most recent history entry don't
|
|
|
|
|
bother asking for the git tree again (useful for bulk actions)."
|
|
|
|
|
|
2018-05-21 19:53:42 +02:00
|
|
|
|
(let ((cwd (substring-no-properties
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(or (car mu4e--patch-directory-history)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
"not-a-dir"))))
|
2015-08-31 16:07:10 +02:00
|
|
|
|
(unless (and (stringp cwd) (string= default-directory cwd))
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(setq cwd (mu4e--read-patch-directory "Target directory: ")))
|
2018-05-21 19:53:42 +02:00
|
|
|
|
(let ((default-directory cwd))
|
|
|
|
|
(shell-command
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(format "git am %s %s"
|
|
|
|
|
(if signoff "--signoff" "")
|
|
|
|
|
(shell-quote-argument (mu4e-message-field msg :path)))))))
|
2013-10-25 13:09:26 +02:00
|
|
|
|
|
2020-02-18 22:39:30 +01:00
|
|
|
|
;;; Tagging
|
2012-10-29 11:26:17 +01:00
|
|
|
|
|
2012-12-06 19:14:05 +01:00
|
|
|
|
(defvar mu4e-action-tags-header "X-Keywords"
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Header where tags are stored.
|
|
|
|
|
Used by `mu4e-action-retag-message'. Make sure it is one of the
|
|
|
|
|
headers mu recognizes for storing tags: X-Keywords, X-Label,
|
|
|
|
|
Keywords. Also note that changing this setting on already tagged
|
|
|
|
|
messages can lead to messages with multiple tags headers.")
|
2012-12-06 19:14:05 +01:00
|
|
|
|
|
2014-10-30 14:08:14 +01:00
|
|
|
|
(defvar mu4e-action-tags-completion-list '()
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"List of tags for completion in `mu4e-action-retag-message'.")
|
2014-10-30 14:08:14 +01:00
|
|
|
|
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(defun mu4e--contains-line-matching (regexp path)
|
2020-02-13 09:28:15 +01:00
|
|
|
|
"Return non-nil if the file at PATH contain a line matching REGEXP.
|
|
|
|
|
Otherwise return nil."
|
2013-03-11 22:36:36 +01:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert-file-contents path)
|
2013-03-17 18:45:16 +01:00
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (point-min))
|
2020-02-13 09:28:15 +01:00
|
|
|
|
(re-search-forward regexp nil t))))
|
2013-03-11 22:36:36 +01:00
|
|
|
|
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(defun mu4e--replace-first-line-matching (regexp to-string path)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Replace first line matching REGEXP in PATH with TO-STRING."
|
2013-03-11 22:36:36 +01:00
|
|
|
|
(with-temp-file path
|
|
|
|
|
(insert-file-contents path)
|
2013-03-17 18:45:16 +01:00
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (point-min))
|
2013-03-17 10:58:08 +01:00
|
|
|
|
(if (re-search-forward regexp nil t)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(replace-match to-string nil nil)))))
|
2013-03-11 22:36:36 +01:00
|
|
|
|
|
2021-08-29 18:53:53 +02:00
|
|
|
|
(declare-function mu4e--server-add "mu4e-server")
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(defun mu4e--refresh-message (path)
|
|
|
|
|
"Re-parse message at PATH.
|
|
|
|
|
if this works, we will
|
|
|
|
|
receive (:info add :path <path> :docid <docid>) as well as (:update
|
|
|
|
|
<msg-sexp>)."
|
2021-08-29 18:53:53 +02:00
|
|
|
|
(mu4e--server-add path))
|
2021-08-29 16:30:10 +02:00
|
|
|
|
|
2012-12-15 11:06:32 +01:00
|
|
|
|
(defun mu4e-action-retag-message (msg &optional retag-arg)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Change tags of MSG with RETAG-ARG.
|
2014-10-30 14:08:14 +01:00
|
|
|
|
|
2019-04-15 06:38:58 +02:00
|
|
|
|
RETAG-ARG is a comma-separated list of additions and removals.
|
2014-10-30 14:08:14 +01:00
|
|
|
|
|
2019-04-15 06:38:58 +02:00
|
|
|
|
Example: +tag,+long tag,-oldtag
|
2022-05-27 20:00:37 +02:00
|
|
|
|
would add \"tag\" and \"long tag\", and remove \"oldtag\"."
|
2014-10-30 14:08:14 +01:00
|
|
|
|
(let* (
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(path (mu4e-message-field msg :path))
|
|
|
|
|
(oldtags (mu4e-message-field msg :tags))
|
|
|
|
|
(tags-completion
|
|
|
|
|
(append
|
|
|
|
|
mu4e-action-tags-completion-list
|
|
|
|
|
(mapcar (lambda (tag) (format "+%s" tag))
|
|
|
|
|
mu4e-action-tags-completion-list)
|
|
|
|
|
(mapcar (lambda (tag) (format "-%s" tag))
|
|
|
|
|
oldtags)))
|
|
|
|
|
(retag (if retag-arg
|
|
|
|
|
(split-string retag-arg ",")
|
|
|
|
|
(completing-read-multiple "Tags: " tags-completion)))
|
|
|
|
|
(header mu4e-action-tags-header)
|
|
|
|
|
(sep (cond ((string= header "Keywords") ", ")
|
|
|
|
|
((string= header "X-Label") " ")
|
|
|
|
|
((string= header "X-Keywords") ", ")
|
|
|
|
|
(t ", ")))
|
|
|
|
|
(taglist (if oldtags (copy-sequence oldtags) '()))
|
|
|
|
|
tagstr)
|
2014-10-30 14:08:14 +01:00
|
|
|
|
(dolist (tag retag taglist)
|
2013-03-16 16:58:17 +01:00
|
|
|
|
(cond
|
2020-02-11 12:00:46 +01:00
|
|
|
|
((string-match "^\\+\\(.+\\)" tag)
|
|
|
|
|
(setq taglist (push (match-string 1 tag) taglist)))
|
|
|
|
|
((string-match "^\\-\\(.+\\)" tag)
|
|
|
|
|
(setq taglist (delete (match-string 1 tag) taglist)))
|
|
|
|
|
(t
|
|
|
|
|
(setq taglist (push tag taglist)))))
|
2013-03-17 18:45:16 +01:00
|
|
|
|
|
2012-12-06 19:14:05 +01:00
|
|
|
|
(setq taglist (sort (delete-dups taglist) 'string<))
|
|
|
|
|
(setq tagstr (mapconcat 'identity taglist sep))
|
2013-03-17 18:45:16 +01:00
|
|
|
|
|
2013-03-11 22:36:36 +01:00
|
|
|
|
(setq tagstr (replace-regexp-in-string "[\\&]" "\\\\\\&" tagstr))
|
|
|
|
|
(setq tagstr (replace-regexp-in-string "[/]" "\\&" tagstr))
|
2013-03-17 18:45:16 +01:00
|
|
|
|
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(if (not (mu4e--contains-line-matching (concat header ":.*") path))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
;; Add tags header just before the content
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(mu4e--replace-first-line-matching
|
2020-02-11 12:00:46 +01:00
|
|
|
|
"^$" (concat header ": " tagstr "\n") path)
|
2013-03-17 18:45:16 +01:00
|
|
|
|
|
2013-03-11 22:36:36 +01:00
|
|
|
|
;; replaces keywords, restricted to the header
|
2021-08-30 21:02:49 +02:00
|
|
|
|
(mu4e--replace-first-line-matching
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(concat header ":.*")
|
|
|
|
|
(concat header ": " tagstr)
|
|
|
|
|
path))
|
2013-03-17 18:45:16 +01:00
|
|
|
|
|
2012-12-15 11:06:32 +01:00
|
|
|
|
(mu4e-message (concat "tagging: " (mapconcat 'identity taglist ", ")))
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(mu4e--refresh-message path)))
|
2015-11-07 06:36:31 +01:00
|
|
|
|
|
|
|
|
|
(defun mu4e-action-show-thread (msg)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
"Show thread for message at point with point remaining on MSG.
|
|
|
|
|
I.e., point remains on the message with the message-id where the
|
|
|
|
|
action was invoked. If invoked in view mode, continue to display
|
|
|
|
|
the message."
|
2015-11-07 06:36:31 +01:00
|
|
|
|
(let ((msgid (mu4e-message-field msg :message-id)))
|
|
|
|
|
(when msgid
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(let ((mu4e-search-threads t)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(mu4e-headers-include-related t))
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(mu4e-search
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(format "msgid:%s" msgid)
|
|
|
|
|
nil nil nil
|
|
|
|
|
msgid (and (eq major-mode 'mu4e-view-mode)
|
|
|
|
|
(not (eq mu4e-split-view 'single-window))))))))
|
2020-02-11 13:26:45 +01:00
|
|
|
|
;;; _
|
2012-04-24 17:11:56 +02:00
|
|
|
|
(provide 'mu4e-actions)
|
2019-04-15 06:38:58 +02:00
|
|
|
|
;;; mu4e-actions.el ends here
|