mirror of https://github.com/djcb/mu.git
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:
parent
e6be09e626
commit
9157d9102d
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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..
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
1060
mu4e/mu4e-utils.el
1060
mu4e/mu4e-utils.el
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
201
mu4e/mu4e.el
201
mu4e/mu4e.el
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue