Merge branch 'wip/djcb/compose-ng'

The new message composer, which replaces mu4e's draft-creation with the
one from gnus.
This commit is contained in:
Dirk-Jan C. Binnema 2023-10-25 22:05:55 +03:00
commit d923890e2b
12 changed files with 970 additions and 1687 deletions

View File

@ -59,6 +59,42 @@
*** mu4e
**** message composer
- Overhaul of the message composer; it is now closer to the Gnus/Message
composer functions (e.g. the whole mu4e-specific draft setup is gone);
this reduces code size and offers some new capabilities.
More of the ~message-~ functions can be used now.
- Variables ~mu4e-compose-signature~, ~mu4e-compose-cite-function~ are gone
(with aliases in place), use ~message-signature~, ~message-cite-function~
instead. There's a special ~mu4e-message-cite-nothing~ for the case where
you do not want to cite anything.
- There's a new function ~mu4e-compose-wide-reply~ (bound to =W=) which does a
wide-reply, a.k.a., 'reply to all'
- The special mailing list handling is gone; ~mu4e-compose-reply~ and
~mu4e-compose-wide-reply~ should take care of that. There's also
~message-reply-to-function~ you can customize for ultimate control; see [[info:(message) Reply][info
(message) Reply]] for details.
- ~mu4e-compose-in-new-frame~ has been generalized (in a backward-compatible
way) to ~mu4e-compose-switch~, which lets you decide whether a message
should be composed in the current window (default), a new window or a new
frame.
- ~mu4e-compose-context-switch~ is gone; it was a little too fragile. Best
change when creating the message (=mu4e= asks you by default, see
~mu4e-compose-context-policy~).
- iCalendar support is a work-in-progress with the new editor. One change is
that support is now _automatically_ available.
**** other
- New command ~mu4e-search-query~ (bound to =c=) which lets you pick a query
(from bookmark / maildir shortcuts) with completion in main / headers /
view buffers.

View File

@ -43,7 +43,6 @@ mu4e_srcs=[
'mu4e-contacts.el',
'mu4e-context.el',
'mu4e-contrib.el',
'mu4e-draft.el',
'mu4e-folders.el',
'mu4e.el',
'mu4e-headers.el',

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@
;;; Code:
(require 'cl-lib)
(require 'message)
(require 'mu4e-helpers)
(require 'mu4e-update)
@ -79,13 +80,6 @@ their canonical counterpart; useful as an example."
(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)
(cond
@ -154,18 +148,18 @@ with both the plain addresses and /regular expressions/."
(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-personal-or-alternative-address ()
"Return a function matching user's addresses.
Function takes one parameter, an address. This glues mu4e's
personal addresses together with gnus'
`message-alternative-emails'."
(let* ((alts message-alternative-emails))
(lambda (addr)
(or (mu4e-personal-address-p addr)
(cond
((functionp alts) (funcall alts addr))
((stringp alts) (string-match alts addr))
(t nil))))))
;; Helpers
@ -192,7 +186,7 @@ matches, nil is returned, if not, it returns a symbol
((= (aref ph 0) ?\")
(if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph)
'rfc822-quoted-string
'rfc822-containing-quote)) ; starts with quote, but doesn't end with one
'rfc822-containing-quote)) ; starts with quote, but doesn't end with one
((string-match-p "[\"]" ph) 'rfc822-containing-quote)
((string-match-p "[\000-\037()\*<>@,;:\\\.]+" ph) nil)
(t 'rfc822-atom)))

View File

@ -1,738 +0,0 @@
;;; mu4e-draft.el --- Creating draft messages -*- lexical-binding: t -*-
;;
;; Copyright (C) 2011-2023 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:
;; In this file, various functions to create draft messages
;;; Code:
(require 'cl-lib)
(require 'mu4e-message)
(require 'mu4e-contacts)
(require 'mu4e-folders)
(require 'message) ;; mail-header-separator
;;; 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 a symbol `ask', `all', `sender'. Note that this option
only applies to non-mailing-list message; for mailing-list
messages, mu4e always asks."
:type '(choice (const ask)
(const all)
(const sender))
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-to-address nil
"When non-nil, the Reply-To address.
Useful when this is not equal to the From: address."
:type '(choice (const :tag "Same as the \"From:\" address" nil)
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)
(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.")
(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-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 a symbol, 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, do not include self.
Selfness is decided by `mu4e-personal-address-p'"
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-compose-cite-function
(or message-cite-function 'message-cite-original-without-signature)
"The function for citing message in replies and forwards.
This is the mu4e-specific version of
`message-cite-function'."
:type 'function
:group 'mu4e-compose)
(defcustom mu4e-compose-signature
(or message-signature "Sent with my mu4e")
"The message signature.
\(i.e. the blob at the bottom of messages). This is the
mu4e-specific version of `message-signature'."
:type '(choice string
(const :tag "None" nil)
(const :tag "Contents of signature file" t)
function sexp)
:risky t
:group 'mu4e-compose)
(defcustom mu4e-compose-signature-auto-include t
"Whether to automatically include a message-signature."
:type 'boolean
:group 'mu4e-compose)
(defvar mu4e-user-agent-string
(format "mu4e %s; emacs %s" mu4e-mu-version emacs-version)
"The User-Agent string for mu4e, or nil.")
(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
its settings apply."
(with-temp-buffer
(when (fboundp 'mu4e-view-message-text) ;; keep bytecompiler happy
(insert (mu4e-view-message-text msg))
(message-yank-original)
(goto-char (point-min))
(push-mark (point-max))
;; set the the signature separator to 'loose', since in the real world,
;; many message don't follow the standard...
(let ((message-signature-separator "^-- *$")
(message-signature-insert-empty-line t))
(funcall mu4e-compose-cite-function))
(pop-mark)
(goto-char (point-min))
(buffer-string))))
(defun mu4e~draft-header (hdr val)
"Return a header line of the form \"HDR: VAL\".
If VAL is a non-empty list, unpack it.
If VAL is nil, return nil."
;; note: the propertize here is currently useless, since gnus sets its own
;; later.
(when-let ((val
(if (stringp val)
val
;; otherwise, convert list into comma sep'd list
;; of e-mail addresses
(unless (null val)
(mapconcat
(lambda (contact) (mu4e-contact-full contact)) val ", ")))))
(format "%s: %s\n"
(propertize hdr 'face 'mu4e-header-key-face)
(propertize val 'face 'mu4e-header-value-face))))
(defconst mu4e~max-reference-num 21
"Specifies the maximum number of References:.
As suggested by `message-shorten-references'.")
(defun mu4e~shorten-1 (list cut surplus)
"Cut SURPLUS elements out of LIST.
Beginning with CUTth
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 ;; 30 by RFC1153
(re-search-forward "\\(^-\\{30\\}.*$\\)" nil t)
(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
existing References + In-Reply-To (which may be empty, an note
that :references includes the old in-reply-to as well) and the
message-id. If the message-id is empty, returns the old
References. If both are empty, return nil."
(let* ( ;; these are the ones from the message being replied to / forwarded
(refs (mu4e-message-field msg :references))
(msgid (mu4e-message-field msg :message-id))
;; now, append in
(refs (if (and msgid (not (string= msgid "")))
(append refs (list msgid)) refs))
;; no doubles
(refs (cl-delete-duplicates refs :test #'equal))
(refnum (length refs))
(cut 2))
;; remove some refs when there are too many
(when (> refnum mu4e~max-reference-num)
(let ((surplus (- refnum mu4e~max-reference-num)))
(mu4e~shorten-1 refs cut surplus)))
(mapconcat (lambda (id) (format "<%s>" id)) refs " ")))
;;; Determine the recipient fields for new messages
(defun mu4e~draft-address-cell-equal (cell1 cell2)
"Return t if CELL1 and CELL2 have the same e-mail address.
The comparison is done case-insensitively. If the cells done
match return nil. CELL1 and CELL2 are cons cells of the
form (NAME . EMAIL)."
(string=
(downcase (or (mu4e-contact-email cell1) ""))
(downcase (or (mu4e-contact-email cell2) ""))))
(defun mu4e~draft-create-to-lst (origmsg)
"Create a list of address for the To: in a new message.
This is based on the original message ORIGMSG. If the Reply-To
address is set, use that, otherwise use the From address. Note,
whatever was in the To: field before, goes to the Cc:-list (if
we're doing a reply-to-all). Special case: if we were the sender
of the original, we simple copy the list form the original."
(let* ((reply-to (or (plist-get origmsg :reply-to)
(plist-get origmsg :from)))
(reply-to (cl-delete-duplicates reply-to
:test #'mu4e~draft-address-cell-equal)))
(if mu4e-compose-dont-reply-to-self
(cl-delete-if
(lambda (to-cell)
(mu4e-personal-address-p (mu4e-contact-email to-cell)))
reply-to)
reply-to)))
(defun mu4e~strip-ignored-addresses (addrs)
"Return all addresses that are not to be ignored.
I.e. return all the addresses in ADDRS not matching
`mu4e-compose-reply-ignore-address'."
(cond
((null mu4e-compose-reply-ignore-address)
addrs)
((functionp mu4e-compose-reply-ignore-address)
(cl-remove-if
(lambda (elt)
(funcall mu4e-compose-reply-ignore-address (mu4e-contact-email elt)))
addrs))
(t
;; regexp or list of regexps
(let* ((regexp mu4e-compose-reply-ignore-address)
(regexp (if (listp regexp)
(mapconcat (lambda (elt) (concat "\\(" elt "\\)"))
regexp "\\|")
regexp)))
(cl-remove-if
(lambda (elt)
(string-match regexp (mu4e-contact-email elt)))
addrs)))))
(defun mu4e~draft-create-cc-lst (origmsg &optional reply-all include-from)
"Create a list of address for the Cc: in a new message.
This is based on the original message ORIGMSG, and whether it's a
REPLY-ALL."
(when reply-all
(let* ((cc-lst ;; get the cc-field from the original, remove dups
(cl-delete-duplicates
(append
(plist-get origmsg :to)
(plist-get origmsg :cc)
(when include-from
(or (plist-get origmsg :reply-to)
(plist-get origmsg :from)))
(plist-get origmsg :list-post))
:test #'mu4e~draft-address-cell-equal))
;; now we have the basic list, but we must remove
;; addresses also in the To: list
(cc-lst
(cl-delete-if
(lambda (cc-cell)
(cl-find-if
(lambda (to-cell)
(mu4e~draft-address-cell-equal cc-cell to-cell))
(mu4e~draft-create-to-lst origmsg)))
cc-lst))
;; remove ignored addresses
(cc-lst (mu4e~strip-ignored-addresses cc-lst))
;; finally, we need to remove ourselves from the cc-list
;; unless mu4e-compose-keep-self-cc is non-nil
(cc-lst
(if (or mu4e-compose-keep-self-cc (null user-mail-address))
cc-lst
(cl-delete-if
(lambda (cc-cell)
(mu4e-personal-address-p (mu4e-contact-email cc-cell)))
cc-lst))))
cc-lst)))
(defun mu4e~draft-recipients-construct (field origmsg &optional reply-all include-from)
"Create value (a string) for the recipient FIELD.
\(which is a symbol, :to or :cc), based on the original message ORIGMSG,
and (optionally) REPLY-ALL which indicates this is a reply-to-all
message. Return nil if there are no recipients for the particular field."
(cl-case field
(:to
(mu4e~draft-create-to-lst origmsg))
(:cc
(mu4e~draft-create-cc-lst origmsg reply-all include-from))
(otherwise
(mu4e-error "Unsupported field"))))
(defun mu4e~draft-from-construct ()
"Construct a value for the From:-field of the reply.
This is based on the variable `user-full-name' and
`user-mail-address'; if the latter is nil, function returns nil."
(when user-mail-address
(mu4e-contact-full (mu4e-contact-make
user-full-name
user-mail-address))))
;;; Header separators
(defun mu4e~draft-insert-mail-header-separator ()
"Insert `mail-header-separator' in the first empty line of the message.
`message-mode' needs this line to know where the headers end and
the body starts. Note, in `mu4e-compose-mode', we use
`before-save-hook' and `after-save-hook' to ensure that this
separator is never written to the message file. Also see
`mu4e-remove-mail-header-separator'."
;; we set this here explicitly, since (as it has happened) a wrong
;; value for this (such as "") breaks address completion and other things
(set (make-local-variable 'mail-header-separator) "--text follows this line--")
(put 'mail-header-separator 'permanent-local t)
(save-excursion
;; make sure there's not one already
(mu4e~draft-remove-mail-header-separator)
(let ((sepa (propertize mail-header-separator
'intangible t
;; don't make this read-only, message-mode
;; seems to require it being writable in some cases
;;'read-only "Can't touch this"
'rear-nonsticky t
'font-lock-face 'mu4e-compose-separator-face)))
(widen)
;; search for the first empty line
(goto-char (point-min))
(if (search-forward-regexp "^$" nil t)
(progn
(replace-match sepa)
;; `message-narrow-to-headers` searches for a
;; `mail-header-separator` followed by a new line. Therefore, we
;; must insert a newline if on the last line of the buffer.
(when (= (point) (point-max))
(insert "\n")))
(progn ;; no empty line? then prepend one
(goto-char (point-max))
(insert "\n" sepa))))))
(defun mu4e~draft-remove-mail-header-separator ()
"Remove `mail-header-separator'.
We do this before saving a
file (and restore it afterwards), to ensure that the separator
never hits the disk. Also see
`mu4e~draft-insert-mail-header-separator."
(save-excursion
(widen)
(goto-char (point-min))
;; remove the --text follows this line-- separator
(when (search-forward-regexp (concat "^" mail-header-separator) nil t)
(let ((inhibit-read-only t))
(replace-match "")))))
(defun mu4e~draft-reply-all-p (origmsg)
"Ask user whether she wants to reply to *all* recipients.
If there is just one recipient of ORIGMSG do nothing."
(let* ((recipnum
(+ (length (mu4e~draft-create-to-lst origmsg))
(length (mu4e~draft-create-cc-lst origmsg t))))
(response
(if (< recipnum 2)
'all ;; with less than 2 recipients, we can reply to 'all'
(mu4e-read-option
"Reply to "
`( (,(format "all %d recipients" recipnum) . all)
("sender only" . sender-only))))))
(eq response 'all)))
(defun mu4e~draft-message-filename-construct (&optional flagstr)
"Construct a randomized name for a message file with flags FLAGSTR.
It looks something like
<time>-<random>.<hostname>:2,
You can append flags."
(let* ((sysname (if (fboundp 'system-name)
(system-name)
(with-no-warnings system-name)))
(sysname (if (string= sysname "") "localhost" sysname))
(hostname (downcase
(save-match-data
(substring sysname
(string-match "^[^.]+" sysname)
(match-end 0))))))
(format "%s.%04x%04x%04x%04x.%s%s2,%s"
(format-time-string "%s" (current-time))
(random 65535) (random 65535) (random 65535) (random 65535)
hostname mu4e-maildir-info-delimiter (or flagstr ""))))
(defun mu4e~draft-common-construct ()
"Construct the common headers for each message."
(concat
(when-let ((organization (message-make-organization)))
(mu4e~draft-header "Organization" organization))
(when mu4e-user-agent-string
(mu4e~draft-header "User-agent" mu4e-user-agent-string))
(mu4e~draft-header "Date" (message-make-date))))
(defconst mu4e~draft-reply-prefix "Re: "
"String to prefix replies with.")
(defun mu4e~draft-reply-construct-recipients (origmsg)
"Determine the to/cc recipients for a reply message."
(let* ((return-to (or (plist-get origmsg :reply-to) (plist-get origmsg :from)))
(reply-to-self (mu4e-personal-address-p (plist-get (car return-to) :email)))
;; reply-to-self implies reply-all
(reply-all (or reply-to-self
(eq mu4e-compose-reply-recipients 'all)
(and (not (eq mu4e-compose-reply-recipients 'sender))
(mu4e~draft-reply-all-p origmsg)))))
(concat
;; if there's no-one in To, copy the CC-list
(if (zerop (length (mu4e~draft-create-to-lst origmsg)))
(mu4e~draft-header "To" (mu4e~draft-recipients-construct
:cc origmsg reply-all))
;; otherwise...
(concat
(mu4e~draft-header "To" (mu4e~draft-recipients-construct
:to origmsg reply-all))
(mu4e~draft-header "Cc" (mu4e~draft-recipients-construct
:cc origmsg reply-all)))))))
(defun mu4e~draft-reply-construct-recipients-list (origmsg)
"Determine the to/cc recipients for a reply message to a
mailing-list."
(let* ( ;; reply-to-self implies reply-all
(list-post (plist-get origmsg :list-post))
(return-to (or (plist-get origmsg :reply-to) (plist-get origmsg :from)))
(recipnum
(+ (length (mu4e~draft-create-to-lst origmsg))
(length (mu4e~draft-create-cc-lst origmsg t t))))
(sender (mu4e-contact-full (car return-to)))
(reply-type
(mu4e-read-option
"Reply to mailing-list "
`( (,(format "all %d recipient(s)" recipnum) . all)
(,(format "list-only (%s)" (cdar list-post)) . list-only)
(,(format "sender-only (%s)" sender) . sender-only)))))
(cl-case reply-type
(all
(concat
(mu4e~draft-header "To" (mu4e~draft-recipients-construct :to origmsg))
(mu4e~draft-header "Cc" (mu4e~draft-recipients-construct :cc origmsg t t))))
(list-only
(mu4e~draft-header "To" list-post))
(sender-only
(mu4e~draft-header "To" return-to)))))
(defun mu4e~draft-reply-construct (origmsg)
"Create a draft message as a reply to ORIGMSG.
Replying-to-self is special; in that case, the To and Cc fields
will be the same as in the original."
(let* ((old-msgid (plist-get origmsg :message-id))
(subject (concat mu4e~draft-reply-prefix
(message-strip-subject-re
(or (plist-get origmsg :subject) ""))))
(list-post (plist-get origmsg :list-post)))
(concat
(mu4e~draft-header "From" (or (mu4e~draft-from-construct) ""))
(mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address)
(if list-post ;; mailing-lists are a bit special.
(mu4e~draft-reply-construct-recipients-list origmsg)
(mu4e~draft-reply-construct-recipients origmsg))
(mu4e~draft-header "Subject" subject)
(mu4e~draft-header "References"
(mu4e~draft-references-construct origmsg))
(mu4e~draft-common-construct)
(when old-msgid
(mu4e~draft-header "In-reply-to" (format "<%s>" old-msgid)))
"\n\n"
(mu4e~draft-cite-original origmsg))))
(defconst mu4e~draft-forward-prefix "Fwd: "
"String to prefix replies with.")
(defun mu4e~draft-forward-construct (origmsg)
"Create a draft forward message for original message ORIGMSG."
(let ((subject
(or (plist-get origmsg :subject) "")))
(concat
(mu4e~draft-header "From" (or (mu4e~draft-from-construct) ""))
(mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address)
(mu4e~draft-header "To" "")
(mu4e~draft-common-construct)
(mu4e~draft-header "References"
(mu4e~draft-references-construct origmsg))
(mu4e~draft-header "Subject"
(concat
;; if there's no Fwd: yet, prepend it
(if (string-match "^Fwd:" subject)
""
mu4e~draft-forward-prefix)
subject))
(unless mu4e-compose-forward-as-attachment
(concat
"\n\n"
(mu4e~draft-cite-original origmsg))))))
(defun mu4e~draft-newmsg-construct ()
"Create a new message."
(concat
(mu4e~draft-header "From" (or (mu4e~draft-from-construct) ""))
(mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address)
(mu4e~draft-header "To" "")
(mu4e~draft-header "Subject" "")
(mu4e~draft-common-construct)))
(defvar mu4e~draft-drafts-folder nil
"The drafts-folder for this compose buffer.
This is based on `mu4e-drafts-folder', which is evaluated once.")
(defun mu4e~draft-open-file (path)
"Open the the draft file at PATH."
(find-file-noselect path))
(defun mu4e~draft-determine-path (draft-dir)
"Determines the path for a new draft file in DRAFT-DIR."
(format "%s/%s/cur/%s"
(mu4e-root-maildir) draft-dir (mu4e~draft-message-filename-construct "DS")))
(defun mu4e-draft-open (compose-type &optional msg)
"Open a draft file for a message MSG.
In case of a new message (when COMPOSE-TYPE is `reply', `forward'
or `new'), open an existing draft (when COMPOSE-TYPE is `edit'),
or re-send an existing message (when COMPOSE-TYPE is `resend').
The name of the draft folder is constructed from the
concatenation of `(mu4e-root-maildir)' and `mu4e-drafts-folder' (the
latter will be evaluated). The message file name is a unique name
determined by `mu4e-send-draft-file-name'. The initial contents
will be created from either `mu4e~draft-reply-construct', or
`mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'.
Returns the newly-created draft buffer."
(let ((draft-dir nil)
(draft-buffer))
(cl-case compose-type
(edit
;; 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-buffer (mu4e~draft-open-file (mu4e-message-field msg :path))))
(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)))
(let ((draft-path (mu4e~draft-determine-path draft-dir)))
(copy-file (mu4e-message-field msg :path) draft-path)
(setq draft-buffer (mu4e~draft-open-file draft-path))))
((reply forward new)
;; case-3: creating a new message; in this case, we can determine
;; mu4e-get-drafts-folder
(setq draft-dir (mu4e-get-drafts-folder msg))
(let ((draft-path (mu4e~draft-determine-path draft-dir))
(initial-contents
(cl-case compose-type
(reply (mu4e~draft-reply-construct msg))
(forward (mu4e~draft-forward-construct msg))
(new (mu4e~draft-newmsg-construct)))))
(setq draft-buffer (mu4e~draft-open-file draft-path))
(set-buffer draft-buffer)
(insert initial-contents)
(newline)
;; include the message signature (if it's set)
(if (and mu4e-compose-signature-auto-include mu4e-compose-signature)
(let ((message-signature mu4e-compose-signature))
(save-excursion
(message-insert-signature)
(mu4e~fontify-signature))))))
(t (mu4e-error "Unsupported compose-type %S" compose-type)))
;; if we didn't find a draft folder yet, try some default
(unless draft-dir
(setq draft-dir (mu4e-get-drafts-folder msg)))
;; evaluate mu4e~drafts-drafts-folder once, here, and use that value
;; throughout.
(set (make-local-variable 'mu4e~draft-drafts-folder) draft-dir)
(put 'mu4e~draft-drafts-folder 'permanent-local t)
(unless mu4e~draft-drafts-folder
(mu4e-error "Failed to determine drafts folder"))
;; return the name of the draft buffer
draft-buffer))
;;; _
(provide 'mu4e-draft)
;;; mu4e-draft.el ends here

View File

@ -984,10 +984,6 @@ after the end of the search results."
(define-key map "a" #'mu4e-headers-action)
;; message composition
(define-key map "R" #'mu4e-compose-reply)
(define-key map "F" #'mu4e-compose-forward)
(define-key map "C" #'mu4e-compose-new)
(define-key map "E" #'mu4e-compose-edit)
(define-key map (kbd "RET") #'mu4e-headers-view-message)
(define-key map [mouse-2] #'mu4e-headers-view-message)
@ -1162,6 +1158,7 @@ The following specs are supported:
(mu4e-context-minor-mode)
(mu4e-update-minor-mode)
(mu4e-search-minor-mode)
(mu4e-compose-minor-mode)
(hl-line-mode 1)
(mu4e--modeline-register #'mu4e--search-modeline-item)

View File

@ -1,4 +1,4 @@
;;; mu4e-icalendar.el --- iCcalendar & diary integration -*- lexical-binding: t; -*-
;;; mu4e-icalendar.el --- iCalendar & diary integration -*- lexical-binding: t; -*-
;; Copyright (C) 2019-2023 Christophe Troestler
@ -56,7 +56,7 @@
(require 'mu4e-helpers)
(require 'mu4e-contacts)
(require 'mu4e-headers)
(require 'mu4e-view)
(require 'mu4e-obsolete)
;;; Configuration
@ -78,21 +78,6 @@
:group 'mu4e-icalendar)
;;;###autoload
(defun mu4e-icalendar-setup ()
"Perform the necessary initialization to use mu4e-icalendar."
(gnus-icalendar-setup)
(cl-defmethod gnus-icalendar-event:inline-reply-buttons :around
((event gnus-icalendar-event) handle)
(if (and (boundp 'mu4e--view-rendering)
(gnus-icalendar-event:rsvp event))
(let ((method (gnus-icalendar-event:method event)))
(when (or (string= method "REQUEST") (string= method "PUBLISH"))
`(("Accept" mu4e-icalendar-reply (,handle accepted ,event))
("Tentative" mu4e-icalendar-reply (,handle tentative ,event))
("Decline" mu4e-icalendar-reply (,handle declined ,event)))))
(cl-call-next-method event handle))))
(defun mu4e--icalendar-has-email (email list)
"Check that EMAIL is in LIST."
(let ((email (downcase email)))
@ -100,6 +85,14 @@
(and (stringp e) (string= email (downcase e)))))
list)))
(declare-function mu4e--view-mode-p "mu4e-view")
(defun mu4e--icalendar-reply (orig data)
"Wrapper for using either `mu4e-icalender-reply' or the ORIG function."
(funcall (if (mu4e--view-mode-p) #'mu4e-icalendar-reply orig) data))
(advice-add #'gnus-icalendar-reply :around #'mu4e--icalendar-reply)
;;(advice-remove #'gnus-icalendar-reply #'mu4e--icalendar-reply)
(defun mu4e-icalendar-reply (data)
"Reply to the text/calendar event present in DATA."
;; Based on `gnus-icalendar-reply'.
@ -117,70 +110,55 @@
(when reply
(cl-labels
((fold-icalendar-buffer
()
(goto-char (point-min))
(while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t)
(replace-match "\\1\n \\2")
(goto-char (line-beginning-position)))))
()
(goto-char (point-min))
(while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t)
(replace-match "\\1\n \\2")
(goto-char (line-beginning-position)))))
(let ((ical-name gnus-icalendar-reply-bufname))
(let ((ical-name gnus-icalendar-reply-bufname))
(with-current-buffer (get-buffer-create ical-name)
(delete-region (point-min) (point-max))
(insert reply)
(fold-icalendar-buffer)
(when (and charset (string= (downcase charset) "utf-8"))
(decode-coding-region (point-min) (point-max) 'utf-8)))
;; Compose the reply message.
(save-excursion
(let ((message-signature nil)
(mu4e-compose-cite-function #'mu4e--icalendar-delete-citation)
(mu4e-sent-messages-behavior 'delete)
(mu4e-compose-reply-recipients 'sender)
(ical-msg (cl-copy-list msg)))
;; Make sure the reply is sent to email of the organiser with
;; proper name.
(let* ((organizer (gnus-icalendar-event:organizer event))
(reply-to (car (plist-get msg :reply-to)))
(from (car (plist-get msg :from)))
(name (or (plist-get reply-to :name)
(plist-get from :name))))
;; Add :reply-to field when incomplete or absent
(unless (or (string= organizer "")
(mu4e--icalendar-has-email organizer reply-to))
(plist-put ical-msg :reply-to
`((:name ,name :email ,organizer))))
(plist-put ical-msg :subject
(concat (capitalize (symbol-name status))
": " (gnus-icalendar-event:summary event))))
(mu4e~compose-handler
'reply ical-msg
`((:buffer-name ,ical-name
:mime-type "text/calendar; method=REPLY; charset=utf-8")))
(message-goto-body)
(set-buffer-modified-p nil); not yet modified by user
(when mu4e-icalendar-trash-after-reply
;; Override `mu4e-sent-handler' set by `mu4e-compose-mode' to
;; also trash the message (thus must be appended to hooks).
(add-hook 'message-sent-hook
(mu4e--icalendar-trash-message-hook msg)
90 t)))))
;; Back in article buffer
(setq-local gnus-icalendar-reply-status status)
(save-excursion ;; Compose the reply message.
(let* ((message-signature nil)
(organizer (gnus-icalendar-event:organizer event))
(organizer (when (and organizer
(not (string-empty-p organizer)))
organizer))
(organizer (or organizer
(car (plist-get msg :reply-to))
(car (plist-get msg :from))
(mu4e-warn "Cannot find organizer"))))
(mu4e--compose-setup
'reply
(lambda (_parent)
(message-reply organizer)
(goto-char (point-max))
(message-goto-body)
(mml-insert-multipart "alternative")
(mml-insert-empty-tag 'part 'type "text/plain")
(mml-attach-buffer ical-name
"text/calendar; method=REPLY; charset=UTF-8")
(when mu4e-icalendar-trash-after-reply
;; Override `mu4e-sent-handler' set by `mu4e-compose-mode' to
;; also trash the message (thus must be appended to hooks).
(add-hook 'message-sent-hook
(mu4e--icalendar-trash-message-hook msg) 90 t))))
(when gnus-icalendar-org-enabled-p
(if (gnus-icalendar-find-org-event-file event)
(gnus-icalendar--update-org-event event status)
(gnus-icalendar:org-event-save event status)))
(when mu4e-icalendar-diary-file
(mu4e--icalendar-insert-diary event status
mu4e-icalendar-diary-file))))))
(defun mu4e--icalendar-delete-citation ()
"Function passed to `mu4e-compose-cite-function' to remove the citation."
(message-cite-original-without-signature)
(kill-region (point-min) (point-max)))
(when gnus-icalendar-org-enabled-p
(if (gnus-icalendar-find-org-event-file event)
(gnus-icalendar--update-org-event event status)
(gnus-icalendar:org-event-save event status)))
(when mu4e-icalendar-diary-file
(mu4e--icalendar-insert-diary event status
mu4e-icalendar-diary-file)))))))))
(declare-function mu4e-view-headers-next "mu4e-view")
(defun mu4e--icalendar-trash-message (original-msg)
"Trash the message ORIGINAL-MSG and move to the next one."
(lambda (docid path)
@ -200,7 +178,7 @@
(kill-buffer-and-window))))))
(defun mu4e--icalendar-trash-message-hook (original-msg)
"Trash the icalender message ORIGINAL-MSG."
"Trash the iCalendar message ORIGINAL-MSG."
(lambda ()
(setq mu4e-sent-func
(mu4e--icalendar-trash-message original-msg))))

View File

@ -1,6 +1,6 @@
;;; mu4e-obsolete-vars.el --- Obsolete things -*- lexical-binding: t -*-
;;; mu4e-obsolete.el --- Obsolete things -*- lexical-binding: t -*-
;; Copyright (C) 2022 Dirk-Jan C. Binnema
;; Copyright (C) 2022-2023 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -28,7 +28,7 @@
;;; Code:
;; mu4e-draft
;; mu4e-draft/compose
(make-obsolete-variable 'mu4e-reply-to-address
'mu4e-compose-reply-to-address
@ -54,6 +54,15 @@
(make-obsolete-variable 'mu4e-compose-auto-include-date
"This is done unconditionally now" "1.3.5")
(make-obsolete-variable 'mu4e-compose-signature-auto-include
"Usage message-signature directly" "1.11.22")
(define-obsolete-variable-alias
'mu4e-compose-signature 'message-signature "1.11.22")
(define-obsolete-variable-alias
'mu4e-compose-cite-function 'message-cite-function "1.11.22")
(define-obsolete-variable-alias
'mu4e-compose-in-new-frame 'mu4e-compose-switch "1.11.22")
;; mu4e-message
@ -111,9 +120,9 @@
;; mu4e-org
(make-obsolete 'org-mu4e-open 'mu4e-org-open "1.3.6")
(make-obsolete 'org-mu4e-store-and-capture
'mu4e-org-store-and-capture "1.3.6")
(define-obsolete-function-alias 'org-mu4e-open 'mu4e-org-open "1.3.6")
(define-obsolete-function-alias 'org-mu4e-store-and-capture
'mu4e-org-store-and-capture "1.3.6")
;; mu4e-search
@ -227,8 +236,32 @@
;; mu4e-folder
(make-obsolete-variable 'mu4e-cache-maildir-list "No longer used" "1.11.15")
;; mu4e-contacts
(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")
(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")
;; calendar
(define-obsolete-function-alias 'mu4e-icalendar-setup #'ignore '"1.11.22")
;; mu4e.
(make-obsolete 'mu4e-clear-caches "No longer used" "1.11.15")
(provide 'mu4e-obsolete)
;;; mu4e-obsolete.el ends here

View File

@ -721,8 +721,8 @@ determine which browser function to use."
(detect-coding-region (point-min) (point-max) t)))
;; Possibly add headers (before "Attachments")
(gnus-display-mime-function (mu4e--view-gnus-display-mime msg))
(gnus-icalendar-additional-identities
(mu4e-personal-addresses 'no-regexp)))
(message-alternative-emails
(mu4e-personal-or-alternative-address)))
(condition-case err
(progn
(mm-enable-multibyte)
@ -905,11 +905,6 @@ This is useful for advising some Gnus-functionality that does not work in mu4e."
(define-key map "k" #'mu4e-view-save-url)
(define-key map "f" #'mu4e-view-fetch-url)
(define-key map "F" #'mu4e-compose-forward)
(define-key map "R" #'mu4e-compose-reply)
(define-key map "C" #'mu4e-compose-new)
(define-key map "E" #'mu4e-compose-edit)
(define-key map "." #'mu4e-view-raw-message)
(define-key map "," #'mu4e-sexp-at-point)
(define-key map "|" #'mu4e-view-pipe)
@ -1067,6 +1062,7 @@ Based on Gnus' article-mode."
(use-local-map mu4e-view-mode-map)
(mu4e-context-minor-mode)
(mu4e-search-minor-mode)
(mu4e-compose-minor-mode)
(setq buffer-undo-list t) ;; don't record undo info
;; support bookmarks.

View File

@ -76,11 +76,6 @@ function; this is no longer supported; instead you can use
(const :tag "Don't split" nil))
:group 'mu4e-headers)
(defcustom mu4e-compose-in-new-frame nil
"Whether to compose messages in a new frame."
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-headers-visible-lines 10
"Number of lines to display in the header view when using the
horizontal split-view. This includes the header-line at the top,
@ -300,8 +295,6 @@ for BUFFER-OR-NAME to be displayed in."
((eq buffer-type 'main) '(display-buffer-reuse-window
display-buffer-reuse-mode-window
display-buffer-full-frame))
((and (eq buffer-type 'compose) mu4e-compose-in-new-frame)
'(display-buffer-pop-up-frame))
((memq buffer-type '(headers compose))
'(display-buffer-reuse-window
display-buffer-reuse-mode-window

View File

@ -253,7 +253,7 @@ chance."
(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-compose-func #'mu4e--compose-setup)
(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)

View File

@ -89,7 +89,7 @@ with answers to frequently asked questions, @ref{FAQ}.
* Main view:: The @t{mu4e} overview
* Headers view:: Lists of message headers
* Message view:: Viewing specific messages
* Editor view:: Creating and editing messages
* Composer:: Creating and editing messages
* Searching:: Some more background on searching/queries`
* Marking:: Marking messages and performing actions
* Contexts:: Defining contexts and switching between them
@ -765,7 +765,7 @@ maildir are listed, in the @ref{Headers view}.
you for a search query, and after entering one, shows the results in the
@ref{Headers view}.
@item @t{[C]ompose a new message}: after pressing @key{C}, you are dropped in
the @ref{Editor view} to write a new message.
the @ref{Composer} to write a new message.
@end itemize
@node Bookmarks and Maildirs
@ -1024,10 +1024,9 @@ S-TAB toggle all threading
composition
-----------
R,F,C reply/forward/compose
R,W,F,C reply/reply-to-all/forward/compose
E edit (only allowed for draft messages)
misc
----
a execute some custom action on a header
@ -1039,9 +1038,10 @@ C-c C-u update mail & reindex
q leave the headers buffer
@end verbatim
Furthermore, a number of keybindings are available through minor modes:
Some keybindings are available through minor modes:
@itemize
@item Context; see @pxref{Contexts}.
@item Composition; see @pxref{Composer} and @t{mu4e-compose-minor-mode}
@end itemize
@node HV Marking
@ -1394,7 +1394,7 @@ x execute actions for the marked messages
composition
-----------
R,F,C reply/forward/compose
R,W,F,C reply/reply-to-all/forward/compose
E edit (only allowed for draft messages)
actions
@ -1422,9 +1422,10 @@ C-S-u update mail & reindex
q leave the message view
@end verbatim
Furthermore, a number of keybindings are available through minor modes:
Some keybindings are available through minor modes:
@itemize
@item Context; see @pxref{Contexts}.
@item Context; see @pxref{Contexts}
@item Composition; see @pxref{Composer} and @t{mu4e-compose-minor-mode}
@end itemize
For the marking commands, please refer to @ref{Marking messages}.
@ -1597,26 +1598,30 @@ You can @emph{tear off} the window a message is in and place it in a
new frame by typing @key{C-x w ^ f}. You can also detach a window and
put it in its own tab with @key{C-x w ^ t}.
@node Editor view
@chapter The editor view
@node Composer
@chapter Composer
Writing e-mail messages takes place in the Editor View. @t{mu4e}'s editor view
builds on top of Gnus' @t{message-mode}. Most of the @t{message-mode}
functionality is available, as well some @t{mu4e}-specifics. Its major mode is
@code{mu4e-compose-mode}.
Writing e-mail messages takes place in the Composer. @t{mu4e}'s re-uses much of
Gnus' @t{message-mode}.
Much of the @t{message-mode} functionality is available, as well some
@t{mu4e}-specifics. See @ref{(message) Top} for details; not every setting is
necessarily also supported in @t{mu4e}.
The major mode for the composer is @code{mu4e-compose-mode}.
@menu
* Overview: EV Overview. What is the Editor view
* Keybindings: EV Keybindings. Doing things with your keyboard
* Composer overview: Composer overview. What is the composer good for
* Keybindings: Composer Keybindings. Doing things with your keyboard
* Address autocompletion:: Quickly entering known addresses
* Compose hooks::Calling functions when composing
* Signing and encrypting:: Support for cryptography
* Queuing mail:: Sending mail when the time is ripe
* Message signatures:: Adding your personal footer to messages
* Other settings::Miscellanea
* Other settings::Miscellaneous
@end menu
@node EV Overview
@node Composer overview
@section Overview
@cartouche
@ -1632,14 +1637,62 @@ functionality is available, as well some @t{mu4e}-specifics. Its major mode is
>
> Dude - how are things?
>
> Later -- wally.
> Later -- Wally.
@end verbatim
@end cartouche
@node EV Keybindings
@node Entering the composer
@section Entering the composer
There are a view different ways to @emph{enter} the composer; i.e., from other
@t{mu4e} views or even completely outside.
If you want the composer to start in a new frame or window, see the variable
@t{mu4e-compose-switch}.
@subsection New message
You can start composing a completely new message with @t{mu4e-compose-new} (with
@kbd{N} from within @t{mu4e}.
@subsection Reply
You can compose a reply to an existing message with @t{mu4e-compose-reply} (with
@kbd{R} from within the headers view or when looking at some specific message.
When you want to reply to @emph{all} recipients of a message, you can use
@t{mu4e-compose-wide-reply}, bound to @kbd{W}. This is often called
``reply-to-all'', while Gnus uses the term ``wide reply''.
By default, the reply will cite the message being replied to. If you do not want
that, you can set (or @t{let}-bind) @t{message-cite-function} to
@t{mu4e-message-cite-nothing}.
See @ref{(message) Reply} and @ref{(message) Wide Reply} for further
information.
@subsection Forward
You can forward some existing message with @t{mu4e-compose-forward} (with
@kbd{F} from within the headers view or when looking at some specific message.
For more information, see @ref{(message) Forwarding}.
To influence the way a message is forwarded, you can use the variables
@code{message-forward-as-mime} and @code{message-forward-show-mml}.
@subsection Resend
You can re-send some existing message with @t{mu4e-compose-resend} from within
the headers view or when looking at some specific message.
This re-sends the message without letting you edit it, as per @ref{(message)
Resending}.
@node Composer Keybindings
@section Keybindings
@t{mu4e}'s editor view derives from Gnus' message editor and shares most of
@t{mu4e}'s composer derives from Gnus' message editor and shares most of
its keybindings. Here are some of the more useful ones (you can use the menu
to find more):
@ -1649,11 +1702,11 @@ key description
C-c C-c send message
C-c C-d save to drafts and leave
C-c C-k kill the message buffer (the message remains in the draft folder)
C-c C-a attach a file (pro-tip: drag & drop works as well)
C-c C-a attach a file (pro-tip: drag & drop works as well in graphical context)
C-c C-; switch the context
(mu4e-specific)
C-S-u update mail & reindex
C-S-u update mail & re-index
@end verbatim
@node Address autocompletion
@ -1718,27 +1771,7 @@ is also at your disposal.
@end itemize
@noindent
Let's look at some examples. First, suppose we want to set the
@t{From:}-address for a reply message based on the receiver of the original:
@lisp
;; 1) messages to me@@foo.example.com should be replied with From:me@@foo.example.com
;; 2) messages to me@@bar.example.com should be replied with From:me@@bar.example.com
;; 3) all other mail should use From:me@@cuux.example.com
(add-hook 'mu4e-compose-pre-hook
(defun my-set-from-address ()
"Set the From address based on the To address of the original."
(let ((msg mu4e-compose-parent-message)) ;; msg is shorter...
(when msg
(setq user-mail-address
(cond
((mu4e-message-contact-field-matches msg :to "me@@foo.example.com")
"me@@foo.example.com")
((mu4e-message-contact-field-matches msg :to "me@@bar.example.com")
"me@@bar.example.com")
(t "me@@cuux.example.com")))))))
@end lisp
Secondly, as mentioned, @code{mu4e-compose-mode-hook} is especially
Let's look at an examples. As mentioned, @code{mu4e-compose-mode-hook} is especially
useful for editing-related settings. For example:
@lisp
(add-hook 'mu4e-compose-mode-hook
@ -1832,13 +1865,9 @@ do this accidentally!
@section Message signatures
Message signatures are the standard footer blobs in e-mail messages where you
can put in information you want to include in every message. The text to
include is set with @code{mu4e-compose-signature}.
If you don't want to include this automatically with each message,
you can set @code{mu4e-compose-signature-auto-include} to @code{nil}; you can
then still include the signature manually, using the function
@code{message-insert-signature}, typically bound to @kbd{C-c C-w}.
can put in information you want to include in every message. The text to include
is set with @code{message-signature} (older @t{mu4e} used
@code{mu4e-compose-signature}, but that has been obsoleted).
@node Other settings
@section Other settings
@ -2618,7 +2647,7 @@ when starting; see the discussion in the previous section.
:vars '( ( user-mail-address . "aliced@@home.example.com" )
( user-full-name . "Alice Derleth" )
( message-user-organization . "Homebase" )
( mu4e-compose-signature .
( message-signature .
(concat
"Alice Derleth\n"
"Lauttasaari, Finland\n"))))
@ -2631,10 +2660,10 @@ when starting; see the discussion in the previous section.
:match-func (lambda (msg)
(when msg
(string-match-p "^/Arkham" (mu4e-message-field msg :maildir))))
:vars '( ( user-mail-address . "aderleth@@miskatonic.example.com" )
( user-full-name . "Alice Derleth" )
:vars '( ( user-mail-address . "aderleth@@miskatonic.example.com" )
( user-full-name . "Alice Derleth" )
( message-user-organization . "Miskatonic University" )
( mu4e-compose-signature .
( message-signature .
(concat
"Prof. Alice Derleth\n"
"Miskatonic University, Dept. of Occult Sciences\n"))))
@ -2648,9 +2677,9 @@ when starting; see the discussion in the previous section.
:match-func (lambda (msg)
(when msg
(string= (mu4e-message-field msg :maildir) "/cycling")))
:vars '( ( user-mail-address . "aderleth@@example.com" )
( user-full-name . "AliceD" )
( mu4e-compose-signature . nil)))))
:vars '( ( user-mail-address . "aderleth@@example.com" )
( user-full-name . "AliceD" )
( message-signature . nil)))))
;; set `mu4e-context-policy` and `mu4e-compose-policy` to tweak when mu4e should
;; guess or ask the correct context, e.g.
@ -3743,8 +3772,7 @@ customize.
(setq mu4e-compose-reply-to-address "foo@@bar.example.com"
user-mail-address "foo@@bar.example.com"
user-full-name "Foo X. Bar")
(setq mu4e-compose-signature
"Foo X. Bar\nhttp://www.example.com\n")
(setq message-signature "Foo X. Bar\nhttp://www.example.com\n")
;; smtp mail setting
(setq
@ -3902,7 +3930,7 @@ Next step: let's make a @t{mu4e} configuration for this:
(setq
user-mail-address "USERNAME@@gmail.com"
user-full-name "Foo X. Bar"
mu4e-compose-signature
message-signature
(concat
"Foo X. Bar\n"
"http://www.example.com\n"))
@ -3932,8 +3960,8 @@ Next step: let's make a @t{mu4e} configuration for this:
(setq message-kill-buffer-on-exit t)
@end lisp
And that's it --- put the above in your @file{~/.emacs}, change @t{USERNAME}
etc.@: to your own, and restart Emacs, and run @kbd{M-x mu4e}.
And that's it --- put the above in your emacs initialization file, change
@t{USERNAME} etc. to your own, restart Emacs, and run @kbd{M-x mu4e}.
@node CONF Other settings
@section Other settings
@ -4209,13 +4237,6 @@ your configuration (courtesy of user @t{kpachnis}):
mml2015-encrypt-to-self t
mml2015-sign-with-sender t)
@end lisp
@subsection Can I `bounce' or `resend' messages?
Somewhat --- it is possible to edit a (copy of) an existing message and then
send it, using @code{M-x mu4e-compose-resend}. This gives you a raw copy of the
message, including all headers, encoded parts and so on. Reason for this is that
for resending, it is important not to change anything (except perhaps for the
@t{To:} address when bouncing); since we cannot losslessly decode an existing
message, you get the raw version.
@node Writing messages
@section Writing messages