Merge pull request #1390 from Ambrevar/easy-accounts

mu4e: Easy accounts with make-mu4e-context-account
This commit is contained in:
Dirk-Jan C. Binnema 2019-04-17 22:59:30 +03:00 committed by GitHub
commit 2ec1b46076
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 223 additions and 6 deletions

View File

@ -22,7 +22,7 @@
;;; Commentary: ;;; 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. ;; used e.g. to switch between accounts.
(require 'cl-lib) (require 'cl-lib)
(require 'mu4e-utils) (require 'mu4e-utils)
@ -69,13 +69,129 @@ none."
that takes a message plist for the message replied to or that takes a message plist for the message replied to or
forwarded, and nil otherwise. Before composing a new message, forwarded, and nil otherwise. Before composing a new message,
`mu4e' switches to the first context for which `match-func' `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" name ;; name of the context, e.g. "work"
(enter-func nil) ;; function invoked when entering the context (enter-func nil) ;; function invoked when entering the context
(leave-func nil) ;; function invoked when leaving the context (leave-func nil) ;; function invoked when leaving the context
(match-func nil) ;; function that takes a msg-proplist, and return t (match-func nil) ;; function that takes a msg-proplist, and return t
;; if it matches, nil otherwise ;; 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) (defun mu4e~context-ask-user (prompt)
"Let user choose some context based on its name." "Let user choose some context based on its name."

View File

@ -742,6 +742,29 @@ after the end of the search results."
(mu4e~headers-defun-mark-for unread) (mu4e~headers-defun-mark-for unread)
(mu4e~headers-defun-mark-for action) (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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; headers-mode and mode-map ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar mu4e-headers-mode-map nil (defvar mu4e-headers-mode-map nil
"Keymap for *mu4e-headers* buffers.") "Keymap for *mu4e-headers* buffers.")
@ -801,8 +824,8 @@ after the end of the search results."
(define-key map "y" 'mu4e-select-other-view) (define-key map "y" 'mu4e-select-other-view)
;; marking/unmarking ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; marking/unmarking ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-key map (kbd "<backspace>") 'mu4e-headers-mark-for-trash) (define-key map (kbd "<backspace>") 'mu4e-headers-mark-or-move-to-trash)
(define-key map (kbd "d") 'mu4e-headers-mark-for-trash) (define-key map (kbd "d") 'mu4e-headers-mark-or-move-to-trash)
(define-key map (kbd "<delete>") 'mu4e-headers-mark-for-delete) (define-key map (kbd "<delete>") 'mu4e-headers-mark-for-delete)
(define-key map (kbd "<deletechar>") 'mu4e-headers-mark-for-delete) (define-key map (kbd "<deletechar>") 'mu4e-headers-mark-for-delete)
(define-key map (kbd "D") 'mu4e-headers-mark-for-delete) (define-key map (kbd "D") 'mu4e-headers-mark-for-delete)

View File

@ -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)) (define-key map "A" (if mu4e-view-use-gnus 'ignore 'mu4e-view-attachment-action))
;; marking/unmarking ;; 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 "<delete>") 'mu4e-view-mark-for-delete) (define-key map (kbd "<delete>") 'mu4e-view-mark-for-delete)
(define-key map (kbd "<deletechar>") 'mu4e-view-mark-for-delete) (define-key map (kbd "<deletechar>") 'mu4e-view-mark-for-delete)
(define-key map (kbd "D") '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~view-in-headers-context
(mu4e-mark-execute-all))) (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 ;; URL handling
(defun mu4e~view-get-urls-num (prompt &optional multi) (defun mu4e~view-get-urls-num (prompt &optional multi)

View File

@ -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 * Marking messages::Selecting message do something with them
* What to mark for::What can we do with them * What to mark for::What can we do with them
* Executing the marks::Do it * Executing the marks::Do it
* Trashing messages::Exceptions for mailboxes like Gmail
* Leaving the headers buffer::Handling marks automatically when leaving * Leaving the headers buffer::Handling marks automatically when leaving
* Built-in marking functions::Helper functions for dealing with them * Built-in marking functions::Helper functions for dealing with them
* Custom mark functions::Define your own mark function * 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 right before execution of each mark. The hook is called with two
arguments, the mark and the message itself. 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 @node Leaving the headers buffer
@section Leaving the headers buffer @section Leaving the headers buffer
@ -2415,6 +2439,7 @@ example:
* Context policies::How to determine the current context * Context policies::How to determine the current context
* Contexts and special folders::Using context variables to determine them * Contexts and special folders::Using context variables to determine them
* Contexts example::How to define contexts * Contexts example::How to define contexts
* Account setup helper::Easy context creation with sane defaults
* Some context tricks::Other thing to do with contexts * Some context tricks::Other thing to do with contexts
@end menu @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. and commas and note the '.' between variable name and its value.
@end itemize @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 @node Some context tricks
@section Some context tricks @section Some context tricks