diff --git a/mu4e/mu4e-context.el b/mu4e/mu4e-context.el index 43688770..eaafdd83 100644 --- a/mu4e/mu4e-context.el +++ b/mu4e/mu4e-context.el @@ -22,7 +22,7 @@ ;;; Commentary: -;; A mu4e 'context' is a a set of variable-settings and functions, which can be +;; A mu4e 'context' is a set of variable-settings and functions, which can be ;; used e.g. to switch between accounts. (require 'cl-lib) (require 'mu4e-utils) @@ -69,13 +69,129 @@ none." that takes a message plist for the message replied to or forwarded, and nil otherwise. Before composing a new message, `mu4e' switches to the first context for which `match-func' - returns t." + returns t. +- `vars': variables to set when entering context." name ;; name of the context, e.g. "work" (enter-func nil) ;; function invoked when entering the context (leave-func nil) ;; function invoked when leaving the context (match-func nil) ;; function that takes a msg-proplist, and return t ;; if it matches, nil otherwise - vars) ;; alist of variables. + vars) ;; alist of variables. + +(defvar mu4e-no-trash-providers '("gmail.com" "googlemail.com") + "List of email providers that don't support the trash flag.") + +(cl-defun make-mu4e-context-account (name &key + enter-func + leave-func + match-func + vars + ;; We set sane defaults for the following variables. They will be added to + ;; the context vars. + (user-mail-address user-mail-address) + (smtpmail-smtp-user smtpmail-smtp-user) + ;; Folders: + maildir + (drafts-folder "drafts") + (sent-folder "sent") + (trash-folder "trash") + (refile-folder "archive") + ;; Trash fix. + no-trash-flag + ;; Rule for matching the context. + predicate) + "Create a context with sane defaults (see `make-mu4e-context'). +Also: +- Add the context to the `mu4e-contexts'. +- Update the bookmarks to ignore the trash folder if NO-TRASH-FLAG is non-nil. +- Update the `mu4e-user-mail-address-list'. + +Options beyond those of `make-mu4e-context': +- `user-mail-address': Defaults to the global value when the context is created. +- `smtpmail-smtp-user': Defaults to the global value if non-nil when the context + is created, or the context `user-mail-address' otherwise. +- `maildir': Mailbox folder name in as stored in `mu4e-maildir' (just the name, + there must be no '/'). Defaults to `name'. +- `drafts-folder': Context value of `mu4e-drafts-folder'. Defaults to + \"drafts\". +- `sent-folder': Context value of `mu4e-sent-folder'. Defaults to \"sent\". +- `trash-folder': Context value of `mu4e-trash-folder'. Defaults to \"trash\". +- `refile-folder': Context value of `mu4e-refile-folder'. Defaults to + \"refile\". +- `no-trash-flag': If non-nil, the maildir will be added to + `mu4e-move-to-trash-patterns' so that trashing moves the message instead of flagging. +- `predicate': A function that takes a message and returns non-nil if it matches + the context. This is only used if `match-func' is not provided, in which case + the context is always matched against the message folder. + +Example of a mailbox where only the sent-folder differs from the +default folders (see `make-mu4e-context' and `mu4e-context'): + + (let ((gandi-smtp-vars '((smtpmail-smtp-server . \"mail.gandi.net\") + (smtpmail-stream-type . starttls) + (smtpmail-smtp-service . 587)))) + (make-mu4e-context-account + :name \"personal\" + :user-mail-address \"john@doe.xyz\" + :sent-folder \"Sent\" + :vars gandi-smtp-vars) + (make-mu4e-context-account + :name \"work\" + :user-mail-address \"john@work.org\" + :sent-folder \"Sent\" + :predicate (lambda (msg) + (mu4e-message-contact-field-matches + msg '(:from :to) \"boss@work.org\")) + :vars gandi-smtp-vars))" + (cl-assert name) + (setq maildir (concat "/" (or maildir name) "/") + smtpmail-smtp-user (or smtpmail-smtp-user user-mail-address) + no-trash-flag (or no-trash-flag + (string-match (regexp-opt mu4e-no-trash-providers) + user-mail-address))) + (when no-trash-flag + ;; Exclude trash folder from all bookmarks. This is useful for mailboxes + ;; which don't use the "trash" flag like Gmail. + (dolist (bookmark mu4e-bookmarks) + ;; TODO: mu4e-bookmark-query does not work here, why? + (setf (car bookmark) (format "NOT maildir:\"%s\" and %s" + mu4e-trash-folder + (car bookmark)))) + ;; If this is a Gmail context, we add the maildir to the pattern list so + ;; that they can be properly trashed. + (add-to-list 'mu4e-move-to-trash-patterns (concat "^" maildir))) + ;; TODO: Seems that mu4e fails to start when no default folder is set. + ;; The following setq is a workaround. + (setq mu4e-drafts-folder (concat maildir drafts-folder) + mu4e-sent-folder (concat maildir sent-folder) + mu4e-trash-folder (concat maildir trash-folder) + mu4e-refile-folder (concat maildir refile-folder)) + (let ((context (make-mu4e-context :name name + :enter-func enter-func + :leave-func leave-func + :match-func match-func + :vars vars))) + (unless (mu4e-context-match-func context) + (setf (mu4e-context-match-func context) + `(lambda (msg) + (when msg + (or + ,(when predicate + `(funcall ,predicate msg)) + (string-prefix-p ,maildir (mu4e-message-field msg :maildir))))))) + (setf (mu4e-context-vars context) + (append `((user-mail-address . ,user-mail-address) + (smtpmail-smtp-user . ,smtpmail-smtp-user) + (mu4e-drafts-folder . ,mu4e-drafts-folder) + (mu4e-sent-folder . ,mu4e-sent-folder) + (mu4e-trash-folder . ,mu4e-trash-folder) + (mu4e-refile-folder . ,mu4e-refile-folder)) + (mu4e-context-vars context))) + ;; Required when using multiple addresses and if we don't want to + ;; reply to ourselves. + (add-to-list 'mu4e-user-mail-address-list user-mail-address) + (add-to-list 'mu4e-contexts context) + context)) (defun mu4e~context-ask-user (prompt) "Let user choose some context based on its name." diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index e6247df5..3ecaacfb 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -742,6 +742,29 @@ after the end of the search results." (mu4e~headers-defun-mark-for unread) (mu4e~headers-defun-mark-for action) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e-move-to-trash-patterns '() + "List of regexps to match for moving to trash instead of flagging them. +This is particularly useful for mailboxes that don't use the +trash flag like Gmail. See `mu4e-headers-mark-or-move-to-trash' +and `mu4e-view-mark-or-move-to-trash'.") + +(defun mu4e-headers-mark-or-move-to-trash () + "Mark message for \"move\" to the trash folder if the message +maildir matches any regexp in `mu4e-move-to-trash-patterns'. +Otherwise mark with the \"trash\" flag. +Also see `mu4e-view-mark-or-move-to-trash'." + (interactive) + (let ((msg-dir (mu4e-message-field (mu4e-message-at-point) :maildir))) + (if (not (seq-filter (lambda (re) + (string-match re msg-dir)) + mu4e-move-to-trash-patterns)) + (mu4e-headers-mark-for-trash) + (mu4e-mark-set 'move (if (functionp mu4e-trash-folder) + (funcall mu4e-trash-folder (mu4e-message-at-point)) + mu4e-trash-folder)) + (mu4e-headers-next)))) + ;;; headers-mode and mode-map ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar mu4e-headers-mode-map nil "Keymap for *mu4e-headers* buffers.") @@ -801,8 +824,8 @@ after the end of the search results." (define-key map "y" 'mu4e-select-other-view) ;; marking/unmarking ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (define-key map (kbd "") 'mu4e-headers-mark-for-trash) - (define-key map (kbd "d") 'mu4e-headers-mark-for-trash) + (define-key map (kbd "") 'mu4e-headers-mark-or-move-to-trash) + (define-key map (kbd "d") 'mu4e-headers-mark-or-move-to-trash) (define-key map (kbd "") 'mu4e-headers-mark-for-delete) (define-key map (kbd "") 'mu4e-headers-mark-for-delete) (define-key map (kbd "D") 'mu4e-headers-mark-for-delete) diff --git a/mu4e/mu4e-view.el b/mu4e/mu4e-view.el index 4e8378d7..5a12fee9 100644 --- a/mu4e/mu4e-view.el +++ b/mu4e/mu4e-view.el @@ -743,7 +743,7 @@ FUNC should be a function taking two arguments: (define-key map "A" (if mu4e-view-use-gnus 'ignore 'mu4e-view-attachment-action)) ;; marking/unmarking - (define-key map "d" 'mu4e-view-mark-for-trash) + (define-key map "d" 'mu4e-view-mark-or-move-to-trash) (define-key map (kbd "") 'mu4e-view-mark-for-delete) (define-key map (kbd "") 'mu4e-view-mark-for-delete) (define-key map (kbd "D") 'mu4e-view-mark-for-delete) @@ -1487,6 +1487,13 @@ list." (mu4e~view-in-headers-context (mu4e-mark-execute-all))) +(defun mu4e-view-mark-or-move-to-trash (&optional n) + "See `mu4e-headers-mark-or-move-to-trash'." + (interactive "P") + (mu4e~view-in-headers-context + (mu4e-headers-mark-or-move-to-trash) + (mu4e~headers-move (or n 1)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; URL handling (defun mu4e~view-get-urls-num (prompt &optional multi) diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index 0bc5ab24..4115683d 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -2183,6 +2183,7 @@ can happen in both the @ref{Headers view} and the @ref{Message view}. * Marking messages::Selecting message do something with them * What to mark for::What can we do with them * Executing the marks::Do it +* Trashing messages::Exceptions for mailboxes like Gmail * Leaving the headers buffer::Handling marks automatically when leaving * Built-in marking functions::Helper functions for dealing with them * Custom mark functions::Define your own mark function @@ -2258,6 +2259,29 @@ A hook, @code{mu4e-mark-execute-pre-hook}, is available which is run right before execution of each mark. The hook is called with two arguments, the mark and the message itself. +@node Trashing messages +@section Trashing messages + +For regular mailboxes, trashing works like other marks: when executed, +the message is flagged as trashed. Depending on your mailbox provider, +the trash flag is used to automatically move the message to the trash +folder (@code{mu4e-trash-folder}) for instance. + +Some mailboxes behave differently however and they don't interpret the +trash flag. In cases like Gmail, the message must be @emph{moved} to +the trash folder and the trash flag must not be used. + +@code{mu4e} has provisions for non-standard mailboxes: if a message +maildir matches a regular expression in +@code{mu4e-move-to-trash-patterns} then the message is moved instead of +being flagged. When a context is created with +@code{make-mu4e-context-account} (see @ref{Account setup helper}), the +pattern is automatically added for you. + +This should work fine for Gmail and similar mailboxes. Note that in the +case of Gmail, you might have to configure your mailbox ``expunge'' +settings. + @node Leaving the headers buffer @section Leaving the headers buffer @@ -2415,6 +2439,7 @@ example: * Context policies::How to determine the current context * Contexts and special folders::Using context variables to determine them * Contexts example::How to define contexts +* Account setup helper::Easy context creation with sane defaults * Some context tricks::Other thing to do with contexts @end menu @@ -2626,6 +2651,52 @@ no context matches (or if you always want to be asked). and commas and note the '.' between variable name and its value. @end itemize +@node Account setup helper +@section Account setup helper + +Contexts can be cumbersome to set up. Thankfully @code{mu4e} provides a +helper function @code{make-mu4e-context-account} to easily get started. +The function helps initializing the context plus a couple of variables +with sane defaults. +Everything should work out of the box in most cases. + +A short example for two contexts: + +@lisp +(let ((gandi-smtp-vars '((smtpmail-smtp-server . "mail.gandi.net") + (smtpmail-stream-type . starttls) + (smtpmail-smtp-service . 587)))) + (make-mu4e-context-account + :name "personal" + :user-mail-address "john@doe.xyz" + :sent-folder "Sent" + :vars gandi-smtp-vars) + (make-mu4e-context-account + :name "work" + :user-mail-address "john@work.org" + :sent-folder "Sent" + :predicate (lambda (msg) + (mu4e-message-contact-field-matches + msg '(:from :to) "boss@work.org")) + :vars gandi-smtp-vars)) +@end lisp + +A couple of things to note: + +@itemize +@item Only the @code{name} slot is mandatory. +@item The maildir default to the context name. +@item Folders only need to be given a name, not a relative path. +They will be automatically stored under the maildir. +@item When the @code{match-func} is not provided, the context is matched +against @code{predicate} if provided or the maildir of the current +message otherwise. +@end itemize + +If the context created by @code{make-mu4e-context-account} is not +enough, you can display the generated context with e.g. @code{M-x +describe-variable mu4e-contexts} and tweak the result as needed. + @node Some context tricks @section Some context tricks