1;;; mu4e-draft.el -- part of mu4e, the mu mail user agent for emacs -*- lexical-binding: t -*- 2;; 3;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema 4 5;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> 6;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> 7 8;; This file is not part of GNU Emacs. 9 10;; mu4e is free software: you can redistribute it and/or modify 11;; it under the terms of the GNU General Public License as published by 12;; the Free Software Foundation, either version 3 of the License, or 13;; (at your option) any later version. 14 15;; mu4e is distributed in the hope that it will be useful, 16;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18;; GNU General Public License for more details. 19 20;; You should have received a copy of the GNU General Public License 21;; along with mu4e. If not, see <http://www.gnu.org/licenses/>. 22 23;;; Commentary: 24 25;; In this file, various functions to create draft messages 26 27;;; Code: 28 29(require 'cl-lib) 30(require 'mu4e-vars) 31(require 'mu4e-utils) 32(require 'mu4e-message) 33(require 'message) ;; mail-header-separator 34 35;;; Options 36 37(defcustom mu4e-compose-dont-reply-to-self nil 38 "If non-nil, don't include self. 39(as decided by `mu4e-personal-address-p')" 40 :type 'boolean 41 :group 'mu4e-compose) 42 43(defcustom mu4e-compose-cite-function 44 (or message-cite-function 'message-cite-original-without-signature) 45 "The function for citing message in replies and forwards. 46This is the mu4e-specific version of 47`message-cite-function'." 48 :type 'function 49 :group 'mu4e-compose) 50 51(defcustom mu4e-compose-signature 52 (or message-signature "Sent with my mu4e") 53 "The message signature. 54\(i.e. the blob at the bottom of messages). This is the 55mu4e-specific version of `message-signature'." 56 :type '(choice string 57 (const :tag "None" nil) 58 (const :tag "Contents of signature file" t) 59 function sexp) 60 :risky t 61 :group 'mu4e-compose) 62 63(defcustom mu4e-compose-signature-auto-include t 64 "Whether to automatically include a message-signature." 65 :type 'boolean 66 :group 'mu4e-compose) 67 68(make-obsolete-variable 'mu4e-compose-auto-include-date 69 "This is done unconditionally now" "1.3.5") 70 71(defcustom mu4e-compose-in-new-frame nil 72 "Whether to compose messages in a new frame." 73 :type 'boolean 74 :group 'mu4e-compose) 75 76(defvar mu4e-user-agent-string 77 (format "mu4e %s; emacs %s" mu4e-mu-version emacs-version) 78 "The User-Agent string for mu4e, or nil.") 79 80(defvar mu4e-view-date-format) 81 82(defun mu4e~draft-cite-original (msg) 83 "Return a cited version of the original message MSG as a plist. 84This function uses `mu4e-compose-cite-function', and as such all 85its settings apply." 86 (with-temp-buffer 87 (when (fboundp 'mu4e-view-message-text) ;; keep bytecompiler happy 88 (let ((mu4e-view-date-format "%Y-%m-%dT%T%z")) 89 (insert (mu4e-view-message-text msg))) 90 (message-yank-original) 91 (goto-char (point-min)) 92 (push-mark (point-max)) 93 ;; set the the signature separator to 'loose', since in the real world, 94 ;; many message don't follow the standard... 95 (let ((message-signature-separator "^-- *$") 96 (message-signature-insert-empty-line t)) 97 (funcall mu4e-compose-cite-function)) 98 (pop-mark) 99 (goto-char (point-min)) 100 (buffer-string)))) 101 102(defun mu4e~draft-header (hdr val) 103 "Return a header line of the form \"HDR: VAL\". 104If VAL is nil, return nil." 105 ;; note: the propertize here is currently useless, since gnus sets its own 106 ;; later. 107 (when val (format "%s: %s\n" 108 (propertize hdr 'face 'mu4e-header-key-face) 109 (propertize val 'face 'mu4e-header-value-face)))) 110 111(defconst mu4e~max-reference-num 21 112 "Specifies the maximum number of References:. 113As suggested by `message-shorten-references'.") 114 115(defun mu4e~shorten-1 (list cut surplus) 116 "Cut SURPLUS elements out of LIST. 117Beginning with CUTth 118one. Code borrowed from `message-shorten-1'." 119 (setcdr (nthcdr (- cut 2) list) 120 (nthcdr (+ (- cut 2) surplus 1) list))) 121 122(defun mu4e~draft-references-construct (msg) 123 "Construct the value of the References: header based on MSG. 124This assumes a comma-separated string. Normally, this the concatenation of the 125existing References + In-Reply-To (which may be empty, an note 126that :references includes the old in-reply-to as well) and the 127message-id. If the message-id is empty, returns the old 128References. If both are empty, return nil." 129 (let* ( ;; these are the ones from the message being replied to / forwarded 130 (refs (mu4e-message-field msg :references)) 131 (msgid (mu4e-message-field msg :message-id)) 132 ;; now, append in 133 (refs (if (and msgid (not (string= msgid ""))) 134 (append refs (list msgid)) refs)) 135 ;; no doubles 136 (refs (cl-delete-duplicates refs :test #'equal)) 137 (refnum (length refs)) 138 (cut 2)) 139 ;; remove some refs when there are too many 140 (when (> refnum mu4e~max-reference-num) 141 (let ((surplus (- refnum mu4e~max-reference-num))) 142 (mu4e~shorten-1 refs cut surplus))) 143 (mapconcat (lambda (id) (format "<%s>" id)) refs " "))) 144 145 146;;; Determine the recipient fields for new messages 147 148(defun mu4e~draft-recipients-list-to-string (lst) 149 "Convert a lst LST of address cells into a string. 150This is specified as a comma-separated list of e-mail addresses. 151If LST is nil, returns nil." 152 (when lst 153 (mapconcat 154 (lambda (addrcell) 155 (let ((name (car addrcell)) 156 (email (cdr addrcell))) 157 (if name 158 (format "%s <%s>" (mu4e~rfc822-quoteit name) email) 159 (format "%s" email)))) 160 lst ", "))) 161 162(defun mu4e~draft-address-cell-equal (cell1 cell2) 163 "Return t if CELL1 and CELL2 have the same e-mail address. 164The comparison is done case-insensitively. If the cells done 165match return nil. CELL1 and CELL2 are cons cells of the 166form (NAME . EMAIL)." 167 (string= 168 (downcase (or (cdr cell1) "")) 169 (downcase (or (cdr cell2) "")))) 170 171 172(defun mu4e~draft-create-to-lst (origmsg) 173 "Create a list of address for the To: in a new message. 174This is based on the original message ORIGMSG. If the Reply-To 175address is set, use that, otherwise use the From address. Note, 176whatever was in the To: field before, goes to the Cc:-list (if 177we're doing a reply-to-all). Special case: if we were the sender 178of the original, we simple copy the list form the original." 179 (let ((reply-to 180 (or (plist-get origmsg :reply-to) (plist-get origmsg :from)))) 181 (cl-delete-duplicates reply-to :test #'mu4e~draft-address-cell-equal) 182 (if mu4e-compose-dont-reply-to-self 183 (cl-delete-if 184 (lambda (to-cell) 185 (mu4e-personal-address-p (cdr to-cell))) 186 reply-to) 187 reply-to))) 188 189 190(defun mu4e~strip-ignored-addresses (addrs) 191 "Return all addresses that are not to be ignored. 192I.e. return all the addresses in ADDRS not matching 193`mu4e-compose-reply-ignore-address'." 194 (cond 195 ((null mu4e-compose-reply-ignore-address) 196 addrs) 197 ((functionp mu4e-compose-reply-ignore-address) 198 (cl-remove-if 199 (lambda (elt) 200 (funcall mu4e-compose-reply-ignore-address (cdr elt))) 201 addrs)) 202 (t 203 ;; regexp or list of regexps 204 (let* ((regexp mu4e-compose-reply-ignore-address) 205 (regexp (if (listp regexp) 206 (mapconcat (lambda (elt) (concat "\\(" elt "\\)")) 207 regexp "\\|") 208 regexp))) 209 (cl-remove-if 210 (lambda (elt) 211 (string-match regexp (cdr elt))) 212 addrs))))) 213 214(defun mu4e~draft-create-cc-lst (origmsg &optional reply-all include-from) 215 "Create a list of address for the Cc: in a new message. 216This is based on the original message ORIGMSG, and whether it's a 217REPLY-ALL." 218 (when reply-all 219 (let* ((cc-lst ;; get the cc-field from the original, remove dups 220 (cl-delete-duplicates 221 (append 222 (plist-get origmsg :to) 223 (plist-get origmsg :cc) 224 (when include-from(plist-get origmsg :from)) 225 (plist-get origmsg :list-post)) 226 :test #'mu4e~draft-address-cell-equal)) 227 ;; now we have the basic list, but we must remove 228 ;; addresses also in the To: list 229 (cc-lst 230 (cl-delete-if 231 (lambda (cc-cell) 232 (cl-find-if 233 (lambda (to-cell) 234 (mu4e~draft-address-cell-equal cc-cell to-cell)) 235 (mu4e~draft-create-to-lst origmsg))) 236 cc-lst)) 237 ;; remove ignored addresses 238 (cc-lst (mu4e~strip-ignored-addresses cc-lst)) 239 ;; finally, we need to remove ourselves from the cc-list 240 ;; unless mu4e-compose-keep-self-cc is non-nil 241 (cc-lst 242 (if (or mu4e-compose-keep-self-cc (null user-mail-address)) 243 cc-lst 244 (cl-delete-if 245 (lambda (cc-cell) 246 (mu4e-personal-address-p (cdr cc-cell))) 247 cc-lst)))) 248 cc-lst))) 249 250(defun mu4e~draft-recipients-construct (field origmsg &optional reply-all include-from) 251 "Create value (a string) for the recipient FIELD. 252\(which is a symbol, :to or :cc), based on the original message ORIGMSG, 253and (optionally) REPLY-ALL which indicates this is a reply-to-all 254message. Return nil if there are no recipients for the particular field." 255 (mu4e~draft-recipients-list-to-string 256 (cl-case field 257 (:to 258 (mu4e~draft-create-to-lst origmsg)) 259 (:cc 260 (mu4e~draft-create-cc-lst origmsg reply-all include-from)) 261 (otherwise 262 (mu4e-error "Unsupported field"))))) 263 264;;; RFC2822 handling of phrases in mail-addresses 265;; 266;; The optional display-name contains a phrase, it sits before the 267;; angle-addr as specified in RFC2822 for email-addresses in header 268;; fields. Contributed by jhelberg. 269 270(defun mu4e~rfc822-phrase-type (ph) 271 "Return an atom or quoted-string for the phrase PH. 272This checks for empty string first. Then quotes around the phrase 273\(returning 'rfc822-quoted-string). Then whether there is a quote 274inside the phrase (returning 'rfc822-containing-quote). 275The reverse of the RFC atext definition is then tested. 276If it matches, nil is returned, if not, it is an 'rfc822-atom, which 277is returned." 278 (cond 279 ((= (length ph) 0) 'rfc822-empty) 280 ((= (aref ph 0) ?\") 281 (if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph) 282 'rfc822-quoted-string 283 'rfc822-containing-quote)) ; starts with quote, but doesn't end with one 284 ((string-match-p "[\"]" ph) 'rfc822-containing-quote) 285 ((string-match-p "[\000-\037()\*<>@,;:\\\.]+" ph) nil) 286 (t 'rfc822-atom))) 287 288(defun mu4e~rfc822-quoteit (ph) 289 "Quote an RFC822 phrase PH only if necessary. 290Atoms and quoted strings don't need quotes. The rest do. In 291case a phrase contains a quote, it will be escaped." 292 (let ((type (mu4e~rfc822-phrase-type ph))) 293 (cond 294 ((eq type 'rfc822-atom) ph) 295 ((eq type 'rfc822-quoted-string) ph) 296 ((eq type 'rfc822-containing-quote) 297 (format "\"%s\"" 298 (replace-regexp-in-string "\"" "\\\\\"" ph))) 299 (t (format "\"%s\"" ph))))) 300 301 302(defun mu4e~draft-from-construct () 303 "Construct a value for the From:-field of the reply. 304This is based on the variable `user-full-name' and 305`user-mail-address'; if the latter is nil, function returns nil." 306 (when user-mail-address 307 (if user-full-name 308 (format "%s <%s>" (mu4e~rfc822-quoteit user-full-name) user-mail-address) 309 (format "%s" user-mail-address)))) 310 311 312;;; Header separators 313 314(defun mu4e~draft-insert-mail-header-separator () 315 "Insert `mail-header-separator' in the first empty line of the message. 316`message-mode' needs this line to know where the headers end and 317the body starts. Note, in `mu4e-compose-mode', we use 318`before-save-hook' and `after-save-hook' to ensure that this 319separator is never written to the message file. Also see 320`mu4e-remove-mail-header-separator'." 321 ;; we set this here explicitly, since (as it has happened) a wrong 322 ;; value for this (such as "") breaks address completion and other things 323 (set (make-local-variable 'mail-header-separator) "--text follows this line--") 324 (put 'mail-header-separator 'permanent-local t) 325 (save-excursion 326 ;; make sure there's not one already 327 (mu4e~draft-remove-mail-header-separator) 328 (let ((sepa (propertize mail-header-separator 329 'intangible t 330 ;; don't make this read-only, message-mode 331 ;; seems to require it being writable in some cases 332 ;;'read-only "Can't touch this" 333 'rear-nonsticky t 334 'font-lock-face 'mu4e-compose-separator-face))) 335 (widen) 336 ;; search for the first empty line 337 (goto-char (point-min)) 338 (if (search-forward-regexp "^$" nil t) 339 (progn 340 (replace-match sepa) 341 ;; `message-narrow-to-headers` searches for a 342 ;; `mail-header-separator` followed by a new line. Therefore, we 343 ;; must insert a newline if on the last line of the buffer. 344 (when (= (point) (point-max)) 345 (insert "\n"))) 346 (progn ;; no empty line? then prepend one 347 (goto-char (point-max)) 348 (insert "\n" sepa)))))) 349 350(defun mu4e~draft-remove-mail-header-separator () 351 "Remove `mail-header-separator'. 352We do this before saving a 353file (and restore it afterwards), to ensure that the separator 354never hits the disk. Also see 355`mu4e~draft-insert-mail-header-separator." 356 (save-excursion 357 (widen) 358 (goto-char (point-min)) 359 ;; remove the --text follows this line-- separator 360 (when (search-forward-regexp (concat "^" mail-header-separator) nil t) 361 (let ((inhibit-read-only t)) 362 (replace-match ""))))) 363 364(defun mu4e~draft-reply-all-p (origmsg) 365 "Ask user whether she wants to reply to *all* recipients. 366If there is just one recipient of ORIGMSG do nothing." 367 (let* ((recipnum 368 (+ (length (mu4e~draft-create-to-lst origmsg)) 369 (length (mu4e~draft-create-cc-lst origmsg t)))) 370 (response 371 (if (< recipnum 2) 372 'all ;; with less than 2 recipients, we can reply to 'all' 373 (mu4e-read-option 374 "Reply to " 375 `( (,(format "all %d recipients" recipnum) . all) 376 ("sender only" . sender-only)))))) 377 (eq response 'all))) 378 379(defun mu4e~draft-message-filename-construct (&optional flagstr) 380 "Construct a randomized name for a message file with flags FLAGSTR. 381It looks something like 382 <time>-<random>.<hostname>:2, 383You can append flags." 384 (let* ((sysname (if (fboundp 'system-name) 385 (system-name) 386 (with-no-warnings system-name))) 387 (sysname (if (string= sysname "") "localhost" sysname)) 388 (hostname (downcase 389 (save-match-data 390 (substring sysname 391 (string-match "^[^.]+" sysname) 392 (match-end 0)))))) 393 (format "%s.%04x%04x%04x%04x.%s%s2,%s" 394 (format-time-string "%s" (current-time)) 395 (random 65535) (random 65535) (random 65535) (random 65535) 396 hostname mu4e-maildir-info-delimiter (or flagstr "")))) 397 398(defun mu4e~draft-common-construct () 399 "Construct the common headers for each message." 400 (concat 401 (when mu4e-user-agent-string 402 (mu4e~draft-header "User-agent" mu4e-user-agent-string)) 403 (mu4e~draft-header "Date" (message-make-date)))) 404 405(defconst mu4e~draft-reply-prefix "Re: " 406 "String to prefix replies with.") 407 408(defun mu4e~draft-reply-construct-recipients (origmsg) 409 "Determine the to/cc recipients for a reply message." 410 (let* ((reply-to-self (mu4e-message-contact-field-matches-me origmsg :from)) 411 ;; reply-to-self implies reply-all 412 (reply-all (or reply-to-self 413 (eq mu4e-compose-reply-recipients 'all) 414 (and (not (eq mu4e-compose-reply-recipients 'sender)) 415 (mu4e~draft-reply-all-p origmsg))))) 416 (concat 417 (if reply-to-self 418 ;; When we're replying to ourselves, simply keep the same headers. 419 (concat 420 (mu4e~draft-header "To" (mu4e~draft-recipients-list-to-string 421 (mu4e-message-field origmsg :to))) 422 (mu4e~draft-header "Cc" (mu4e~draft-recipients-list-to-string 423 (mu4e-message-field origmsg :cc)))) 424 425 ;; if there's no-one in To, copy the CC-list 426 (if (zerop (length (mu4e~draft-create-to-lst origmsg))) 427 (mu4e~draft-header "To" (mu4e~draft-recipients-construct 428 :cc origmsg reply-all)) 429 ;; otherwise... 430 (concat 431 (mu4e~draft-header "To" (mu4e~draft-recipients-construct :to origmsg)) 432 (mu4e~draft-header "Cc" (mu4e~draft-recipients-construct :cc origmsg reply-all)))))))) 433 434(defun mu4e~draft-reply-construct-recipients-list (origmsg) 435 "Determine the to/cc recipients for a reply message to a 436mailing-list." 437 (let* ( ;; reply-to-self implies reply-all 438 (list-post (plist-get origmsg :list-post)) 439 (from (plist-get origmsg :from)) 440 (recipnum 441 (+ (length (mu4e~draft-create-to-lst origmsg)) 442 (length (mu4e~draft-create-cc-lst origmsg t t)))) 443 (reply-type 444 (mu4e-read-option 445 "Reply to mailing-list " 446 `( (,(format "all %d recipient(s)" recipnum) . all) 447 (,(format "list-only (%s)" (cdar list-post)) . list-only) 448 (,(format "sender-only (%s)" (cdar from)) . sender-only))))) 449 (cl-case reply-type 450 (all 451 (concat 452 (mu4e~draft-header "To" (mu4e~draft-recipients-construct :to origmsg)) 453 (mu4e~draft-header "Cc" (mu4e~draft-recipients-construct :cc origmsg t t)))) 454 (list-only 455 (mu4e~draft-header "To" 456 (mu4e~draft-recipients-list-to-string list-post))) 457 (sender-only 458 (mu4e~draft-header "To" 459 (mu4e~draft-recipients-list-to-string from)))))) 460 461(defun mu4e~draft-reply-construct (origmsg) 462 "Create a draft message as a reply to ORIGMSG. 463Replying-to-self is special; in that case, the To and Cc fields 464will be the same as in the original." 465 (let* ((old-msgid (plist-get origmsg :message-id)) 466 (subject (concat mu4e~draft-reply-prefix 467 (message-strip-subject-re 468 (or (plist-get origmsg :subject) "")))) 469 (list-post (plist-get origmsg :list-post))) 470 (concat 471 (mu4e~draft-header "From" (or (mu4e~draft-from-construct) "")) 472 (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address) 473 474 (if list-post ;; mailing-lists are a bit special. 475 (mu4e~draft-reply-construct-recipients-list origmsg) 476 (mu4e~draft-reply-construct-recipients origmsg)) 477 478 (mu4e~draft-header "Subject" subject) 479 (mu4e~draft-header "References" 480 (mu4e~draft-references-construct origmsg)) 481 (mu4e~draft-common-construct) 482 (when old-msgid 483 (mu4e~draft-header "In-reply-to" (format "<%s>" old-msgid))) 484 "\n\n" 485 (mu4e~draft-cite-original origmsg)))) 486 487(defconst mu4e~draft-forward-prefix "Fwd: " 488 "String to prefix replies with.") 489 490(defun mu4e~draft-forward-construct (origmsg) 491 "Create a draft forward message for original message ORIGMSG." 492 (let ((subject 493 (or (plist-get origmsg :subject) ""))) 494 (concat 495 (mu4e~draft-header "From" (or (mu4e~draft-from-construct) "")) 496 (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address) 497 (mu4e~draft-header "To" "") 498 (mu4e~draft-common-construct) 499 (mu4e~draft-header "References" 500 (mu4e~draft-references-construct origmsg)) 501 (mu4e~draft-header "Subject" 502 (concat 503 ;; if there's no Fwd: yet, prepend it 504 (if (string-match "^Fwd:" subject) 505 "" 506 mu4e~draft-forward-prefix) 507 subject)) 508 (unless mu4e-compose-forward-as-attachment 509 (concat 510 "\n\n" 511 (mu4e~draft-cite-original origmsg)))))) 512 513(defun mu4e~draft-newmsg-construct () 514 "Create a new message." 515 (concat 516 (mu4e~draft-header "From" (or (mu4e~draft-from-construct) "")) 517 (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address) 518 (mu4e~draft-header "To" "") 519 (mu4e~draft-header "Subject" "") 520 (mu4e~draft-common-construct))) 521 522(defvar mu4e~draft-drafts-folder nil 523 "The drafts-folder for this compose buffer. 524This is based on `mu4e-drafts-folder', which is evaluated once.") 525 526(defun mu4e~draft-open-file (path switch-function) 527 "Open the the draft file at PATH." 528 (let ((buf (find-file-noselect path))) 529 (funcall (or 530 switch-function 531 (and mu4e-compose-in-new-frame 'switch-to-buffer-other-frame) 532 'switch-to-buffer) 533 buf))) 534 535 536(defun mu4e~draft-determine-path (draft-dir) 537 "Determines the path for a new draft file in DRAFT-DIR." 538 (format "%s/%s/cur/%s" 539 (mu4e-root-maildir) draft-dir (mu4e~draft-message-filename-construct "DS"))) 540 541 542(defun mu4e-draft-open (compose-type &optional msg switch-function) 543 "Open a draft file for a message MSG. 544In case of a new message (when COMPOSE-TYPE is `reply', `forward' 545 or `new'), open an existing draft (when COMPOSE-TYPE is `edit'), 546 or re-send an existing message (when COMPOSE-TYPE is `resend'). 547 548The name of the draft folder is constructed from the 549concatenation of `(mu4e-root-maildir)' and `mu4e-drafts-folder' (the 550latter will be evaluated). The message file name is a unique name 551determined by `mu4e-send-draft-file-name'. The initial contents 552will be created from either `mu4e~draft-reply-construct', or 553`mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'." 554 (let ((draft-dir nil)) 555 (cl-case compose-type 556 557 (edit 558 ;; case-1: re-editing a draft messages. in this case, we do know the 559 ;; full path, but we cannot really know 'drafts folder'... we make a 560 ;; guess 561 (setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path))) 562 (mu4e~draft-open-file (mu4e-message-field msg :path) switch-function)) 563 564 (resend 565 ;; case-2: copy some exisisting message to a draft message, then edit 566 ;; that. 567 (setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path))) 568 (let ((draft-path (mu4e~draft-determine-path draft-dir))) 569 (copy-file (mu4e-message-field msg :path) draft-path) 570 (mu4e~draft-open-file draft-path switch-function))) 571 572 ((reply forward new) 573 ;; case-3: creating a new message; in this case, we can determine 574 ;; mu4e-get-drafts-folder 575 (setq draft-dir (mu4e-get-drafts-folder msg)) 576 (let ((draft-path (mu4e~draft-determine-path draft-dir)) 577 (initial-contents 578 (cl-case compose-type 579 (reply (mu4e~draft-reply-construct msg)) 580 (forward (mu4e~draft-forward-construct msg)) 581 (new (mu4e~draft-newmsg-construct))))) 582 (mu4e~draft-open-file draft-path switch-function) 583 (insert initial-contents) 584 (newline) 585 ;; include the message signature (if it's set) 586 (if (and mu4e-compose-signature-auto-include mu4e-compose-signature) 587 (let ((message-signature mu4e-compose-signature)) 588 (save-excursion 589 (message-insert-signature) 590 (mu4e~fontify-signature)))))) 591 (t (mu4e-error "Unsupported compose-type %S" compose-type))) 592 ;; if we didn't find a draft folder yet, try some default 593 (unless draft-dir 594 (setq draft-dir (mu4e-get-drafts-folder msg))) 595 ;; evaluate mu4e~drafts-drafts-folder once, here, and use that value 596 ;; throughout. 597 (set (make-local-variable 'mu4e~draft-drafts-folder) draft-dir) 598 (put 'mu4e~draft-drafts-folder 'permanent-local t) 599 (unless mu4e~draft-drafts-folder 600 (mu4e-error "Failed to determine drafts folder")))) 601 602;;; _ 603(provide 'mu4e-draft) 604;;; mu4e-draft.el ends here 605