mu4e-contacts: cleanups, save-match-data

Whitespace cleanups

Flycheck cleanups

Save the match data for (after|before)-save-hook
This commit is contained in:
Dirk-Jan C. Binnema
2019-05-25 17:38:58 +03:00
parent 5a8ac5c023
commit 2beb2eda02

View File

@ -1,7 +1,6 @@
;; -*-mode: emacs-lisp; tab-width: 8; indent-tabs-mode: t -*-
;; mu4e-compose.el -- part of mu4e, the mu mail user agent for emacs ;; mu4e-compose.el -- part of mu4e, the mu mail user agent for emacs
;; ;;
;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema ;; Copyright (C) 2011-2019 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -105,14 +104,14 @@ which takes no arguments, and which should return one of the mentioned
symbols, for example: symbols, for example:
(setq mu4e-sent-messages-behavior (lambda () (setq mu4e-sent-messages-behavior (lambda ()
(if (string= (message-sendmail-envelope-from) \"foo@example.com\") (if (string= (message-sendmail-envelope-from) \"foo@example.com\")
'delete 'sent))) 'delete 'sent)))
The various `message-' functions from `message-mode' are available The various `message-' functions from `message-mode' are available
for querying the message information." for querying the message information."
:type '(choice (const :tag "move message to mu4e-sent-folder" sent) :type '(choice (const :tag "move message to mu4e-sent-folder" sent)
(const :tag "move message to mu4e-trash-folder" trash) (const :tag "move message to mu4e-trash-folder" trash)
(const :tag "delete message" delete)) (const :tag "delete message" delete))
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-context-policy 'ask (defcustom mu4e-compose-context-policy 'ask
@ -131,13 +130,13 @@ contexts match, we have the following choices:
Also see `mu4e-context-policy'." Also see `mu4e-context-policy'."
:type '(choice :type '(choice
(const :tag "Always ask what context to use" always-ask) (const :tag "Always ask what context to use" always-ask)
(const :tag "Ask if none of the contexts match" 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 "Ask when there's no context yet" ask-if-none)
(const :tag "Pick the first context if none match" pick-first) (const :tag "Pick the first context if none match" pick-first)
(const :tag "Don't change the context when none match" nil) (const :tag "Don't change the context when none match" nil)
:safe 'symbolp :safe 'symbolp
:group 'mu4e-compose)) :group 'mu4e-compose))
(defcustom mu4e-compose-crypto-reply-encrypted-policy 'sign-and-encrypt (defcustom mu4e-compose-crypto-reply-encrypted-policy 'sign-and-encrypt
"Policy for signing/encrypting replies to encrypted messages. "Policy for signing/encrypting replies to encrypted messages.
@ -148,12 +147,12 @@ We have the following choices:
- `encrypt': encrypt the reply, but don't sign it. - `encrypt': encrypt the reply, but don't sign it.
- anything else: do nothing." - anything else: do nothing."
:type '(choice :type '(choice
(const :tag "Sign the reply" sign) (const :tag "Sign the reply" sign)
(const :tag "Sign and encrypt the reply" sign-and-encrypt) (const :tag "Sign and encrypt the reply" sign-and-encrypt)
(const :tag "Encrypt the reply" encrypt) (const :tag "Encrypt the reply" encrypt)
(const :tag "Don't do anything" nil) (const :tag "Don't do anything" nil)
:safe 'symbolp :safe 'symbolp
:group 'mu4e-compose)) :group 'mu4e-compose))
(defcustom mu4e-compose-crypto-reply-plain-policy nil (defcustom mu4e-compose-crypto-reply-plain-policy nil
"Policy for signing/encrypting replies to messages received unencrypted. "Policy for signing/encrypting replies to messages received unencrypted.
@ -164,28 +163,24 @@ We have the following choices:
- `encrypt': encrypt the reply, but don't sign it. - `encrypt': encrypt the reply, but don't sign it.
- anything else: do nothing." - anything else: do nothing."
:type '(choice :type '(choice
(const :tag "Sign the reply" sign) (const :tag "Sign the reply" sign)
(const :tag "Sign and encrypt the reply" sign-and-encrypt) (const :tag "Sign and encrypt the reply" sign-and-encrypt)
(const :tag "Encrypt the reply" encrypt) (const :tag "Encrypt the reply" encrypt)
(const :tag "Don't do anything" nil) (const :tag "Don't do anything" nil)
:safe 'symbolp :safe 'symbolp
:group 'mu4e-compose)) :group 'mu4e-compose))
(defcustom mu4e-compose-crypto-reply-policy nil "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")
(make-obsolete-variable 'mu4e-compose-crypto-reply-policy "The use of the (make-obsolete-variable 'mu4e-compose-crypto-reply-policy "The use of the
'mu4e-compose-crypto-reply-policy' variable is deprecated. 'mu4e-compose-crypto-reply-policy' variable is deprecated.
'mu4e-compose-crypto-reply-plain-policy' and 'mu4e-compose-crypto-reply-plain-policy' and
'mu4e-compose-crypto-reply-encrypted-policy' should be used instead" 'mu4e-compose-crypto-reply-encrypted-policy' should be used instead"
"2017-09-02") "2017-09-02")
(defcustom mu4e-compose-format-flowed nil (defcustom mu4e-compose-format-flowed nil
"Whether to compose messages to be sent as format=flowed (or "Whether to compose messages to be sent as format=flowed.
with long lines if `use-hard-newlines' is set to nil). The \(Or with long lines if variable `use-hard-newlines' is set to
variable `fill-flowed-encode-column' lets you customize the nil). The variable `fill-flowed-encode-column' lets you customize
width beyond which format=flowed lines are wrapped." the width beyond which format=flowed lines are wrapped."
:type 'boolean :type 'boolean
:safe 'booleanp :safe 'booleanp
:group 'mu4e-compose) :group 'mu4e-compose)
@ -206,8 +201,8 @@ place to do that."
:group 'mu4e-compose) :group 'mu4e-compose)
(defvar mu4e-compose-type nil (defvar mu4e-compose-type nil
"The compose-type for this buffer, which is a symbol, `new', "The compose-type for this buffer.
`forward', `reply' or `edit'.") This is a symbol, `new', `forward', `reply' or `edit'.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -249,24 +244,25 @@ Messages are captured with `mu4e-action-capture-message'."
"Maybe setup Fcc, based on `mu4e-sent-messages-behavior'. "Maybe setup Fcc, based on `mu4e-sent-messages-behavior'.
If needed, set the Fcc header, and register the handler function." If needed, set the Fcc header, and register the handler function."
(let* ((sent-behavior (let* ((sent-behavior
;; Note; we cannot simply use functionp here, since at least ;; Note; we cannot simply use functionp here, since at least
;; delete is a function, too... ;; delete is a function, too...
(if (member mu4e-sent-messages-behavior '(delete trash sent)) (if (member mu4e-sent-messages-behavior '(delete trash sent))
mu4e-sent-messages-behavior mu4e-sent-messages-behavior
(if (functionp mu4e-sent-messages-behavior) (if (functionp mu4e-sent-messages-behavior)
(funcall mu4e-sent-messages-behavior) (funcall mu4e-sent-messages-behavior)
mu4e-sent-messages-behavior))) mu4e-sent-messages-behavior)))
(mdir (mdir
(cl-case sent-behavior (cl-case sent-behavior
(delete nil) (delete nil)
(trash (mu4e-get-trash-folder mu4e-compose-parent-message)) (trash (mu4e-get-trash-folder mu4e-compose-parent-message))
(sent (mu4e-get-sent-folder mu4e-compose-parent-message)) (sent (mu4e-get-sent-folder mu4e-compose-parent-message))
(otherwise (otherwise
(mu4e-error "unsupported value '%S' `mu4e-sent-messages-behavior'." (mu4e-error "Unsupported value '%S'
mu4e-sent-messages-behavior)))) `mu4e-sent-messages-behavior'"
(fccfile (and mdir mu4e-sent-messages-behavior))))
(concat mu4e-maildir mdir "/cur/" (fccfile (and mdir
(mu4e~draft-message-filename-construct "S"))))) (concat mu4e-maildir mdir "/cur/"
(mu4e~draft-message-filename-construct "S")))))
;; if there's an fcc header, add it to the file ;; if there's an fcc header, add it to the file
(when fccfile (when fccfile
(message-add-header (concat "Fcc: " fccfile "\n")) (message-add-header (concat "Fcc: " fccfile "\n"))
@ -274,17 +270,17 @@ If needed, set the Fcc header, and register the handler function."
;; etc. if you run it after mu4e so, (hack hack) we reset it to the old ;; etc. if you run it after mu4e so, (hack hack) we reset it to the old
;; handler after we've done our thing. ;; handler after we've done our thing.
(setq message-fcc-handler-function (setq message-fcc-handler-function
(lexical-let ((maildir mdir) (old-handler message-fcc-handler-function)) (lexical-let ((maildir mdir) (old-handler message-fcc-handler-function))
(lambda (file) (lambda (file)
(setq message-fcc-handler-function old-handler) ;; reset the fcc handler (setq message-fcc-handler-function old-handler) ;; reset the fcc handler
(let ((mdir-path (concat mu4e-maildir maildir))) (let ((mdir-path (concat mu4e-maildir maildir)))
;; Create the full maildir structure for the sent folder if it doesn't exist. ;; Create the full maildir structure for the sent folder if it doesn't exist.
;; `mu4e~proc-mkdir` runs asynchronously but no matter whether it runs before or after ;; `mu4e~proc-mkdir` runs asynchronously but no matter whether it runs before or after
;; `write-file`, the sent maildir ends up in the correct state. ;; `write-file`, the sent maildir ends up in the correct state.
(unless (file-exists-p mdir-path) (unless (file-exists-p mdir-path)
(mu4e~proc-mkdir mdir-path))) (mu4e~proc-mkdir mdir-path)))
(write-file file) ;; writing maildirs files is easy (write-file file) ;; writing maildirs files is easy
(mu4e~proc-add file (or maildir "/")))))))) ;; update the database (mu4e~proc-add file (or maildir "/")))))))) ;; update the database
(defvar mu4e-compose-hidden-headers (defvar mu4e-compose-hidden-headers
`("^References:" "^Face:" "^X-Face:" `("^References:" "^Face:" "^X-Face:"
@ -300,27 +296,31 @@ If needed, set the Fcc header, and register the handler function."
"^\\(To\\|B?Cc\\|Reply-To\\|From\\):") "^\\(To\\|B?Cc\\|Reply-To\\|From\\):")
(defun mu4e~compose-register-message-save-hooks () (defun mu4e~compose-register-message-save-hooks ()
"Just before saving, we remove the mail-header-separator; just "Just before saving, we remove the `mail-header-separator'.
after saving we restore it; thus, the separator should never Just after saving we restore it; thus, the separator should never
appear on disk." appear on disk."
(add-hook 'before-save-hook (add-hook 'before-save-hook
'mu4e~draft-remove-mail-header-separator nil t) (lambda()
(save-match-data
(mu4e~draft-remove-mail-header-separator))) nil t)
(add-hook 'after-save-hook (add-hook 'after-save-hook
(lambda () (lambda ()
(mu4e~compose-set-friendly-buffer-name) (save-match-data
(mu4e~draft-insert-mail-header-separator) (mu4e~compose-set-friendly-buffer-name)
;; hide some headers again (mu4e~draft-insert-mail-header-separator)
(mu4e~compose-hide-headers) ;; hide some headers again
(widen) (mu4e~compose-hide-headers)
(set-buffer-modified-p nil) (widen)
(mu4e-message "Saved (%d lines)" (count-lines (point-min) (point-max))) (set-buffer-modified-p nil)
;; update the file on disk -- ie., without the separator (mu4e-message "Saved (%d lines)" (count-lines (point-min) (point-max)))
(mu4e~proc-add (buffer-file-name) mu4e~draft-drafts-folder)) nil t)) ;; update the file on disk -- ie., without the separator
(mu4e~proc-add (buffer-file-name) mu4e~draft-drafts-folder))) nil t))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; address completion; inspired by org-contacts.el and ;; address completion; inspired by org-contacts.el and
;; https://github.com/nordlow/elisp/blob/master/mine/completion-styles-cycle.el ;; https://github.com/nordlow/elisp/blob/master/mine/completion-styles-cycle.el
(defun mu4e~compose-complete-handler (str pred action) (defun mu4e~compose-complete-handler (str pred action)
"Complete address STR with predication PRED for ACTION."
(cond (cond
((eq action nil) ((eq action nil)
(try-completion str mu4e~contacts pred)) (try-completion str mu4e~contacts pred))
@ -330,29 +330,29 @@ appear on disk."
;; our contacts are already sorted - just need to tell the ;; our contacts are already sorted - just need to tell the
;; completion machinery not to try to undo that... ;; completion machinery not to try to undo that...
'(metadata '(metadata
(display-sort-function . identity) (display-sort-function . identity)
(cycle-sort-function . identity))))) (cycle-sort-function . identity)))))
(defun mu4e~compose-complete-contact (&optional start) (defun mu4e~compose-complete-contact (&optional start)
"Complete the text at START with a contact. "Complete the text at START with a contact.
Ie. either 'name <email>' or 'email')." Ie. either 'name <email>' or 'email')."
(interactive) (interactive)
(let ((mail-abbrev-mode-regexp mu4e~compose-address-fields-regexp) (let ((mail-abbrev-mode-regexp mu4e~compose-address-fields-regexp)
(eoh ;; end-of-headers (eoh ;; end-of-headers
(save-excursion (save-excursion
(goto-char (point-min)) (goto-char (point-min))
(search-forward-regexp mail-header-separator nil t)))) (search-forward-regexp mail-header-separator nil t))))
;; try to complete only when we're in the headers area, ;; try to complete only when we're in the headers area,
;; looking at an address field. ;; looking at an address field.
(when (and eoh (> eoh (point)) (mail-abbrev-in-expansion-header-p)) (when (and eoh (> eoh (point)) (mail-abbrev-in-expansion-header-p))
(let* ((end (point)) (let* ((end (point))
(start (start
(or start (or start
(save-excursion (save-excursion
(re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*") (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
(goto-char (match-end 0)) (goto-char (match-end 0))
(point))))) (point)))))
(list start end 'mu4e~compose-complete-handler))))) (list start end 'mu4e~compose-complete-handler)))))
(defun mu4e~compose-setup-completion () (defun mu4e~compose-setup-completion ()
"Set up auto-completion of addresses." "Set up auto-completion of addresses."
@ -363,9 +363,9 @@ Ie. either 'name <email>' or 'email')."
'mu4e~compose-complete-contact nil t)) 'mu4e~compose-complete-contact nil t))
(defun mu4e~remove-refs-maybe () (defun mu4e~remove-refs-maybe ()
"Remove the References: header if the In-Reply-To header is "Remove References: if In-Reply-To: is missing.
missing. This allows the user to effectively start a new This allows the user to effectively start a new message-thread by
message-thread by removing the In-Reply-To header." removing the In-Reply-To header."
(unless (message-fetch-field "in-reply-to") (unless (message-fetch-field "in-reply-to")
(message-remove-header "References"))) (message-remove-header "References")))
@ -382,10 +382,11 @@ message-thread by removing the In-Reply-To header."
map))) map)))
(defun mu4e-fill-paragraph (&optional region) (defun mu4e-fill-paragraph (&optional region)
"If `use-hard-newlines', takes a multi-line paragraph and makes "Re-layout either the whole message or REGION.
it into a single line of text. Assume paragraphs are separated If variable `use-hard-newlines', takes a multi-line paragraph and
by blank lines. If `use-hard-newlines' is not enabled, this makes it into a single line of text. Assume paragraphs are
simply executes `fill-paragraph'." separated by blank lines. If variable `use-hard-newlines' is not
set, this simply executes `fill-paragraph'."
;; Inspired by https://www.emacswiki.org/emacs/UnfillParagraph ;; Inspired by https://www.emacswiki.org/emacs/UnfillParagraph
(interactive (progn (barf-if-buffer-read-only) '(t))) (interactive (progn (barf-if-buffer-read-only) '(t)))
(if mu4e-compose-format-flowed (if mu4e-compose-format-flowed
@ -404,7 +405,8 @@ simply executes `fill-paragraph'."
(turn-on-auto-fill))) (turn-on-auto-fill)))
(defun mu4e~compose-remap-faces () (defun mu4e~compose-remap-faces ()
"Our parent `message-mode' uses font-locking for the compose "Remap `message-mode' faces to mu4e ones.
Our parent `message-mode' uses font-locking for the compose
buffers; lets remap its faces so it uses the ones for mu4e." buffers; lets remap its faces so it uses the ones for mu4e."
;; normal headers ;; normal headers
(face-remap-add-relative 'message-header-name (face-remap-add-relative 'message-header-name
@ -456,68 +458,68 @@ buffers; lets remap its faces so it uses the ones for mu4e."
;; offer completion for e-mail addresses ;; offer completion for e-mail addresses
(when mu4e-compose-complete-addresses (when mu4e-compose-complete-addresses
(unless mu4e~contacts ;; work-around for https://github.com/djcb/mu/issues/1016 (unless mu4e~contacts ;; work-around for https://github.com/djcb/mu/issues/1016
(mu4e~request-contacts-maybe)) (mu4e~request-contacts-maybe))
(mu4e~compose-setup-completion)) (mu4e~compose-setup-completion))
(if mu4e-compose-format-flowed (if mu4e-compose-format-flowed
(progn (progn
(turn-off-auto-fill) (turn-off-auto-fill)
(setq truncate-lines nil (setq truncate-lines nil
word-wrap t word-wrap t
mml-enable-flowed t mml-enable-flowed t
use-hard-newlines t) use-hard-newlines t)
(visual-line-mode t)) (visual-line-mode t))
(setq mml-enable-flowed nil)) (setq mml-enable-flowed nil))
(let ((keymap (lookup-key message-mode-map [menu-bar text]))) (let ((keymap (lookup-key message-mode-map [menu-bar text])))
(when keymap (when keymap
(define-key-after (define-key-after
keymap keymap
[mu4e-hard-newlines] [mu4e-hard-newlines]
'(menu-item "Format=flowed" mu4e-toggle-use-hard-newlines '(menu-item "Format=flowed" mu4e-toggle-use-hard-newlines
:button (:toggle . use-hard-newlines) :button (:toggle . use-hard-newlines)
:help "Toggle format=flowed" :help "Toggle format=flowed"
:visible (eq major-mode 'mu4e-compose-mode) :visible (eq major-mode 'mu4e-compose-mode)
:enable mu4e-compose-format-flowed) :enable mu4e-compose-format-flowed)
'sep) 'sep)
(define-key-after (define-key-after
keymap keymap
[mu4e-electric-quote-mode] [mu4e-electric-quote-mode]
'(menu-item "Electric quote" electric-quote-local-mode '(menu-item "Electric quote" electric-quote-local-mode
:button (:toggle . electric-quote-mode) :button (:toggle . electric-quote-mode)
:help "Toggle Electric quote mode" :help "Toggle Electric quote mode"
:visible (and (eq major-mode 'mu4e-compose-mode) :visible (and (eq major-mode 'mu4e-compose-mode)
(functionp 'electric-quote-local-mode))) (functionp 'electric-quote-local-mode)))
'mu4e-hard-newlines))) 'mu4e-hard-newlines)))
(when (lookup-key mml-mode-map [menu-bar Attachments]) (when (lookup-key mml-mode-map [menu-bar Attachments])
(define-key-after (define-key-after
(lookup-key mml-mode-map [menu-bar Attachments]) (lookup-key mml-mode-map [menu-bar Attachments])
[mu4e-compose-attach-captured-message] [mu4e-compose-attach-captured-message]
'(menu-item "Attach captured message" '(menu-item "Attach captured message"
mu4e-compose-attach-captured-message mu4e-compose-attach-captured-message
:help "Attach message captured in Headers View (with 'a c')" :help "Attach message captured in Headers View (with 'a c')"
:visible (eq major-mode 'mu4e-compose-mode)) :visible (eq major-mode 'mu4e-compose-mode))
(quote Attach\ External...))) (quote Attach\ External...)))
;; setup the fcc-stuff, if needed ;; setup the fcc-stuff, if needed
(add-hook 'message-send-hook (add-hook 'message-send-hook
(lambda () ;; mu4e~compose-save-before-sending (lambda () ;; mu4e~compose-save-before-sending
;; when in-reply-to was removed, remove references as well. ;; when in-reply-to was removed, remove references as well.
(when (eq mu4e-compose-type 'reply) (when (eq mu4e-compose-type 'reply)
(mu4e~remove-refs-maybe)) (mu4e~remove-refs-maybe))
(when use-hard-newlines (when use-hard-newlines
(mu4e-send-harden-newlines)) (mu4e-send-harden-newlines))
;; for safety, always save the draft before sending ;; for safety, always save the draft before sending
(set-buffer-modified-p t) (set-buffer-modified-p t)
(save-buffer) (save-buffer)
(mu4e~compose-setup-fcc-maybe) (mu4e~compose-setup-fcc-maybe)
(widen)) nil t) (widen)) nil t)
;; when the message has been sent. ;; when the message has been sent.
(add-hook 'message-sent-hook (add-hook 'message-sent-hook
(lambda () ;; mu4e~compose-mark-after-sending (lambda () ;; mu4e~compose-mark-after-sending
(setq mu4e-sent-func 'mu4e-sent-handler) (setq mu4e-sent-func 'mu4e-sent-handler)
(mu4e~proc-sent (buffer-file-name) mu4e~draft-drafts-folder)) nil t)) (mu4e~proc-sent (buffer-file-name) mu4e~draft-drafts-folder)) nil t))
;; mark these two hooks as permanent-local, so they'll survive mode-changes ;; mark these two hooks as permanent-local, so they'll survive mode-changes
;; (put 'mu4e~compose-save-before-sending 'permanent-local-hook t) ;; (put 'mu4e~compose-save-before-sending 'permanent-local-hook t)
(put 'mu4e~compose-mark-after-sending 'permanent-local-hook t)) (put 'mu4e~compose-mark-after-sending 'permanent-local-hook t))
@ -533,36 +535,37 @@ buffers; lets remap its faces so it uses the ones for mu4e."
"Maximum length of the mu4e-send-buffer-name.") "Maximum length of the mu4e-send-buffer-name.")
(defun mu4e~compose-set-friendly-buffer-name (&optional compose-type) (defun mu4e~compose-set-friendly-buffer-name (&optional compose-type)
"Set some user-friendly buffer name based on the compose type." "Set some user-friendly buffer name based on the COMPOSE-TYPE."
(let* ((subj (message-field-value "subject")) (let* ((subj (message-field-value "subject"))
(subj (unless (and subj (string-match "^[:blank:]*$" subj)) subj)) (subj (unless (and subj (string-match "^[:blank:]*$" subj)) subj))
(str (or subj (str (or subj
(cl-case compose-type (cl-case compose-type
(reply "*reply*") (reply "*reply*")
(forward "*forward*") (forward "*forward*")
(otherwise "*draft*"))))) (otherwise "*draft*")))))
(rename-buffer (generate-new-buffer-name (rename-buffer (generate-new-buffer-name
(truncate-string-to-width str (truncate-string-to-width str
mu4e~compose-buffer-max-name-length mu4e~compose-buffer-max-name-length
nil nil t) nil nil t)
(buffer-name))))) (buffer-name)))))
(defun mu4e~compose-crypto-reply (parent compose-type) (defun mu4e~compose-crypto-reply (parent compose-type)
"When composing a reply to an encrypted message, we can "Possibly encrypt or sign a message based on PARENT and COMPOSE-TYPE.
When composing a reply to an encrypted message, we can
automatically encrypt that reply. When the message is unencrypted, automatically encrypt that reply. When the message is unencrypted,
we can decide what we want to do." we can decide what we want to do."
(if (and (eq compose-type 'reply) (if (and (eq compose-type 'reply)
(and parent (member 'encrypted (mu4e-message-field parent :flags)))) (and parent (member 'encrypted (mu4e-message-field parent :flags))))
(cl-case mu4e-compose-crypto-reply-encrypted-policy (cl-case mu4e-compose-crypto-reply-encrypted-policy
(sign (mml-secure-message-sign)) (sign (mml-secure-message-sign))
(encrypt (mml-secure-message-encrypt)) (encrypt (mml-secure-message-encrypt))
(sign-and-encrypt (mml-secure-message-sign-encrypt)) (sign-and-encrypt (mml-secure-message-sign-encrypt))
(message "Do nothing")) (message "Do nothing"))
(cl-case mu4e-compose-crypto-reply-plain-policy (cl-case mu4e-compose-crypto-reply-plain-policy
(sign (mml-secure-message-sign)) (sign (mml-secure-message-sign))
(encrypt (mml-secure-message-encrypt)) (encrypt (mml-secure-message-encrypt))
(sign-and-encrypt (mml-secure-message-sign-encrypt)) (sign-and-encrypt (mml-secure-message-sign-encrypt))
(message "Do nothing"))) (message "Do nothing")))
) )
@ -602,8 +605,8 @@ tempfile)."
(condition-case nil (condition-case nil
(mu4e-draft-open compose-type original-msg) (mu4e-draft-open compose-type original-msg)
(quit (set-window-configuration winconf) (quit (set-window-configuration winconf)
(mu4e-message "Operation aborted") (mu4e-message "Operation aborted")
(cl-return-from mu4e~compose-handler)))) (cl-return-from mu4e~compose-handler))))
;; insert mail-header-separator, which is needed by message mode to separate ;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk ;; headers and body. will be removed before saving to disk
(mu4e~draft-insert-mail-header-separator) (mu4e~draft-insert-mail-header-separator)
@ -615,10 +618,10 @@ tempfile)."
(goto-char (point-max)) ;; put attachments at the end (goto-char (point-max)) ;; put attachments at the end
(if (and (eq compose-type 'forward) mu4e-compose-forward-as-attachment) (if (and (eq compose-type 'forward) mu4e-compose-forward-as-attachment)
(mu4e-compose-attach-message original-msg) (mu4e-compose-attach-message original-msg)
(dolist (att includes) (dolist (att includes)
(mml-attach-file (mml-attach-file
(plist-get att :file-name) (plist-get att :mime-type))))) (plist-get att :file-name) (plist-get att :mime-type)))))
(mu4e~compose-set-friendly-buffer-name compose-type) (mu4e~compose-set-friendly-buffer-name compose-type)
@ -628,10 +631,10 @@ tempfile)."
;; otherwise, it depends... ;; otherwise, it depends...
(cl-case message-cite-reply-position (cl-case message-cite-reply-position
((above traditional) ((above traditional)
(message-goto-body)) (message-goto-body))
(t (t
(when (message-goto-signature) (when (message-goto-signature)
(forward-line -2))))) (forward-line -2)))))
;; bind to `mu4e-compose-parent-message' of compose buffer ;; bind to `mu4e-compose-parent-message' of compose buffer
(set (make-local-variable 'mu4e-compose-parent-message) original-msg) (set (make-local-variable 'mu4e-compose-parent-message) original-msg)
@ -660,16 +663,16 @@ tempfile)."
"Try to go back to some previous buffer, in the order view->headers->main." "Try to go back to some previous buffer, in the order view->headers->main."
(unless (eq mu4e-split-view 'single-window) (unless (eq mu4e-split-view 'single-window)
(if (buffer-live-p (mu4e-get-view-buffer)) (if (buffer-live-p (mu4e-get-view-buffer))
(switch-to-buffer (mu4e-get-view-buffer)) (switch-to-buffer (mu4e-get-view-buffer))
(if (buffer-live-p (mu4e-get-headers-buffer)) (if (buffer-live-p (mu4e-get-headers-buffer))
(switch-to-buffer (mu4e-get-headers-buffer)) (switch-to-buffer (mu4e-get-headers-buffer))
;; if all else fails, back to the main view ;; if all else fails, back to the main view
(when (fboundp 'mu4e) (mu4e)))))) (when (fboundp 'mu4e) (mu4e))))))
(defun mu4e-sent-handler (docid path) (defun mu4e-sent-handler (docid path)
"Handler function, called with DOCID and PATH for the just-sent "Handler called with DOCID and PATH for the just-sent message.
message. For Forwarded ('Passed') and Replied messages, try to set For Forwarded ('Passed') and Replied messages, try to set the
the appropriate flag at the message forwarded or replied-to." appropriate flag at the message forwarded or replied-to."
(mu4e~compose-set-parent-flag path) (mu4e~compose-set-parent-flag path)
(when (file-exists-p path) ;; maybe the draft was not saved at all (when (file-exists-p path) ;; maybe the draft was not saved at all
(mu4e~proc-remove docid)) (mu4e~proc-remove docid))
@ -677,9 +680,9 @@ the appropriate flag at the message forwarded or replied-to."
;; this seems a bit hamfisted... ;; this seems a bit hamfisted...
(dolist (buf (buffer-list)) (dolist (buf (buffer-list))
(when (and (buffer-file-name buf) (when (and (buffer-file-name buf)
(string= (buffer-file-name buf) path)) (string= (buffer-file-name buf) path))
(if message-kill-buffer-on-exit (if message-kill-buffer-on-exit
(kill-buffer buf)))) (kill-buffer buf))))
(mu4e~switch-back-to-mu4e-buffer) (mu4e~switch-back-to-mu4e-buffer)
(mu4e-message "Message sent")) (mu4e-message "Message sent"))
@ -693,12 +696,13 @@ It restores mu4e window layout after killing the compose-buffer."
(when (not (equal current-buffer (current-buffer))) (when (not (equal current-buffer (current-buffer)))
;; Restore mu4e ;; Restore mu4e
(if mu4e-compose-in-new-frame (if mu4e-compose-in-new-frame
(delete-frame) (delete-frame)
(mu4e~switch-back-to-mu4e-buffer))))) (mu4e~switch-back-to-mu4e-buffer)))))
(defun mu4e~compose-set-parent-flag (path) (defun mu4e~compose-set-parent-flag (path)
"Set the 'replied' \"R\" flag on messages we replied to, and the "Set flags for replied-t and forwarded for the message at PATH.
'passed' \"F\" flag on message we have forwarded. That is, set the 'replied' \"R\" flag on messages we replied to,
and the 'passed' \"F\" flag on message we have forwarded.
If a message has an 'in-reply-to' header, it is considered a reply If a message has an 'in-reply-to' header, it is considered a reply
to the message with the corresponding message id. If it does not to the message with the corresponding message id. If it does not
@ -717,31 +721,31 @@ buffer."
(let ((buf (find-file-noselect path))) (let ((buf (find-file-noselect path)))
(when buf (when buf
(with-current-buffer buf (with-current-buffer buf
(message-narrow-to-headers-or-head) (message-narrow-to-headers-or-head)
(let ((in-reply-to (message-fetch-field "in-reply-to")) (let ((in-reply-to (message-fetch-field "in-reply-to"))
(forwarded-from) (forwarded-from)
(references (message-fetch-field "references"))) (references (message-fetch-field "references")))
(unless in-reply-to (unless in-reply-to
(when references (when references
(with-temp-buffer ;; inspired by `message-shorten-references'. (with-temp-buffer ;; inspired by `message-shorten-references'.
(insert references) (insert references)
(goto-char (point-min)) (goto-char (point-min))
(let ((refs)) (let ((refs))
(while (re-search-forward "<[^ <]+@[^ <]+>" nil t) (while (re-search-forward "<[^ <]+@[^ <]+>" nil t)
(push (match-string 0) refs)) (push (match-string 0) refs))
;; the last will be the first ;; the last will be the first
(setq forwarded-from (first refs)))))) (setq forwarded-from (first refs))))))
;; remove the <> ;; remove the <>
(when (and in-reply-to (string-match "<\\(.*\\)>" in-reply-to)) (when (and in-reply-to (string-match "<\\(.*\\)>" in-reply-to))
(mu4e~proc-move (match-string 1 in-reply-to) nil "+R-N")) (mu4e~proc-move (match-string 1 in-reply-to) nil "+R-N"))
(when (and forwarded-from (string-match "<\\(.*\\)>" forwarded-from)) (when (and forwarded-from (string-match "<\\(.*\\)>" forwarded-from))
(mu4e~proc-move (match-string 1 forwarded-from) nil "+P-N"))))))) (mu4e~proc-move (match-string 1 forwarded-from) nil "+P-N")))))))
(defun mu4e-compose (compose-type) (defun mu4e-compose (compose-type)
"Start composing a message of COMPOSE-TYPE, where COMPOSE-TYPE "Start composing a message of COMPOSE-TYPE.
is a symbol, one of `reply', `forward', `edit', `resend' COMPOSE-TYPE is a symbol, one of `reply', `forward', `edit',
`new'. All but `new' take the message at point as input. Symbol `resend' `new'. All but `new' take the message at point as input.
`edit' is only allowed for draft messages." Symbol `edit' is only allowed for draft messages."
(let ((msg (mu4e-message-at-point 'noerror))) (let ((msg (mu4e-message-at-point 'noerror)))
;; some sanity checks ;; some sanity checks
(unless (or msg (eq compose-type 'new)) (unless (or msg (eq compose-type 'new))
@ -749,7 +753,7 @@ is a symbol, one of `reply', `forward', `edit', `resend'
(unless (member compose-type '(reply forward edit resend new)) (unless (member compose-type '(reply forward edit resend new))
(mu4e-error "Invalid compose type '%S'" compose-type)) (mu4e-error "Invalid compose type '%S'" compose-type))
(when (and (eq compose-type 'edit) (when (and (eq compose-type 'edit)
(not (member 'draft (mu4e-message-field msg :flags)))) (not (member 'draft (mu4e-message-field msg :flags))))
(mu4e-warn "Editing is only allowed for draft messages")) (mu4e-warn "Editing is only allowed for draft messages"))
;; 'new is special, since it takes no existing message as arg; therefore, we ;; 'new is special, since it takes no existing message as arg; therefore, we
@ -758,22 +762,22 @@ is a symbol, one of `reply', `forward', `edit', `resend'
(mu4e~compose-handler 'new) (mu4e~compose-handler 'new)
;; otherwise, we need the doc-id ;; otherwise, we need the doc-id
(let* ((docid (mu4e-message-field msg :docid)) (let* ((docid (mu4e-message-field msg :docid))
;; decrypt (or not), based on `mu4e-decryption-policy'. ;; decrypt (or not), based on `mu4e-decryption-policy'.
(decrypt (decrypt
(and (member 'encrypted (mu4e-message-field msg :flags)) (and (member 'encrypted (mu4e-message-field msg :flags))
(if (eq mu4e-decryption-policy 'ask) (if (eq mu4e-decryption-policy 'ask)
(yes-or-no-p (mu4e-format "Decrypt message?")) (yes-or-no-p (mu4e-format "Decrypt message?"))
mu4e-decryption-policy)))) mu4e-decryption-policy))))
;; if there's a visible view window, select that before starting ;; if there's a visible view window, select that before starting
;; composing a new message, so that one will be replaced by the compose ;; composing a new message, so that one will be replaced by the compose
;; window. The 10-or-so line headers buffer is not a good place to write ;; window. The 10-or-so line headers buffer is not a good place to write
;; it... ;; it...
(unless (eq mu4e-split-view 'single-window) (unless (eq mu4e-split-view 'single-window)
(let ((viewwin (get-buffer-window (mu4e-get-view-buffer)))) (let ((viewwin (get-buffer-window (mu4e-get-view-buffer))))
(when (window-live-p viewwin) (when (window-live-p viewwin)
(select-window viewwin)))) (select-window viewwin))))
;; talk to the backend ;; talk to the backend
(mu4e~proc-compose compose-type decrypt docid))))) (mu4e~proc-compose compose-type decrypt docid)))))
(defun mu4e-compose-reply () (defun mu4e-compose-reply ()
"Compose a reply for the message at point in the headers buffer." "Compose a reply for the message at point in the headers buffer."
@ -809,11 +813,41 @@ draft message."
;;;###autoload ;;;###autoload
(defun mu4e~compose-mail (&optional to subject other-headers continue (defun mu4e~compose-mail (&optional to subject other-headers continue
switch-function yank-action send-actions return-action) switch-function yank-action send-actions return-action)
"This is mu4e's implementation of `compose-mail'." "This is mu4e's implementation of `compose-mail'.
Quoting its docstring:
Start composing a mail message to send.
This uses the users chosen mail composition package
as selected with the variable mail-user-agent.
The optional arguments TO and SUBJECT specify recipients
and the initial Subject field, respectively.
;; create a new draft message 'resetting' (as below) is not actually needed in OTHER-HEADERS is an alist specifying additional
;; this case, but let's prepare for the re-edit case as well header fields. Elements look like (HEADER . VALUE) where both
HEADER and VALUE are strings.
CONTINUE, if non-nil, says to continue editing a message already
being composed. Interactively, CONTINUE is the prefix argument.
SWITCH-FUNCTION, if non-nil, is a function to use to
switch to and display the buffer used for mail composition.
YANK-ACTION, if non-nil, is an action to perform, if and when necessary,
to insert the raw text of the message being replied to.
It has the form (FUNCTION . ARGS). The user agent will apply
FUNCTION to ARGS, to insert the raw text of the original message.
\(The user agent will also run mail-citation-hook, *after* the
original text has been inserted in this way.)
SEND-ACTIONS is a list of actions to call when the message is sent.
Each action has the form (FUNCTION . ARGS).
RETURN-ACTION, if non-nil, is an action for returning to the
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."
;; 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
(mu4e~compose-handler 'new) (mu4e~compose-handler 'new)
(when (message-goto-to) ;; reset to-address, if needed (when (message-goto-to) ;; reset to-address, if needed
@ -880,7 +914,7 @@ Go to the end of the message (before signature) or, if already there, go to the
end of the buffer." end of the buffer."
(interactive) (interactive)
(let ((old-position (point)) (let ((old-position (point))
(message-position (save-excursion (message-goto-body) (point)))) (message-position (save-excursion (message-goto-body) (point))))
(goto-char (point-max)) (goto-char (point-max))
(when (re-search-backward message-signature-separator message-position t) (when (re-search-backward message-signature-separator message-position t)
(forward-line -1)) (forward-line -1))
@ -891,7 +925,4 @@ end of the buffer."
(vector 'remap 'end-of-buffer) 'mu4e-compose-goto-bottom) (vector 'remap 'end-of-buffer) 'mu4e-compose-goto-bottom)
(provide 'mu4e-compose) (provide 'mu4e-compose)
;;; mu4e-compose.el ends here
;; Load mu4e completely even when this file was loaded through
;; autoload.
(require 'mu4e)