mirror of https://github.com/djcb/mu.git
Merge pull request #1390 from Ambrevar/easy-accounts
mu4e: Easy accounts with make-mu4e-context-account
This commit is contained in:
commit
2ec1b46076
|
@ -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."
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue