;; mu4e-draft.el -- part of mu4e, the mu mail user agent for emacs ;; ;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema ;; Author: Dirk-Jan C. Binnema ;; Maintainer: Dirk-Jan C. Binnema ;; This file is not part of GNU Emacs. ;; ;; GNU Emacs 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. ;; GNU Emacs 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 GNU Emacs. If not, see . ;;; Commentary: ;; In this file, various functions to create draft messages ;; Code (eval-when-compile (byte-compile-disable-warning 'cl-functions)) (require 'cl) (require 'mu4e-vars) (require 'mu4e-utils) (require 'mu4e-message) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defcustom mu4e-compose-dont-reply-to-self nil "If non-nil, don't include self (that is, any member of `mu4e-user-mail-address-list') in replies." :type 'boolean :group 'mu4e-compose) (defun mu4e~draft-user-agent-construct () "Return the User-Agent string for mu4e. This is either the value of `mu4e-user-agent', or, if not set, a string based on the versions of mu4e and emacs." (format "mu4e %s; emacs %s" mu4e-mu-version emacs-version)) (defun mu4e~draft-cite-original (msg) "Return a cited version of the original message MSG as a plist. This function use gnus' `message-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)) ;; this seems to be needed, otherwise existing signatures ;; won't be stripped (message-yank-original) (goto-char (point-min)) (push-mark (point-max)) (funcall message-cite-function) (pop-mark) (buffer-string)))) (defun mu4e~draft-header (hdr val) "Return a header line of the form \"HDR: VAL\". If VAL is nil, return nil." (when val (format "%s: %s\n" hdr val))) (defun mu4e~draft-references-construct (msg) "Construct the value of the References: header based on MSG as 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 (delete-duplicates refs :test #'equal))) (mapconcat (lambda (id) (format "<%s>" id)) refs " "))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; determine the recipient fields for new messages (defun mu4e~draft-recipients-list-to-string (lst) "Convert a lst LST of address cells into a string with a list of e-mail addresses. If LST is nil, returns nil." (when lst (mapconcat (lambda (addrcell) (let ((name (car addrcell)) (email (cdr addrcell))) (if name (format "%s <%s>" (mu4e~rfc822-quoteit name) email) (format "%s" email)))) lst ", "))) (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 (cdr cell1) "")) (downcase (or (cdr cell2) "")))) (defun mu4e~draft-create-to-lst (origmsg) "Create a list of address for the To: in a new message, 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)." (let ((reply-to (or (plist-get origmsg :reply-to) (plist-get origmsg :from)))) (delete-duplicates reply-to :test #'mu4e~draft-address-cell-equal) (if mu4e-compose-dont-reply-to-self (delete-if (lambda (to-cell) (member-if (lambda (addr) (string= (downcase addr) (downcase (cdr to-cell)))) mu4e-user-mail-address-list)) reply-to) reply-to))) (defun mu4e~draft-create-cc-lst (origmsg reply-all) "Create a list of address for the Cc: in a new message, 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 (delete-duplicates (append (plist-get origmsg :to) (plist-get origmsg :cc)) :test #'mu4e~draft-address-cell-equal)) ;; now we have the basic list, but we must remove ;; addresses also in the to list (cc-lst (delete-if (lambda (cc-cell) (find-if (lambda (to-cell) (mu4e~draft-address-cell-equal cc-cell to-cell)) (mu4e~draft-create-to-lst origmsg))) 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 (delete-if (lambda (cc-cell) (member-if (lambda (addr) (string= (downcase addr) (downcase (cdr cc-cell)))) mu4e-user-mail-address-list)) cc-lst)))) cc-lst))) (defun mu4e~draft-recipients-construct (field origmsg &optional reply-all) "Create value (a string) for the recipient field FIELD (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." (mu4e~draft-recipients-list-to-string (case field (:to (mu4e~draft-create-to-lst origmsg)) (:cc (mu4e~draft-create-cc-lst origmsg reply-all)) (otherwise (mu4e-error "Unsupported field"))))) (defun mu4e~draft-from-construct () "Construct a value for the From:-field of the reply to MSG, based on `user-full-name' and `user-mail-address'; if the latter is nil, function returns nil." (when user-mail-address (if user-full-name (format "%s <%s>" user-full-name user-mail-address) (format "%s" user-mail-address)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (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) (purecopy "--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 'read-only "Can't touch this" 'rear-nonsticky t 'font-lock-face 'mu4e-system-face))) (widen) ;; search for the first empty line (goto-char (point-min)) (if (search-forward-regexp "^$" nil t) (replace-match (concat sepa)) (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-user-wants-reply-all (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 1) 'all ;; with one recipient, 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