mu4e-utils: refactor into mu4e-helpers, separate files

Usurp more of the utils code than can be re-used without further dependencies in
helpers.

Split off specific parts in their own file.

After the helper/utils changes, update the rest of mu4e to take the changes into
account.
This commit is contained in:
Dirk-Jan C. Binnema 2021-08-29 17:30:10 +03:00
parent e6be09e626
commit 9157d9102d
23 changed files with 2119 additions and 2335 deletions

View File

@ -30,18 +30,11 @@
(require 'cl-lib)
(require 'ido)
(require 'mu4e-utils)
(require 'mu4e-helpers)
(require 'mu4e-message)
(require 'mu4e-search)
(require 'mu4e-meta)
(declare-function mu4e~proc-extract "mu4e-proc")
(declare-function mu4e-headers-search "mu4e-headers")
(defvar mu4e-headers-include-related)
(defvar mu4e-headers-show-threads)
(defvar mu4e-view-show-addresses)
(defvar mu4e-view-date-format)
;;; Count lines
@ -177,6 +170,14 @@ Otherwise return nil."
(if (re-search-forward regexp nil t)
(replace-match to-string nil nil)))))
(declare-function mu4e~proc-add "mu4e-proc")
(defun mu4e--refresh-message (path)
"Re-parse message at PATH.
if this works, we will
receive (:info add :path <path> :docid <docid>) as well as (:update
<msg-sexp>)."
(mu4e~proc-add path))
(defun mu4e-action-retag-message (msg &optional retag-arg)
"Change tags of MSG with RETAG-ARG.
@ -231,7 +232,7 @@ would add 'tag' and 'long tag', and remove 'oldtag'."
path))
(mu4e-message (concat "tagging: " (mapconcat 'identity taglist ", ")))
(mu4e-refresh-message path)))
(mu4e--refresh-message path)))
(defun mu4e-action-show-thread (msg)
"Show thread for message at point with point remaining on MSG.
@ -240,14 +241,13 @@ action was invoked. If invoked in view mode, continue to display
the message."
(let ((msgid (mu4e-message-field msg :message-id)))
(when msgid
(let ((mu4e-headers-show-threads t)
(let ((mu4e-search-threads t)
(mu4e-headers-include-related t))
(mu4e-headers-search
(mu4e-search
(format "msgid:%s" msgid)
nil nil nil
msgid (and (eq major-mode 'mu4e-view-mode)
(not (eq mu4e-split-view 'single-window))))))))
;;; _
(provide 'mu4e-actions)
;;; mu4e-actions.el ends here

150
mu4e/mu4e-bookmarks.el Normal file
View File

@ -0,0 +1,150 @@
;;; mu4e-bookmarks.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;; mu4e is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; mu4e is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'cl-lib)
(require 'mu4e-helpers)
;;; Configuration
(defgroup mu4e-bookmarks nil
"Settings for bookmarks."
:group 'mu4e)
;; for backward compatibility, when a bookmark was defined with defstruct.
(cl-defun make-mu4e-bookmark (&key name query key)
"Create a mu4e plist.
It has has with the following elements:
- NAME: the user-visible name of the bookmark
- KEY: a single key to search for this bookmark
- QUERY: the query for this bookmark. Either a literal string or a function
that evaluates to a string."
`(:name ,name :query ,query :key ,key))
(make-obsolete 'make-mu4e-bookmark "`unneeded; `mu4e-bookmarks'
are plists" "1.3.7")
(defcustom mu4e-bookmarks
'(( :name "Unread messages"
:query "flag:unread AND NOT flag:trashed"
:key ?u)
( :name "Today's messages"
:query "date:today..now"
:key ?t)
( :name "Last 7 days"
:query "date:7d..now"
:hide-unread t
:key ?w)
( :name "Messages with images"
:query "mime:image/*"
:key ?p))
"List of pre-defined queries that are shown on the main screen.
Each of the list elements is a plist with at least:
`:name' - the name of the query
`:query' - the query expression or function
`:key' - the shortcut key.
Note that the :query parameter can be a function/lambda.
Optionally, you can add the following:
`:hide' - if t, the bookmark is hidden from the main-view and
speedbar.
`:hide-unread' - do not show the counts of unread/total number
of matches for the query in the main-view. This can be useful
if a bookmark uses a very slow query. :hide-unread
is implied from :hide. Furthermore, it is implied if
`:query' is a function.
Queries used to determine the unread/all counts do _not_ apply
`mu4e-query-rewrite-function'; nor do they discard duplicate or
unreadable messages (for efficiency). Thus, the numbers shown may
differ from the number you get from a 'real' query."
:type '(repeat (plist))
:version "1.3.9"
:group 'mu4e-bookmarks)
(defun mu4e-ask-bookmark (prompt)
"Ask the user for a bookmark (using PROMPT) as defined in
`mu4e-bookmarks', then return the corresponding query."
(unless (mu4e-bookmarks) (mu4e-error "No bookmarks defined"))
(let* ((prompt (mu4e-format "%s" prompt))
(bmarks
(mapconcat
(lambda (bm)
(concat
"[" (propertize (make-string 1 (plist-get bm :key))
'face 'mu4e-highlight-face)
"]"
(plist-get bm :name))) (mu4e-bookmarks) ", "))
(kar (read-char (concat prompt bmarks))))
(mu4e-get-bookmark-query kar)))
(defun mu4e-get-bookmark-query (kar)
"Get the corresponding bookmarked query for shortcut KAR.
Raise an error if none is found."
(let* ((chosen-bm
(or (cl-find-if
(lambda (bm)
(= kar (plist-get bm :key)))
(mu4e-bookmarks))
(mu4e-warn "Unknown shortcut '%c'" kar)))
(expr (plist-get chosen-bm :query))
(expr (if (not (functionp expr)) expr
(funcall expr)))
(query (eval expr)))
(if (stringp query)
query
(mu4e-warn "Expression must evaluate to query string ('%S')" expr))))
(defun mu4e-bookmark-define (query name key)
"Define a bookmark for QUERY with NAME and shortcut KEY.
Append it to `mu4e-bookmarks'. Replaces any existing bookmark
with KEY."
(setq mu4e-bookmarks
(cl-remove-if
(lambda (bm)
(= (plist-get bm :key) key))
(mu4e-bookmarks)))
(cl-pushnew `(:name ,name
:query ,query
:key ,key)
mu4e-bookmarks :test 'equal))
(defun mu4e-bookmarks ()
"Get `mu4e-bookmarks' in the (new) format.
Convert from the old format if needed."
(cl-map 'list
(lambda (item)
(if (and (listp item) (= (length item) 3))
`(:name ,(nth 1 item)
:query ,(nth 0 item)
:key ,(nth 2 item))
item))
mu4e-bookmarks))
(provide 'mu4e-bookmarks)
;;; mu4e-bookmarks.el ends here

View File

@ -72,192 +72,17 @@
(require 'smtpmail)
(require 'rfc2368)
(require 'mu4e-utils)
(require 'mu4e-vars)
(require 'mu4e-proc)
(require 'mu4e-actions)
(require 'mu4e-message)
(require 'mu4e-draft)
(require 'mu4e-context)
;;; Composing / Sending messages
(defgroup mu4e-compose nil
"Customizations for composing/sending messages."
:group 'mu4e)
(defcustom mu4e-sent-messages-behavior 'sent
"Determines what mu4e does with sent messages.
This is one of the symbols:
* `sent' move the sent message to the Sent-folder (`mu4e-sent-folder')
* `trash' move the sent message to the Trash-folder (`mu4e-trash-folder')
* `delete' delete the sent message.
Note, when using GMail/IMAP, you should set this to either
`trash' or `delete', since GMail already takes care of keeping
copies in the sent folder.
Alternatively, `mu4e-sent-messages-behavior' can be a function
which takes no arguments, and which should return one of the mentioned
symbols, for example:
(setq mu4e-sent-messages-behavior (lambda ()
(if (string= (message-sendmail-envelope-from) \"foo@example.com\")
'delete 'sent)))
The various `message-' functions from `message-mode' are available
for querying the message information."
:type '(choice (const :tag "move message to mu4e-sent-folder" sent)
(const :tag "move message to mu4e-trash-folder" trash)
(const :tag "delete message" delete))
:group 'mu4e-compose)
(defcustom mu4e-compose-context-policy 'ask
"Policy for determining the context when composing a new message.
If the value is `always-ask', ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is used. Otherwise, if none of the
contexts match, we have the following choices:
- `pick-first': pick the first of the contexts available (ie. the default)
- `ask': ask the user
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
- nil: return nil; leaves the current context as is.
Also see `mu4e-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 "Ask when there's no context yet" ask-if-none)
(const :tag "Pick the 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-crypto-policy
'(encrypt-encrypted-replies sign-encrypted-replies)
"Policy to control when messages will be signed/encrypted.
The value is a list, whose members determine the behaviour of
`mu4e~compose-crypto-message'. Specifically, it might contain:
- `sign-all-messages': Always add a signature.
- `sign-new-messages': Add a signature to new message, ie.
messages that aren't responses to another message.
- `sign-forwarded-messages': Add a signature when forwarding
a message
- `sign-edited-messages': Add a signature to drafts
- `sign-all-replies': Add a signature when responding to
another message.
- `sign-plain-replies': Add a signature when responding to
non-encrypted messages.
- `sign-encrypted-replies': Add a signature when responding
to encrypted messages.
It should be noted that certain symbols have priorities over one
another. So `sign-all-messages' implies `sign-all-replies', which
in turn implies `sign-plain-replies'. Adding both to the set, is
not a contradiction, but a redundant configuration.
All `sign-*' options have a `encrypt-*' analogue."
:type '(set :greedy t
(const :tag "Sign all messages" sign-all-messages)
(const :tag "Encrypt all messages" encrypt-all-messages)
(const :tag "Sign new messages" sign-new-messages)
(const :tag "Encrypt new messages" encrypt-new-messages)
(const :tag "Sign forwarded messages" sign-forwarded-messages)
(const :tag "Encrypt forwarded messages" encrypt-forwarded-messages)
(const :tag "Sign edited messages" sign-edited-messages)
(const :tag "Encrypt edited messages" edited-forwarded-messages)
(const :tag "Sign all replies" sign-all-replies)
(const :tag "Encrypt all replies" encrypt-all-replies)
(const :tag "Sign replies to plain messages" sign-plain-replies)
(const :tag "Encrypt replies to plain messages" encrypt-plain-replies)
(const :tag "Sign replies to encrypted messages" sign-encrypted-replies)
(const :tag "Encrypt replies to encrypted messages" encrypt-encrypted-replies))
:group 'mu4e-compose)
(defcustom mu4e-compose-crypto-reply-encrypted-policy nil
"Policy for signing/encrypting replies to encrypted messages.
We have the following choices:
- `sign': sign the reply
- `sign-and-encrypt': sign and encrypt the reply
- `encrypt': encrypt the reply, but don't sign it.
- anything else: do nothing."
:type '(choice
(const :tag "Sign the reply" sign)
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
(const :tag "Encrypt the reply" encrypt)
(const :tag "Don't do anything" nil))
:safe 'symbolp
:group 'mu4e-compose)
(make-obsolete-variable 'mu4e-compose-crypto-reply-encrypted-policy "The use of the
'mu4e-compose-crypto-reply-encrypted-policy' variable is deprecated.
'mu4e-compose-crypto-policy' should be used instead"
"2020-03-06")
(defcustom mu4e-compose-crypto-reply-plain-policy nil
"Policy for signing/encrypting replies to messages received unencrypted.
We have the following choices:
- `sign': sign the reply
- `sign-and-encrypt': sign and encrypt the reply
- `encrypt': encrypt the reply, but don't sign it.
- anything else: do nothing."
:type '(choice
(const :tag "Sign the reply" sign)
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
(const :tag "Encrypt the reply" encrypt)
(const :tag "Don't do anything" nil))
:safe 'symbolp
:group 'mu4e-compose)
(make-obsolete-variable 'mu4e-compose-crypto-reply-plain-policy "The use of the
'mu4e-compose-crypto-reply-plain-policy' variable is deprecated.
'mu4e-compose-crypto-policy' should be used instead"
"2020-03-06")
(make-obsolete-variable 'mu4e-compose-crypto-reply-policy "The use of the
'mu4e-compose-crypto-reply-policy' variable is deprecated.
'mu4e-compose-crypto-reply-plain-policy' and
'mu4e-compose-crypto-reply-encrypted-policy' should be used instead"
"2017-09-02")
(defcustom mu4e-compose-format-flowed nil
"Whether to compose messages to be sent as format=flowed.
\(Or with long lines if variable `use-hard-newlines' is set to
nil). The variable `fill-flowed-encode-column' lets you customize
the width beyond which format=flowed lines are wrapped."
:type 'boolean
:safe 'booleanp
:group 'mu4e-compose)
(defcustom mu4e-compose-pre-hook nil
"Hook run just *before* message composition starts.
If the compose-type is either 'reply' or 'forward', the variable
`mu4e-compose-parent-message' points to the message replied to /
being forwarded / edited, and `mu4e-compose-type' contains the
type of message to be composed.
Note that there is no draft message yet when this hook runs, it
is meant for influencing the how mu4e constructs the draft
message. If you want to do something with the draft messages after
it has been constructed, `mu4e-compose-mode-hook' would be the
place to do that."
:type 'hook
:group 'mu4e-compose)
(defvar mu4e-compose-type nil
"The compose-type for this buffer.
This is a symbol, `new', `forward', `reply' or `edit'.")
;;; Configuration
;; see mu4e-drafts.el
;;; Attachments
(defun mu4e-compose-attach-message (msg)
"Insert message MSG as an attachment."
(let ((path (plist-get msg :path)))
@ -393,9 +218,9 @@ Message-ID."
"Complete address STR with predication PRED for ACTION."
(cond
((eq action nil)
(try-completion str mu4e~contacts-hash pred))
(try-completion str mu4e--contacts-hash pred))
((eq action t)
(all-completions str mu4e~contacts-hash pred))
(all-completions str mu4e--contacts-hash pred))
((eq action 'metadata)
;; our contacts are already sorted - just need to tell the
;; completion machinery not to try to undo that...
@ -529,8 +354,9 @@ buffers; lets remap its faces so it uses the ones for mu4e."
(mu4e~compose-register-message-save-hooks)
;; offer completion for e-mail addresses
(when mu4e-compose-complete-addresses
(unless mu4e~contacts-hash ;; work-around for https://github.com/djcb/mu/issues/1016
(mu4e~request-contacts-maybe))
(unless mu4e--contacts-hash
;; work-around for https://github.com/djcb/mu/issues/1016
(mu4e--request-contacts-maybe))
(mu4e~compose-setup-completion))
(if mu4e-compose-format-flowed
(progn
@ -986,6 +812,8 @@ draft message."
;; mu4e-compose-func and mu4e-send-func are wrappers so we can set ourselves
;; as default emacs mailer (define-mail-user-agent etc.)
(declare-function mu4e "mu4e")
;;;###autoload
(defun mu4e~compose-mail (&optional to subject other-headers _continue
switch-function yank-action _send-actions _return-action)
@ -1022,7 +850,7 @@ caller. It has the form (FUNCTION . ARGS). The function is
called after the mail has been sent or put aside, and the mail
buffer buried."
(unless (mu4e-running-p)
(mu4e~start))
(mu4e))
;; create a new draft message 'resetting' (as below) is not actually needed in this case, but
;; let's prepare for the re-edit case as well

231
mu4e/mu4e-contacts.el Normal file
View File

@ -0,0 +1,231 @@
;;; mu4e-contacts.el -- part of mu4e -*- lexical-binding: t -*-
;; Copyright (C) 2021 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;; mu4e is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; mu4e is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Utility functions used in the mu4e
;;; Code:
(require 'cl-lib)
(require 'mu4e-helpers)
;;; Configuration
(defcustom mu4e-compose-complete-addresses t
"Whether to do auto-completion of e-mail addresses."
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-compose-complete-only-personal nil
"Whether to consider only 'personal' e-mail addresses for completion.
That is, addresses from messages where user was explicitly in one
of the address fields (this excludes mailing list messages).
These addresses are the ones specified with `mu init'."
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-compose-complete-only-after "2014-01-01"
"Consider only contacts last seen after this date.
Date must be a string of the form YYY-MM-DD.
This is useful for limiting a potentially enormous set of
contacts for auto-completion to just those that are present in
the e-mail corpus in recent timses. Set to nil to not have any
time-based restriction."
:type 'string
:group 'mu4e-compose)
;; names and mail-addresses can be mapped onto their canonical
;; counterpart. use the customizeable function
;; mu4e-canonical-contact-function to do that. below the identity
;; function for mapping a contact onto the canonical one.
(defun mu4e-contact-identity (contact)
"Return the name and the mail-address of a CONTACT.
It is used as the identity function for converting contacts to
their canonical counterpart; useful as an example."
(let ((name (plist-get contact :name))
(mail (plist-get contact :mail)))
(list :name name :mail mail)))
(make-obsolete-variable 'mu4e-contact-rewrite-function
"mu4e-contact-process-function (see docstring)"
"mu4e 1.3.2")
(make-obsolete-variable 'mu4e-compose-complete-ignore-address-regexp
"mu4e-contact-process-function (see docstring)"
"mu4e 1.3.2")
(defcustom mu4e-contact-process-function
(lambda(addr) ;; filter-out no-reply addresses
(unless (string-match-p "no[t]?[-\\.]?repl\\(y\\|ies\\)" addr)
addr))
"Function for processing contact information for use in auto-completion.
The function receives the contact as a string, e.g
\"Foo Bar <foo.bar@example.com>\"
\"cuux@example.com\"
The function should return either:
- nil: do not use this contact for completion
- the (possibly rewritten) address, which must be
an RFC-2822-compatible e-mail address."
:type 'function
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-ignore-address
'("no-?reply")
"Addresses to prune when doing wide replies.
This can be a regexp matching the address, a list of regexps or a
predicate function. A value of nil keeps all the addresses."
:type '(choice
(const nil)
function
string
(repeat string))
:group 'mu4e-compose)
;;; Internal variables
(defvar mu4e--contacts-tstamp "0"
"Timestamp for the most recent contacts update." )
(defvar mu4e--contacts-hash nil
"Hash that maps contacts (ie. 'name <e-mail>') to an integer for sorting.
We need to keep this information around to quickly re-sort
subsets of the contacts in the completions function in
mu4e-compose.")
;;; user mail address
(defun mu4e-personal-addresses(&optional no-regexp)
"Get the list user's personal addresses, as passed to mu init.
The address are either plain e-mail address or /regular
expressions/. When NO-REGEXP is non-nil, do not include regexp
address patterns (if any)."
(seq-remove
(lambda(addr) (and no-regexp (string-match-p "^/.*/" addr)))
(when (mu4e-server-properties)
(plist-get (mu4e-server-properties) :personal-addresses))))
(defun mu4e-personal-address-p (addr)
"Is ADDR a personal address?
Evaluate to nil if ADDR matches any of the personal addresses.
Uses (mu4e-personal-addresses) for the addresses with both the plain
addresses and /regular expressions/."
(when addr
(seq-find
(lambda (m)
(if (string-match "/\\(.*\\)/" m)
(let ((rx (match-string 1 m))
(case-fold-search t))
(if (string-match rx addr) t nil))
(eq t (compare-strings addr nil nil m nil nil 'case-insensitive))))
(mu4e-personal-addresses))))
(define-obsolete-function-alias 'mu4e-user-mail-address-p
'mu4e-personal-address-p "1.5.5")
;; don't use the older vars anymore
(make-obsolete-variable 'mu4e-user-mail-address-regexp
'mu4e-user-mail-address-list "0.9.9.x")
(make-obsolete-variable 'mu4e-my-email-addresses
'mu4e-user-mail-address-list "0.9.9.x")
(make-obsolete-variable 'mu4e-user-mail-address-list
"determined by server; see `mu4e-personal-addresses'."
"1.3.8")
(defun mu4e--update-contacts (contacts &optional tstamp)
"Receive a sorted list of CONTACTS newer than TSTAMP.
Each of the contacts has the form
(FULL_EMAIL_ADDRESS . RANK) and fill `mu4e--contacts-hash' with
it, with each contact mapped to an integer for their ranking.
This is used by the completion function in mu4e-compose."
;; We have our nicely sorted list, map them to a list
;; of increasing integers. We use that map in the composer
;; to sort them there. It would have been so much easier if emacs
;; allowed us to use the sorted-list as-is, but no such luck.
(let ((n 0))
(unless mu4e--contacts-hash
(setq mu4e--contacts-hash (make-hash-table :test 'equal :weakness nil
:size (length contacts))))
(dolist (contact contacts)
(cl-incf n)
(let* ((address (plist-get contact :address))
(address
(if (functionp mu4e-contact-process-function)
(funcall mu4e-contact-process-function address)
address)))
(when address ;; note the explicit deccode; the strings we get are
;; utf-8, but emacs doesn't know yet.
(puthash (decode-coding-string address 'utf-8)
(plist-get contact :rank) mu4e--contacts-hash))))
(setq mu4e--contacts-tstamp (or tstamp "0"))
(unless (zerop n)
(mu4e-index-message "Contacts updated: %d; total %d"
n (hash-table-count mu4e--contacts-hash)))))
(defun mu4e-contacts-info ()
"Display information about the contacts-cache.
For testing/debugging."
(interactive)
(with-current-buffer (get-buffer-create "*mu4e-contacts-info*")
(erase-buffer)
(insert (format "complete addresses: %s\n"
(if mu4e-compose-complete-addresses "yes" "no")))
(insert (format "only personal addresses: %s\n"
(if mu4e-compose-complete-only-personal "yes" "no")))
(insert (format "only addresses seen after: %s\n"
(or mu4e-compose-complete-only-after "no restrictions")))
(when mu4e--contacts-hash
(insert (format "number of contacts cached: %d\n\n"
(hash-table-count mu4e--contacts-hash)))
(let ((contacts))
(maphash (lambda (addr rank)
(setq contacts (cons (cons rank addr) contacts)))
mu4e--contacts-hash)
(setq contacts (sort contacts
(lambda(cell1 cell2) (< (car cell1) (car cell2)))))
(dolist (contact contacts)
(insert (format "%s\n" (cdr contact))))))
(pop-to-buffer "*mu4e-contacts-info*")))
(declare-function mu4e~proc-contacts "mu4e-proc")
(defun mu4e--request-contacts-maybe ()
"If `mu4e-compose-complete-addresses' is non-nil, get/update
the list of contacts we use for autocompletion; otherwise, do
nothing."
(when mu4e-compose-complete-addresses
(mu4e~proc-contacts
mu4e-compose-complete-only-personal
mu4e-compose-complete-only-after
mu4e--contacts-tstamp)))
(provide 'mu4e-contacts)
;;; mu4e-contacts.el ends here

View File

@ -31,6 +31,30 @@
;;; Configuration
(defcustom mu4e-context-policy 'ask-if-none
"The policy to determine the context when entering the mu4e main view.
If the value is `always-ask', ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is used. Otherwise, if none of the
contexts match, we have the following choices:
- `pick-first': pick the first of the contexts available (ie. the default)
- `ask': ask the user
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
- nil: return nil; leaves the current context as is.
Also see `mu4e-compose-context-policy'."
:type '(choice
(const :tag "Always ask what context to use, even if one matches"
always-ask)
(const :tag "Ask if none of the contexts match" ask)
(const :tag "Ask when there's no context yet" ask-if-none)
(const :tag "Pick the first context if none match" pick-first)
(const :tag "Don't change the context when none match" nil))
:group 'mu4e)
(defvar mu4e-contexts nil
"The list of `mu4e-context' objects describing mu4e's contexts.")
@ -42,12 +66,10 @@
'((t :inherit mu4e-title-face :weight bold))
"Face for displaying the context in the modeline."
:group 'mu4e-faces)
(defvar mu4e--context-current nil
"The current context.
Internal; use `mu4e-context-switch' to change it.")
(defun mu4e-context-current (&optional output)
"Get the currently active context, or nil if there is none.
@ -188,6 +210,16 @@ global-mode-line."
(make-local-variable 'global-mode-string)
'(:eval (mu4e-context-label))))
(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))))
(cl-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))))
(define-minor-mode mu4e-context-minor-mode
"Mode for switching the mu4e context."
:global nil

View File

@ -96,7 +96,7 @@ BOOKMARK is a bookmark name or a bookmark record."
(docid (cdr path))
(query (car path)))
(call-interactively 'mu4e)
(mu4e-headers-search query)
(mu4e-search query)
(sit-for 0.5)
(mu4e~headers-goto-docid docid)
(mu4e~headers-highlight docid)
@ -172,6 +172,17 @@ For example for bogofile, use \"/usr/bin/bogofilter -Sn < %s\"")
;; allowing files to be attached to an email via mu4e using the
;; eshell. Does not depend on gnus.
(defun mu4e~active-composition-buffers ()
"Return all active mu4e composition buffers"
(let (buffers)
(save-excursion
(dolist (buffer (buffer-list t))
(set-buffer buffer)
(when (eq major-mode 'mu4e-compose-mode)
(push (buffer-name buffer) buffers))))
(nreverse buffers)))
(defun eshell/mu4e-attach (&rest args)
"Attach files to a mu4e message using eshell. If no mu4e
buffers found, compose a new message and then attach the file."

View File

@ -27,12 +27,232 @@
;;; Code:
(require 'cl-lib)
(require 'mu4e-vars)
(require 'mu4e-utils)
(require 'mu4e-message)
(require 'mu4e-contacts)
(require 'mu4e-folders)
(require 'message) ;; mail-header-separator
;;; Options
;;; Configuration
(defgroup mu4e-compose nil
"Customizations for composing/sending messages."
:group 'mu4e)
(defcustom mu4e-compose-reply-recipients 'ask
"Which recipients to use when replying to a message.
May be 'ask, 'all, 'sender. Note that that only applies to
non-mailing-list message; for those, mu4e always asks."
:type '(choice ask
all
sender)
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-to-address nil
"The Reply-To address.
Useful when this is not equal to the From: address."
:type 'string
:group 'mu4e-compose)
(defcustom mu4e-compose-forward-as-attachment nil
"Whether to forward messages as attachments instead of inline."
:type 'boolean
:group 'mu4e-compose)
;; backward compatibility
(make-obsolete-variable 'mu4e-reply-to-address
'mu4e-compose-reply-to-address
"v0.9.9")
(defcustom mu4e-compose-keep-self-cc nil
"When non-nil. keep your e-mail address in Cc: when replying."
:type 'boolean
:group 'mu4e-compose)
(defvar mu4e-compose-parent-message nil
"The parent message plist.
This is the message being replied to, forwarded or edited; used
in `mu4e-compose-pre-hook'. For new messages, it is nil.")
(make-obsolete-variable 'mu4e-auto-retrieve-keys "no longer used." "1.3.1")
(defcustom mu4e-decryption-policy t
"Policy for dealing with replying/forwarding encrypted parts.
The setting is a symbol:
* t: try to decrypt automatically
* `ask': ask before decrypting anything
* nil: don't try to decrypt anything."
:type '(choice (const :tag "Try to decrypt automatically" t)
(const :tag "Ask before decrypting anything" ask)
(const :tag "Don't try to decrypt anything" nil))
:group 'mu4e-compose)
;;; Composing / Sending messages
(defcustom mu4e-sent-messages-behavior 'sent
"Determines what mu4e does with sent messages.
This is one of the symbols:
* `sent' move the sent message to the Sent-folder (`mu4e-sent-folder')
* `trash' move the sent message to the Trash-folder (`mu4e-trash-folder')
* `delete' delete the sent message.
Note, when using GMail/IMAP, you should set this to either
`trash' or `delete', since GMail already takes care of keeping
copies in the sent folder.
Alternatively, `mu4e-sent-messages-behavior' can be a function
which takes no arguments, and which should return one of the mentioned
symbols, for example:
(setq mu4e-sent-messages-behavior (lambda ()
(if (string= (message-sendmail-envelope-from) \"foo@example.com\")
'delete 'sent)))
The various `message-' functions from `message-mode' are available
for querying the message information."
:type '(choice (const :tag "move message to mu4e-sent-folder" sent)
(const :tag "move message to mu4e-trash-folder" trash)
(const :tag "delete message" delete))
:group 'mu4e-compose)
(defcustom mu4e-compose-context-policy 'ask
"Policy for determining the context when composing a new message.
If the value is `always-ask', ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is used. Otherwise, if none of the
contexts match, we have the following choices:
- `pick-first': pick the first of the contexts available (ie. the default)
- `ask': ask the user
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
- nil: return nil; leaves the current context as is.
Also see `mu4e-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 "Ask when there's no context yet" ask-if-none)
(const :tag "Pick the 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-crypto-policy
'(encrypt-encrypted-replies sign-encrypted-replies)
"Policy to control when messages will be signed/encrypted.
The value is a list, whose members determine the behaviour of
`mu4e~compose-crypto-message'. Specifically, it might contain:
- `sign-all-messages': Always add a signature.
- `sign-new-messages': Add a signature to new message, ie.
messages that aren't responses to another message.
- `sign-forwarded-messages': Add a signature when forwarding
a message
- `sign-edited-messages': Add a signature to drafts
- `sign-all-replies': Add a signature when responding to
another message.
- `sign-plain-replies': Add a signature when responding to
non-encrypted messages.
- `sign-encrypted-replies': Add a signature when responding
to encrypted messages.
It should be noted that certain symbols have priorities over one
another. So `sign-all-messages' implies `sign-all-replies', which
in turn implies `sign-plain-replies'. Adding both to the set, is
not a contradiction, but a redundant configuration.
All `sign-*' options have a `encrypt-*' analogue."
:type '(set :greedy t
(const :tag "Sign all messages" sign-all-messages)
(const :tag "Encrypt all messages" encrypt-all-messages)
(const :tag "Sign new messages" sign-new-messages)
(const :tag "Encrypt new messages" encrypt-new-messages)
(const :tag "Sign forwarded messages" sign-forwarded-messages)
(const :tag "Encrypt forwarded messages" encrypt-forwarded-messages)
(const :tag "Sign edited messages" sign-edited-messages)
(const :tag "Encrypt edited messages" edited-forwarded-messages)
(const :tag "Sign all replies" sign-all-replies)
(const :tag "Encrypt all replies" encrypt-all-replies)
(const :tag "Sign replies to plain messages" sign-plain-replies)
(const :tag "Encrypt replies to plain messages" encrypt-plain-replies)
(const :tag "Sign replies to encrypted messages" sign-encrypted-replies)
(const :tag "Encrypt replies to encrypted messages" encrypt-encrypted-replies))
:group 'mu4e-compose)
(defcustom mu4e-compose-crypto-reply-encrypted-policy nil
"Policy for signing/encrypting replies to encrypted messages.
We have the following choices:
- `sign': sign the reply
- `sign-and-encrypt': sign and encrypt the reply
- `encrypt': encrypt the reply, but don't sign it.
- anything else: do nothing."
:type '(choice
(const :tag "Sign the reply" sign)
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
(const :tag "Encrypt the reply" encrypt)
(const :tag "Don't do anything" nil))
:safe 'symbolp
:group 'mu4e-compose)
(make-obsolete-variable 'mu4e-compose-crypto-reply-encrypted-policy "The use of the
'mu4e-compose-crypto-reply-encrypted-policy' variable is deprecated.
'mu4e-compose-crypto-policy' should be used instead"
"2020-03-06")
(defcustom mu4e-compose-crypto-reply-plain-policy nil
"Policy for signing/encrypting replies to messages received unencrypted.
We have the following choices:
- `sign': sign the reply
- `sign-and-encrypt': sign and encrypt the reply
- `encrypt': encrypt the reply, but don't sign it.
- anything else: do nothing."
:type '(choice
(const :tag "Sign the reply" sign)
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
(const :tag "Encrypt the reply" encrypt)
(const :tag "Don't do anything" nil))
:safe 'symbolp
:group 'mu4e-compose)
(make-obsolete-variable 'mu4e-compose-crypto-reply-plain-policy "The use of the
'mu4e-compose-crypto-reply-plain-policy' variable is deprecated.
'mu4e-compose-crypto-policy' should be used instead"
"2020-03-06")
(make-obsolete-variable 'mu4e-compose-crypto-reply-policy "The use of the
'mu4e-compose-crypto-reply-policy' variable is deprecated.
'mu4e-compose-crypto-reply-plain-policy' and
'mu4e-compose-crypto-reply-encrypted-policy' should be used instead"
"2017-09-02")
(defcustom mu4e-compose-format-flowed nil
"Whether to compose messages to be sent as format=flowed.
\(Or with long lines if variable `use-hard-newlines' is set to
nil). The variable `fill-flowed-encode-column' lets you customize
the width beyond which format=flowed lines are wrapped."
:type 'boolean
:safe 'booleanp
:group 'mu4e-compose)
(defcustom mu4e-compose-pre-hook nil
"Hook run just *before* message composition starts.
If the compose-type is either 'reply' or 'forward', the variable
`mu4e-compose-parent-message' points to the message replied to /
being forwarded / edited, and `mu4e-compose-type' contains the
type of message to be composed.
Note that there is no draft message yet when this hook runs, it
is meant for influencing the how mu4e constructs the draft
message. If you want to do something with the draft messages after
it has been constructed, `mu4e-compose-mode-hook' would be the
place to do that."
:type 'hook
:group 'mu4e-compose)
(defcustom mu4e-compose-dont-reply-to-self nil
"If non-nil, don't include self.
@ -79,6 +299,11 @@ mu4e-specific version of `message-signature'."
(defvar mu4e-view-date-format)
(defvar mu4e-compose-type nil
"The compose-type for this buffer.
This is a symbol, `new', `forward', `reply' or `edit'.")
(defun mu4e~draft-cite-original (msg)
"Return a cited version of the original message MSG as a plist.
This function uses `mu4e-compose-cite-function', and as such all
@ -119,6 +344,22 @@ one. Code borrowed from `message-shorten-1'."
(setcdr (nthcdr (- cut 2) list)
(nthcdr (+ (- cut 2) surplus 1) list)))
(defun mu4e~fontify-signature ()
"Give the message signatures a distinctive color. This is used in
the view and compose modes and will color each signature in digest messages adhering to RFC 1153."
(let ((inhibit-read-only t))
(save-excursion
;; give the footer a different color...
(goto-char (point-min))
(while (re-search-forward "^-- *$" nil t)
(let ((p (point))
(end (or
(re-search-forward "\\(^-\\{30\\}.*$\\)" nil t) ;; 30 by RFC1153
(point-max))))
(add-text-properties p end '(face mu4e-footer-face)))))))
(defun mu4e~draft-references-construct (msg)
"Construct the value of the References: header based on MSG.
This assumes a comma-separated string. Normally, this the concatenation of the
@ -558,13 +799,13 @@ will be created from either `mu4e~draft-reply-construct', or
;; case-1: re-editing a draft messages. in this case, we do know the
;; full path, but we cannot really know 'drafts folder'... we make a
;; guess
(setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path)))
(setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path)))
(mu4e~draft-open-file (mu4e-message-field msg :path) switch-function))
(resend
;; case-2: copy some exisisting message to a draft message, then edit
;; that.
(setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path)))
(setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path)))
(let ((draft-path (mu4e~draft-determine-path draft-dir)))
(copy-file (mu4e-message-field msg :path) draft-path)
(mu4e~draft-open-file draft-path switch-function)))

354
mu4e/mu4e-folders.el Normal file
View File

@ -0,0 +1,354 @@
;;; mu4e-folders.el -- part of mu4e -*- lexical-binding: t -*-
;; Copyright (C) 2021 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;; mu4e is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; mu4e is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Dealing with maildirs & folders
;;; Code:
(require 'cl-lib)
(require 'mu4e-helpers)
(require 'mu4e-context)
(require 'mu4e-proc)
;;; Customization
(defgroup mu4e-folders nil
"Special folders."
:group 'mu4e)
(defcustom mu4e-drafts-folder "/drafts"
"Folder for draft messages, relative to the root maildir.
For instance, \"/drafts\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note, the message
parameter refers to the original message being replied to / being
forwarded / re-edited and is nil otherwise. `mu4e-drafts-folder'
is only evaluated once."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-refile-folder "/archive"
"Folder for refiling messages, relative to the root maildir.
For instance \"/Archive\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note that the
message parameter refers to the message-at-point."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-sent-folder "/sent"
"Folder for sent messages, relative to the root maildir.
For instance, \"/Sent Items\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note that the
message parameter refers to the original message being replied to
/ being forwarded / re-edited, and is nil otherwise."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-trash-folder "/trash"
"Folder for trashed messages, relative to the root maildir.
For instance, \"/trash\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. When using
`mu4e-trash-folder' in the headers view (when marking messages
for trash). Note that the message parameter refers to the
message-at-point. When using it when composing a message (see
`mu4e-sent-messages-behavior'), this refers to the original
message being replied to / being forwarded / re-edited, and is
nil otherwise."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-maildir-shortcuts nil
"A list of maildir shortcuts.
This makes it possible to quickly go to a particular
maildir (folder), or quickly moving messages to them (e.g., for
archiving or refiling).
Each of the list elements is a plist with at least:
`:maildir' - the maildir for the shortcut (e.g. \"/archive\")
`:key' - the shortcut key.
Optionally, you can add the following:
`:hide' - if t, the shortcut is hidden from the main-view and
speedbar.
`:hide-unread' - do not show the counts of unread/total number
of matches for the maildir in the main-view, and is implied
from `:hide'.
For backward compatibility, an older form is recognized as well:
(maildir . key), where MAILDIR is a maildir (such as
\"/archive/\"), and key is a single character.
You can use these shortcuts in the headers and view buffers, for
example with `mu4e-mark-for-move-quick' (or 'm', by default) or
`mu4e-jump-to-maildir' (or 'j', by default), followed by the
designated shortcut character for the maildir.
Unlike in search queries, folder names with spaces in them must
NOT be quoted, since mu4e does this for you."
:type '(repeat (cons (string :tag "Maildir") character))
:version "1.3.9"
:group 'mu4e-folders)
(defcustom mu4e-maildir-info-delimiter
(if (member system-type '(ms-dos windows-nt cygwin))
";" ":")
"Separator character between message identifier and flags.
It defaults to ':' on most platforms, except on Windows, where it
is not allowed and we use ';' for compatibility with mbsync,
offlineimap and other programs."
:type 'string
:group 'mu4e-folders)
(defcustom mu4e-attachment-dir (expand-file-name "~/")
"Default directory for attaching and saving attachments.
This can be either a string (a file system path), or a function
that takes a filename and the mime-type as arguments, and returns
the attachment dir. See Info node `(mu4e) Attachments' for
details.
When this called for composing a message, both filename and
mime-type are nill."
:type 'directory
:group 'mu4e-folders
:safe 'stringp)
(defun mu4e-maildir-shortcuts ()
"Get `mu4e-maildir-shortcuts' in the (new) format.
Converts from the old format if needed."
(cl-map 'list
(lambda (item) ;; convert from old format?
(if (and (consp item) (not (consp (cdr item))))
`(:maildir ,(car item) :key ,(cdr item))
item))
mu4e-maildir-shortcuts))
(defun mu4e--maildirs-with-query ()
"Llike `mu4e-maildir-shortcuts', but with :query populated.
This is meant to be the exact same data structure as
`mu4e-bookmarks'."
(cl-mapcar
(lambda (m)
(append
;; we want to change the :maildir key to :name, and add a :query key
(list :name (plist-get m :maildir)
:query (format "maildir:\"%s\"" (plist-get m :maildir)))
;; next we want to append any other keys to our previous list (e.g. :hide,
;; :key, etc) but skipping :maildir (since it's renamed to :name)
(cl-loop for (key value) on m by 'cddr
when (not (equal key :maildir))
append (list key value))))
(mu4e-maildir-shortcuts)))
;; the standard folders can be functions too
(defun mu4e--get-folder (foldervar msg)
"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 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, optionallly based on MSG.
See `mu4e-drafts-folder'." (mu4e--get-folder 'mu4e-drafts-folder msg))
(defun mu4e-get-refile-folder (&optional msg)
"Get the folder for refiling, optionallly based on MSG.
See `mu4e-refile-folder'." (mu4e--get-folder 'mu4e-refile-folder msg))
(defun mu4e-get-sent-folder (&optional msg)
"Get the sent folder, optionallly based on MSG.
See `mu4e-sent-folder'." (mu4e--get-folder 'mu4e-sent-folder msg))
(defun mu4e-get-trash-folder (&optional msg)
"Get the sent folder, optionallly based on MSG.
See `mu4e-trash-folder'." (mu4e--get-folder 'mu4e-trash-folder msg))
;;; Maildirs
(defun mu4e--guess-maildir (path)
"Guess the maildir for PATH, or nil if cannot find it."
(let ((idx (string-match (mu4e-root-maildir) path)))
(when (and idx (zerop idx))
(replace-regexp-in-string
(mu4e-root-maildir)
""
(expand-file-name
(concat path "/../.."))))))
(defun mu4e-create-maildir-maybe (dir)
"Offer to create maildir DIR if it does not exist yet.
Return t if the dir already existed, or an attempt has been made to
create it -- we cannot be sure creation succeeded here, since this
is done asynchronously. Otherwise, return nil. NOte, DIR has to be
an absolute path."
(if (and (file-exists-p dir) (not (file-directory-p dir)))
(mu4e-error "File %s exists, but is not a directory" dir))
(cond
((file-directory-p dir) t)
((yes-or-no-p (mu4e-format "%s does not exist yet. Create now?" dir))
(mu4e~proc-mkdir dir) t)
(t nil)))
(defun mu4e~get-maildirs-1 (path mdir)
"Get maildirs for MDIR under PATH.
Do so recursively and produce a list of relative paths."
(let ((dirs)
(dentries
(ignore-errors
(directory-files-and-attributes
(concat path mdir) nil
"^[^.]\\|\\.[^.][^.]" t))))
(dolist (dentry dentries)
(when (and (booleanp (cadr dentry)) (cadr dentry))
(if (file-accessible-directory-p
(concat (mu4e-root-maildir) "/" mdir "/" (car dentry) "/cur"))
(setq dirs (cons (concat mdir (car dentry)) dirs)))
(unless (member (car dentry) '("cur" "new" "tmp"))
(setq dirs
(append dirs
(mu4e~get-maildirs-1 path
(concat mdir
(car dentry) "/")))))))
dirs))
(defvar mu4e-cache-maildir-list nil
"Whether to cache the list of maildirs.
Set it to t if you find
that generating the list on the fly is too slow. If you do, you
can set `(mu4e-root-maildir)-list' to nil to force regenerating the
cache the next time `mu4e-get-maildirs' gets called.")
(defvar mu4e-maildir-list nil
"Cached list of maildirs.")
(defun mu4e-get-maildirs ()
"Get maildirs under `mu4e-maildir'.
Do so recursively, and produce a list of relative paths (ie.,
/archive, /sent etc.). Most of the work is done in
`mu4e~get-maildirs-1'. Note, these results are /cached/ if
`mu4e-cache-maildir-list' is customized to non-nil. In that case,
the list of maildirs will not change until you restart mu4e."
(unless (and mu4e-maildir-list mu4e-cache-maildir-list)
(setq mu4e-maildir-list
(sort
(append
(when (file-accessible-directory-p
(concat (mu4e-root-maildir) "/cur")) '("/"))
(mu4e~get-maildirs-1 (mu4e-root-maildir) "/"))
(lambda (s1 s2) (string< (downcase s1) (downcase s2))))))
mu4e-maildir-list)
(defun mu4e-ask-maildir (prompt)
"Ask the user for a shortcut (using PROMPT).
As per (mu4e-maildir-shortcuts), then return the corresponding folder
name. If the special shortcut 'o' (for _o_ther) is used, or if
`(mu4e-maildir-shortcuts)' evaluates to nil, let user choose from
all maildirs under `mu4e-maildir'."
(let ((prompt (mu4e-format "%s" prompt)))
(if (not (mu4e-maildir-shortcuts))
(substring-no-properties
(funcall mu4e-completing-read-function prompt (mu4e-get-maildirs)))
(let* ((mlist (append (mu4e-maildir-shortcuts)
'((:maildir "ther" :key ?o))))
(fnames
(mapconcat
(lambda (item)
(concat
"["
(propertize (make-string 1 (plist-get item :key))
'face 'mu4e-highlight-face)
"]"
(plist-get item :maildir)))
mlist ", "))
(kar (read-char (concat prompt fnames))))
(if (member kar '(?/ ?o)) ;; user chose 'other'?
(substring-no-properties
(funcall mu4e-completing-read-function prompt
(mu4e-get-maildirs) nil nil "/"))
(or (plist-get
(cl-find-if (lambda (item) (= kar (plist-get item :key)))
(mu4e-maildir-shortcuts)) :maildir)
(mu4e-warn "Unknown shortcut '%c'" kar)))))))
(defun mu4e-ask-maildir-check-exists (prompt)
"Like `mu4e-ask-maildir', PROMPT for existence of the maildir.
Offer to create it if it does not exist yet."
(let* ((mdir (mu4e-ask-maildir prompt))
(fullpath (concat (mu4e-root-maildir) mdir)))
(unless (file-directory-p fullpath)
(and (yes-or-no-p
(mu4e-format "%s does not exist. Create now?" fullpath))
(mu4e~proc-mkdir fullpath)))
mdir))
;; mu4e-attachment-dir is either a string or a function that takes a
;; filename and the mime-type as argument, either (or both) which can
;; be nil
(defun mu4e~get-attachment-dir (&optional fname mimetype)
"Get the directory for saving attachments from
`mu4e-attachment-dir' (which can be either a string or a function,
see its docstring)."
(let
((dir
(cond
((stringp mu4e-attachment-dir)
mu4e-attachment-dir)
((functionp mu4e-attachment-dir)
(funcall mu4e-attachment-dir fname mimetype))
(t
(mu4e-error "unsupported type for mu4e-attachment-dir" )))))
(if dir
(expand-file-name dir)
(mu4e-error "mu4e-attachment-dir evaluates to nil"))))
(provide 'mu4e-folders)
;;; mu4e-folders.el ends here

View File

@ -34,6 +34,8 @@
(require 'mailcap)
(require 'mule-util) ;; seems _some_ people need this for truncate-string-ellipsis
(require 'mu4e-update)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-proc)
(require 'mu4e-vars)
@ -43,11 +45,14 @@
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)
(require 'mu4e-folders)
(declare-function mu4e-view "mu4e-view")
(declare-function mu4e~main-view "mu4e-main")
;;; Options
;;; Configuration
(defgroup mu4e-headers nil
"Settings for the headers view."
@ -175,7 +180,7 @@ one of: `:date', `:subject', `:size', `:prio', `:from', `:to.',
`:list'.
Note that when threading is enabled (through
`mu4e-headers-show-threads'), the headers are exclusively sorted
`mu4e-search-threads'), the headers are exclusively sorted
chronologically (`:date') by the newest message in the thread.")
(defvar mu4e-headers-sort-direction 'descending
@ -284,6 +289,7 @@ followed by the docid, followed by `mu4e~headers-docid-post'.")
"List of cells describing the various sort-options.
In the format needed for `mu4e-read-option'.")
;;; Clear
(defvar mu4e~headers-render-start nil)
@ -293,6 +299,11 @@ In the format needed for `mu4e-read-option'.")
"If non-nil, report on the time it took to render the messages.
This is mostly useful for profiling.")
(defun mu4e~headers-clear (&optional msg)
"Clear the header buffer and related data structures."
(when (buffer-live-p (mu4e-get-headers-buffer))
@ -744,14 +755,19 @@ if provided, or at the end of the buffer otherwise."
;;; Performing queries (internal)
(defconst mu4e~search-message "Searching...")
(defconst mu4e~no-matches "No matching messages found")
(defconst mu4e~end-of-results "End of search results")
(defun mu4e--search-execute (expr ignore-history)
"Search for query EXPR.
Switch to the output buffer for the results. If IGNORE-HISTORY is
true, do *not* update the query history stack."
(let* ((buf (get-buffer-create mu4e~headers-buffer-name))
(let* ((buf (get-buffer-create mu4e-headers-buffer-name))
(inhibit-read-only t)
(rewritten-expr (funcall mu4e-query-rewrite-function expr))
(rewritten-expr (funcall mu4e-search-query-rewrite-function expr))
(maxnum (unless mu4e-search-full mu4e-search-results-limit)))
(with-current-buffer buf
(mu4e-headers-mode)
@ -770,17 +786,13 @@ true, do *not* update the query history stack."
(mu4e~headers-clear mu4e~search-message)
(mu4e~proc-find
rewritten-expr
mu4e-headers-show-threads
mu4e-search-threads
mu4e-headers-sort-field
mu4e-headers-sort-direction
maxnum
mu4e-headers-skip-duplicates
mu4e-headers-include-related)))
(defconst mu4e~search-message "Searching...")
(defconst mu4e~no-matches "No matching messages found")
(defconst mu4e~end-of-results "End of search results")
(defvar mu4e~headers-view-target nil
"Whether to automatically view (open) the target message (as
per `mu4e~headers-msgid-target').")
@ -866,10 +878,6 @@ after the end of the search results."
(setq mu4e-headers-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
;; for terminal users
(define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
(define-key map "j" 'mu4e~headers-jump-to-maildir)
(define-key map "O" 'mu4e-headers-change-sorting)
@ -968,8 +976,8 @@ after the end of the search results."
(define-key menumap [toggle-threading]
'(menu-item "Toggle threading" mu4e-headers-toggle-threading
:button (:toggle .
(and (boundp 'mu4e-headers-show-threads)
mu4e-headers-show-threads))))
(and (boundp 'mu4e-search-threads)
mu4e-search-threads))))
(define-key menumap "|" '("Pipe through shell" . mu4e-view-pipe))
(define-key menumap [sepa1] '("--"))
@ -1036,7 +1044,7 @@ after the end of the search results."
(mapcar
(lambda (item)
(let* ( ;; with threading enabled, we're necessarily sorting by date.
(sort-field (if mu4e-headers-show-threads :date mu4e-headers-sort-field))
(sort-field (if mu4e-search-threads :date mu4e-headers-sort-field))
(field (car item)) (width (cdr item))
(info (cdr (assoc field
(append mu4e-header-info mu4e-header-info-custom))))
@ -1115,6 +1123,7 @@ no user-interaction ongoing."
(mu4e~mark-initialize) ;; initialize the marking subsystem
(mu4e-context-minor-mode)
(mu4e-update-minor-mode)
(mu4e-search-minor-mode)
(hl-line-mode 1))
@ -1220,9 +1229,9 @@ docid is not found."
(if mu4e-use-fancy-chars
(cddr flag-cell) (cadr flag-cell) )
""))
`((,mu4e-headers-full-search . ,mu4e-headers-full-label)
`((,mu4e-search-full . ,mu4e-headers-full-label)
(,mu4e-headers-include-related . ,mu4e-headers-related-label)
(,mu4e-headers-show-threads . ,mu4e-headers-threaded-label))
(,mu4e-search-threads . ,mu4e-headers-threaded-label))
""))
(name "mu4e-headers"))
@ -1491,17 +1500,17 @@ re-run the last search."
(mu4e-search-rerun)))
(defun mu4e-headers-toggle-threading (&optional dont-refresh)
"Toggle `mu4e-headers-show-threads'. With prefix-argument, do
"Toggle `mu4e-search-threads'. With prefix-argument, do
_not_ refresh the last search with the new setting for threading."
(interactive "P")
(mu4e~headers-toggle "Threading" 'mu4e-headers-show-threads dont-refresh))
(mu4e~headers-toggle "Threading" 'mu4e-search-threads dont-refresh))
(defun mu4e-headers-toggle-full-search (&optional dont-refresh)
"Toggle `mu4e-headers-full-search'. With prefix-argument, do
"Toggle `mu4e-search-full'. With prefix-argument, do
_not_ refresh the last search with the new setting for threading."
(interactive "P")
(mu4e~headers-toggle "Full-search"
'mu4e-headers-full-search dont-refresh))
'mu4e-search-full dont-refresh))
(defun mu4e-headers-toggle-include-related (&optional dont-refresh)
"Toggle `mu4e-headers-include-related'. With prefix-argument, do
@ -1520,14 +1529,6 @@ _not_ refresh the last search with the new setting for threading."
(defvar mu4e~headers-loading-buf nil
"A buffer for loading a message view.")
(defun mu4e~decrypt-p (msg)
"Should we decrypt this message?"
(when mu4e-view-use-old ;; we don't decrypt in the gnus-view case
(and (member 'encrypted (mu4e-message-field msg :flags))
(if (eq mu4e-decryption-policy 'ask)
(yes-or-no-p (mu4e-format "Decrypt message?"))
mu4e-decryption-policy))))
(defun mu4e-headers-view-message ()
"View message at point .
If there's an existing window for the view, re-use that one . If
@ -1545,8 +1546,8 @@ window . "
(if (functionp mu4e-view-auto-mark-as-read)
(funcall mu4e-view-auto-mark-as-read msg)
mu4e-view-auto-mark-as-read))
(decrypt (mu4e~decrypt-p msg))
(verify mu4e-view-use-old)
(decrypt nil) ;; XXX remove
(verify nil) ;; XXX remove
(viewwin (mu4e~headers-redraw-get-view-window)))
(unless (window-live-p viewwin)
(mu4e-error "Cannot get a message view"))
@ -1741,6 +1742,32 @@ other windows."
(kill-buffer)
(mu4e~main-view 'refresh))))
;;; Loading messages
;;
(defvar mu4e-loading-mode-map nil "Keymap for *mu4e-loading* buffers.")
(unless mu4e-loading-mode-map
(setq mu4e-loading-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "n" 'ignore)
(define-key map "p" 'ignore)
(define-key map "q"
(lambda()(interactive)
(if (eq mu4e-split-view 'single-window)
'kill-buffer
'kill-buffer-and-window)))
map)))
(fset 'mu4e-loading-mode-map mu4e-loading-mode-map)
(define-derived-mode mu4e-loading-mode special-mode
"mu4e:loading"
(use-local-map mu4e-loading-mode-map)
(let ((inhibit-read-only t))
(erase-buffer)
(insert (propertize "Loading message..."
'face 'mu4e-system-face 'intangible t))))
;;; _
(provide 'mu4e-headers)
;;; mu4e-headers.el ends here

View File

@ -28,6 +28,7 @@
;;; Code:
(require 'seq)
(require 'ido)
(require 'cl-lib)
@ -44,6 +45,126 @@ If the string exceeds this limit, it will be truncated to fit."
:type 'integer
:group 'mu4e)
(defcustom mu4e-completing-read-function 'ido-completing-read
"Function to be used to receive user-input during completion.
Suggested possible values are:
* `completing-read': built-in completion method
* `ido-completing-read': dynamic completion within the minibuffer."
:type 'function
:options '(completing-read ido-completing-read)
:group 'mu4e)
(defcustom mu4e-use-fancy-chars nil
"When set, allow fancy (Unicode) characters for marks/threads.
You can customize the exact fancy characters used with
`mu4e-marks' and various `mu4e-headers-..-mark' and
`mu4e-headers..-prefix' variables."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-display-update-status-in-modeline nil
"Non-nil value will display the update status in the modeline."
:group 'mu4e
:type 'boolean)
;; maybe move the next ones... but they're convenient
;; here because they're needed in multiple buffers.
(defcustom mu4e-view-auto-mark-as-read t
"Automatically mark messages are 'read' when you read them.
This is the default behavior, but can be turned off, for example
when using a read-only file-system.
This can also be set to a function; if so, receives a message
plist which should evaluate to nil if the message should *not* be
marked as read-only, or non-nil otherwise."
:type '(choice
boolean
function)
:group 'mu4e-view)
(defcustom mu4e-split-view 'horizontal
"How to show messages / headers.
A symbol which is either:
* `horizontal': split horizontally (headers on top)
* `vertical': split vertically (headers on the left).
* `single-window': view and headers in one window (mu4e will try not to
touch your window layout), main view in minibuffer
* anything else: don't split (show either headers or messages,
not both)
Also see `mu4e-headers-visible-lines'
and `mu4e-headers-visible-columns'."
:type '(choice (const :tag "Split horizontally" horizontal)
(const :tag "Split vertically" vertical)
(const :tag "Single window" single-window)
(const :tag "Don't split" nil))
:group 'mu4e-headers)
;;; Buffers
(defconst mu4e-main-buffer-name " *mu4e-main*"
"Name of the mu4e main buffer.
The default name starts with SPC and therefore is not visible in
buffer list.")
(defconst mu4e-headers-buffer-name "*mu4e-headers*"
"Name of the buffer for message headers.")
(defconst mu4e-embedded-buffer-name " *mu4e-embedded*"
"Name for the embedded message view buffer.")
(defun mu4e-get-headers-buffer()
"Get the name of the headers buffer."
(get-buffer mu4e-headers-buffer-name))
(defun mu4e-get-view-buffer()
"Get the name of the view buffer."
;; avoid a 'require.
(when (boundp 'gnus-article-buffer) gnus-article-buffer))
(defun mu4e-select-other-view ()
"Switch between headers view and message view."
(interactive)
(let* ((other-buf
(cond
((eq major-mode 'mu4e-headers-mode)
(mu4e-get-view-buffer))
((eq major-mode 'mu4e-view-mode)
(mu4e-get-headers-buffer))))
(other-win (and other-buf (get-buffer-window other-buf))))
(if (window-live-p other-win)
(select-window other-win)
(mu4e-message "No window to switch to"))))
;;; Windows
(defun mu4e-hide-other-mu4e-buffers ()
"Bury mu4e buffers.
Hide (main, headers, view) (and delete all windows displaying
it). Do _not_ bury the current buffer, though."
(interactive)
(unless (eq mu4e-split-view 'single-window)
(let ((curbuf (current-buffer)))
;; note: 'walk-windows' does not seem to work correctly when modifying
;; windows; therefore, the doloops here
(dolist (frame (frame-list))
(dolist (win (window-list frame nil))
(with-current-buffer (window-buffer win)
(unless (eq curbuf (current-buffer))
(when (member major-mode '(mu4e-headers-mode mu4e-view-mode))
(when (eq t (window-deletable-p win))
(delete-window win))))))) t)))
;;; Modeline
(defun mu4e-quote-for-modeline (str)
"Quote STR to be used literally in the modeline.
The string will be shortened to fit if its length exceeds
`mu4e-modeline-max-width'."
(replace-regexp-in-string
"%" "%%"
(truncate-string-to-width str mu4e-modeline-max-width 0 nil t)))
;;; Messages, warnings and errors
(defun mu4e-format (frm &rest args)
@ -175,16 +296,6 @@ Function will return the cdr of the list element."
(plist-get mu4e--server-props :version))
(mu4e-error "Version unknown; did you start mu4e?")))
(defun mu4e-personal-addresses(&optional no-regexp)
"Get the list user's personal addresses, as passed to mu init.
The address are either plain e-mail address or /regular
expressions/. When NO-REGEXP is non-nil, do not include regexp
address patterns (if any)."
(seq-remove
(lambda(addr) (and no-regexp (string-match-p "^/.*/" addr)))
(when mu4e--server-props
(plist-get mu4e--server-props :personal-addresses))))
(defun mu4e-last-query-results ()
"Get the results (counts) of the last cached queries.
@ -279,17 +390,9 @@ log-buffer. See `mu4e-show-log'."
(mu4e-warn "No debug log available"))
(switch-to-buffer buf)))
;;; Misc
(defun mu4e-quote-for-modeline (str)
"Quote STR to be used literally in the modeline.
The string will be shortened to fit if its length exceeds
`mu4e-modeline-max-width'."
(replace-regexp-in-string
"%" "%%"
(truncate-string-to-width str mu4e-modeline-max-width 0 nil t)))
;;; Flags
;; Converting flags->string and vice-versa
(defun mu4e-flags-to-string (flags)
@ -337,6 +440,8 @@ http://cr.yp.to/proto/maildir.html."
(_ nil))))
str))))
;;; Misc
(defun mu4e-display-size (size)
"Get a human-friendly string representation of SIZE (in bytes)."
(cond
@ -383,6 +488,31 @@ This includes expanding e.g. 3-5 into 3,4,5. If the letter
(mu4e-warn "Attachment number must be greater than 0 (%d)" x))))
list)))
(defun mu4e-make-temp-file (ext)
"Create a self-destructing temporary file with extension EXT.
The file will self-destruct in a short while, enough to open it
in an external program."
(let ((tmpfile (make-temp-file "mu4e-" nil (concat "." ext))))
(run-at-time "30 sec" nil
(lambda () (ignore-errors (delete-file tmpfile))))
tmpfile))
(defun mu4e-display-manual ()
"Display the mu4e manual page for the current mode.
Or go to the top level if there is none."
(interactive)
(info (cl-case major-mode
('mu4e-main-mode "(mu4e)Main view")
('mu4e-headers-mode "(mu4e)Headers view")
('mu4e-view-mode "(mu4e)Message view")
(t "mu4e"))))
;;; Macros
(defmacro mu4e-setq-if-nil (var val)
"Set VAR to VAL if VAR is nil."
`(unless ,var (setq ,var ,val)))
(provide 'mu4e-helpers)
;;; mu4e-helpers.el ends here

View File

@ -53,14 +53,31 @@
(require 'cl-lib)
(require 'mu4e-mark)
(require 'mu4e-utils)
(require 'mu4e-helpers)
(require 'mu4e-contacts)
(require 'mu4e-headers)
(require 'mu4e-view)
(require 'mu4e-vars)
(when mu4e-view-use-old
(mu4e-error "iCalender support is not available with the old viewer"))
;;; Configuration
;;;; Calendar
(defgroup mu4e-icalendar nil
"Icalendar related settings."
:group 'mu4e)
(defcustom mu4e-icalendar-trash-after-reply nil
"If non-nil, trash the icalendar invitation after replying."
:type 'boolean
:group 'mu4e-icalendar)
(defcustom mu4e-icalendar-diary-file nil
"If non-nil, the file in which to add events upon reply."
:type '(choice (const :tag "Do not insert a diary entry" nil)
(string :tag "Insert a diary entry in this file"))
:group 'mu4e-icalendar)
;;;###autoload
(defun mu4e-icalendar-setup ()
"Perform the necessary initialization to use mu4e-icalendar."

View File

@ -1,6 +1,6 @@
;;; mu4e-lists.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
;;; mu4e-lists.el -- part of mu4e -*- lexical-binding: t -*-
;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -27,7 +27,10 @@
;;; Code:
(defvar mu4e~mailing-lists
(require 'cl-lib)
;;; Configuration
(defvar mu4e-mailing-lists
'( ("bbdb-info.lists.sourceforge.net" . "BBDB")
("boost-announce.lists.boost.org" . "BoostA")
("boost-interest.lists.boost.org" . "BoostI")
@ -81,22 +84,50 @@
("wl-en.ml.gentei.org" . "WdrLust")
("xapian-devel.lists.xapian.org" . "Xapian")
("zsh-users.zsh.org" . "ZshUsr"))
"AList of cells (MAILING-LIST-ID . SHORTNAME)")
"AList of cells (MAILING-LIST-ID . SHORTNAME).")
(defcustom mu4e-user-mailing-lists nil
"An alist with cells (MAILING-LIST-ID . SHORTNAME); these are
used in addition to the built-in list `mu4e~mailing-lists'."
"An alist with cells (MAILING-LIST-ID . SHORTNAME).
These are used in addition to the built-in list `mu4e~mailing-lists'."
:group 'mu4e-headers
:type '(repeat (cons string string)))
(defcustom mu4e-mailing-list-patterns nil
"A list of regex patterns to capture a shortname out of a list
ID. For the first regex that matches, its first matchgroup will
be used as the shortname."
"A list of regexps to capture a shortname out of a list-id.
For the first regex that matches, its first matchgroup will be
used as the shortname."
:group 'mu4e-headers
:type '(repeat (regexp)))
(defvar mu4e--lists-hash nil
"Hashtable of mailing-list-id => shortname.
Based on `mu4e-mailing-lists' and `mu4e-user-mailing-lists'.")
(defun mu4e-get-mailing-list-shortname (list-id)
"Get the shortname for a mailing-list with list-id LIST-ID.
Based on `mu4e-mailing-lists', `mu4e-user-mailing-lists', and
`mu4e-mailing-list-patterns'."
(unless mu4e--lists-hash
(setq mu4e--lists-hash (make-hash-table :test 'equal))
(dolist (cell mu4e-mailing-lists)
(puthash (car cell) (cdr cell) mu4e--lists-hash))
(dolist (cell mu4e-user-mailing-lists)
(puthash (car cell) (cdr cell) mu4e--lists-hash)))
(or
(gethash list-id mu4e--lists-hash)
(and (boundp 'mu4e-mailing-list-patterns)
(cl-member-if
(lambda (pattern)
(string-match pattern list-id))
mu4e-mailing-list-patterns)
(match-string 1 list-id))
;; if it's not in the db, take the part until the first dot if there is one;
;; otherwise just return the whole thing
(if (string-match "\\([^.]*\\)\\." list-id)
(match-string 1 list-id)
list-id)))
;;; _
(provide 'mu4e-lists)
;;; mu4e-lists.el ends here

View File

@ -25,14 +25,20 @@
;;; Code:
(require 'smtpmail) ;; the queueing stuff (silence elint)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-helpers) ;; utility functions
(require 'mu4e-context) ;; the context
(require 'mu4e-bookmarks)
(require 'mu4e-folders)
(require 'mu4e-update)
(require 'mu4e-contacts)
(require 'mu4e-search)
(require 'mu4e-vars) ;; mu-wide variables
(require 'cl-lib)
;;; Mode
;; Configuration
(define-obsolete-variable-alias
'mu4e-main-buffer-hide-personal-addresses
@ -49,6 +55,37 @@ part of the personal addresses.")
"When set to t, do not hide bookmarks or maildirs that have
no unread messages.")
;;; Mode
(define-derived-mode mu4e-org-mode org-mode "mu4e:org"
"Major mode for mu4e documents, derived from
`org-mode'.")
(defun mu4e-info (path)
"Show a buffer with the information (an org-file) at PATH."
(unless (file-exists-p path)
(mu4e-error "Cannot find %s" path))
(let ((curbuf (current-buffer)))
(find-file path)
(mu4e-org-mode)
(setq buffer-read-only t)
(define-key mu4e-org-mode-map (kbd "q")
`(lambda ()
(interactive)
(bury-buffer)
(switch-to-buffer ,curbuf)))))
(defun mu4e-about ()
"Show the mu4e 'about' page."
(interactive)
(mu4e-info (concat mu4e-doc-dir "/mu4e-about.org")))
(defun mu4e-news ()
"Show the mu4e 'about' page."
(interactive)
(mu4e-info (concat mu4e-doc-dir "/NEWS.org")))
(defvar mu4e-main-mode-map
(let ((map (make-sparse-keymap)))
@ -85,6 +122,7 @@ no unread messages.")
overwrite-mode 'overwrite-mode-binary)
(mu4e-context-minor-mode)
(mu4e-search-minor-mode)
(mu4e-update-minor-mode)
(set (make-local-variable 'revert-buffer-function) #'mu4e~main-view-real))
@ -121,15 +159,23 @@ clicked."
'mouse-face 'highlight newstr)
newstr))
(defun mu4e--longest-of-maildirs-and-bookmarks ()
"Return the length of longest name of bookmarks and maildirs."
(cl-loop for b in (append (mu4e-bookmarks)
(mu4e--maildirs-with-query))
maximize (string-width (plist-get b :name))))
(defun mu4e~main-bookmarks ()
;; TODO: it's a bit uncool to hard-code the "b" shortcut...
(cl-loop with bmks = (mu4e-bookmarks)
with longest = (mu4e~longest-of-maildirs-and-bookmarks)
with longest = (mu4e--longest-of-maildirs-and-bookmarks)
with queries = (mu4e-last-query-results)
for bm in bmks
for key = (string (plist-get bm :key))
for name = (plist-get bm :name)
for query = (funcall (or mu4e-query-rewrite-function #'identity)
for query = (funcall (or mu4e-search-query-rewrite-function #'identity)
(plist-get bm :query))
for qcounts = (and (stringp query)
(cl-loop for q in queries
@ -162,8 +208,8 @@ clicked."
(defun mu4e~main-maildirs ()
"Return a string of maildirs with their counts."
(cl-loop with mds = (mu4e~maildirs-with-query)
with longest = (mu4e~longest-of-maildirs-and-bookmarks)
(cl-loop with mds = (mu4e--maildirs-with-query)
with longest = (mu4e--longest-of-maildirs-and-bookmarks)
with queries = (plist-get mu4e--server-props :queries)
for m in mds
for key = (string (plist-get m :key))
@ -217,13 +263,15 @@ clicked."
"The revert buffer function for `mu4e-main-mode'."
(mu4e~main-view-real-1 'refresh))
(declare-function mu4e--start "mu4e")
(defun mu4e~main-view-real-1 (&optional refresh)
"Create `mu4e-main-buffer-name' and set it up.
When REFRESH is non nil refresh infos from server."
(let ((inhibit-read-only t))
;; Maybe refresh infos from server.
(if refresh
(mu4e~start 'mu4e~main-redraw-buffer)
(mu4e--start 'mu4e~main-redraw-buffer)
(mu4e~main-redraw-buffer))))
(defun mu4e~main-redraw-buffer ()

View File

@ -31,6 +31,7 @@
(require 'mu4e-proc)
(require 'mu4e-utils)
(require 'mu4e-message)
(require 'mu4e-folders)
;; keep byte-compiler happy
(declare-function mu4e~headers-mark "mu4e-headers")

View File

@ -38,52 +38,14 @@
(defvar mu4e~view-message)
(defvar shr-inhibit-images)
(defcustom mu4e-html2text-command 'mu4e-shr2text
"Either a shell command or a function that converts from html to plain text.
If it is a shell command, the command reads html from standard
input and outputs plain text on standard output. If you use the
htmltext program, it's recommended you use \"html2text -utf8
-width 72\". Alternatives are the python-based html2markdown, w3m
and on MacOS you may want to use textutil.
It can also be a function, which takes a messsage-plist as
argument and is expected to return the textified html as output.
For backward compatibility, it can also be a parameterless
function which is run in the context of a buffer with the html
and expected to transform this (like the `html2text' function).
In all cases, the output is expected to be in UTF-8 encoding.
The default is to use the shr renderer."
:type '(choice string function)
:group 'mu4e-view)
(defcustom mu4e-view-prefer-html nil
"Whether to base the body display on the html-version.
If the e-mail message has no html-version the plain-text version
is always used."
:type 'boolean
:group 'mu4e-view)
(defcustom mu4e-view-html-plaintext-ratio-heuristic 5
"Ratio between the length of the html and the plain text part.
Below this ratio mu4e will consider the plain text part to be
'This messages requires html' text bodies. You can neutralize
it (always show the text version) by using
`most-positive-fixnum'."
:type 'integer
:group 'mu4e-view)
(defvar mu4e-message-body-rewrite-functions '(mu4e-message-outlook-cleanup)
"List of functions to transform the message body text.
The functions take two parameters, MSG and TXT, which are the
message-plist and the text, which is the plain-text version,
ossibly converted from html and/or transformed by earlier rewrite
functions.")
(make-obsolete-variable 'mu4e-html2text-command "No longer in use" "1.7.0")
(make-obsolete-variable 'mu4e-view-prefer-html "No longer in use" "1.7.0")
(make-obsolete-variable 'mu4e-view-html-plaintext-ratio-heuristic
"No longer in use" "1.7.0")
(make-obsolete-variable 'mu4e-message-body-rewrite-functions
"No longer in use" "1.7.0")
;;; Message fields
(defsubst mu4e-message-field-raw (msg field)
@ -169,89 +131,11 @@ This is equivalent to:
(mu4e-message-field (mu4e-message-at-point) FIELD)."
(mu4e-message-field (mu4e-message-at-point) field))
(defvar mu4e~message-body-html nil
"Whether the body text uses HTML.")
(defun mu4e~message-use-html-p (msg prefer-html)
"Do we want to PREFER-HTML for MSG?
Determine whether we want
to use html or text. The decision is based on PREFER-HTML and
whether the message supports the given representation."
(let* ((txt (mu4e-message-field msg :body-txt))
(html (mu4e-message-field msg :body-html))
(txt-len (length txt))
(html-len (length html))
(txt-limit (* mu4e-view-html-plaintext-ratio-heuristic txt-len))
(txt-limit (if (>= txt-limit 0) txt-limit most-positive-fixnum)))
(cond
; user prefers html --> use html if there is
(prefer-html (> html-len 0))
;; otherwise (user prefers text) still use html if there is not enough
;; text
((< txt-limit html-len) t)
;; otherwise, use text
(t nil))))
(defun mu4e~message-body-has-content-type-param (msg param)
"Does the MSG have a content-type parameter PARAM?"
(cdr
(assoc param (mu4e-message-field msg :body-txt-params))))
(defun mu4e~safe-iequal (a b)
"Is string A equal to a downcased B?"
(and b (equal (downcase b) a)))
(defun mu4e-message-body-text (msg &optional prefer-html)
"Get the body in text form for message MSG.
This is either :body-txt, or if not available, :body-html
converted to text, using `mu4e-html2text-command' is non-nil, it
will use that. Normally, this function prefers the text part,
unless PREFER-HTML is non-nil."
(setq mu4e~message-body-html (mu4e~message-use-html-p msg prefer-html))
(let ((body
(if mu4e~message-body-html
;; use an htmml body
(cond
((stringp mu4e-html2text-command)
(mu4e~html2text-shell msg mu4e-html2text-command))
((functionp mu4e-html2text-command)
(if (help-function-arglist mu4e-html2text-command)
(funcall mu4e-html2text-command msg)
;; oldskool parameterless mu4e-html2text-command
(mu4e~html2text-wrapper mu4e-html2text-command msg)))
(t (mu4e-error "Invalid `mu4e-html2text-command'")))
;; use a text body
(or (with-temp-buffer
(insert (or (mu4e-message-field msg :body-txt) ""))
(if (mu4e~safe-iequal "flowed"
(mu4e~message-body-has-content-type-param
msg "format"))
(fill-flowed nil
(mu4e~safe-iequal
"yes"
(mu4e~message-body-has-content-type-param
msg "delsp"))))
(buffer-string)) ""))))
(dolist (func mu4e-message-body-rewrite-functions)
(setq body (funcall func msg body)))
body))
(defun mu4e-message-outlook-cleanup (_msg body)
"Clean-up MSG's BODY.
Esp. MS-Outlook-originating message may not advertise the correct
encoding (e.g. 'iso-8859-1' instead of 'windows-1252'), thus
giving us these funky chars. here, we either remove them, or
replace with."
(with-temp-buffer
(insert body)
(goto-char (point-min))
(while (re-search-forward "\015 ’]" nil t)
(replace-match
(cond
((string= (match-string 0) "’") "'")
((string= (match-string 0) " ") " ")
(t ""))))
(buffer-string)))
(defun mu4e-message-body-text (_msg &optional _prefer-html)
"Get the body in text form for message MSG."
"" ;; not implemented for Gnus mode.
)
(defun mu4e-message-contact-field-matches (msg cfield rx)
"Does MSG's contact-field CFIELD match rx?
@ -284,7 +168,8 @@ expressions, in which case any of those are tried for a match."
(mu4e-message-field msg cfield))))))
(defun mu4e-message-contact-field-matches-me (msg cfield)
"Does contact-field CFIELD in MSG match me? Checks whether any
"Does contact-field CFIELD in MSG match me?
Checks whether any
of the of the contacts in field CFIELD (either :to, :from, :cc or
:bcc) of msg MSG matches *me*, that is, any of the addresses for
which `mu4e-personal-address-p' return t. Returns the contact
@ -293,19 +178,20 @@ cell that matched, or nil."
(mu4e-message-field msg cfield)))
(defun mu4e-message-sent-by-me (msg)
"Is this message (to be) sent by me?
"Is this MSG (to be) sent by me?
Checks if the from field matches user's personal addresses."
(mu4e-message-contact-field-matches-me msg :from))
(defun mu4e-message-personal-p (msg)
"Does message have user's personal address in any of the
contact fields?"
"Does MSG have user's personal address?
In any of the contact
fields?"
(cl-some
(lambda (field)
(mu4e-message-contact-field-matches-me msg field))
'(:from :to :cc :bcc)))
(defsubst mu4e-message-part-field (msgpart field)
(defsubst mu4e-message-part-field (msgpart field)
"Get some FIELD from MSGPART.
A part would look something like:
(:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)."
@ -322,41 +208,14 @@ symbol, see `mu4e-header-info'."
(plist-get (mu4e-message-at-point) field))
;;; Html2Text
(make-obsolete 'mu4e-shr2text "No longer in use" "1.7.0")
(defun mu4e~html2text-wrapper (func msg)
"Apply FUNC on a temporary buffer with html from MSG.
Return the buffer contents."
(with-temp-buffer
(insert (or (mu4e-message-field msg :body-html) ""))
(funcall func)
(or (buffer-string) "")))
(defun mu4e-shr2text (msg)
"Convert html in MSG to text using the shr engine.
This can be used in `mu4e-html2text-command' in a new enough
Emacs. Based on code by Titus von der Malsburg."
(mu4e~html2text-wrapper
(lambda ()
(let (
;; When HTML emails contain references to remote images,
;; retrieving these images leaks information. For example,
;; the sender can see when I opened the email and from which
;; computer (IP address). For this reason, it is preferable
;; to not retrieve images.
;; See this discussion on mu-discuss:
;; https://groups.google.com/forum/#!topic/mu-discuss/gr1cwNNZnXo
(shr-inhibit-images t))
(shr-render-region (point-min) (point-max)))) msg))
(defun mu4e~html2text-shell (msg _cmd)
"Convert html2 text in MSG using a shell function CMD."
(mu4e~html2text-wrapper
(lambda ()
(let* ((tmp-file (mu4e-make-temp-file "html")))
(write-region (point-min) (point-max) tmp-file)
(erase-buffer)
(call-process-shell-command mu4e-html2text-command tmp-file t t)
(delete-file tmp-file))) msg))
(defun mu4e-copy-message-path ()
"Copy the message-path of message at point to the kill ring."
(interactive)
(let ((path (mu4e-message-field-at-point :path)))
(kill-new path)
(mu4e-message "Saved '%s' to kill-ring" path)))
;;; _
(provide 'mu4e-message)

View File

@ -122,7 +122,7 @@ the query (for links starting with 'query:')."
((string-match "^msgid:\\(.+\\)" link)
(mu4e-view-message-with-message-id (match-string 1 link)))
((string-match "^query:\\(.+\\)" link)
(mu4e-headers-search (match-string 1 link) current-prefix-arg))
(mu4e-search (match-string 1 link) current-prefix-arg))
(t (mu4e-error "Unrecognized link type '%s'" link))))
(make-obsolete 'org-mu4e-open 'mu4e-org-open "1.3.6")

View File

@ -25,9 +25,114 @@
;;; Code:
(require 'mu4e-helpers)
(require 'mu4e-utils)
(require 'mu4e-meta)
;;; Configuration
(defcustom mu4e-mu-home nil
"Location of an alternate mu home dir.
If not set, use the defaults, based on the XDG Base Directory
Specification."
:group 'mu4e
:type '(choice (const :tag "Default location" nil)
(directory :tag "Specify location"))
:safe 'stringp)
(defcustom mu4e-mu-binary (executable-find "mu")
"Name of the mu-binary to use.
If it cannot be found in your PATH, you can specify the full
path."
:type 'file
:group 'mu4e
:safe 'stringp)
(defcustom mu4e-mu-debug nil
"Whether to run the mu binary in debug-mode.
Setting this to t increases the amount of information in the log."
:type 'boolean
:group 'mu4e)
(make-obsolete-variable
'mu4e-maildir
"determined by server; see `mu4e-root-maildir'." "1.3.8")
(defcustom mu4e-change-filenames-when-moving nil
"Change message file names when moving them.
When moving messages to different folders, normally mu/mu4e keep
the base filename the same (the flags-part of the filename may
change still). With this option set to non-nil, mu4e instead
changes the filename. This latter behavior works better with some
IMAP-synchronization programs such as mbsync; the default works
better with e.g. offlineimap."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
;; Handlers are not strictly internal, but are not meant
;; for overriding outside mu4e. The are mainly for breaking
;; dependency cycles.
(defvar mu4e-error-func nil
"Function called for each error received.
The function is passed an error plist as argument. See
`mu4e~proc-filter' for the format.")
(defvar mu4e-update-func nil
"Function called for each :update sexp returned.
The function is passed a msg sexp as argument.
See `mu4e~proc-filter' for the format.")
(defvar mu4e-remove-func nil
"Function called for each :remove sexp returned.
This happens when some message has been deleted. The function is
passed the docid of the removed message.")
(defvar mu4e-sent-func nil
"Function called for each :sent sexp received.
This happens when some message has been sent. The function is
passed the docid and the draft-path of the sent message.")
(defvar mu4e-view-func nil
"Function called for each single-message sexp.
The function is passed a message sexp as argument. See
`mu4e~proc-filter' for the format.")
(defvar mu4e-header-func nil
"Function called for each message-header received.
The function is passed a msg plist as argument. See
`mu4e~proc-filter' for the format.")
(defvar mu4e-found-func nil
"Function called for when we received a :found sexp.
This happens after the headers have been returned, to report on
the number of matches. See `mu4e~proc-filter' for the format.")
(defvar mu4e-erase-func nil
"Function called we receive an :erase sexp.
This before new headers are displayed, to clear the current
headers buffer. See `mu4e~proc-filter' for the format.")
(defvar mu4e-compose-func nil
"Function called for each compose message received.
I.e., the original message that is used as basis for composing a
new message (i.e., either a reply or a forward); the function is
passed msg and a symbol (either reply or forward). See
`mu4e~proc-filter' for the format of <msg-plist>.")
(defvar mu4e-info-func nil
"Function called for each (:info type ....) sexp received.
from the server process.")
(defvar mu4e-pong-func nil
"Function called for each (:pong type ....) sexp received.")
(defvar mu4e-contacts-func nil
"A function called for each (:contacts (<list-of-contacts>)
sexp received from the server process.")
(make-obsolete-variable 'mu4e-temp-func "No longer used" "1.7.0")
;;; Internal vars
(defvar mu4e~proc-buf nil
@ -48,11 +153,10 @@
(concat mu4e~cookie-pre "\\([[:xdigit:]]+\\)" mu4e~cookie-post)
"Regular expression matching the length cookie.
Match 1 will be the length (in hex).")
;;; Functions
(defun mu4e~proc-running-p ()
"Whether the mu process is running."
(defun mu4e-running-p ()
"Whether mu4e is running.
Checks whether the server process is live."
(and mu4e~proc-process
(memq (process-status mu4e~proc-process)
'(run open listen connect stop))
@ -205,15 +309,6 @@ The server output is as follows:
(plist-get sexp :original)
(plist-get sexp :include)))
;; do something with a temporary file
((plist-get sexp :temp)
(funcall mu4e-temp-func
(plist-get sexp :temp) ;; name of the temp file
(plist-get sexp :what) ;; what to do with it
;; (pipe|emacs|open-with...)
(plist-get sexp :docid) ;; docid of the message
(plist-get sexp :param)));; parameter for the action
;; get some info
((plist-get sexp :info)
(funcall mu4e-info-func sexp))
@ -310,7 +405,7 @@ backslashes and double-quotes."
(defun mu4e~call-mu (form)
"Call 'mu' with some command."
(unless (mu4e~proc-running-p) (mu4e~proc-start))
(unless (mu4e-running-p) (mu4e~proc-start))
(let* ((print-length nil) (print-level nil)
(cmd (format "%S" form)))
(mu4e-log 'to-server "%s" cmd)
@ -482,9 +577,9 @@ the function registered as `mu4e-view-func'."
:docid ,(if (stringp docid-or-msgid) nil docid-or-msgid)
:msgid ,(if (stringp docid-or-msgid) docid-or-msgid nil)
:mark-as-read ,mark-as-read
:extract-images ,(if mu4e-view-show-images t nil)
:decrypt ,(and decrypt t)
:verify ,(and verify t))))
:extract-images nil ;; XXX remove
:decrypt ,(and decrypt t) ;; XXX remove
:verify ,(and verify t)))) ;; XXX remove
(defun mu4e~proc-view-path (path &optional images decrypt verify)
"View message at PATH..

View File

@ -1,6 +1,6 @@
;;; mu4e-speedbar --- Speedbar support for mu4e -*- lexical-binding: t -*-
;; Copyright (C) 2012-2020 Antono Vasiljev, Dirk-Jan C. Binnema
;; Copyright (C) 2012-2021 Antono Vasiljev, Dirk-Jan C. Binnema
;; Author: Antono Vasiljev <self@antono.info>
;; Version: 0.1
@ -35,7 +35,7 @@
(require 'mu4e-vars)
(require 'mu4e-headers)
(require 'mu4e-context)
(require 'mu4e-utils)
(require 'mu4e-bookmarks)
(defvar mu4e-main-speedbar-key-map nil
"Keymap used when in mu4e display mode.")
@ -91,8 +91,7 @@
(defun mu4e~speedbar-maildir (&optional _text token _ident)
"Jump to maildir TOKEN. TEXT and INDENT are not used."
(dframe-with-attached-buffer
(mu4e-headers-search (concat "\"maildir:" token "\"")
current-prefix-arg)))
(mu4e-search (concat "\"maildir:" token "\"") current-prefix-arg)))
(defun mu4e~speedbar-render-bookmark-list ()
"Insert the list of bookmarks in the speedbar"
@ -110,7 +109,7 @@
(defun mu4e~speedbar-bookmark (&optional _text token _ident)
"Run bookmarked query TOKEN. TEXT and INDENT are not used."
(dframe-with-attached-buffer
(mu4e-headers-search token current-prefix-arg)))
(mu4e-search token current-prefix-arg)))
;;;###autoload
(defun mu4e-speedbar-buttons (&optional _buffer)

320
mu4e/mu4e-update.el Normal file
View File

@ -0,0 +1,320 @@
;;; mu4e-update.el -- part of mu4e, -*- lexical-binding: t -*-
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;; mu4e is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; mu4e is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Updating the mu4e message: calling a mail retrieval program
;; and re-running the index.
;;; Code:
(require 'mu4e-helpers)
(require 'mu4e-proc)
;;; Customization
(defcustom mu4e-get-mail-command "true"
"Shell command to run to retrieve new mail.
Common values are \"offlineimap\", \"fetchmail\" or \"mbsync\", but
arbitrary shell-commands can be used.
When set to the literal string \"true\" (the default), the
command simply finishes successfully (running the 'true' command)
without retrieving any mail. This can be useful when mail is
already retrieved in another way."
:type 'string
:group 'mu4e
:safe 'stringp)
(defcustom mu4e-index-update-error-warning t
"Whether to display warnings during the retrieval process.
This depends on the `mu4e-get-mail-command' exit code."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-update-error-continue t
"Whether to continue with indexing after an error during retrieval."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-update-in-background t
"Whether to retrieve mail in the background."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-cleanup t
"Whether to run a cleanup phase after indexing.
That is, validate that each message in the message store has a
corresponding message file in the filesystem.
Having this option as t ensures that no non-existing messages are
shown but can slow with large message stores on slow file-systems."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-lazy-check nil
"Whether to only use a 'lazy check' during reindexing.
This influences how we decide whether a message
needs (re)indexing or not.
When this is set to non-nil, mu only uses the directory
timestamps to decide whether it needs to check the messages
beneath it. This makes indexing much faster, but might miss some
changes. For this, you might want to occasionally call
`mu4e-update-index-nonlazy'."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-update-interval nil
"Number of seconds between mail retrieval/indexing.
If nil, don't update automatically. Note, changes in
`mu4e-update-interval' only take effect after restarting mu4e."
:type '(choice (const :tag "No automatic update" nil)
(integer :tag "Seconds"))
:group 'mu4e
:safe 'integerp)
(defvar mu4e-update-pre-hook nil
"Hook run just *before* the mail-retrieval / database updating process starts.
You can use this hook for example to `mu4e-get-mail-command' with
some specific setting.")
(defcustom mu4e-hide-index-messages nil
"Whether to hide the \"Indexing...\" and contacts messages."
:type 'boolean
:group 'mu4e)
(defvar mu4e-index-updated-hook nil
"Hook run when the indexing process had one or more updated messages.
This can be used as a simple way to invoke some action when new
messages appear, but note that an update in the index does not
necessarily mean a new message.")
(defvar mu4e-message-changed-hook nil
"Hook run when there is a message changed in db.
For new messages, it depends on `mu4e-index-updated-hook'. This
can be used as a simple way to invoke some action when a message
changed.")
(make-obsolete-variable 'mu4e-msg-changed-hook
'mu4e-message-changed-hook "0.9.19")
;;; Internal variables
(defvar mu4e--progress-reporter nil
"Internal, the progress reporter object.")
(defvar mu4e--update-timer nil
"The mu4e update timer.")
(defconst mu4e--update-name " *mu4e-update*"
"Name of the process and buffer to update mail.")
(defconst mu4e--update-buffer-height 8
"Height of the mu4e message retrieval/update buffer.")
(defvar mu4e--get-mail-ask-password "mu4e get-mail: Enter password: "
"Query string for `mu4e-get-mail-command' password.")
(defvar mu4e--get-mail-password-regexp "^Remote: Enter password: $"
"Regexp for a `mu4e-get-mail-command' password query.")
(defun mu4e--get-mail-process-filter (proc msg)
"Filter the MSG output of the `mu4e-get-mail-command' PROC.
Currently the filter only checks if the command asks for a
password by matching the output against
`mu4e~get-mail-password-regexp'. The messages are inserted into
the process buffer.
Also scrolls to the final line, and update the progress
throbber."
(when mu4e--progress-reporter
(progress-reporter-update mu4e--progress-reporter))
(when (string-match mu4e--get-mail-password-regexp msg)
(if (process-get proc 'x-interactive)
(process-send-string proc
(concat (read-passwd mu4e--get-mail-ask-password)
"\n"))
;; TODO kill process?
(mu4e-error "Unrecognized password request")))
(when (process-buffer proc)
(let ((inhibit-read-only t)
(procwin (get-buffer-window (process-buffer proc))))
;; Insert at end of buffer. Leave point alone.
(with-current-buffer (process-buffer proc)
(goto-char (point-max))
(if (string-match ".*\r\\(.*\\)" msg)
(progn
;; kill even with \r
(end-of-line)
(let ((end (point)))
(beginning-of-line)
(delete-region (point) end))
(insert (match-string 1 msg)))
(insert msg)))
;; Auto-scroll unless user is interacting with the window.
(when (and (window-live-p procwin)
(not (eq (selected-window) procwin)))
(with-selected-window procwin
(goto-char (point-max)))))))
(defun mu4e-index-message (frm &rest args)
"Display FRM with ARGS like `mu4e-message' for index messages.
However, if `mu4e-hide-index-messages' is non-nil, do not display anything."
(unless mu4e-hide-index-messages
(apply 'mu4e-message frm args)))
(defun mu4e-update-index ()
"Update the mu4e index."
(interactive)
(mu4e~proc-index mu4e-index-cleanup mu4e-index-lazy-check))
(defun mu4e-update-index-nonlazy ()
"Update the mu4e index non-lazily.
This is just a convenience wrapper for indexing the non-lazy way
if you otherwise want to use `mu4e-index-lazy-check'."
(interactive)
(let ((mu4e-index-cleanup t) (mu4e-index-lazy-check nil))
(mu4e-update-index)))
(defvar mu4e--update-buffer nil
"The buffer of the update process when updating.")
(define-derived-mode mu4e--update-mail-mode
special-mode "mu4e:update"
"Major mode used for retrieving new e-mail messages in `mu4e'.")
(define-key mu4e--update-mail-mode-map (kbd "q") 'mu4e-kill-update-mail)
(defun mu4e--temp-window (buf height)
"Create a temporary window with HEIGHT at the bottom BUF."
(let ((win
(split-window
(frame-root-window)
(- (window-height (frame-root-window)) height))))
(set-window-buffer win buf)
(set-window-dedicated-p win t)
win))
(defun mu4e--update-sentinel-func (proc _msg)
"Sentinel function for the update process PROC."
(when mu4e--progress-reporter
(progress-reporter-done mu4e--progress-reporter)
(setq mu4e--progress-reporter nil))
(unless mu4e-hide-index-messages
(message nil))
(if (or (not (eq (process-status proc) 'exit))
(/= (process-exit-status proc) 0))
(progn
(when mu4e-index-update-error-warning
(mu4e-message "Update process returned with non-zero exit code")
(sit-for 5))
(when mu4e-index-update-error-continue
(mu4e-update-index)))
(mu4e-update-index))
(when (buffer-live-p mu4e--update-buffer)
(unless (eq mu4e-split-view 'single-window)
(mapc #'delete-window (get-buffer-window-list mu4e--update-buffer)))
(kill-buffer mu4e--update-buffer)))
;; complicated function, as it:
;; - needs to check for errors
;; - (optionally) pop-up a window
;; - (optionally) check password requests
(defun mu4e--update-mail-and-index-real (run-in-background)
"Get a new mail by running `mu4e-get-mail-command'.
If
RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
run in the background; otherwise, pop up a window."
(let* ((process-connection-type t)
(proc (start-process-shell-command
"mu4e-update" mu4e--update-name
mu4e-get-mail-command))
(buf (process-buffer proc))
(win (or run-in-background
(mu4e--temp-window buf mu4e--update-buffer-height))))
(setq mu4e--update-buffer buf)
(when (window-live-p win)
(with-selected-window win
;; ;;(switch-to-buffer buf)
;; (set-window-dedicated-p win t)
(erase-buffer)
(insert "\n") ;; FIXME -- needed so output starts
(mu4e--update-mail-mode)))
(setq mu4e--progress-reporter
(unless mu4e-hide-index-messages
(make-progress-reporter
(mu4e-format "Retrieving mail..."))))
(set-process-sentinel proc 'mu4e--update-sentinel-func)
;; if we're running in the foreground, handle password requests
(unless run-in-background
(process-put proc 'x-interactive (not run-in-background))
(set-process-filter proc 'mu4e--get-mail-process-filter))))
(defun mu4e-update-mail-and-index (run-in-background)
"Get a new mail by running `mu4e-get-mail-command'.
If RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
run in the background; otherwise, pop up a window."
(interactive "P")
(unless mu4e-get-mail-command
(mu4e-error "`mu4e-get-mail-command' is not defined"))
(if (and (buffer-live-p mu4e--update-buffer)
(process-live-p (get-buffer-process mu4e--update-buffer)))
(mu4e-message "Update process is already running")
(progn
(run-hooks 'mu4e-update-pre-hook)
(mu4e--update-mail-and-index-real run-in-background))))
(defun mu4e-kill-update-mail ()
"Stop the update process by killing it."
(interactive)
(let* ((proc (and (buffer-live-p mu4e--update-buffer)
(get-buffer-process mu4e--update-buffer))))
(when (process-live-p proc)
(kill-process proc t))))
(define-obsolete-function-alias 'mu4e-interrupt-update-mail
'mu4e-kill-update-mail "1.0-alpha0")
(define-minor-mode mu4e-update-minor-mode
"Mode for triggering mu4e updates."
:global nil
:init-value nil ;; disabled by default
:group 'mu4e
:lighter ""
:keymap
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-S-u") #'mu4e-update-mail-and-index)
;; for terminal users
(define-key map (kbd "C-c C-u") #'mu4e-update-mail-and-index)
map))
(provide 'mu4e-update)
;;; mu4e-update.el ends here

File diff suppressed because it is too large Load Diff

View File

@ -26,114 +26,13 @@
(require 'mu4e-meta)
(require 'message)
(declare-function mu4e-error "mu4e-utils")
;;; Customization
(require 'mu4e-helpers)
;;; Configuration
(defgroup mu4e nil
"mu4e - mu for emacs"
"Mu4e - an email-client for Emacs."
:group 'mail)
(defcustom mu4e-org-support t
"Support org-mode links."
:type 'boolean
:group 'mu4e)
(defgroup mu4e-view nil
"Settings for the message view."
:group 'mu4e)
(defcustom mu4e-view-use-old nil
"If non-nil, use the old viewer.
Otherwise, use the new, Gnus-based viewer."
:type 'boolean
:group 'mu4e-view)
(make-obsolete-variable 'mu4e-view-use-gnus 'mu4e-view-use-old "1.5.10")
(defcustom mu4e-speedbar-support nil
"Support having a speedbar to navigate folders/bookmarks."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-get-mail-command "true"
"Shell command to run to retrieve new mail.
Common values are \"offlineimap\", \"fetchmail\" or \"mbsync\", but
arbitrary shell-commands can be used.
When set to the literal string \"true\" (the default), the
command simply finishes successfully (running the 'true' command)
without retrieving any mail. This can be useful when mail is
already retrieved in another way."
:type 'string
:group 'mu4e
:safe 'stringp)
(defcustom mu4e-index-update-error-warning t
"Whether to display warnings during the retrieval process.
This depends on the `mu4e-get-mail-command' exit code."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-update-error-continue t
"Whether to continue with indexing after an error during retrieval."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-update-in-background t
"Whether to retrieve mail in the background."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-cleanup t
"Whether to run a cleanup phase after indexing.
That is, validate that each message in the message store has a
corresponding message file in the filesystem.
Having this option as t ensures that no non-existing messages are
shown but can slow with large message stores on slow file-systems."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-index-lazy-check nil
"Whether to only use a 'lazy check' during reindexing.
This influences how we decide whether a message
needs (re)indexing or not.
When this is set to non-nil, mu only uses the directory
timestamps to decide whether it needs to check the messages
beneath it. This makes indexing much faster, but might miss some
changes. For this, you might want to occasionally call
`mu4e-update-index-nonlazy'."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-update-interval nil
"Number of seconds between mail retrieval/indexing.
If nil, don't update automatically. Note, changes in
`mu4e-update-interval' only take effect after restarting mu4e."
:type '(choice (const :tag "No automatic update" nil)
(integer :tag "Seconds"))
:group 'mu4e
:safe 'integerp)
(defvar mu4e-update-pre-hook nil
"Hook run just *before* the mail-retrieval / database updating process starts.
You can use this hook for example to `mu4e-get-mail-command' with
some specific setting.")
(defcustom mu4e-hide-index-messages nil
"Whether to hide the \"Indexing...\" and contacts messages."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-headers-include-related t
"With this option set to non-nil, not just return the matches for
a searches, but also messages that are related (through their
@ -150,501 +49,13 @@ and offlineimap."
:type 'boolean
:group 'mu4e-headers)
(defcustom mu4e-change-filenames-when-moving nil
"Change message file names when moving them.
When moving messages to different folders, normally mu/mu4e keep
the base filename the same (the flags-part of the filename may
change still). With this option set to non-nil, mu4e instead
changes the filename. This latter behavior works better with some
IMAP-synchronization programs such as mbsync; the default works
better with e.g. offlineimap."
:type 'boolean
:group 'mu4e
:safe 'booleanp)
(defcustom mu4e-attachment-dir (expand-file-name "~/")
"Default directory for attaching and saving attachments.
This can be either a string (a file system path), or a function
that takes a filename and the mime-type as arguments, and returns
the attachment dir. See Info node `(mu4e) Attachments' for
details.
When this called for composing a message, both filename and
mime-type are nill."
:type 'directory
:group 'mu4e
:safe 'stringp)
;; don't use the older vars anymore
(make-obsolete-variable 'mu4e-user-mail-address-regexp
'mu4e-user-mail-address-list "0.9.9.x")
(make-obsolete-variable 'mu4e-my-email-addresses
'mu4e-user-mail-address-list "0.9.9.x")
(make-obsolete-variable 'mu4e-user-mail-address-list
"determined by server; see `mu4e-personal-addresses'." "1.3.8")
(defcustom mu4e-use-fancy-chars nil
"When set, allow fancy (Unicode) characters for marks/threads.
You can customize the exact fancy characters used with
`mu4e-marks' and various `mu4e-headers-..-mark' and
`mu4e-headers..-prefix' variables."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-date-format-long "%c"
"Date format to use in the message view.
Follows the format of `format-time-string'."
:type 'string
:group 'mu4e)
;; for backward compatibility, when a bookmark was defined with defstruct.
(cl-defun make-mu4e-bookmark (&key name query key)
"Create a mu4e proplist with the following elements:
- `name': the user-visible name of the bookmark
- `key': a single key to search for this bookmark
- `query': the query for this bookmark. Either a literal string or a function
that evaluates to a string."
`(:name ,name :query ,query :key ,key))
(make-obsolete 'make-mu4e-bookmark "`unneeded; `mu4e-bookmarks'
are plists" "1.3.7")
(defcustom mu4e-bookmarks
'(( :name "Unread messages"
:query "flag:unread AND NOT flag:trashed"
:key ?u)
( :name "Today's messages"
:query "date:today..now"
:key ?t)
( :name "Last 7 days"
:query "date:7d..now"
:hide-unread t
:key ?w)
( :name "Messages with images"
:query "mime:image/*"
:key ?p))
"List of pre-defined queries that are shown on the main screen.
Each of the list elements is a plist with at least:
`:name' - the name of the query
`:query' - the query expression or function
`:key' - the shortcut key.
Note that the :query parameter can be a function/lambda.
Optionally, you can add the following:
`:hide' - if t, the bookmark is hidden from the main-view and
speedbar.
`:hide-unread' - do not show the counts of unread/total number
of matches for the query in the main-view. This can be useful
if a bookmark uses a very slow query. :hide-unread
is implied from :hide. Furthermore, it is implied if
`:query' is a function.
Queries used to determine the unread/all counts do _not_ apply
`mu4e-query-rewrite-function'; nor do they discard duplicate or
unreadable messages (for efficiency). Thus, the numbers shown may
differ from the number you get from a 'real' query."
:type '(repeat (plist))
:version "1.3.9"
:group 'mu4e)
(defcustom mu4e-query-rewrite-function 'identity
"Function that takes a search expression string, and returns a
possibly changed search expression string.
This function is applied on the search expression just before
searching, and allows users to modify the query.
For instance, we could change and of workmail into
\"maildir:/long-path-to-work-related-emails\", by setting the function
(setq mu4e-query-rewrite-function
(lambda(expr)
(replace-regexp-in-string \"workmail\"
\"maildir:/long-path-to-work-related-emails\" expr)))
It is good to remember that the replacement does not understand
anything about the query, it just does text replacement."
:type 'function
:group 'mu4e)
(defun mu4e-bookmarks ()
"Get `mu4e-bookmarks' in the (new) format, converting from the
old format if needed."
(cl-map 'list
(lambda (item)
(if (and (listp item) (= (length item) 3))
`(:name ,(nth 1 item)
:query ,(nth 0 item)
:key ,(nth 2 item))
item))
mu4e-bookmarks))
(defcustom mu4e-split-view 'horizontal
"How to show messages / headers.
A symbol which is either:
* `horizontal': split horizontally (headers on top)
* `vertical': split vertically (headers on the left).
* `single-window': view and headers in one window (mu4e will try not to
touch your window layout), main view in minibuffer
* anything else: don't split (show either headers or messages,
not both)
Also see `mu4e-headers-visible-lines'
and `mu4e-headers-visible-columns'."
:type '(choice (const :tag "Split horizontally" horizontal)
(const :tag "Split vertically" vertical)
(const :tag "Single window" single-window)
(const :tag "Don't split" nil))
:group 'mu4e-headers)
(defcustom mu4e-view-max-specpdl-size 4096
"The value of `max-specpdl-size' for displaying messages with Gnus."
:type 'integer
:group 'mu4e-view)
(defcustom mu4e-view-show-images nil
"If non-nil, automatically display images in the view
buffer. Applies only to the _old_ message view."
:type 'boolean
:group 'mu4e-view)
(defcustom mu4e-view-auto-mark-as-read t
"Automatically mark messages are 'read' when you read them.
This is the default behavior, but can be turned off, for example
when using a read-only file-system.
This can also be set to a function; if so, receives a message
plist which should evaluate to nil if the message should *not* be
marked as read-only, or non-nil otherwise."
:type '(choice
boolean
function)
:group 'mu4e-view)
(defcustom mu4e-confirm-quit t
"Whether to confirm to quit mu4e."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-cited-regexp
"^\\(\\([[:alpha:]]+\\)\\|\\( *\\)\\)\\(\\(>+ ?\\)+\\)"
"Regex that determines whether a line is a citation.
This recognizes lines starting with numbers of '>'
and spaces as well as citations of the type \"John> ... \"."
:type 'string
:group 'mu4e)
(defcustom mu4e-completing-read-function 'ido-completing-read
"Function to be used to receive user-input during completion.
This is used to receive the name of the maildir to switch to via
`mu4e~headers-jump-to-maildir'.
Suggested possible values are:
* `completing-read': built-in completion method
* `ido-completing-read': dynamic completion within the minibuffer."
:type 'function
:options '(completing-read ido-completing-read)
:group 'mu4e)
(defcustom mu4e-context-policy 'ask-if-none
"The policy to determine the context when entering the mu4e main view.
If the value is `always-ask', ask the user unconditionally.
In all other cases, if any context matches (using its match
function), this context is used. Otherwise, if none of the
contexts match, we have the following choices:
- `pick-first': pick the first of the contexts available (ie. the default)
- `ask': ask the user
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
- nil: return nil; leaves the current context as is.
Also see `mu4e-compose-context-policy'."
:type '(choice
(const :tag "Always ask what context to use, even if one matches"
always-ask)
(const :tag "Ask if none of the contexts match" ask)
(const :tag "Ask when there's no context yet" ask-if-none)
(const :tag "Pick the first context if none match" pick-first)
(const :tag "Don't change the context when none match" nil))
:group 'mu4e)
;;;; Crypto
(defgroup mu4e-crypto nil
"Crypto-related settings."
:group 'mu4e)
(make-obsolete-variable 'mu4e-auto-retrieve-keys "no longer used." "1.3.1")
(defcustom mu4e-decryption-policy t
"Policy for dealing with encrypted parts.
The setting is a symbol:
* t: try to decrypt automatically
* `ask': ask before decrypting anything
* nil: don't try to decrypt anything.
Note that this is not used unless `mu4e-view-use-old' is enabled."
:type '(choice (const :tag "Try to decrypt automatically" t)
(const :tag "Ask before decrypting anything" ask)
(const :tag "Don't try to decrypt anything" nil))
:group 'mu4e-crypto)
;;;; Address completion
;;
;; We put these options here rather than in mu4e-compose, because
;; mu4e-utils needs them.
(defgroup mu4e-compose nil
"Message-composition related settings."
:group 'mu4e)
(defcustom mu4e-compose-complete-addresses t
"Whether to do auto-completion of e-mail addresses."
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-compose-complete-only-personal nil
"Whether to consider only 'personal' e-mail addresses for completion.
That is, addresses from messages where user was explicitly in one
of the address fields (this excludes mailing list messages).
These addresses are the ones specified with `mu init'."
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-compose-complete-only-after "2014-01-01"
"Consider only contacts last seen after this date.
Date must be a string of the form YYY-MM-DD.
This is useful for limiting a potentially enormous set of
contacts for auto-completion to just those that are present in
the e-mail corpus in recent timses. Set to nil to not have any
time-based restriction."
:type 'string
:group 'mu4e-compose)
;; names and mail-addresses can be mapped onto their canonical
;; counterpart. use the customizeable function
;; mu4e-canonical-contact-function to do that. below the identity
;; function for mapping a contact onto the canonical one.
(defun mu4e-contact-identity (contact)
"Return the name and the mail-address of a CONTACT.
It is used as the identity function for converting contacts to
their canonical counterpart; useful as an example."
(let ((name (plist-get contact :name))
(mail (plist-get contact :mail)))
(list :name name :mail mail)))
(make-obsolete-variable 'mu4e-contact-rewrite-function
"mu4e-contact-process-function (see docstring)" "mu4e 1.3.2")
(make-obsolete-variable 'mu4e-compose-complete-ignore-address-regexp
"mu4e-contact-process-function (see docstring)" "mu4e 1.3.2")
(defcustom mu4e-contact-process-function
(lambda(addr) ;; filter-out no-reply addresses
(unless (string-match-p "no[t]?[-\\.]?repl\\(y\\|ies\\)" addr)
addr))
"Function for processing contact information for use in auto-completion.
The function receives the contact as a string, e.g
\"Foo Bar <foo.bar@example.com>\"
\"cuux@example.com\"
The function should return either:
- nil: do not use this contact for completion
- the (possibly rewritten) address, which must be
an RFC-2822-compatible e-mail address."
:type 'function
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-ignore-address
'("no-?reply")
"Addresses to prune when doing wide replies.
This can be a regexp matching the address, a list of regexps or a
predicate function. A value of nil keeps all the addresses."
:type '(choice
(const nil)
function
string
(repeat string))
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-recipients 'ask
"Which recipients to use when replying to a message.
May be 'ask, 'all, 'sender. Note that that only applies to
non-mailing-list message; for those, mu4e always asks."
:type '(choice ask
all
sender)
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-to-address nil
"The Reply-To address.
Useful when this is not equal to the From: address."
:type 'string
:group 'mu4e-compose)
(defcustom mu4e-compose-forward-as-attachment nil
"Whether to forward messages as attachments instead of inline."
:type 'boolean
:group 'mu4e-compose)
;; backward compatibility
(make-obsolete-variable 'mu4e-reply-to-address
'mu4e-compose-reply-to-address
"v0.9.9")
(defcustom mu4e-compose-keep-self-cc nil
"When non-nil. keep your e-mail address in Cc: when replying."
:type 'boolean
:group 'mu4e-compose)
(defvar mu4e-compose-parent-message nil
"The parent message plist.
This is the message being replied to, forwarded or edited; used
in `mu4e-compose-pre-hook'. For new messages, it is nil.")
;;;; Calendar
(defgroup mu4e-icalendar nil
"Icalendar related settings."
:group 'mu4e)
(defcustom mu4e-icalendar-trash-after-reply nil
"If non-nil, trash the icalendar invitation after replying."
:type 'boolean
:group 'mu4e-icalendar)
(defcustom mu4e-icalendar-diary-file nil
"If non-nil, the file in which to add events upon reply."
:type '(choice (const :tag "Do not insert a diary entry" nil)
(string :tag "Insert a diary entry in this file"))
:group 'mu4e-icalendar)
;;;; Folders
(defgroup mu4e-folders nil
"Special folders."
:group 'mu4e)
(defcustom mu4e-drafts-folder "/drafts"
"Your folder for draft messages, relative to the root maildir.
For instance, \"/drafts\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note, the message
parameter refers to the original message being replied to / being
forwarded / re-edited and is nil otherwise. `mu4e-drafts-folder'
is only evaluated once."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-refile-folder "/archive"
"Your folder for refiling messages, relative to the root maildir.
For instance \"/Archive\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note that the
message parameter refers to the message-at-point."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-sent-folder "/sent"
"Your folder for sent messages, relative to the root maildir.
For instance, \"/Sent Items\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note that the
message parameter refers to the original message being replied to
/ being forwarded / re-edited, and is nil otherwise."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-trash-folder "/trash"
"Your folder for trashed messages, relative to the root maildir.
For instance, \"/trash\". Instead of a string, may also be a
function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. When using
`mu4e-trash-folder' in the headers view (when marking messages
for trash). Note that the message parameter refers to the
message-at-point. When using it when composing a message (see
`mu4e-sent-messages-behavior'), this refers to the original
message being replied to / being forwarded / re-edited, and is
nil otherwise."
:type '(choice
(string :tag "Folder name")
(function :tag "Function return folder name"))
:group 'mu4e-folders)
(defcustom mu4e-maildir-shortcuts nil
"A list of maildir shortcuts.
This makes it possible to quickly go to a particular
maildir (folder), or quickly moving messages to them (e.g., for
archiving or refiling).
Each of the list elements is a plist with at least:
`:maildir' - the maildir for the shortcut (e.g. \"/archive\")
`:key' - the shortcut key.
Optionally, you can add the following:
`:hide' - if t, the shortcut is hidden from the main-view and
speedbar.
`:hide-unread' - do not show the counts of unread/total number
of matches for the maildir in the main-view, and is implied
from `:hide'.
For backward compatibility, an older form is recognized as well:
(maildir . key), where MAILDIR is a maildir (such as
\"/archive/\"), and key is a single character.
You can use these shortcuts in the headers and view buffers, for
example with `mu4e-mark-for-move-quick' (or 'm', by default) or
`mu4e-jump-to-maildir' (or 'j', by default), followed by the
designated shortcut character for the maildir.
Unlike in search queries, folder names with spaces in them must
NOT be quoted, since mu4e does this for you."
:type '(repeat (cons (string :tag "Maildir") character))
:version "1.3.9"
:group 'mu4e-folders)
(defcustom mu4e-maildir-info-delimiter
(if (member system-type '(ms-dos windows-nt cygwin))
";" ":")
"Separator character between message identifier and flags.
It defaults to ':' on most platforms, except on Windows,
where it is not allowed and we use ';' for compatibility
with mbsync, offlineimap and other programs."
:type 'string
:group 'mu4e-folders)
(defun mu4e-maildir-shortcuts ()
"Get `mu4e-maildir-shortcuts' in the (new) format, converting
from the old format if needed."
(cl-map 'list
(lambda (item) ;; convert from old format?
(if (and (consp item) (not (consp (cdr item))))
`(:maildir ,(car item) :key ,(cdr item))
item))
mu4e-maildir-shortcuts))
(defcustom mu4e-display-update-status-in-modeline nil
"Non-nil value will display the update status in the modeline."
:group 'mu4e
:type 'boolean)
;;; Faces
(defgroup mu4e-faces nil
@ -1009,111 +420,8 @@ header-view, not including, for instance, the message body.")
;;;; Main
(defvar mu4e-main-buffer-name " *mu4e-main*"
"Name of the mu4e main view buffer. The default name starts
with SPC and therefore is not visible in buffer list.")
;;;; Headers
(defconst mu4e~headers-buffer-name "*mu4e-headers*"
"Name of the buffer for message headers.")
(defvar mu4e~headers-last-query nil
"The present (most recent) query.")
;;;; View
(defconst mu4e~view-buffer-name "*mu4e-view*"
"Name for the message view buffer.")
(defconst mu4e~view-embedded-buffer-name " *mu4e-embedded-view*"
"Name for the embedded message view buffer.")
;;;; Other
(defvar mu4e~contacts-hash nil
"Hash that maps contacts (ie. 'name <e-mail>') to an integer for sorting.
We need to keep this information around to quickly re-sort
subsets of the contacts in the completions function in
mu4e-compose.")
;;; Handler functions
;;
;; The handler functions define what happens when we receive a certain
;; message from the server. Here we register our handler functions;
;; these connect server messages to functions to handle them.
;;
;; These bindings form mu4e's central nervous system so it's not
;; really recommended to override them (they reference various
;; internal bits, which could change).
(defun mu4e~default-handler (&rest args)
"Dummy handler function with arbitrary ARGS."
(error "Not handled: %S" args))
(defvar mu4e-error-func 'mu4e-error-handler
"Function called for each error received.
The function is passed an error plist as argument. See
`mu4e~proc-filter' for the format.")
(defvar mu4e-update-func 'mu4e~headers-update-handler
"Function called for each :update sexp returned.
The function is passed a msg sexp as argument.
See `mu4e~proc-filter' for the format.")
(defvar mu4e-remove-func 'mu4e~headers-remove-handler
"Function called for each :remove sexp returned.
This happens when some message has been deleted. The function is
passed the docid of the removed message.")
(defvar mu4e-sent-func 'mu4e~default-handler
"Function called for each :sent sexp received.
This happens when some message has been sent. The function is
passed the docid and the draft-path of the sent message.")
(defvar mu4e-view-func 'mu4e~headers-view-handler
"Function called for each single-message sexp.
The function is passed a message sexp as argument. See
`mu4e~proc-filter' for the format.")
(defvar mu4e-header-func 'mu4e~headers-header-handler
"Function called for each message-header received.
The function is passed a msg plist as argument. See
`mu4e~proc-filter' for the format.")
(defvar mu4e-found-func 'mu4e~headers-found-handler
"Function called for when we received a :found sexp.
This happens after the headers have been returned, to report on
the number of matches. See `mu4e~proc-filter' for the format.")
(defvar mu4e-erase-func 'mu4e~headers-clear
"Function called we receive an :erase sexp.
This before new headers are displayed, to clear the current
headers buffer. See `mu4e~proc-filter' for the format.")
(defvar mu4e-compose-func 'mu4e~compose-handler
"Function called for each compose message received.
I.e., the original message that is used as basis for composing a
new message (i.e., either a reply or a forward); the function is
passed msg and a symbol (either reply or forward). See
`mu4e~proc-filter' for the format of <msg-plist>.")
(defvar mu4e-info-func 'mu4e-info-handler
"Function called for each (:info type ....) sexp received.
from the server process.")
(defvar mu4e-pong-func 'mu4e~default-handler
"Function called for each (:pong type ....) sexp received.")
(defvar mu4e-contacts-func 'mu4e-contacts-func
"A function called for each (:contacts (<list-of-contacts>)
sexp received from the server process.")
(defvar mu4e-temp-func 'mu4e~view-temp-handler
"A function called for each (:temp <file> <cookie>) sexp.")
;;; Internals
(defvar mu4e~headers-view-win nil

View File

@ -46,6 +46,7 @@
(require 'mu4e-proc)
(require 'mu4e-search)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-contacts)
(require 'mu4e-vars)
;;; Options
@ -87,108 +88,49 @@ The first letter of NAME is used as a shortcut character."
:group 'mu4e-view
:type '(alist :key-type string :value-type function))
(defcustom mu4e-view-max-specpdl-size 4096
"The value of `max-specpdl-size' for displaying messages with Gnus."
:type 'integer
:group 'mu4e-view)
;;; Old options
;; These don't do anything useful when in "gnus" mode, except for avoid errors
;; for people that have these in their config.
(defcustom mu4e-view-show-addresses nil
"Whether to initially show full e-mail addresses for contacts.
Otherwise, just show their names. Ignored when using the gnus-based view."
:type 'boolean
:group 'mu4e-view)
;; Options from the old message view.
(make-obsolete-variable 'mu4e-view-show-addresses
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-wrap-lines nil "0.9.9-dev7")
(make-obsolete-variable 'mu4e-view-hide-cited nil "0.9.9-dev7")
(make-obsolete-variable 'mu4e-view-date-format
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-image-max-width
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-image-max-height
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-save-multiple-attachments-without-asking
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-attachment-assoc
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-attachment-actions
"See mu4e-view-mime-part-actions" "1.7.0")
(make-obsolete-variable 'mu4e-view-header-field-keymap
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-header-field-keymap
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-contacts-header-keymap
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-view-attachments-header-keymap
"Unused with the new message view" "1.7.0")
(make-obsolete-variable 'mu4e-imagemagick-identify nil "1.7.0")
(make-obsolete-variable 'mu4e-view-show-images
"No longer used" "1.7.0")
(make-obsolete-variable 'mu4e-view-gnus "Old view is gone" "1.7.0")
(make-obsolete-variable 'mu4e-view-use-gnus "Gnus view is the default" "1.5.10")
(defcustom mu4e-view-date-format "%c"
"Date format to use in the message view.
In the format of `format-time-string'. Ignored when using the gnus-based view."
:type 'string
:group 'mu4e-view)
(defcustom mu4e-view-image-max-width 800
"The maximum width for images to display.
This is only effective if you're using an Emacs with Imagemagick
support, and `mu4e-view-show-images' is non-nil. Ignored when
using the gnus-based view."
:type 'integer
:group 'mu4e-view)
(defcustom mu4e-view-image-max-height 600
"The maximum height for images to display.
This is only effective if you're using an Emacs with Imagemagick
support, and `mu4e-view-show-images' is non-nil. Ignored when
using the gnus-based view."
:type 'integer
:group 'mu4e-view)
(defcustom mu4e-save-multiple-attachments-without-asking nil
"If non-nil, saving multiple attachments asks once for a
directory and saves all attachments in the chosen directory.
Ignored when using the gnus-based view."
:type 'boolean
:group 'mu4e-view)
(defcustom mu4e-view-attachment-assoc nil
"Alist of (EXTENSION . PROGRAM).
Specify which PROGRAM to use to open attachment with EXTENSION.
Args EXTENSION and PROGRAM should be specified as strings.
Ignored when using the gnus-based view."
:group 'mu4e-view
:type '(alist :key-type string :value-type string))
(defcustom mu4e-view-attachment-actions
'( ("ssave" . mu4e-view-save-attachment-single)
("Ssave multi" . mu4e-view-save-attachment-multi)
("wopen-with" . mu4e-view-open-attachment-with)
("ein-emacs" . mu4e-view-open-attachment-emacs)
("dimport-in-diary" . mu4e-view-import-attachment-diary)
("kimport-public-key" . mu4e-view-import-public-key)
("|pipe" . mu4e-view-pipe-attachment))
"List of actions to perform on message attachments.
The actions are cons-cells of the form:
(NAME . FUNC)
where:
* NAME is the name of the action (e.g. \"Count lines\")
* FUNC is a function which receives two arguments: the message
plist and the attachment number.
The first letter of NAME is used as a shortcut character.
Ignored when using the gnus-based view."
:group 'mu4e-view
:type '(alist :key-type string :value-type function))
;;; Keymaps
(defvar mu4e-view-header-field-keymap
(let ((map (make-sparse-keymap)))
(define-key map [mouse-1] 'mu4e~view-header-field-fold)
(define-key map (kbd "TAB") 'mu4e~view-header-field-fold)
map)
"Keymap used for header fields. Ignored when using the
gnus-based view.")
(defvar mu4e-view-contacts-header-keymap
(let ((map (make-sparse-keymap)))
(define-key map [mouse-2] 'mu4e~view-compose-contact)
(define-key map "C" 'mu4e~view-compose-contact)
(define-key map "c" 'mu4e~view-copy-contact)
map)
"Keymap used for the contacts in the header fields.
Ignored when using the gnus-based view.")
(defvar mu4e-view-attachments-header-keymap
(let ((map (make-sparse-keymap)))
(define-key map [mouse-1] 'mu4e~view-open-attach-from-binding)
(define-key map [?\M-\r] 'mu4e~view-open-attach-from-binding)
(define-key map [mouse-2] 'mu4e~view-save-attach-from-binding)
(define-key map (kbd "<S-return>") 'mu4e~view-save-attach-from-binding)
map)
"Keymap used in the \"Attachments\" header field. Ignored when
using the gnus-based view.")
(make-obsolete-variable 'mu4e-cited-regexp "No longer used" "1.7.0")
;; Helpers
(defun mu4e~view-quit-buffer ()
@ -1262,5 +1204,48 @@ the third MIME-part."
(gnus-article-inline-part (car html-part))
(mu4e-warn "No html part in this message")))
(defun mu4e-process-file-through-pipe (path pipecmd)
"Process file at PATH through a pipe with PIPECMD."
(let ((buf (get-buffer-create "*mu4e-output")))
(with-current-buffer buf
(let ((inhibit-read-only t))
(erase-buffer)
(call-process-shell-command pipecmd path t t)
(view-mode)))
(switch-to-buffer buf)))
;;; Bug Reference mode support
;; This is Emacs 28 stuff but there is no need to guard it with some (f)boundp
;; checks (which would return nil if bug-reference.el is not loaded before
;; mu4e) since the function definition doesn't hurt and `add-hook' works fine
;; for not yet defined variables (by creating them).
(declare-function bug-reference-maybe-setup-from-mail "ext:bug-reference")
(defun mu4e--view-try-setup-bug-reference-mode ()
"Try to guess bug-reference setup from the current mu4e mail.
Looks at the maildir and the mail headers List, List-Id, Maildir,
To, From, Cc, and Subject and tries to guess suitable values for
`bug-reference-bug-regexp' and `bug-reference-url-format' by
matching the maildir name against GROUP-REGEXP and each header
value against HEADER-REGEXP in
`bug-reference-setup-from-mail-alist'."
(when (derived-mode-p 'mu4e-view-mode)
(let (header-values)
(save-excursion
(goto-char (point-min))
(dolist (field '("list" "list-id" "to" "from" "cc" "subject"))
(let ((val (mail-fetch-field field)))
(when val
(push val header-values)))))
(bug-reference-maybe-setup-from-mail
(mail-fetch-field "maildir")
header-values))))
(add-hook 'bug-reference-auto-setup-functions
#'mu4e--view-try-setup-bug-reference-mode)
(provide 'mu4e-view)
;;; mu4e-view.el ends here

View File

@ -1,6 +1,6 @@
;;; mu4e.el --- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
;; Copyright (C) 2011-2019 Dirk-Jan C. Binnema
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -27,13 +27,33 @@
;;; Code:
(require 'mu4e-vars)
(require 'mu4e-headers) ;; headers view
(require 'mu4e-view) ;; message view
(require 'mu4e-main) ;; main screen
(require 'mu4e-compose) ;; message composition / sending
(require 'mu4e-helpers)
(require 'mu4e-folders)
(require 'mu4e-context)
(require 'mu4e-contacts)
(require 'mu4e-headers)
(require 'mu4e-compose)
(require 'mu4e-bookmarks)
(require 'mu4e-update)
(require 'mu4e-main)
(require 'mu4e-proc) ;; communication with backend
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-context) ;; support for contexts
(defcustom mu4e-confirm-quit t
"Whether to confirm to quit mu4e."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-org-support t
"Support Org-mode links."
:type 'boolean
:group 'mu4e)
(defcustom mu4e-speedbar-support nil
"Support having a speedbar to navigate folders/bookmarks."
:type 'boolean
:group 'mu4e)
(when mu4e-speedbar-support
(require 'mu4e-speedbar)) ;; support for speedbar
@ -48,20 +68,177 @@
;;;###autoload
(defun mu4e (&optional background)
"If mu4e is not running yet, start it. Then, show the main
window, unless BACKGROUND (prefix-argument) is non-nil."
"If mu4e is not running yet, start it.
Then, show the main window, unless BACKGROUND (prefix-argument)
is non-nil."
(interactive "P")
;; start mu4e, then show the main view
(mu4e~start (unless background 'mu4e~main-view)))
(mu4e--init-handlers)
(mu4e--start (unless background 'mu4e~main-view)))
(defun mu4e-quit()
"Quit the mu4e session."
(interactive)
(if mu4e-confirm-quit
(when (y-or-n-p (mu4e-format "Are you sure you want to quit?"))
(mu4e~stop))
(mu4e~stop)))
(mu4e--stop))
(mu4e--stop)))
;;; Internals
(defun mu4e--check-requirements ()
"Check for the settings required for running mu4e."
(unless (>= emacs-major-version 25)
(mu4e-error "Emacs >= 25.x is required for mu4e"))
(when (mu4e-server-properties)
(unless (string= (mu4e-server-version) mu4e-mu-version)
(mu4e-error "The mu server has version %s, but we need %s"
(mu4e-server-version) mu4e-mu-version)))
(unless (and mu4e-mu-binary (file-executable-p mu4e-mu-binary))
(mu4e-error "Please set `mu4e-mu-binary' to the full path to the mu
binary"))
(dolist (var '(mu4e-sent-folder mu4e-drafts-folder
mu4e-trash-folder))
(unless (and (boundp var) (symbol-value var))
(mu4e-error "Please set %S" var))
(unless (functionp (symbol-value var)) ;; functions are okay, too
(let* ((dir (symbol-value var))
(path (concat (mu4e-root-maildir) dir)))
(unless (string= (substring dir 0 1) "/")
(mu4e-error "%S must start with a '/'" dir))
(unless (mu4e-create-maildir-maybe path)
(mu4e-error "%s (%S) does not exist" path var))))))
;;; Starting / getting mail / updating the index
(defun mu4e--pong-handler (_data func)
"Handle 'pong' responses from the mu server.
Invoke FUNC if non-nil."
(let ((doccount (plist-get (mu4e-server-properties) :doccount)))
(mu4e--check-requirements)
(when func (funcall func))
(when (zerop doccount)
(mu4e-message "Store is empty; (re)indexing. This may take a while.") ;
(mu4e-update-index))
(when (and mu4e-update-interval (null mu4e--update-timer))
(setq mu4e--update-timer
(run-at-time 0 mu4e-update-interval
(lambda () (mu4e-update-mail-and-index
mu4e-index-update-in-background)))))))
(defun mu4e--start (&optional func)
"Start mu4e.
If `mu4e-contexts' have been defined, but we don't have a context
yet, switch to the matching one, or none matches, the first. If
mu4e is already running, execute function FUNC (if non-nil).
Otherwise, check various requireme`'nts, then start mu4e. When
successful, call FUNC (if non-nil) afterwards."
(unless (mu4e-context-current)
(mu4e--context-autoswitch nil mu4e-context-policy))
(setq mu4e-pong-func (lambda (info) (mu4e--pong-handler info func)))
(mu4e~proc-ping
(mapcar ;; send it a list of queries we'd like to see read/unread info for
(lambda (bm)
(funcall (or mu4e-search-query-rewrite-function #'identity)
(plist-get bm :query)))
;; exclude bookmarks that are not strings, and with certain flags
(seq-filter (lambda (bm)
(and (stringp (plist-get bm :query))
(not (or (plist-get bm :hide)
(plist-get bm :hide-unread)))))
(append (mu4e-bookmarks)
(mu4e--maildirs-with-query)))))
;; maybe request the list of contacts, automatically refreshed after
;; reindexing
(unless mu4e--contacts-hash (mu4e--request-contacts-maybe)))
(defun mu4e--stop ()
"Stop mu4e."
(when mu4e--update-timer
(cancel-timer mu4e--update-timer)
(setq mu4e--update-timer nil))
(mu4e-clear-caches)
(mu4e~proc-kill)
;; kill all mu4e buffers
(mapc
(lambda (buf)
;; When using the Gnus-based viewer, the view buffer has the
;; kill-buffer-hook function mu4e~view-kill-buffer-hook-fn which kills the
;; mm-* buffers created by Gnus' article mode. Those have been returned by
;; `buffer-list' but might already be deleted in case the view buffer has
;; been killed first. So we need a `buffer-live-p' check here.
(when (buffer-live-p buf)
(with-current-buffer buf
(when (member major-mode
'(mu4e-headers-mode mu4e-view-mode mu4e-main-mode))
(kill-buffer)))))
(buffer-list)))
;;; Handlers
(defun mu4e--error-handler (errcode errmsg)
"Handler function for showing an error with ERRCODE and ERRMSG."
;; don't use mu4e-error here; it's running in the process filter context
(cl-case errcode
(4 (mu4e-warn "No matches for this search query."))
(110 (display-warning 'mu4e errmsg :error)) ;; schema version.
(t (error "Error %d: %s" errcode errmsg))))
(defun mu4e--info-handler (info)
"Handler function for (:INFO ...) sexps received from server."
(let* ((type (plist-get info :info))
(processed (plist-get info :processed))
(updated (plist-get info :updated))
(cleaned-up (plist-get info :cleaned-up))
(mainbuf (get-buffer mu4e-main-buffer-name)))
(cond
((eq type 'add) t) ;; do nothing
((eq type 'index)
(if (eq (plist-get info :status) 'running)
(mu4e-index-message
"Indexing... processed %d, updated %d" processed updated)
(progn
(mu4e-index-message
"%s completed; processed %d, updated %d, cleaned-up %d"
(if mu4e-index-lazy-check "Lazy indexing" "Indexing")
processed updated cleaned-up)
;; call the updated hook if anything changed.
(unless (zerop (+ updated cleaned-up))
(run-hooks 'mu4e-index-updated-hook))
(unless (and (not (string= mu4e--contacts-tstamp "0"))
(zerop (plist-get info :updated)))
(mu4e--request-contacts-maybe))
(when (and (buffer-live-p mainbuf) (get-buffer-window mainbuf))
(save-window-excursion
(select-window (get-buffer-window mainbuf))
(mu4e~main-view 'refresh))))))
((plist-get info :message)
(mu4e-index-message "%s" (plist-get info :message))))))
(defun mu4e--init-handlers()
"Initialize the server message handlers.
Only set set them if they were nil before, so overriding has a
chance."
(mu4e-setq-if-nil mu4e-error-func #'mu4e--error-handler)
(mu4e-setq-if-nil mu4e-update-func #'mu4e~headers-update-handler)
(mu4e-setq-if-nil mu4e-remove-func #'mu4e~headers-remove-handler)
(mu4e-setq-if-nil mu4e-view-func #'mu4e~headers-view-handler)
(mu4e-setq-if-nil mu4e-header-func #'mu4e~headers-header-handler)
(mu4e-setq-if-nil mu4e-found-func #'mu4e~headers-found-handler)
(mu4e-setq-if-nil mu4e-erase-func #'mu4e~headers-clear)
(mu4e-setq-if-nil mu4e-sent-func #'mu4e--default-handler)
(mu4e-setq-if-nil mu4e-compose-func #'mu4e~compose-handler)
(mu4e-setq-if-nil mu4e-contacts-func #'mu4e--update-contacts)
(mu4e-setq-if-nil mu4e-info-func #'mu4e--info-handler)
(mu4e-setq-if-nil mu4e-pong-func #'mu4e--default-handler))
(defun mu4e-clear-caches ()
"Clear any cached resources."
(setq
mu4e-maildir-list nil
mu4e--contacts-hash nil
mu4e--contacts-tstamp "0"))
;;; _
(provide 'mu4e)
;;; mu4e.el ends here