From 36750b13d1584f03d7a2c95f7f1c5332dc81723b Mon Sep 17 00:00:00 2001 From: Aaron LI Date: Mon, 16 May 2016 09:41:22 +0800 Subject: _spacemacs.d/local: import mu4e git20160515 --- _spacemacs.d/local/mu4e/mu4e-actions.el | 312 ++++++ _spacemacs.d/local/mu4e/mu4e-compose.el | 778 ++++++++++++++ _spacemacs.d/local/mu4e/mu4e-context.el | 157 +++ _spacemacs.d/local/mu4e/mu4e-contrib.el | 164 +++ _spacemacs.d/local/mu4e/mu4e-draft.el | 474 +++++++++ _spacemacs.d/local/mu4e/mu4e-headers.el | 1694 ++++++++++++++++++++++++++++++ _spacemacs.d/local/mu4e/mu4e-lists.el | 93 ++ _spacemacs.d/local/mu4e/mu4e-main.el | 225 ++++ _spacemacs.d/local/mu4e/mu4e-mark.el | 466 ++++++++ _spacemacs.d/local/mu4e/mu4e-message.el | 284 +++++ _spacemacs.d/local/mu4e/mu4e-meta.el | 11 + _spacemacs.d/local/mu4e/mu4e-proc.el | 524 +++++++++ _spacemacs.d/local/mu4e/mu4e-speedbar.el | 124 +++ _spacemacs.d/local/mu4e/mu4e-utils.el | 1238 ++++++++++++++++++++++ _spacemacs.d/local/mu4e/mu4e-vars.el | 870 +++++++++++++++ _spacemacs.d/local/mu4e/mu4e-view.el | 1541 +++++++++++++++++++++++++++ _spacemacs.d/local/mu4e/mu4e.el | 93 ++ _spacemacs.d/local/mu4e/org-mu4e.el | 323 ++++++ _spacemacs.d/local/mu4e/org-old-mu4e.el | 289 +++++ 19 files changed, 9660 insertions(+) create mode 100644 _spacemacs.d/local/mu4e/mu4e-actions.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-compose.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-context.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-contrib.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-draft.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-headers.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-lists.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-main.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-mark.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-message.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-meta.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-proc.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-speedbar.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-utils.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-vars.el create mode 100644 _spacemacs.d/local/mu4e/mu4e-view.el create mode 100644 _spacemacs.d/local/mu4e/mu4e.el create mode 100644 _spacemacs.d/local/mu4e/org-mu4e.el create mode 100644 _spacemacs.d/local/mu4e/org-old-mu4e.el diff --git a/_spacemacs.d/local/mu4e/mu4e-actions.el b/_spacemacs.d/local/mu4e/mu4e-actions.el new file mode 100644 index 0000000..7f145a1 --- /dev/null +++ b/_spacemacs.d/local/mu4e/mu4e-actions.el @@ -0,0 +1,312 @@ +;;; mu4e-actions.el -- part of mu4e, the mu mail user agent +;; +;; Copyright (C) 2011-2016 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: + +;; Example actions for messages, attachments (see chapter 'Actions' in the +;; manual) + +;;; Code: +(eval-when-compile (byte-compile-disable-warning 'cl-functions)) +(require 'cl) +(require 'ido) + +(require 'mu4e-utils) +(require 'mu4e-message) +(require 'mu4e-meta) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun mu4e-action-count-lines (msg) + "Count the number of lines in the e-mail message. +Works for headers view and message-view." + (message "Number of lines: %s" + (shell-command-to-string + (concat "wc -l < " (shell-quote-argument (mu4e-message-field msg :path)))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e-msg2pdf (concat mu4e-builddir "/toys/msg2pdf/msg2pdf") + "Path to the msg2pdf toy.") + +(defun mu4e-action-view-as-pdf (msg) + "Convert the message to pdf, then show it. +Works for the message view." + (unless (file-executable-p mu4e-msg2pdf) + (mu4e-error "msg2pdf not found; please set `mu4e-msg2pdf'")) + (let* ((pdf + (shell-command-to-string + (concat mu4e-msg2pdf " " + (shell-quote-argument (mu4e-message-field msg :path)) + " 2> /dev/null"))) + (pdf (and pdf (> (length pdf) 5) + (substring pdf 0 -1)))) ;; chop \n + (unless (and pdf (file-exists-p pdf)) + (mu4e-warn "Failed to create PDF file")) + (find-file pdf))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defun mu4e~write-body-to-html (msg) + "Write the body (either html or text) to a temporary file; +return the filename." + (let* ((html (mu4e-message-field msg :body-html)) + (txt (mu4e-message-field msg :body-txt)) + (tmpfile (mu4e-make-temp-file "html")) + (attachments (remove-if (lambda (part) + (or (null (plist-get part :attachment)) + (null (plist-get part :cid)))) + (mu4e-message-field msg :parts)))) + (unless (or html txt) + (mu4e-error "No body part for this message")) + (with-temp-buffer + (insert "\n") + (insert (or html (concat "
" txt "
"))) + (write-file tmpfile) + ;; rewrite attachment urls + (mapc (lambda (attachment) + (goto-char (point-min)) + (while (re-search-forward (format "src=\"cid:%s\"" (plist-get attachment :cid)) nil t) + (if (plist-get attachment :temp) + (replace-match (format "src=\"%s\"" (plist-get attachment :temp))) + (replace-match (format "src=\"%s%s\"" temporary-file-directory (plist-get attachment :name))) + (mu4e~proc-extract 'save (mu4e-message-field :docid) (plist-get attachment :index) mu4e-decryption-policy temporary-file-directory) + (mu4e-remove-file-later (format "%s%s" temporary-file-directory (plist-get attachment :name)))))) + attachments) + (save-buffer) + tmpfile))) + +(defun mu4e-action-view-in-browser (msg) + "View the body of the message in a browser. +You can influence the browser to use with the variable +`browse-url-generic-program', and see the discussion of privacy +aspects in `(mu4e) Displaying rich-text messages'." + (browse-url (concat "file://" + (mu4e~write-body-to-html msg)))) + +(defun mu4e-action-view-with-xwidget (msg) + "View the body of the message inside xwidget-webkit. This is +only available in emacs 25+; also see the discussion of privacy +aspects in `(mu4e) Displaying rich-text messages'." + (unless (fboundp 'xwidget-webkit-browse-url) + (mu4e-error "No xwidget support available")) + (xwidget-webkit-browse-url + (concat "file://" (mu4e~write-body-to-html msg)) t)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defconst mu4e-text2speech-command "festival --tts" + "Program that speaks out text it receives on standard-input.") + +(defun mu4e-action-message-to-speech (msg) + "Pronounce the message text using `mu4e-text2speech-command'." + (unless (mu4e-message-field msg :body-txt) + (mu4e-warn "No text body for this message")) + (with-temp-buffer + (insert (mu4e-message-field msg :body-txt)) + (shell-command-on-region (point-min) (point-max) + mu4e-text2speech-command))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + + + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e-captured-message nil + "The last-captured message (the s-expression).") + +(defun mu4e-action-capture-message (msg) + "Remember MSG; we can create a an attachment based on this msg +with `mu4e-compose-attach-captured-message'." + (setq mu4e-captured-message msg) + (message "Message has been captured")) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e-org-contacts-file nil + "File to store contact information for org-contacts. +Needed by `mu4e-action-add-org-contact'.") + +(eval-when-compile ;; silence compiler warning about free variable + (unless (require 'org-capture nil 'noerror) + (defvar org-capture-templates nil))) + +(defun mu4e-action-add-org-contact (msg) + "Add an org-contact entry based on the From: address of the +current message (in headers or view). You need to set +`mu4e-org-contacts-file' to the full path to the file where you +store your org-contacts." + (unless (require 'org-capture nil 'noerror) + (mu4e-error "org-capture is not available.")) + (unless mu4e-org-contacts-file + (mu4e-error "`mu4e-org-contacts-file' is not defined.")) + (let* ((sender (car-safe (mu4e-message-field msg :from))) + (name (car-safe sender)) (email (cdr-safe sender)) + (blurb + (format + (concat + "* %%?%s\n" + ":PROPERTIES:\n" + ":EMAIL: %s\n" + ":NICK:\n" + ":BIRTHDAY:\n" + ":END:\n\n") + (or name email "") + (or email ""))) + (key "mu4e-add-org-contact-key") + (org-capture-templates + (append org-capture-templates + (list (list key "contacts" 'entry + (list 'file mu4e-org-contacts-file) blurb))))) + (message "%S" org-capture-templates) + (when (fboundp 'org-capture) + (org-capture nil key)))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun mu4e-action-git-apply-patch (msg) + "Apply the git [patch] message." + (let ((path (ido-read-directory-name "Target directory: " + (car ido-work-directory-list) + "~/" t))) + (setf ido-work-directory-list + (cons path (delete path ido-work-directory-list))) + (shell-command + (format "cd %s; git apply %s" + path + (mu4e-message-field msg :path))))) + +(defun mu4e-action-git-apply-mbox (msg) + "Apply and commit the git [patch] message." + (let ((path (ido-read-directory-name "Target directory: " + (car ido-work-directory-list) + "~/" t))) + (setf ido-work-directory-list + (cons path (delete path ido-work-directory-list))) + (shell-command + (format "cd %s; git am %s" + path + (mu4e-message-field msg :path))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e-action-tags-header "X-Keywords" + "Header where tags are stored. Used by `mu4e-action-retag-message'. + Make sure it is one of the headers mu recognizes for storing + tags: X-Keywords, X-Label, Keywords. Also note that changing + this setting on already tagged messages can lead to messages + with multiple tags headers.") + +(defun mu4e~contains-line-matching (regexp path) + "Determine whether the file at path contains a line matching + the given regexp." + (with-temp-buffer + (insert-file-contents path) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward regexp nil t) + t + nil)))) + +(defun mu4e~replace-first-line-matching (regexp to-string path) + "Replace the first line in the file at path that matches regexp + with the string replace." + (with-temp-file path + (insert-file-contents path) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward regexp nil t) + (replace-match to-string nil nil))))) + +(defun mu4e-action-retag-message (msg &optional retag-arg) + "Change tags of a message. Example: +tag \"+long tag\" -oldtag + adds 'tag' and 'long tag', and removes oldtag." + (let* ((retag (or retag-arg (read-string "Tags: "))) + (path (mu4e-message-field msg :path)) + (maildir (mu4e-message-field msg :maildir)) + (oldtags (mu4e-message-field msg :tags)) + (header mu4e-action-tags-header) + (sep (cond ((string= header "Keywords") ", ") + ((string= header "X-Label") " ") + ((string= header "X-Keywords") ", ") + (t ", "))) + (taglist (if oldtags (copy-sequence oldtags) '())) + tagstr) + (dolist (tag (split-string-and-unquote retag) taglist) + (cond + ((string-match "^\\+\\(.+\\)" tag) + (setq taglist (push (match-string 1 tag) taglist))) + ((string-match "^\\-\\(.+\\)" tag) + (setq taglist (delete (match-string 1 tag) taglist))) + (t + (setq taglist (push tag taglist))))) + + (setq taglist (sort (delete-dups taglist) 'string<)) + (setq tagstr (mapconcat 'identity taglist sep)) + + (setq tagstr (replace-regexp-in-string "[\\&]" "\\\\\\&" tagstr)) + (setq tagstr (replace-regexp-in-string "[/]" "\\&" tagstr)) + + (if (not (mu4e~contains-line-matching (concat header ":.*") path)) + ;; Add tags header just before the content + (mu4e~replace-first-line-matching + "^$" (concat header ": " tagstr "\n") path) + + ;; replaces keywords, restricted to the header + (mu4e~replace-first-line-matching + (concat header ":.*") + (concat header ": " tagstr) + path)) + + (mu4e-message (concat "tagging: " (mapconcat 'identity taglist ", "))) + (mu4e-refresh-message path maildir))) + +(defun mu4e-action-show-thread (msg) + "Show all messages that are in the same thread as the message +at point." + (let ((msgid (mu4e-message-field msg :message-id))) + (when msgid + (let ((mu4e-headers-show-threads t) + (mu4e-headers-include-related t)) + (mu4e-headers-search + (format "msgid:%s" msgid)))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(provide 'mu4e-actions) diff --git a/_spacemacs.d/local/mu4e/mu4e-compose.el b/_spacemacs.d/local/mu4e/mu4e-compose.el new file mode 100644 index 0000000..af2b34b --- /dev/null +++ b/_spacemacs.d/local/mu4e/mu4e-compose.el @@ -0,0 +1,778 @@ +;; -*-mode: emacs-lisp; tab-width: 8; indent-tabs-mode: t -*- +;; mu4e-compose.el -- part of mu4e, the mu mail user agent for emacs +;; +;; Copyright (C) 2011-2016 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 compose/send messages, piggybacking on +;; gnus' message mode + + +;; Magic / Rupe Goldberg + +;; 1) When we reply/forward a message, we get it from the backend, ie: +;; we send to the backend (mu4e-compose): +;; compose type:reply docid:30935 +;; backend responds with: +;; (:compose reply :original ( .... )) + + +;; 2) When we compose a message, message and headers are separated by +;; `mail-header-separator', ie. '--text follows this line--. We use +;; before-save-hook and after-save-hook to remove/re-add this special line, so +;; it stays in the buffer, but never hits the disk. +;; see: +;; mu4e~compose-insert-mail-header-separator +;; mu4e~compose-remove-mail-header-separator +;; +;; (maybe we can get away with remove it only just before sending? what does +;; gnus do?) + +;; 3) When sending a message, we want to do a few things: +;; a) move the message from drafts to the sent folder (maybe; depends on +;; `mu4e-sent-messages-behavior') +;; b) if it's a reply, mark the replied-to message as "R", i.e. replied +;; if it's a forward, mark the forwarded message as "P", i.e. passed (forwarded) +;; c) kill all buffers looking at the sent message + +;; a) is dealt with by message-mode, but we need to tell it where to move the +;; sent message. We do this by adding an Fcc: header with the target folder, +;; see `mu4e~compose-setup-fcc-maybe'. Since message-mode does not natively +;; understand maildirs, we also need to tell it what to do, so we also set +;; `message-fcc-handler-function' there. Finally, we add the the message in +;; the sent-folder to the database. +;; +;; b) this is handled in `mu4e~compose-set-parent-flag' +;; +;; c) this is handled in our handler for the `sent'-message from the backend +;; (`mu4e-sent-handler') +;; + +;;; Code: + +(eval-when-compile (byte-compile-disable-warning 'cl-functions)) +(require 'cl) + +(require 'message) +(require 'mail-parse) +(require 'smtpmail) +(require 'rfc2368) + +(require 'mu4e-utils) +(require 'mu4e-vars) +(require 'mu4e-proc) +(require 'mu4e-actions) +(require 'mu4e-message) +(require 'mu4e-draft) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Composing / Sending messages +(defgroup mu4e-compose nil + "Customizations for composing/sending messages." + :group 'mu4e) + +(defcustom mu4e-sent-messages-behavior 'sent + "Determines what mu4e does with sent messages. + +This is one of the symbols: +* `sent' move the sent message to the Sent-folder (`mu4e-sent-folder') +* `trash' move the sent message to the Trash-folder (`mu4e-trash-folder') +* `delete' delete the sent message. + +Note, when using GMail/IMAP, you should set this to either +`trash' or `delete', since GMail already takes care of keeping +copies in the sent folder. + +Alternatively, `mu4e-sent-messages-behavior' can be a function +which takes no arguments, and which should return on 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)) + :safe 'symbolp + :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-format-flowed nil + "Whether to compose messages to be sent as format=flowed (or + with long lines if `use-hard-newlines' is set to nil). The + variable `fill-flowed-encode-column' lets you customize the + width beyond which format=flowed lines are wrapped." + :type 'boolean + :safe 'booleanp + :group 'mu4e-compose) + +(defcustom mu4e-compose-pre-hook nil + "Hook run just *before* message composition starts. +If the compose-type is either 'reply' or 'forward', the variable +`mu4e-compose-parent-message' points to the message replied to / +being forwarded / edited. + +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) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defun mu4e-compose-attach-captured-message () + "Insert the last captured message file as an attachment. +Messages are captured with `mu4e-action-capture-message'." + (interactive) + (unless mu4e-captured-message + (mu4e-warn "No message has been captured")) + (let ((path (plist-get mu4e-captured-message :path))) + (unless (file-exists-p path) + (mu4e-warn "Captured message file not found")) + (mml-attach-file + path + "message/rfc822" + (or (plist-get mu4e-captured-message :subject) "No subject") + "attachment"))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +;; 'fcc' refers to saving a copy of a sent message to a certain folder. that's +;; what these 'Sent mail' folders are for! +;; +;; We let message mode take care of this by adding a field + +;; Fcc: + +;; in the "message-send-hook" (ie., just before sending). message mode will +;; then take care of the saving when the message is actually sent. +;; +;; note, where and if you make this copy depends on the value of +;; `mu4e-sent-messages-behavior'. + +(defun mu4e~compose-setup-fcc-maybe () + "Maybe setup Fcc, based on `mu4e-sent-messages-behavior'. +If needed, set the Fcc header, and register the handler function." + (let* ((sent-behavior + ;; Note; we cannot simply use functionp here, since at least + ;; delete is a function, too... + (if (member mu4e-sent-messages-behavior '(delete trash sent)) + mu4e-sent-messages-behavior + (if (functionp mu4e-sent-messages-behavior) + (funcall mu4e-sent-messages-behavior) + mu4e-sent-messages-behavior))) + (mdir + (case sent-behavior + (delete nil) + (trash (mu4e-get-trash-folder mu4e-compose-parent-message)) + (sent (mu4e-get-sent-folder mu4e-compose-parent-message)) + (otherwise + (mu4e-error "unsupported value '%S' `mu4e-sent-messages-behavior'." + mu4e-sent-messages-behavior)))) + (fccfile (and mdir + (concat mu4e-maildir mdir "/cur/" + (mu4e~draft-message-filename-construct "S"))))) + ;; if there's an fcc header, add it to the file + (when fccfile + (message-add-header (concat "Fcc: " fccfile "\n")) + ;; sadly, we cannot define as 'buffer-local'... this will screw up gnus + ;; etc. if you run it after mu4e so, (hack hack) we reset it to the old + ;; handler after we've done our thing. + (setq message-fcc-handler-function + (lexical-let ((maildir mdir) (old-handler message-fcc-handler-function)) + (lambda (file) + (setq message-fcc-handler-function old-handler) ;; reset the fcc handler + (write-file file) ;; writing maildirs files is easy + (mu4e~proc-add file (or maildir "/")))))))) ;; update the database + +(defvar mu4e-compose-hidden-headers + `("^References:" "^Face:" "^X-Face:" + "^X-Draft-From:" "^User-agent:") + "Hidden headers when composing.") + +(defun mu4e~compose-hide-headers () + "Hide the headers as per `mu4e-compose-hidden-headers'." + (let ((message-hidden-headers mu4e-compose-hidden-headers)) + (message-hide-headers))) + +(defconst mu4e~compose-address-fields-regexp + "^\\(To\\|B?Cc\\|Reply-To\\|From\\):") + +(defun mu4e~compose-register-message-save-hooks () + "Just before saving, we remove the mail-header-separator; just +after saving we restore it; thus, the separator should never +appear on disk." + (add-hook 'before-save-hook + 'mu4e~draft-remove-mail-header-separator nil t) + (add-hook 'after-save-hook + (lambda () + (mu4e~compose-set-friendly-buffer-name) + (mu4e~draft-insert-mail-header-separator) + ;; hide some headers again + (mu4e~compose-hide-headers) + (set-buffer-modified-p nil) + (mu4e-message "Saved (%d lines)" (count-lines (point-min) (point-max))) + ;; 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 +;; https://github.com/nordlow/elisp/blob/master/mine/completion-styles-cycle.el +(defun mu4e~compose-complete-handler (str pred action) + (cond + ((eq action nil) + (try-completion str mu4e~contacts pred)) + ((eq action t) + (all-completions str mu4e~contacts pred)) + ((eq action 'metadata) + ;; our contacts are already sorted - just need to tell the + ;; completion machinery not to try to undo that... + '(metadata + (display-sort-function . mu4e~sort-contacts-for-completion) + (cycle-sort-function . mu4e~sort-contacts-for-completion))))) + +(defun mu4e~compose-complete-contact (&optional start) + "Complete the text at START with a contact. +Ie. either 'name ' or 'email')." + (interactive) + (let ((mail-abbrev-mode-regexp mu4e~compose-address-fields-regexp) + (eoh ;; end-of-headers + (save-excursion + (goto-char (point-min)) + (search-forward-regexp mail-header-separator nil t)))) + ;; try to complete only when we're in the headers area, + ;; looking at an address field. + (when (and eoh (> eoh (point)) (mail-abbrev-in-expansion-header-p)) + (let* ((end (point)) + (start + (or start + (save-excursion + (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*") + (goto-char (match-end 0)) + (point))))) + (list start end 'mu4e~compose-complete-handler))))) + +(defun mu4e~compose-setup-completion () + "Set up auto-completion of addresses." + (set (make-local-variable 'completion-ignore-case) t) + (set (make-local-variable 'completion-cycle-threshold) 7) + (add-to-list (make-local-variable 'completion-styles) 'substring) + (add-hook 'completion-at-point-functions + 'mu4e~compose-complete-contact nil t)) + +(defun mu4e~remove-refs-maybe () + "Remove the References: header if the In-Reply-To header is +missing. This allows the user to effectively start a new +message-thread by removing the In-Reply-To header." + (unless (message-fetch-field "in-reply-to") + (message-remove-header "References"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e-compose-mode-map nil + "Keymap for \"*mu4e-compose*\" buffers.") +(unless mu4e-compose-mode-map + (setq mu4e-compose-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index) + (define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index) + (define-key map (kbd "C-c C-k") 'mu4e-message-kill-buffer) + (define-key map (kbd "M-q") 'mu4e-fill-paragraph) + map))) + +(defun mu4e-fill-paragraph (&optional region) + "If `use-hard-newlines', takes a multi-line paragraph and makes +it into a single line of text. Assume paragraphs are separated +by blank lines. If `use-hard-newlines' is not enabled, this +simply executes `fill-paragraph'." + ;; Inspired by https://www.emacswiki.org/emacs/UnfillParagraph + (interactive (progn (barf-if-buffer-read-only) '(t))) + (if mu4e-compose-format-flowed + (let ((fill-column (point-max)) + (use-hard-newlines nil)); rfill "across" hard newlines + (fill-paragraph nil region)) + (fill-paragraph nil region))) + +(defun mu4e-toggle-use-hard-newlines () + (interactive) + (setq use-hard-newlines (not use-hard-newlines)) + (if use-hard-newlines + (turn-off-auto-fill) + (turn-on-auto-fill))) + + +(defvar mu4e-compose-mode-abbrev-table nil) +(define-derived-mode mu4e-compose-mode message-mode "mu4e:compose" + "Major mode for the mu4e message composition, derived from `message-mode'. +\\{message-mode-map}." + (progn + (use-local-map mu4e-compose-mode-map) + + (set (make-local-variable 'global-mode-string) '(:eval (mu4e-context-label))) + + (set (make-local-variable 'message-signature) mu4e-compose-signature) + ;; set this to allow mu4e to work when gnus-agent is unplugged in gnus + (set (make-local-variable 'message-send-mail-real-function) nil) + (make-local-variable 'message-default-charset) + ;; if the default charset is not set, use UTF-8 + (unless message-default-charset + (setq message-default-charset 'utf-8)) + ;; make sure mu4e is started in the background (ie. we don't want to error + ;; out when sending the message; better to do it now if there's a problem) + (mu4e~start) ;; start mu4e in background, if needed + (mu4e~compose-register-message-save-hooks) + ;; set the default directory to the user's home dir; this is probably more + ;; useful e.g. when finding an attachment file the directory the current + ;; mail files lives in... + (setq default-directory (expand-file-name "~/")) + + ;; offer completion for e-mail addresses + (when mu4e-compose-complete-addresses + (mu4e~compose-setup-completion)) + + (when mu4e-compose-format-flowed + (turn-off-auto-fill) + (setq truncate-lines nil + word-wrap t + use-hard-newlines t) + ;; Set the marks in the fringes before activating visual-line-mode + (set (make-local-variable 'visual-line-fringe-indicators) + '(left-curly-arrow right-curly-arrow)) + (visual-line-mode t)) + + (define-key-after + (lookup-key message-mode-map [menu-bar text]) + [mu4e-hard-newlines] + '(menu-item "Format=flowed" mu4e-toggle-use-hard-newlines + :button (:toggle . use-hard-newlines) + :help "Toggle format=flowed" + :visible (eq major-mode 'mu4e-compose-mode) + :enable mu4e-compose-format-flowed) + 'sep) + + (define-key-after + (lookup-key mml-mode-map [menu-bar Attachments]) + [mu4e-compose-attach-captured-message] + '(menu-item "Attach captured message" + mu4e-compose-attach-captured-message + :help "Attach message captured in Headers View (with 'a c')" + :visible (eq major-mode 'mu4e-compose-mode)) + (quote Attach\ External...)) + + ;; setup the fcc-stuff, if needed + (add-hook 'message-send-hook + (lambda () ;; mu4e~compose-save-before-sending + ;; when in-reply-to was removed, remove references as well. + (when (eq mu4e~compose-type 'reply) + (mu4e~remove-refs-maybe)) + (when use-hard-newlines + (mu4e-send-harden-newlines)) + ;; for safety, always save the draft before sending + (set-buffer-modified-p t) + (save-buffer) + (mu4e~compose-setup-fcc-maybe) + (widen)) nil t) + ;; when the message has been sent. + (add-hook 'message-sent-hook + (lambda () ;; mu4e~compose-mark-after-sending + (setq mu4e-sent-func 'mu4e-sent-handler) + (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 + ;; (put 'mu4e~compose-save-before-sending 'permanent-local-hook t) + (put 'mu4e~compose-mark-after-sending 'permanent-local-hook t)) + +(defun mu4e-send-harden-newlines () + "Set the hard property to all newlines." + (save-excursion + (goto-char (point-min)) + (while (search-forward "\n" nil t) + (put-text-property (1- (point)) (point) 'hard t)))) + +(defconst mu4e~compose-buffer-max-name-length 30 + "Maximum length of the mu4e-send-buffer-name.") + +(defvar mu4e~compose-type nil + "Compose-type for this buffer.") + +(defun mu4e~compose-set-friendly-buffer-name (&optional compose-type) + "Set some user-friendly buffer name based on the compose type." + (let* ((subj (message-field-value "subject")) + (subj (unless (and subj (string-match "^[:blank:]*$" subj)) subj)) + (str (or subj + (case compose-type + (reply "*reply*") + (forward "*forward*") + (otherwise "*draft*"))))) + (rename-buffer (generate-new-buffer-name + (truncate-string-to-width str + mu4e~compose-buffer-max-name-length + nil nil t))))) + +(defun* mu4e~compose-handler (compose-type &optional original-msg includes) + "Create a new draft message, or open an existing one. + +COMPOSE-TYPE determines the kind of message to compose and is a +symbol, either `reply', `forward', `edit', `resend' `new'. `edit' +is for editing existing (draft) messages. When COMPOSE-TYPE is +`reply' or `forward', MSG should be a message plist. If +COMPOSE-TYPE is `new', ORIGINAL-MSG should be nil. + +Optionally (when forwarding, replying) ORIGINAL-MSG is the original +message we will forward / reply to. + +Optionally (when forwarding) INCLUDES contains a list of + (:file-name :mime-type :disposition ) +for the attachements to include; file-name refers to +a file which our backend has conveniently saved for us (as a +tempfile)." + + ;; Run the hooks defined for `mu4e-compose-pre-hook'. If compose-type is + ;; `reply', `forward' or `edit', `mu4e-compose-parent-message' points to the + ;; message being forwarded or replied to, otherwise it is nil. + (set (make-local-variable 'mu4e-compose-parent-message) original-msg) + (put 'mu4e-compose-parent-message 'permanent-local t) + ;; maybe switch the context + (mu4e~context-autoswitch mu4e-compose-parent-message + mu4e-compose-context-policy) + (run-hooks 'mu4e-compose-pre-hook) + + ;; this opens (or re-opens) a messages with all the basic headers set. + (condition-case nil + (mu4e-draft-open compose-type original-msg) + (quit (kill-buffer) (mu4e-message "Operation aborted") + (return-from mu4e~compose-handler))) + ;; insert mail-header-separator, which is needed by message mode to separate + ;; headers and body. will be removed before saving to disk + (mu4e~draft-insert-mail-header-separator) + ;; include files -- e.g. when forwarding a message with attachments, + ;; we take those from the original. + (save-excursion + (goto-char (point-max)) ;; put attachments at the end + (dolist (att includes) + (mml-attach-file + (plist-get att :file-name) (plist-get att :mime-type)))) + ;; buffer is not user-modified yet + (mu4e~compose-set-friendly-buffer-name compose-type) + (set-buffer-modified-p nil) + ;; now jump to some useful positions, and start writing that mail! + + (if (member compose-type '(new forward)) + (message-goto-to) + (message-goto-body)) + ;; bind to `mu4e-compose-parent-message' of compose buffer + (set (make-local-variable 'mu4e-compose-parent-message) original-msg) + (put 'mu4e-compose-parent-message 'permanent-local t) + ;; remember the compose-type + (set (make-local-variable 'mu4e~compose-type) compose-type) + (put 'mu4e~compose-type 'permanent-local t) + + ;; hide some headers + (mu4e~compose-hide-headers) + ;; switch on the mode + (mu4e-compose-mode) + (when mu4e-compose-in-new-frame + ;; make sure to close the frame when we're done with the message these are + ;; all buffer-local; + (push 'delete-frame message-exit-actions) + (push 'delete-frame message-postpone-actions))) + +(defun mu4e-sent-handler (docid path) + "Handler function, called with DOCID and PATH for the just-sent +message. For Forwarded ('Passed') and Replied messages, try to set +the appropriate flag at the message forwarded or replied-to." + (mu4e~compose-set-parent-flag path) + (when (file-exists-p path) ;; maybe the draft was not saved at all + (mu4e~proc-remove docid)) + ;; kill any remaining buffers for the draft file, or they will hang around... + ;; this seems a bit hamfisted... + (dolist (buf (buffer-list)) + (when (and (buffer-file-name buf) + (string= (buffer-file-name buf) path)) + (if message-kill-buffer-on-exit + (kill-buffer buf)))) + ;; now, try to go back to some previous buffer, in the order + ;; view->headers->main + (if (buffer-live-p mu4e~view-buffer) + (switch-to-buffer mu4e~view-buffer) + (if (buffer-live-p mu4e~headers-buffer) + (switch-to-buffer mu4e~headers-buffer) + ;; if all else fails, back to the main view + (when (fboundp 'mu4e) (mu4e)))) + (mu4e-message "Message sent")) + +(defun mu4e-message-kill-buffer () + "Wrapper around `message-kill-buffer'. +It restores mu4e window layout after killing the compose-buffer." + (interactive) + (let ((current-buffer (current-buffer))) + (message-kill-buffer) + ;; Compose buffer killed + (when (not (equal current-buffer (current-buffer))) + ;; Restore mu4e + (if mu4e-compose-in-new-frame + (delete-frame) + (if (buffer-live-p mu4e~view-buffer) + (switch-to-buffer mu4e~view-buffer) + (if (buffer-live-p mu4e~headers-buffer) + (switch-to-buffer mu4e~headers-buffer) + ;; if all else fails, back to the main view + (when (fboundp 'mu4e) (mu4e)))))))) + +(defun mu4e~compose-set-parent-flag (path) + "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 +to the message with the corresponding message id. If it does not +have an 'in-reply-to' header, but does have a 'references' header, +it is considered to be a forward message for the message +corresponding with the /last/ message-id in the references header. + +Now, if the message has been determined to be either a forwarded +message or a reply, we instruct the server to update that message +with resp. the 'P' (passed) flag for a forwarded message, or the +'R' flag for a replied message. The original messages are also +marked as Seen. + +Function assumes that it's executed in the context of the message +buffer." + (let ((buf (find-file-noselect path))) + (when buf + (with-current-buffer buf + (message-narrow-to-headers-or-head) + (let ((in-reply-to (message-fetch-field "in-reply-to")) + (forwarded-from) + (references (message-fetch-field "references"))) + (unless in-reply-to + (when references + (with-temp-buffer ;; inspired by `message-shorten-references'. + (insert references) + (goto-char (point-min)) + (let ((refs)) + (while (re-search-forward "<[^ <]+@[^ <]+>" nil t) + (push (match-string 0) refs)) + ;; the last will be the first + (setq forwarded-from (first refs)))))) + ;; remove the <> + (when (and in-reply-to (string-match "<\\(.*\\)>" in-reply-to)) + (mu4e~proc-move (match-string 1 in-reply-to) nil "+R-N")) + (when (and forwarded-from (string-match "<\\(.*\\)>" forwarded-from)) + (mu4e~proc-move (match-string 1 forwarded-from) nil "+P-N"))))))) + +(defun mu4e-compose (compose-type) + "Start composing a message of COMPOSE-TYPE, where COMPOSE-TYPE +is a symbol, one of `reply', `forward', `edit', `resend' +`new'. All but `new' take the message at point as input. Symbol +`edit' is only allowed for draft messages." + (let ((msg (mu4e-message-at-point 'noerror))) + ;; some sanity checks + (unless (or msg (eq compose-type 'new)) + (mu4e-warn "No message at point")) + (unless (member compose-type '(reply forward edit resend new)) + (mu4e-error "Invalid compose type '%S'" compose-type)) + (when (and (eq compose-type 'edit) + (not (member 'draft (mu4e-message-field msg :flags)))) + (mu4e-warn "Editing is only allowed for draft messages")) + + ;; 'new is special, since it takes no existing message as arg; therefore, we + ;; don't need to involve the backend, and call the handler *directly* + (if (eq compose-type 'new) + (mu4e~compose-handler 'new) + ;; otherwise, we need the doc-id + (let* ((docid (mu4e-message-field msg :docid)) + ;; decrypt (or not), based on `mu4e-decryption-policy'. + (decrypt + (and (member 'encrypted (mu4e-message-field msg :flags)) + (if (eq mu4e-decryption-policy 'ask) + (yes-or-no-p (mu4e-format "Decrypt message?")) + mu4e-decryption-policy)))) + ;; if there's a visible view window, select that before starting 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 it... + (let ((viewwin (get-buffer-window mu4e~view-buffer))) + (when (window-live-p viewwin) + (select-window viewwin))) + ;; talk to the backend + (mu4e~proc-compose compose-type decrypt docid))))) + +(defun mu4e-compose-reply () + "Compose a reply for the message at point in the headers buffer." + (interactive) + (mu4e-compose 'reply)) + +(defun mu4e-compose-forward () + "Forward the message at point in the headers buffer." + (interactive) + (mu4e-compose 'forward)) + +(defun mu4e-compose-edit () + "Edit the draft message at point in the headers buffer. +This is only possible if the message at point is, in fact, a +draft message." + (interactive) + (mu4e-compose 'edit)) + +(defun mu4e-compose-resend () + "Resend the message at point in the headers buffer." + (interactive) + (mu4e-compose 'resend)) + +(defun mu4e-compose-new () + "Start writing a new message." + (interactive) + (mu4e-compose 'new)) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; mu4e-compose-func and mu4e-send-func are wrappers so we can set ourselves +;; as default emacs mailer (define-mail-user-agent etc.) + +;;;###autoload +(defun mu4e~compose-mail (&optional to subject other-headers continue + switch-function yank-action send-actions return-action) + "This is mu4e's implementation of `compose-mail'." + + ;; 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) + + (when (message-goto-to) ;; reset to-address, if needed + (message-delete-line)) + (message-add-header (concat "To: " to "\n")) + + (when (message-goto-subject) ;; reset subject, if needed + (message-delete-line)) + (message-add-header (concat "Subject: " subject "\n")) + + ;; add any other headers specified + (when other-headers + (message-add-header other-headers)) + + ;; yank message + (if (bufferp yank-action) + (list 'insert-buffer yank-action) + yank-action) + + ;; try to put the user at some reasonable spot... + (if (not to) + (message-goto-to) + (if (not subject) + (message-goto-subject) + (message-goto-body)))) + +;; happily, we can re-use most things from message mode +;;;###autoload +(define-mail-user-agent 'mu4e-user-agent + 'mu4e~compose-mail + 'message-send-and-exit + 'message-kill-buffer + 'message-send-hook) +;; Without this `mail-user-agent' cannot be set to `mu4e-user-agent' +;; through customize, as the custom type expects a function. Not +;; sure whether this function is actually ever used; if it is then +;; returning the symbol is probably the correct thing to do, as other +;; such functions suggest. +(defun mu4e-user-agent () + 'mu4e-user-agent) + +(defun mu4e~compose-browse-url-mail (url &optional ignored) + "Adapter for `browse-url-mailto-function." + (let* ((headers (rfc2368-parse-mailto-url url)) + (to (cdr (assoc "To" headers))) + (subject (cdr (assoc "Subject" headers))) + (body (cdr (assoc "Body" headers)))) + (mu4e~compose-mail to subject) + (if body + (progn + (message-goto-body) + (insert body) + (if (not to) + (message-goto-to) + (if (not subject) + (message-goto-subject) + (message-goto-body))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun mu4e-compose-goto-top () + "Go to the beginning of the message or buffer. +Go to the beginning of the message or, if already there, go to the +beginning of the buffer." + (interactive) + (let ((old-position (point))) + (message-goto-body) + (when (equal (point) old-position) + (goto-char (point-min))))) + +(define-key mu4e-compose-mode-map + (vector 'remap 'beginning-of-buffer) 'mu4e-compose-goto-top) + +(defun mu4e-compose-goto-bottom () + "Go to the end of the message or buffer. +Go to the end of the message (before signature) or, if already there, go to the +end of the buffer." + (interactive) + (let ((old-position (point)) + (message-position (save-excursion (message-goto-body) (point)))) + (goto-char (point-max)) + (when (re-search-backward message-signature-separator message-position t) + (forward-line -1)) + (when (equal (point) old-position) + (goto-char (point-max))))) + +(define-key mu4e-compose-mode-map + (vector 'remap 'end-of-buffer) 'mu4e-compose-goto-bottom) + +(provide 'mu4e-compose) + +;; Load mu4e completely even when this file was loaded through +;; autoload. +(require 'mu4e) diff --git a/_spacemacs.d/local/mu4e/mu4e-context.el b/_spacemacs.d/local/mu4e/mu4e-context.el new file mode 100644 index 0000000..f5f8fff --- /dev/null +++ b/_spacemacs.d/local/mu4e/mu4e-context.el @@ -0,0 +1,157 @@ +; mu4e-context.el -- part of mu4e, the mu mail user agent +;; +;; Copyright (C) 2015-2016 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: + +;; A mu4e 'context' is a a set of variable-settings and functions, which can be +;; used e.g. to switch between accounts. + +(eval-when-compile (byte-compile-disable-warning 'cl-functions)) +(require 'cl) + +(require 'mu4e-utils) + +(defvar mu4e-contexts nil "The list of `mu4e-context' objects +describing mu4e's contexts.") + +(defvar mu4e~context-current nil + "The current context; for internal use. Use + `mu4e-context-switch' to change it.") + +(defun mu4e-context-current () + "Get the currently active context, or nil if there is none." + mu4e~context-current) + +(defun mu4e-context-label () + "Propertized string with the current context name, or \"\" if + there is none." + (if (mu4e-context-current) + (concat "[" (propertize (mu4e~quote-for-modeline + (mu4e-context-name (mu4e-context-current))) + 'face 'mu4e-title-face) "]") "")) + +(defstruct mu4e-context + "A mu4e context object with the following members: +- `name': the name of the context, eg. \"Work\" or \"Private\".' +- `enter-func': a parameterless function invoked when entering + this context, or nil +- `leave-func':a parameterless fuction invoked when leaving this + context, or nil +- `match-func': a function called when comnposing a new messages, + and takes a message plist +for the message replied to or forwarded, and nil +otherwise. Before composing a new message, `mu4e' switches to the +first context for which `match-func' return t." + name ;; name of the context, e.g. "work" + (enter-func nil) ;; function invoked when entering the context + (leave-func nil) ;; function invoked when leaving the context + (match-func nil) ;; function that takes a msg-proplist, and return t + ;; if it matches, nil otherwise + vars) ;; alist of variables. + +(defun mu4e~context-ask-user (prompt) + "Let user choose some context based on its name." + (when mu4e-contexts + (let* ((names (map 'list (lambda (context) + (cons (mu4e-context-name context) context)) + mu4e-contexts)) + (context (mu4e-read-option prompt names))) + (or context (mu4e-error "No such context"))))) + +(defun mu4e-context-switch (&optional force name) + "Switch context to a context with NAME which is part of +`mu4e-contexts'; if NAME is nil, query user. + +If the new context is the same and the current context, only +switch (run associated functions) when prefix argument FORCE is +non-nil." + (interactive "P") + (unless mu4e-contexts + (mu4e-error "No contexts defined")) + (let* ((names (map 'list (lambda (context) + (cons (mu4e-context-name context) context)) + mu4e-contexts)) + (context + (if name + (cdr-safe (assoc name names)) + (mu4e~context-ask-user "Switch to context: ")))) + (unless context (mu4e-error "No such context")) + ;; if new context is same as old one one switch with FORCE is set. + (when (or force (not (eq context (mu4e-context-current)))) + (when (and (mu4e-context-current) + (mu4e-context-leave-func mu4e~context-current)) + (funcall (mu4e-context-leave-func mu4e~context-current))) + ;; enter the new context + (when (mu4e-context-enter-func context) + (funcall (mu4e-context-enter-func context))) + (when (mu4e-context-vars context) + (mapc #'(lambda (cell) + (set (car cell) (cdr cell))) + (mu4e-context-vars context))) + (setq mu4e~context-current context) + (mu4e-message "Switched context to %s" (mu4e-context-name context))) + context)) + +(defun mu4e~context-autoswitch (&optional msg policy) + "When contexts are defined but there is no context yet, switch +to the first whose :match-func return non-nil. If none of them +match, return the first. For MSG and POLICY, see `mu4e-context-determine'." + (when mu4e-contexts + (let ((context (mu4e-context-determine msg policy))) + (when context (mu4e-context-switch + nil (mu4e-context-name context)))))) + +(defun mu4e-context-determine (msg &optional policy) + "Return the first context with a match-func that returns t. MSG +points to the plist for the message replied to or forwarded, or +nil if there is no such MSG; similar to what +`mu4e-compose-pre-hook' does. + +POLICY specifies how to do the determination. If POLICY is +'always-ask, we ask the user unconditionally. + +In all other cases, if any context matches (using its match +function), this context is returned. If none of the contexts +match, POLICY determines what to do: + +- pick-first: pick the first of the contexts available +- ask: ask the user +- ask-if-none: ask if there is no context yet +- otherwise, return nil. Effectively, this leaves the current context as it is." + (when mu4e-contexts + (if (eq policy 'always-ask) + (mu4e~context-ask-user "Select context: ") + (or ;; is there a matching one? + (find-if (lambda (context) + (when (mu4e-context-match-func context) + (funcall (mu4e-context-match-func context) msg))) + mu4e-contexts) + ;; no context found yet; consult policy + (case policy + (pick-first (car mu4e-contexts)) + (ask (mu4e~context-ask-user "Select context: ")) + (ask-if-none (or (mu4e-context-current) + (mu4e~context-ask-user "Select context: "))) + (otherwise nil)))))) + +(provide 'mu4e-context) + diff --git a/_spacemacs.d/local/mu4e/mu4e-contrib.el b/_spacemacs.d/local/mu4e/mu4e-contrib.el new file mode 100644 index 0000000..8952401 --- /dev/null +++ b/_spacemacs.d/local/mu4e/mu4e-contrib.el @@ -0,0 +1,164 @@ +;;; mu4e-contrib.el -- part of mu4e, the mu mail user agent +;; +;; Copyright (C) 2013-2016 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: + +;; Some user-contributed functions for mu4e + +;; Contributed by sabof + +(require 'mu4e) + +(defun mu4e-headers-mark-all-unread-read () + "Put a ! \(read) mark on all visible unread messages." + (interactive) + (mu4e-headers-mark-for-each-if + (cons 'read nil) + (lambda (msg param) + (memq 'unread (mu4e-msg-field msg :flags))))) + +(defun mu4e-headers-flag-all-read () + "Flag all visible messages as \"read\"." + (interactive) + (mu4e-headers-mark-all-unread-read) + (mu4e-mark-execute-all t)) + +;;; + +(defun mu4e-headers-mark-all () + "Mark all messages within current query results and ask user to execute which action." + (interactive) + (mu4e-headers-mark-for-each-if + (cons 'something nil) + (lambda (msg param) t)) + (mu4e-mark-execute-all)) + +;;; + +;;; Bookmark handlers +;; +;; Allow bookmarking a mu4e buffer in regular emacs bookmarks. + +;; Probably this can be moved to mu4e-view.el. +(add-hook 'mu4e-view-mode-hook + #'(lambda () + (set (make-local-variable 'bookmark-make-record-function) + 'mu4e-view-bookmark-make-record))) +;; And this can be moved to mu4e-headers.el. +(add-hook 'mu4e-headers-mode-hook + #'(lambda () + (set (make-local-variable 'bookmark-make-record-function) + 'mu4e-view-bookmark-make-record))) + +(defun mu4e-view-bookmark-make-record () + "Make a bookmark entry for a mu4e buffer." + (let* ((msg (mu4e-message-at-point)) + (maildir (plist-get msg :maildir)) + (date (format-time-string "%Y%m%d" (plist-get msg :date))) + (query (format "maildir:%s date:%s" maildir date)) + (docid (plist-get msg :docid)) + (mode (symbol-name major-mode)) + (subject (or (plist-get msg :subject) "No subject"))) + `(,subject + ,@(bookmark-make-record-default 'no-file 'no-context) + (location . (,query . ,docid)) + (mode . ,mode) + (handler . mu4e-bookmark-jump)))) + +(defun mu4e-bookmark-jump (bookmark) + "Handler function for record returned by `mu4e-view-bookmark-make-record'. +BOOKMARK is a bookmark name or a bookmark record." + (let* ((path (bookmark-prop-get bookmark 'location)) + (mode (bookmark-prop-get bookmark 'mode)) + (docid (cdr path)) + (query (car path))) + (call-interactively 'mu4e) + (mu4e-headers-search query) + (sit-for 0.5) + (mu4e~headers-goto-docid docid) + (mu4e~headers-highlight docid) + (unless (string= mode "mu4e-headers-mode") + (call-interactively 'mu4e-headers-view-message) + (run-with-timer 0.1 nil + (lambda (bmk) + (bookmark-default-handler + `("" (buffer . ,(current-buffer)) . + ,(bookmark-get-bookmark-record bmk)))) + bookmark)))) + + + +;;; handling spam with Bogofilter with possibility to define it for SpamAssassin +;;; contributed by Gour + +;; to add the actions to the menu, you can use something like: + +;; (add-to-list 'mu4e-headers-actions +;; '("sMark as spam" . mu4e-register-msg-as-spam) t) +;; (add-to-list 'mu4e-headers-actions +;; '("hMark as ham" . mu4e-register-msg-as-ham) t) + +(defvar mu4e-register-as-spam-cmd nil + "Command for invoking spam processor to register message as spam, +for example for bogofilter, use \"/usr/bin/bogofilter -Ns < %s\" ") + +(defvar mu4e-register-as-ham-cmd nil + "Command for invoking spam processor to register message as ham. +For example for bogofile, use \"/usr/bin/bogofilter -Sn < %s\"") + +(defun mu4e-register-msg-as-spam (msg) + "Mark message as spam." + (interactive) + (let* ((path (shell-quote-argument (mu4e-message-field msg :path))) + (command (format mu4e-register-as-spam-cmd path))) ;; re-register msg as spam + (shell-command command)) +(mu4e-mark-at-point 'delete nil)) + +(defun mu4e-register-msg-as-ham (msg) + "Mark message as ham." + (interactive) + (let* ((path (shell-quote-argument(mu4e-message-field msg :path))) + (command (format mu4e-register-as-ham-cmd path))) ;; re-register msg as ham + (shell-command command)) +(mu4e-mark-at-point 'something nil)) + +;; (add-to-list 'mu4e-view-actions +;; '("sMark as spam" . mu4e-view-register-msg-as-spam) t) +;; (add-to-list 'mu4e-view-actions +;; '("hMark as ham" . mu4e-view-register-msg-as-ham) t) + +(defun mu4e-view-register-msg-as-spam (msg) + "Mark message as spam (view mode)." + (interactive) + (let* ((path (shell-quote-argument (mu4e-message-field msg :path))) + (command (format mu4e-register-as-spam-cmd path))) + (shell-command command)) + (mu4e-view-mark-for-delete)) + +(defun mu4e-view-register-msg-as-ham (msg) + "Mark message as ham (view mode)." + (interactive) + (let* ((path (shell-quote-argument(mu4e-message-field msg :path))) + (command (format mu4e-register-as-ham-cmd path))) + (shell-command command)) + (mu4e-view-mark-for-something)) + +;;; end of spam-filtering functions + +(provide 'mu4e-contrib) diff --git a/_spacemacs.d/local/mu4e/mu4e-draft.el b/_spacemacs.d/local/mu4e/mu4e-draft.el new file mode 100644 index 0000000..c41e38d --- /dev/null +++ b/_spacemacs.d/local/mu4e/mu4e-draft.el @@ -0,0 +1,474 @@ +;; mu4e-draft.el -- part of mu4e, the mu mail user agent for emacs +;; +;; Copyright (C) 2011-2016 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) +(require 'message) ;; mail-header-separator +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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) + +(defcustom mu4e-compose-cite-function + (or message-cite-function 'message-cite-original-without-signature) + "The function to use to cite message in replies and forwarded +messages. 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'." + :group 'mu4e-compose) + +(defcustom mu4e-compose-signature-auto-include t + "Whether to automatically include a message-signature in new +messages (if it is set)." + :type 'boolean + :group 'mu4e-compose) + +(defcustom mu4e-compose-auto-include-date nil + "Whether to include a date header when starting to draft a +message; if nil, only do so when sending the message." + :type 'boolean + :group 'mu4e-compose) + +(defcustom mu4e-compose-in-new-frame nil + "Whether to compose messages in a new frame instead of the +current window." + :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.") + +(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 + (let ((mu4e-view-date-format "%Y-%m-%dT%T%z")) + (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)) + (mu4e~fontify-cited) + (buffer-string)))) + +(defun mu4e~draft-header (hdr val) + "Return a header line of the form \"HDR: VAL\". +If VAL is nil, return nil." + ;; note: the propertize here is currently useless, since gnus sets its own + ;; later. + (when val (format "%s: %s\n" + (propertize hdr 'face 'mu4e-header-key-face) + (propertize val 'face 'mu4e-header-val-face)))) + +(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). +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)))) + (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>" (mu4e~rfc822-quoteit 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) "--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) + (replace-match 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-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 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 +