mu4e: implement context-policies

Allow setting a policy about what context to choose when starting mu4e
and composing a message. Basically:

When you have defined contexts and you start mu4e it decides which
context to use based on the variable `mu4e-context-policy';
similarly, when you compose a new message, the context is determined
using `mu4e-compose-context-policy'.

These policies can be one of the following:
- a symbol always-ask: unconditionally ask the user what context to pick

The other choices only apply if none of the context matches (i.e., if
none of the contexts' match-functions returns t:

- symbol ask: ask the user
- a symbol pick-first: pick the first context
- nil: don't change the context
This commit is contained in:
djcb 2015-12-23 22:01:51 +02:00
parent 4bca0d0739
commit 113c024632
5 changed files with 113 additions and 51 deletions

View File

@ -112,12 +112,34 @@ for example:
The various `message-' functions from `message-mode' are available The various `message-' functions from `message-mode' are available
for querying the message information." for querying the message information."
:type '(choice (const :tag "move message to mu4e-sent-folder" sent) :type '(choice (const :tag "move message to mu4e-sent-folder" sent)
(const :tag "move message to mu4e-trash-folder" trash) (const :tag "move message to mu4e-trash-folder" trash)
(const :tag "delete message" delete)) (const :tag "delete message" delete))
:safe 'symbolp :safe 'symbolp
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-context-policy nil
"Determines how mu4e should determine the context when composing a new message.
If POLICY is
'always-ask, we ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is returned. If none of the contexts
match, POLICY determines what to do:
- pick-first: pick the first of the contexts available
- ask: ask the user
- otherwise, return nil. Effectively, this leaves the current context in place."
:type '(choice
(const :tag "Always ask what context to use" 'always-ask)
(const :tag "Ask if none of the contexts match" 'ask)
(const :tag "Pick the default (first) context if none match" 'pick-first)
(const :tag "Don't change the context when none match" nil)
:safe 'symbolp
:group 'mu4e-compose))
(defcustom mu4e-compose-pre-hook nil (defcustom mu4e-compose-pre-hook nil
"Hook run just *before* message composition starts. "Hook run just *before* message composition starts.
If the compose-type is either 'reply' or 'forward', the variable If the compose-type is either 'reply' or 'forward', the variable
@ -370,10 +392,9 @@ tempfile)."
;; message being forwarded or replied to, otherwise it is nil. ;; message being forwarded or replied to, otherwise it is nil.
(set (make-local-variable 'mu4e-compose-parent-message) original-msg) (set (make-local-variable 'mu4e-compose-parent-message) original-msg)
(put 'mu4e-compose-parent-message 'permanent-local t) (put 'mu4e-compose-parent-message 'permanent-local t)
(let ((context (mu4e-context-determine mu4e-compose-parent-message))) ;; maybe switch the context
(if context (mu4e~context-autoswitch mu4e-compose-parent-message
(mu4e-context-switch nil (mu4e-context-name context)) mu4e-compose-context-policy)
(when mu4e-contexts (mu4e-context-switch nil))))
(run-hooks 'mu4e-compose-pre-hook) (run-hooks 'mu4e-compose-pre-hook)
;; this opens (or re-opens) a messages with all the basic headers set. ;; this opens (or re-opens) a messages with all the basic headers set.

View File

@ -100,13 +100,13 @@ non-nil."
(mu4e-message "Switched context to %s" (mu4e-context-name context))) (mu4e-message "Switched context to %s" (mu4e-context-name context)))
context)) context))
(defun mu4e-context-autoselect () (defun mu4e~context-autoswitch (&optional msg policy)
"When contexts are defined but there is no context yet, switch "When contexts are defined but there is no context yet, switch
to the first whose :match-func return non-nil. If none of them to the first whose :match-func return non-nil. If none of them
match, return the first." match, return the first. For MSG and POLICY, see `mu4e-context-determine'."
(when (and mu4e-contexts (not (mu4e-context-current))) (when mu4e-contexts
(mu4e-context-switch (let ((context (mu4e-context-determine msg policy)))
(mu4e-context-name (mu4e-context-determine nil 'pick-first))))) (when context (mu4e-context-switch (mu4e-context-name context))))))
(defun mu4e-context-determine (msg &optional policy) (defun mu4e-context-determine (msg &optional policy)
"Return the first context with a match-func that returns t. MSG "Return the first context with a match-func that returns t. MSG
@ -114,20 +114,27 @@ points to the plist for the message replied to or forwarded, or
nil if there is no such MSG; similar to what nil if there is no such MSG; similar to what
`mu4e-compose-pre-hook' does. `mu4e-compose-pre-hook' does.
POLICY determines what to do if there are contexts but none match. The following POLICY specifies how to do the determination. If POLICY is
are supported: 'always-ask, we ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is returned. If none of the contexts
match, POLICY determines what to do:
- pick-first: pick the first of the contexts available - pick-first: pick the first of the contexts available
- ask: ask the user - ask: ask the user
- otherwise, return nil. Effectively, this leaves the current context in place." - otherwise, return nil. Effectively, this leaves the current context in place."
(when mu4e-contexts (when mu4e-contexts
(or (find-if (lambda (context) (if (eq policy 'always-ask)
(and (mu4e-context-match-func context) (mu4e~context-ask-user "Select context: ")
(funcall (mu4e-context-match-func context) msg))) mu4e-contexts) (or (find-if (lambda (context)
;; no context found (and (mu4e-context-match-func context)
(case policy (funcall (mu4e-context-match-func context) msg))) mu4e-contexts)
(pick-first (car mu4e-contexts)) ;; no context found
(ask (mu4e~context-ask-user "Select context: ")) (case policy
(otherwise nil))))) (pick-first (car mu4e-contexts))
(ask (mu4e~context-ask-user "Select context: "))
(otherwise nil))))))
(provide 'mu4e-context) (provide 'mu4e-context)

View File

@ -46,7 +46,7 @@
(declare-function mu4e~proc-mkdir "mu4e-proc") (declare-function mu4e~proc-mkdir "mu4e-proc")
(declare-function mu4e~proc-running-p "mu4e-proc") (declare-function mu4e~proc-running-p "mu4e-proc")
(declare-function mu4e-context-autoselect "mu4e-context") (declare-function mu4e~context-autoswitch "mu4e-context")
(declare-function show-all "org") (declare-function show-all "org")
@ -737,9 +737,9 @@ first.
If mu4e is already running, execute function FUNC (if non-nil). If mu4e is already running, execute function FUNC (if non-nil).
Otherwise, check various requirements, then start mu4e. When Otherwise, check various requirements, then start mu4e. When
successful, call FUNC (if non-nil) afterwards." successful, call FUNC (if non-nil) afterwards."
;; maybe switch the context
;; auto-select some account (mu4e~context-autoswitch mu4e-compose-parent-message
(mu4e-context-autoselect) mu4e-compose-context-policy)
;; if we're already running, simply go to the main view ;; if we're already running, simply go to the main view
(if (mu4e-running-p) ;; already running? (if (mu4e-running-p) ;; already running?
(when func ;; yes! run func if defined (when func ;; yes! run func if defined

View File

@ -114,7 +114,6 @@ better with e.g. offlineimap."
:group 'mu4e :group 'mu4e
:safe 'booleanp) :safe 'booleanp)
(defcustom mu4e-attachment-dir (expand-file-name "~/") (defcustom mu4e-attachment-dir (expand-file-name "~/")
"Default directory for saving attachments. "Default directory for saving attachments.
This can be either a string (a file system path), or a function This can be either a string (a file system path), or a function
@ -218,6 +217,29 @@ Suggested possible values are:
:options '(completing-read ido-completing-read) :options '(completing-read ido-completing-read)
:group 'mu4e) :group 'mu4e)
(defcustom mu4e-context-policy 'ask
"Determines how mu4e should determine the context when starting up.
If POLICY is 'always-ask, we ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is returned. If none of the contexts
match, POLICY determines what to do:
- pick-first: pick the first of the contexts available
- ask: ask the user
- otherwise, return nil. Effectively, this leaves the current context in place.
Also see `mu4e-compose-context-policy'."
:type '(choice
(const :tag "Always ask what context to use" 'always-ask)
(const :tag "Ask if none of the contexts match" 'ask)
(const :tag "Pick the default (first) context if none match" 'pick-first)
(const :tag "Don't change the context when none match" nil)
:safe 'symbolp
:group 'mu4e))
;; crypto ;; crypto
(defgroup mu4e-crypto nil (defgroup mu4e-crypto nil
"Crypto-related settings." "Crypto-related settings."

View File

@ -37,7 +37,7 @@ Documentation License.''
@dircategory Emacs @dircategory Emacs
@direntry @direntry
* mu4e: (mu4e). An email client for Emacs. * mu4e: (Mu4e). An email client for GNU/Emacs.
@end direntry @end direntry
@contents @contents
@ -1990,8 +1990,8 @@ also match this extra search pattern. @key{\} takes you back to the previous
query, so, effectively 'widens' the search. Technically, narrowing the results query, so, effectively 'widens' the search. Technically, narrowing the results
of query @t{x} with expression @t{y} implies doing a search @t{(x) AND y}. of query @t{x} with expression @t{y} implies doing a search @t{(x) AND y}.
Note, messages that were not in your in your original search results because Note that messages that were not in your original search results because
of @code{mu4e-headers-results-limit}, may show up in the narrowed query. of @code{mu4e-headers-results-limit} may show up in the narrowed query.
@subsection Including related messages @subsection Including related messages
@anchor{Including related messages} @anchor{Including related messages}
@ -2256,16 +2256,15 @@ example:
@menu @menu
* Defining a context:: * Defining a context::
* Default context:: * Context policies::
* Contexts example:: * Contexts example::
* Contexts notes::
* Some context tricks:: * Some context tricks::
@end menu @end menu
It can be useful to be able to switch between different sets of settings It can be useful to be able to switch between different sets of settings
in @t{mu4e}; typical examples include the case where you have different in @t{mu4e}; typical examples include the case where you have different
e-mail accounts for private and work email, each with their own settings e-mail accounts for private and work email, each with their own settings
for e-mail addresses, mailservers etc. for folders, e-mail addresses, mailservers etc.
The @code{mu4e-context} system is a @t{mu4e}-specific mechanism to allow The @code{mu4e-context} system is a @t{mu4e}-specific mechanism to allow
for that; users can be define different contexts, and either manually for that; users can be define different contexts, and either manually
@ -2307,19 +2306,34 @@ an alist of variable settings for this account.
@t{mu4e} uses a variable @code{mu4e-contexts}, which is a list of such @t{mu4e} uses a variable @code{mu4e-contexts}, which is a list of such
objects. objects.
@node Default context @node Context policies
@section Default context @section Context policies
When you have defined contexts and you start @t{mu4e}, it automatically When you have defined contexts and you start @t{mu4e} it decides which
switches to the first context whose @code{match-func} returns context to use based on the variable @code{mu4e-context-policy};
non-nil. If none of them do, it picks the first. So, put your 'default' similarly, when you compose a new message, the context is determined
context as the first in @code{mu4e-contexts}. using @code{mu4e-compose-context-policy}.
These policies can be one of the following:
@itemize
@item a symbol @t{always-ask}: unconditionally ask the user what context to pick
@end itemize
The other choices only apply if none of the context matches (i.e., if
none of the contexts' match-functions returns @code{t}:
@itemize
@item a symbol @t{ask}: ask the user
@item a symbol @t{pick-first}: pick the first context
@item @t{nil}: don't change the context
@end itemize
@node Contexts example @node Contexts example
@section Example @section Example
Let's look at an example; we define two contexts, 'Private' and 'Work' Let's explain how contexts work by looking at an example. We define two
for a fictional user Alice Derleth. contexts, 'Private' and 'Work' for a fictional user @emph{Alice
Derleth}.
Note that in this case, we automatically switch to the first context Note that in this case, we automatically switch to the first context
when starting; see the discussion in the previous section. when starting; see the discussion in the previous section.
@ -2354,16 +2368,13 @@ when starting; see the discussion in the previous section.
"Miskatonic University, Dept. of Occult Sciences\n")))))) "Miskatonic University, Dept. of Occult Sciences\n"))))))
@end lisp @end lisp
@node Contexts notes A couple of notes about this example:
@section Context notes
Couple of notes:
@itemize @itemize
@item You can manually switch the focus use @code{M-x mu4e-context-switch}, by default bound to @kbd{;} in headers, view and main mode. @item You can manually switch the focus use @code{M-x mu4e-context-switch}, by default bound to @kbd{;} in headers, view and main mode.
The current focus appears in the mode-line. The current focus appears in the mode-line.
@item Normally, @code{M-x mu4e-context-switch} does not call the enter/leave functions if the 'new' context is the same as the old one. @item Normally, @code{M-x mu4e-context-switch} does not call the enter or leave functions if the 'new' context is the same as the old one.
However, with a prefix-argument (@kbd{C-u}), you can force @t{mu4e} to call However, with a prefix-argument (@kbd{C-u}), you can force @t{mu4e} to
those function even in that case. invoke those function even in that case.
@item The function @code{mu4e-context-current} returns the current-context; the current context is also visiable in the mode-line when in @item The function @code{mu4e-context-current} returns the current-context; the current context is also visiable in the mode-line when in
headers, view or main mode. headers, view or main mode.
@item You can set any kind of variable; including settings for mail servers etc. However, settings like @code{mu4e-maildir} @item You can set any kind of variable; including settings for mail servers etc. However, settings like @code{mu4e-maildir}
@ -2378,13 +2389,13 @@ context we are entering.
@node Some context tricks @node Some context tricks
@section Some context tricks @section Some context tricks
It is possible to automatically fill @code{mu4e-user-address-list} by It is possible to automatically fill @code{mu4e-user-address-list} by
concatenating the @code{user-mail-address} fields of all contexts: concatenating the @code{user-mail-address} fields of all contexts:
@lisp @lisp
;; This sets `mu4e-user-mail-address-list' to the concatenation of all `user-mail-address' values ;; This sets `mu4e-user-mail-address-list' to the concatenation of all
;; for all contexts. If you have other mail addresses as well, you'll need to add those manually. ;; `user-mail-address' values for all contexts. If you have other mail
;; addresses as well, you'll need to add those manually.
(setq mu4e-user-mail-address-list (setq mu4e-user-mail-address-list
(delq nil (delq nil
(mapcar (lambda (context) (mapcar (lambda (context)
@ -2906,6 +2917,7 @@ two days, you could add this to @code{org-capture-templates}:
If you use the functionality a lot, you may want to define key-bindings If you use the functionality a lot, you may want to define key-bindings
for that in headers and view mode: for that in headers and view mode:
@lisp @lisp
(define-key mu4e-headers-mode-map (kbd "C-c c") 'org-mu4e-store-and-capture) (define-key mu4e-headers-mode-map (kbd "C-c c") 'org-mu4e-store-and-capture)
(define-key mu4e-view-mode-map (kbd "C-c c") 'org-mu4e-store-and-capture) (define-key mu4e-view-mode-map (kbd "C-c c") 'org-mu4e-store-and-capture)