diff --git a/mu4e/mu4e-context.el b/mu4e/mu4e-context.el index 0a111ad3..4e73f9f4 100644 --- a/mu4e/mu4e-context.el +++ b/mu4e/mu4e-context.el @@ -22,16 +22,20 @@ ;;; Commentary: -;; A mu4e 'context' is a a set of variable-settings and hooks, which can be used e.g. to switch -;; between accounts. +;; A mu4e 'context' is a a set of variable-settings and functions, which can be +;; used e.g. to switch between accounts. +(eval-when-compile (byte-compile-disable-warning 'cl-functions)) (require 'cl) + (require 'mu4e-utils) -(defvar mu4e-contexts nil "The list of context objects.") +(defvar mu4e-contexts nil "The list of `mu4e-context' objects +describing mu4e's contexts.") (defvar mu4e~context-current nil - "The current context. Use `mu4e-context-switch' to change it.") + "The current context; for internal use. Use + `mu4e-context-switch' to change it.") (defun mu4e-context-current () "Get the currently active context, or nil if there is none." @@ -47,11 +51,15 @@ (defstruct mu4e-context "A mu4e context object with the following members: - `name': the name of the context, eg. \"Work\" or \"Private\".' -- `enter-func': a parameterless function invoked when entering this context, or nil -- `leave-func':a parameterless fuction invoked when leaving this context, or nil -- `match-func': a function called when comnposing a new messages, and 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' return t." +- `enter-func': a parameterless function invoked when entering + this context, or nil +- `leave-func':a parameterless fuction invoked when leaving this + context, or nil +- `match-func': a function called when comnposing a new messages, + and 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' return t." 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 @@ -62,7 +70,8 @@ for the message replied to or forwarded, and nil otherwise. Before composing a n (defun mu4e~context-ask-user (prompt) "Let user choose some context based on its name." (when mu4e-contexts - (let* ((names (map 'list (lambda (context) (cons (mu4e-context-name context) context)) + (let* ((names (map 'list (lambda (context) + (cons (mu4e-context-name context) context)) mu4e-contexts)) (context (mu4e-read-option prompt names))) (or context (mu4e-error "No such context"))))) @@ -87,7 +96,8 @@ non-nil." (unless context (mu4e-error "No such context")) ;; if new context is same as old one one switch with FORCE is set. (when (or force (not (eq context (mu4e-context-current)))) - (when (and (mu4e-context-current) (mu4e-context-leave-func mu4e~context-current)) + (when (and (mu4e-context-current) + (mu4e-context-leave-func mu4e~context-current)) (funcall (mu4e-context-leave-func mu4e~context-current))) ;; enter the new context (when (mu4e-context-enter-func context) @@ -124,13 +134,14 @@ 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." +- otherwise, return nil. Effectively, this leaves the current context as it is." (when mu4e-contexts (if (eq policy 'always-ask) (mu4e~context-ask-user "Select context: ") (or (find-if (lambda (context) (and (mu4e-context-match-func context) - (funcall (mu4e-context-match-func context) msg))) mu4e-contexts) + (funcall (mu4e-context-match-func context) msg))) + mu4e-contexts) ;; no context found (case policy (pick-first (car mu4e-contexts)) diff --git a/mu4e/mu4e-utils.el b/mu4e/mu4e-utils.el index 866cab05..527db205 100644 --- a/mu4e/mu4e-utils.el +++ b/mu4e/mu4e-utils.el @@ -46,8 +46,9 @@ (declare-function mu4e~proc-mkdir "mu4e-proc") (declare-function mu4e~proc-running-p "mu4e-proc") -(declare-function mu4e~context-autoswitch "mu4e-context") - +(declare-function mu4e~context-autoswitch "mu4e-context") +(declare-function mu4e-context-determine "mu4e-context") +(declare-function mu4e-context-vars "mu4e-context") (declare-function show-all "org") @@ -90,26 +91,36 @@ User's addresses are set in `mu4e-user-mail-address-list')." t)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - +(defmacro with~mu4e-context-vars (context &rest body) + "Evaluate BODY, with variables let-bound for CONTEXT (if any). +`funcall'." + (declare (indent 2)) + `(let* ((vars (and ,context (mu4e-context-vars ,context)))) + (progv ;; XXX: perhaps use eval's lexical environment instead of progv? + (mapcar (lambda(cell) (car cell)) vars) + (mapcar (lambda(cell) (cdr cell)) vars) + (eval ,@body)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; the standard folders can be functions too (defun mu4e~get-folder (foldervar msg) - "Get message folder FOLDERVAR. + "Within the mu-context of MSG, get message folder FOLDERVAR. If FOLDER is a string, return it, if it is a function, evaluate this function with MSG as parameter (which may be `nil'), and return the result." - (unless (member foldervar '(mu4e-sent-folder mu4e-drafts-folder - mu4e-trash-folder mu4e-refile-folder)) - (mu4e-error "Folder must be either mu4e-sent-folder, - mu4e-drafts-folder or mu4e-trash-folder (not %S)" foldervar)) - (let* ((folder (symbol-value foldervar)) - (val - (cond - ((stringp folder) folder) - ((functionp folder) (funcall folder msg)) - (t (mu4e-error "unsupported type for %S" folder))))) - (or val (mu4e-error "%S evaluates to nil" foldervar)))) + (unless (member foldervar + '(mu4e-sent-folder mu4e-drafts-folder + mu4e-trash-folder mu4e-refile-folder)) + (mu4e-error "Folder must be one of mu4e-(sent|drafts|trash|refile)-folder")) + ;; get the value with the vars for the relevants context let-bound + (with~mu4e-context-vars (mu4e-context-determine msg nil) + (let* ((folder (symbol-value foldervar)) + (val + (cond + ((stringp folder) folder) + ((functionp folder) (funcall folder msg)) + (t (mu4e-error "unsupported type for %S" folder))))) + (or val (mu4e-error "%S evaluates to nil" foldervar))))) (defun mu4e-get-drafts-folder (&optional msg) "Get the sent folder. See `mu4e-drafts-folder'." diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index 059245dc..5fb8cc25 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -439,11 +439,13 @@ with @t{MU4E-PATH} replaced with the actual path. @section Folders The next step is to tell @t{mu4e} where it can find your Maildir, and -some special folders. So, for example@footnote{Note that the folders -(@t{mu4e-sent-folder}, @t{mu4e-drafts-folder}, @t{mu4e-trash-folder} and +some special folders. + +So, for example@footnote{Note that the folders (@t{mu4e-sent-folder}, +@t{mu4e-drafts-folder}, @t{mu4e-trash-folder} and @t{mu4e-refile-folder}) can also be @emph{functions} that are evaluated at runtime. This allows for dynamically changing them depending on the -context. See @ref{Dynamic folders} for details.}: +situation. See @ref{Dynamic folders} for details.}: @lisp ;; these are actually the defaults (setq @@ -458,6 +460,9 @@ Note, @code{mu4e-maildir} takes an actual filesystem-path, the other folder names are all relative to @code{mu4e-maildir}. Also note that this must @emph{not} be a symbolic link. +If you use @t{mu4e-context}, see @ref{Contexts and special folders} for +what that means for these special folders. + @node Retrieval and indexing @section Retrieval and indexing with mu4e @@ -2274,6 +2279,7 @@ example: @menu * Defining a context:: * Context policies:: +* Contexts and special folders:: * Contexts example:: * Some context tricks:: @end menu @@ -2345,6 +2351,31 @@ none of the contexts' match-functions returns @code{t}: @item @t{nil}: don't change the context @end itemize +@node Contexts and special folders +@section Contexts and special folders + +As we discussed in @ref{Folders} and @ref{Dynamic folders}, @t{mu4e} +recognizes a number of special folders: @code{mu4e-sent-folder}, +@code{mu4e-drafts-folder}, @code{mu4e-trash-folder} and +@code{mu4e-refile-folder}. + +When you have a headers-buffer with messages that belong to different +contexts (say, a few different accounts), it is desirable for each of +them to use the specific folders for their own context - so, for +instance, if you trash a message, it needs to go to the trash-folder for +the account it belongs to, which is not necessarily the current context. + +To make this easy to do, whenever @t{mu4e} needs to know the value for +such a special folder for a given message, it tries to determine the +appropriate context using @code{mu4e-context-determine}. If it finds +one, it let-binds the @code{vars} for that account, and then determines +the value for the folder. It does not, however, call the @t{enter-func}, +since we are not really switching context. + +In practice, this what this means that a long as each of the accounts +has a good @t{match-func}, all message operations automatically find the +appropriate folders. + @node Contexts example @section Example @@ -2362,7 +2393,8 @@ when starting; see the discussion in the previous section. :enter-func (lambda () (mu4e-message "Switch to the Private context")) ;; leave-func not defined :match-func (lambda (msg) - (when msg (mu4e-message-contact-field-matches msg :to "aliced@@home.example.com"))) + (when msg (mu4e-message-contact-field-matches msg + :to "aliced@@home.example.com"))) :vars '( ( mail-reply-to . "aliced@@home.example.com" ) ( user-mail-address . "aliced@@home.example.com" ) ( user-full-name . "Alice Derleth" ) @@ -2375,7 +2407,8 @@ when starting; see the discussion in the previous section. :enter-func (lambda () (mu4e-message "Switch to the Work context")) ;; leave-fun not defined :match-func (lambda (msg) - (when msg (mu4e-message-contact-field-matches msg :to "aderleth@@miskatonic.example.com"))) + (when msg (mu4e-message-contact-field-matches msg + :to "aderleth@@miskatonic.example.com"))) :vars '( ( mail-reply-to . "aderleth@@miskatonic.example.com" ) ( user-mail-address . "aderleth@@miskatonic.example.com" ) ( user-full-name . "Alice Derleth" ) @@ -2446,6 +2479,9 @@ parameter, and returns the desired folder name. This chapter shows you how to do that. For a more general discussion of how to extend @t{mu4e} and writing your own functions, see @ref{Extending mu4e}. +If you use @t{mu4e-context}, see @ref{Contexts and special folders} for +what that means for these special folders. + @menu * Smart refiling:: Automatically choose the target folder * Other dynamic folders:: Flexible folders for sent, trash, drafts